ridley 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.gitignore +17 -0
  2. data/.travis.yml +5 -0
  3. data/Gemfile +3 -0
  4. data/Guardfile +20 -0
  5. data/LICENSE +201 -0
  6. data/README.md +273 -0
  7. data/Thorfile +48 -0
  8. data/lib/ridley.rb +48 -0
  9. data/lib/ridley/connection.rb +131 -0
  10. data/lib/ridley/context.rb +25 -0
  11. data/lib/ridley/dsl.rb +58 -0
  12. data/lib/ridley/errors.rb +82 -0
  13. data/lib/ridley/log.rb +10 -0
  14. data/lib/ridley/middleware.rb +19 -0
  15. data/lib/ridley/middleware/chef_auth.rb +45 -0
  16. data/lib/ridley/middleware/chef_response.rb +28 -0
  17. data/lib/ridley/middleware/parse_json.rb +107 -0
  18. data/lib/ridley/resource.rb +305 -0
  19. data/lib/ridley/resources/client.rb +75 -0
  20. data/lib/ridley/resources/cookbook.rb +27 -0
  21. data/lib/ridley/resources/data_bag.rb +75 -0
  22. data/lib/ridley/resources/data_bag_item.rb +186 -0
  23. data/lib/ridley/resources/environment.rb +45 -0
  24. data/lib/ridley/resources/node.rb +34 -0
  25. data/lib/ridley/resources/role.rb +33 -0
  26. data/lib/ridley/version.rb +3 -0
  27. data/ridley.gemspec +39 -0
  28. data/spec/acceptance/client_resource_spec.rb +135 -0
  29. data/spec/acceptance/cookbook_resource_spec.rb +46 -0
  30. data/spec/acceptance/data_bag_item_resource_spec.rb +171 -0
  31. data/spec/acceptance/data_bag_resource_spec.rb +51 -0
  32. data/spec/acceptance/environment_resource_spec.rb +171 -0
  33. data/spec/acceptance/node_resource_spec.rb +218 -0
  34. data/spec/acceptance/role_resource_spec.rb +200 -0
  35. data/spec/fixtures/reset.pem +27 -0
  36. data/spec/spec_helper.rb +25 -0
  37. data/spec/support/each_matcher.rb +12 -0
  38. data/spec/support/shared_examples/ridley_resource.rb +237 -0
  39. data/spec/support/spec_helpers.rb +11 -0
  40. data/spec/unit/ridley/connection_spec.rb +167 -0
  41. data/spec/unit/ridley/errors_spec.rb +34 -0
  42. data/spec/unit/ridley/middleware/chef_auth_spec.rb +14 -0
  43. data/spec/unit/ridley/middleware/chef_response_spec.rb +213 -0
  44. data/spec/unit/ridley/middleware/parse_json_spec.rb +74 -0
  45. data/spec/unit/ridley/resource_spec.rb +214 -0
  46. data/spec/unit/ridley/resources/client_spec.rb +47 -0
  47. data/spec/unit/ridley/resources/cookbook_spec.rb +5 -0
  48. data/spec/unit/ridley/resources/data_bag_item_spec.rb +42 -0
  49. data/spec/unit/ridley/resources/data_bag_spec.rb +15 -0
  50. data/spec/unit/ridley/resources/environment_spec.rb +73 -0
  51. data/spec/unit/ridley/resources/node_spec.rb +5 -0
  52. data/spec/unit/ridley/resources/role_spec.rb +5 -0
  53. data/spec/unit/ridley_spec.rb +32 -0
  54. metadata +451 -0
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ridley::Middleware::ChefAuth do
4
+ let(:server_url) { "https://api.opscode.com/organizations/vialstudios/" }
5
+
6
+ subject do
7
+ Faraday.new(server_url) do |conn|
8
+ conn.request :chef_auth, "reset", "/Users/reset/.chef/reset.pem"
9
+ conn.adapter Faraday.default_adapter
10
+ end
11
+ end
12
+
13
+ pending
14
+ end
@@ -0,0 +1,213 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ridley::Middleware::ChefResponse do
4
+ let(:server_url) { "https://api.opscode.com/organizations/vialstudios/" }
5
+
6
+ describe "ClassMethods" do
7
+ subject { Ridley::Middleware::ChefResponse }
8
+
9
+ describe "::success?" do
10
+ let(:env) { double('env') }
11
+
12
+ it "returns true if response status between 200 and 210" do
13
+ (200..210).each do |code|
14
+ env.should_receive(:[]).with(:status).and_return(code)
15
+
16
+ subject.success?(env).should be_true
17
+ end
18
+ end
19
+
20
+ it "returns false if response status is in the 300 range" do
21
+ (300..399).each do |code|
22
+ env.should_receive(:[]).with(:status).and_return(code)
23
+
24
+ subject.success?(env).should be_false
25
+ end
26
+ end
27
+
28
+ it "returns false if response status is in the 400 range" do
29
+ (400..499).each do |code|
30
+ env.should_receive(:[]).with(:status).and_return(code)
31
+
32
+ subject.success?(env).should be_false
33
+ end
34
+ end
35
+
36
+ it "returns false if response status is in the 500 range" do
37
+ (500..599).each do |code|
38
+ env.should_receive(:[]).with(:status).and_return(code)
39
+
40
+ subject.success?(env).should be_false
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ subject do
47
+ Faraday.new(server_url) do |conn|
48
+ conn.response :chef_response
49
+ conn.response :json
50
+ conn.adapter Faraday.default_adapter
51
+ end
52
+ end
53
+
54
+ let(:chef_ok) do
55
+ {
56
+ status: 200,
57
+ body: generate_normalized_json(name: "reset-role", description: "a new role"),
58
+ headers: {
59
+ content_type: "application/json; charset=utf8"
60
+ }
61
+ }
62
+ end
63
+
64
+ let(:chef_bad_request) do
65
+ {
66
+ status: 400,
67
+ body: generate_normalized_json(error: "400 - Bad Request: Valid X-CHEF-VERSION header is required."),
68
+ headers: {
69
+ content_type: "application/json; charset=utf8"
70
+ }
71
+ }
72
+ end
73
+
74
+ let(:chef_unauthorized) do
75
+ {
76
+ status: 401,
77
+ body: generate_normalized_json(error: "401 - Unauthorized. You must properly authenticate your API requests!"),
78
+ headers: {
79
+ content_type: "application/json; charset=utf8"
80
+ }
81
+ }
82
+ end
83
+
84
+ let(:chef_forbidden) do
85
+ {
86
+ status: 403,
87
+ body: generate_normalized_json(error: "403 - Forbidden."),
88
+ headers: {
89
+ content_type: "application/json; charset=utf8"
90
+ }
91
+ }
92
+ end
93
+
94
+ let(:chef_not_found) do
95
+ {
96
+ status: 404,
97
+ body: generate_normalized_json(error: [ "No routes match the request: /organizations/vialstudios/cookbookss/not_existant" ]),
98
+ headers: {
99
+ content_type: "application/json; charset=utf8"
100
+ }
101
+ }
102
+ end
103
+
104
+ let(:chef_conflict) do
105
+ {
106
+ status: 409,
107
+ body: generate_normalized_json(error: "409 - Conflict."),
108
+ headers: {
109
+ content_type: "application/json; charset=utf8"
110
+ }
111
+ }
112
+ end
113
+
114
+ describe "400 Bad Request" do
115
+ before(:each) do
116
+ stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_bad_request)
117
+ end
118
+
119
+ it "raises a Ridley::Errors::HTTPBadRequest" do
120
+ lambda {
121
+ subject.get('cookbooks')
122
+ }.should raise_error(Ridley::Errors::HTTPBadRequest)
123
+ end
124
+
125
+ it "should have the body of the response as the error's message" do
126
+ lambda {
127
+ subject.get('cookbooks')
128
+ }.should raise_error("errors: '400 - Bad Request: Valid X-CHEF-VERSION header is required.'")
129
+ end
130
+ end
131
+
132
+ describe "401 Unauthorized" do
133
+ before(:each) do
134
+ stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_unauthorized)
135
+ end
136
+
137
+ it "raises a Ridley::Errors::HTTPUnauthorized" do
138
+ lambda {
139
+ subject.get('cookbooks')
140
+ }.should raise_error(Ridley::Errors::HTTPUnauthorized)
141
+ end
142
+
143
+ it "should have the body of the response as the error's message" do
144
+ lambda {
145
+ subject.get('cookbooks')
146
+ }.should raise_error("errors: '401 - Unauthorized. You must properly authenticate your API requests!'")
147
+ end
148
+ end
149
+
150
+ describe "403 Forbidden" do
151
+ before(:each) do
152
+ stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_forbidden)
153
+ end
154
+
155
+ it "raises a Ridley::Errors::HTTPForbidden" do
156
+ lambda {
157
+ subject.get('cookbooks')
158
+ }.should raise_error(Ridley::Errors::HTTPForbidden)
159
+ end
160
+
161
+ it "should have the body of the response as the error's message" do
162
+ lambda {
163
+ subject.get('cookbooks')
164
+ }.should raise_error("errors: '403 - Forbidden.'")
165
+ end
166
+ end
167
+
168
+ describe "404 Not Found" do
169
+ before(:each) do
170
+ stub_request(:get, File.join(server_url, 'not_existant_route')).to_return(chef_not_found)
171
+ end
172
+
173
+ it "raises a Ridley::Errors::HTTPNotFound" do
174
+ lambda {
175
+ subject.get('not_existant_route')
176
+ }.should raise_error(Ridley::Errors::HTTPNotFound)
177
+ end
178
+
179
+ it "should have the body of the response as the error's message" do
180
+ lambda {
181
+ subject.get('not_existant_route')
182
+ }.should raise_error(Ridley::Errors::HTTPNotFound, "errors: 'No routes match the request: /organizations/vialstudios/cookbookss/not_existant'")
183
+ end
184
+ end
185
+
186
+ describe "409 Conflict" do
187
+ before(:each) do
188
+ stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_conflict)
189
+ end
190
+
191
+ it "raises a Ridley::Errors::HTTPForbidden" do
192
+ lambda {
193
+ subject.get('cookbooks')
194
+ }.should raise_error(Ridley::Errors::HTTPConflict)
195
+ end
196
+
197
+ it "should have the body of the response as the error's message" do
198
+ lambda {
199
+ subject.get('cookbooks')
200
+ }.should raise_error("errors: '409 - Conflict.'")
201
+ end
202
+ end
203
+
204
+ describe "200 OK" do
205
+ before(:each) do
206
+ stub_request(:get, File.join(server_url, 'roles/reset')).to_return(chef_ok)
207
+ end
208
+
209
+ it "returns a body containing a hash" do
210
+ subject.get('roles/reset').env[:body].should be_a(Hash)
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ridley::Middleware::ParseJson do
4
+ let(:server_url) { "https://api.opscode.com/organizations/vialstudios/" }
5
+
6
+ describe "ClassMethods" do
7
+ subject { Ridley::Middleware::ParseJson }
8
+
9
+ describe "::response_type" do
10
+ it "returns the first element of the response content-type" do
11
+ env = double('env')
12
+ env.stub(:[]).with(:response_headers).and_return(
13
+ 'content-type' => 'text/html; charset=utf-8'
14
+ )
15
+
16
+ subject.response_type(env).should eql("text/html")
17
+ end
18
+ end
19
+
20
+ describe "::json_response?" do
21
+ it "returns true if the value of content-type includes 'application/json'" do
22
+ env = double('env')
23
+ env.stub(:[]).with(:response_headers).and_return(
24
+ 'content-type' => 'application/json; charset=utf8'
25
+ )
26
+
27
+ subject.json_response?(env).should be_true
28
+ end
29
+
30
+ it "returns true if the value of content-type does not include 'application/json' and the body contains JSON" do
31
+ env = double('env')
32
+ env.stub(:[]).with(:response_headers).and_return(
33
+ 'content-type' => 'text/plain'
34
+ )
35
+ subject.should_receive(:looks_like_json?).with(env).and_return(true)
36
+
37
+ subject.json_response?(env).should be_true
38
+ end
39
+
40
+ it "returns false if the value of content-type does not include 'application/json' and the body does not contain JSON" do
41
+ env = double('env')
42
+ env.stub(:[]).with(:response_headers).and_return(
43
+ 'content-type' => 'text/plain'
44
+ )
45
+ subject.should_receive(:looks_like_json?).with(env).and_return(false)
46
+
47
+ subject.json_response?(env).should be_false
48
+ end
49
+ end
50
+
51
+ describe "::looks_like_json?" do
52
+ let(:env) { double('env') }
53
+
54
+ it "returns true if the given string contains JSON brackets" do
55
+ env.stub(:[]).with(:body).and_return("{\"name\":\"jamie\"}")
56
+
57
+ subject.looks_like_json?(env).should be_true
58
+ end
59
+
60
+ it "returns false if the given string does not contain JSON brackets" do
61
+ env.stub(:[]).with(:body).and_return("name")
62
+
63
+ subject.looks_like_json?(env).should be_false
64
+ end
65
+ end
66
+ end
67
+
68
+ subject do
69
+ Faraday.new(server_url) do |conn|
70
+ conn.response :json
71
+ conn.adapter Faraday.default_adapter
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,214 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ridley::Resource do
4
+ let(:connection) { double('connection') }
5
+
6
+ describe "ClassMethods" do
7
+ subject do
8
+ Class.new do
9
+ include Ridley::Resource
10
+ end
11
+ end
12
+
13
+ it_behaves_like "a Ridley Resource", subject.call
14
+
15
+ describe "::initialize" do
16
+ it "has an empty Hash for attributes if no attributes have been defined" do
17
+ klass = subject.new(connection)
18
+
19
+ klass.attributes.should be_empty
20
+ end
21
+
22
+ it "assigns the given attributes to the attribute hash if the attribute is defined on the class" do
23
+ subject.attribute(:name)
24
+ klass = subject.new(connection, name: "a name")
25
+
26
+ klass.name.should eql("a name")
27
+ klass.attributes.should have_key(:name)
28
+ end
29
+
30
+ it "skips attributes which are not defined on the class when assigning attributes" do
31
+ klass = subject.new(connection, fake: "not here")
32
+
33
+ klass.attributes.should_not have_key(:fake)
34
+ end
35
+
36
+ it "merges the default values for attributes into the attributes hash" do
37
+ subject.stub(:attributes).and_return(Set.new([:name]))
38
+ subject.should_receive(:attribute_defaults).and_return(name: "whatever")
39
+ klass = subject.new(connection)
40
+
41
+ klass.attributes[:name].should eql("whatever")
42
+ end
43
+
44
+ it "explicit attributes take precedence over defaults" do
45
+ subject.stub(:attributes).and_return(Set.new([:name]))
46
+ subject.stub(:attribute_defaults).and_return(name: "default")
47
+
48
+ klass = subject.new(connection, name: "explicit_name")
49
+
50
+ klass.attributes[:name].should eql("explicit_name")
51
+ end
52
+ end
53
+
54
+ describe "::attributes" do
55
+ it "returns a Set" do
56
+ subject.attributes.should be_a(Set)
57
+ end
58
+ end
59
+
60
+ describe "::attribute" do
61
+ it "adds the given symbol to the attributes Set" do
62
+ subject.attribute(:name)
63
+
64
+ subject.attributes.should include(:name)
65
+ end
66
+
67
+ it "convers the given string into a symbol and adds it to the attributes Set" do
68
+ subject.attribute('last_name')
69
+
70
+ subject.attributes.should include(:last_name)
71
+ end
72
+
73
+ describe "setting a default value for the attribute" do
74
+ it "allows a string as the default value for the attribute" do
75
+ subject.attribute(:name, default: "jamie")
76
+
77
+ subject.attribute_defaults[:name].should eql("jamie")
78
+ end
79
+
80
+ it "allows a false boolean as the default value for the attribute" do
81
+ subject.attribute(:admin, default: false)
82
+
83
+ subject.attribute_defaults[:admin].should eql(false)
84
+ end
85
+
86
+ it "allows a true boolean as the default value for the attribute" do
87
+ subject.attribute(:admin, default: true)
88
+
89
+ subject.attribute_defaults[:admin].should eql(true)
90
+ end
91
+
92
+ it "allows nil as the default value for the attribute" do
93
+ subject.attribute(:certificate, default: nil)
94
+
95
+ subject.attribute_defaults[:certificate].should be_nil
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "::set_chef_type" do
101
+ it "sets the chef_type attr on the class" do
102
+ subject.set_chef_type("environment")
103
+
104
+ subject.chef_type.should eql("environment")
105
+ end
106
+ end
107
+
108
+ describe "::set_resource_path" do
109
+ it "sets the resource_path attr on the class" do
110
+ subject.set_resource_path("environments")
111
+
112
+ subject.resource_path.should eql("environments")
113
+ end
114
+ end
115
+
116
+ describe "::set_chef_json_class" do
117
+ it "sets the chef_json_class attr on the class" do
118
+ subject.set_chef_json_class("Chef::Environment")
119
+
120
+ subject.chef_json_class.should eql("Chef::Environment")
121
+ end
122
+
123
+ it "sets the value of the :json_class attribute default to the given value" do
124
+ subject.set_chef_json_class("Chef::Environment")
125
+
126
+ subject.attribute_defaults.should have_key(:json_class)
127
+ subject.attribute_defaults[:json_class].should eql("Chef::Environment")
128
+ end
129
+ end
130
+
131
+ describe "::set_chef_id" do
132
+ it "sets the chef_id attribute on the class" do
133
+ subject.set_chef_id(:environment)
134
+
135
+ subject.chef_id.should eql(:environment)
136
+ end
137
+ end
138
+
139
+ describe "::resource_path" do
140
+ it "returns the underscored and plural name of the including class if nothing is set" do
141
+ subject.resource_path.should eql(subject.class.name.underscore.pluralize)
142
+ end
143
+ end
144
+
145
+ describe "::chef_type" do
146
+ it "returns the underscored name of the including class if nothing is set" do
147
+ subject.chef_type.should eql(subject.class.name.underscore)
148
+ end
149
+ end
150
+
151
+ describe "::chef_json_class" do
152
+ it "returns the chef_json if nothing has been set" do
153
+ subject.chef_json_class.should be_nil
154
+ end
155
+ end
156
+
157
+ describe "::chef_id" do
158
+ it "returns nil if nothing is set" do
159
+ subject.chef_id.should be_nil
160
+ end
161
+ end
162
+ end
163
+
164
+ subject do
165
+ Class.new do
166
+ include Ridley::Resource
167
+ end.new(connection)
168
+ end
169
+
170
+ describe "#attribute?" do
171
+ context "if the class has the attribute defined" do
172
+ before(:each) do
173
+ subject.class.attribute(:name)
174
+ end
175
+
176
+ it "returns false if the attribute has no value" do
177
+ subject.name = nil
178
+
179
+ subject.attribute?(:name).should be_false
180
+ end
181
+
182
+ it "returns true if the attribute has a value" do
183
+ subject.name = "reset"
184
+
185
+ subject.attribute?(:name).should be_true
186
+ end
187
+ end
188
+
189
+ context "if the class has the attribute defined with a default value" do
190
+ before(:each) do
191
+ subject.class.attribute(:name, default: "exception")
192
+ end
193
+
194
+ it "returns true if the attribute has not been explicitly set" do
195
+ subject.attribute?(:name).should be_true
196
+ end
197
+ end
198
+
199
+ context "if the class does not have the attribute defined" do
200
+ it "returns false" do
201
+ subject.attribute?(:name).should be_false
202
+ end
203
+ end
204
+ end
205
+
206
+ describe "#attributes=" do
207
+ it "assigns the hash of attributes to the objects attributes" do
208
+ subject.class.attribute(:name)
209
+ subject.attributes = { name: "reset" }
210
+
211
+ subject.attributes[:name].should eql("reset")
212
+ end
213
+ end
214
+ end