ridley 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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