chef-api 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/CHANGELOG.md +17 -0
  4. data/README.md +42 -0
  5. data/chef-api.gemspec +1 -1
  6. data/lib/chef-api/configurable.rb +3 -2
  7. data/lib/chef-api/connection.rb +77 -47
  8. data/lib/chef-api/defaults.rb +28 -8
  9. data/lib/chef-api/errors.rb +32 -3
  10. data/lib/chef-api/resource.rb +1 -0
  11. data/lib/chef-api/resources/base.rb +30 -21
  12. data/lib/chef-api/resources/client.rb +6 -8
  13. data/lib/chef-api/resources/collection_proxy.rb +19 -2
  14. data/lib/chef-api/resources/data_bag.rb +1 -1
  15. data/lib/chef-api/resources/organization.rb +22 -0
  16. data/lib/chef-api/resources/user.rb +72 -1
  17. data/lib/chef-api/schema.rb +59 -21
  18. data/lib/chef-api/version.rb +1 -1
  19. data/lib/chef-api.rb +26 -5
  20. data/spec/integration/resources/client_spec.rb +54 -0
  21. data/spec/integration/resources/user_spec.rb +8 -0
  22. data/spec/spec_helper.rb +1 -1
  23. data/spec/support/chef_server.rb +1 -0
  24. data/spec/unit/errors_spec.rb +294 -0
  25. data/spec/unit/resources/client_spec.rb +0 -16
  26. data/spec/unit/resources/connection_spec.rb +51 -0
  27. data/templates/errors/abstract_method.erb +5 -0
  28. data/templates/errors/cannot_regenerate_key.erb +1 -0
  29. data/templates/errors/chef_api_error.erb +1 -0
  30. data/templates/errors/file_not_found.erb +1 -0
  31. data/templates/errors/http_bad_request.erb +3 -0
  32. data/templates/errors/http_forbidden_request.erb +3 -0
  33. data/templates/errors/http_gateway_timeout.erb +3 -0
  34. data/templates/errors/http_method_not_allowed.erb +3 -0
  35. data/templates/errors/http_not_acceptable.erb +3 -0
  36. data/templates/errors/http_not_found.erb +3 -0
  37. data/templates/errors/http_server_unavailable.erb +1 -0
  38. data/templates/errors/http_unauthorized_request.erb +3 -0
  39. data/templates/errors/insufficient_file_permissions.erb +1 -0
  40. data/templates/errors/invalid_resource.erb +1 -0
  41. data/templates/errors/invalid_validator.erb +1 -0
  42. data/templates/errors/missing_url_parameter.erb +1 -0
  43. data/templates/errors/not_a_directory.erb +1 -0
  44. data/templates/errors/resource_already_exists.erb +1 -0
  45. data/templates/errors/resource_not_found.erb +1 -0
  46. data/templates/errors/resource_not_mutable.erb +1 -0
  47. data/templates/errors/unknown_attribute.erb +1 -0
  48. metadata +43 -17
  49. data/lib/chef-api/logger.rb +0 -160
  50. data/lib/chef-api/proxy.rb +0 -72
  51. data/locales/en.yml +0 -89
@@ -20,7 +20,7 @@ module ChefAPI
20
20
  # @todo doc
21
21
  #
22
22
  def from_url(url, prefix = {})
23
- from_json(ChefAPI.connection.get(url), prefix)
23
+ from_json(connection.get(url), prefix)
24
24
  end
25
25
 
26
26
  #
@@ -78,13 +78,13 @@ module ChefAPI
78
78
  # has_many :environments, class_name: 'Environment'
79
79
  #
80
80
  def has_many(method, options = {})
81
- class_name = options[:class_name] || Util.camelize(method).sub(/s$/, '')
81
+ class_name = options[:class_name] || "Resource::#{Util.camelize(method).sub(/s$/, '')}"
82
82
  rest_endpoint = options[:rest_endpoint] || method
83
83
 
84
84
  class_eval <<-EOH, __FILE__, __LINE__ + 1
85
85
  def #{method}
86
86
  associations[:#{method}] ||=
87
- CollectionProxy.new(self, #{class_name}, '#{rest_endpoint}')
87
+ Resource::CollectionProxy.new(self, #{class_name}, '#{rest_endpoint}')
88
88
  end
89
89
  EOH
90
90
  end
@@ -138,7 +138,7 @@ module ChefAPI
138
138
  #
139
139
  def post(body, prefix = {})
140
140
  path = expanded_collection_path(prefix)
141
- ChefAPI.connection.post(path, body)
141
+ connection.post(path, body)
142
142
  end
143
143
 
144
144
  #
@@ -159,7 +159,7 @@ module ChefAPI
159
159
  #
160
160
  def put(id, body, prefix = {})
161
161
  path = resource_path(id, prefix)
162
- ChefAPI.connection.put(path, body)
162
+ connection.put(path, body)
163
163
  end
164
164
 
165
165
  #
@@ -173,7 +173,7 @@ module ChefAPI
173
173
  #
174
174
  def delete(id, prefix = {})
175
175
  path = resource_path(id, prefix)
176
- ChefAPI.connection.delete(path)
176
+ connection.delete(path)
177
177
  true
178
178
  rescue Error::HTTPNotFound
179
179
  true
@@ -195,7 +195,7 @@ module ChefAPI
195
195
  #
196
196
  def list(prefix = {})
197
197
  path = expanded_collection_path(prefix)
198
- ChefAPI.connection.get(path).keys.sort
198
+ connection.get(path).keys.sort
199
199
  end
200
200
 
201
201
  #
@@ -249,7 +249,7 @@ module ChefAPI
249
249
  return nil if id.nil?
250
250
 
251
251
  path = resource_path(id, prefix)
252
- response = ChefAPI.connection.get(path)
252
+ response = connection.get(path)
253
253
  from_json(response, prefix)
254
254
  rescue Error::HTTPNotFound
255
255
  nil
@@ -363,7 +363,7 @@ module ChefAPI
363
363
  #
364
364
  def each(prefix = {}, &block)
365
365
  collection(prefix).each do |resource, path|
366
- response = ChefAPI.connection.get(path)
366
+ response = connection.get(path)
367
367
  result = from_json(response, prefix)
368
368
 
369
369
  block.call(result) if block
@@ -383,9 +383,8 @@ module ChefAPI
383
383
  #
384
384
  # Return an array of all resources in the collection.
385
385
  #
386
- # @warn
387
- # Unless you need the _entire_ collection, please consider using the
388
- # {size} and {each} methods instead as they are much more perforant.
386
+ # @note Unless you need the _entire_ collection, please consider using the
387
+ # {size} and {each} methods instead as they are much more perforant.
389
388
  #
390
389
  # @return [Array<Resource::Base>]
391
390
  #
@@ -470,7 +469,7 @@ module ChefAPI
470
469
  # a list of resources in the collection
471
470
  #
472
471
  def collection(prefix = {})
473
- ChefAPI.connection.get(expanded_collection_path(prefix))
472
+ connection.get(expanded_collection_path(prefix))
474
473
  end
475
474
 
476
475
  #
@@ -516,6 +515,15 @@ module ChefAPI
516
515
  URI.escape(value)
517
516
  end.sub(/^\//, '') # Remove leading slash
518
517
  end
518
+
519
+ #
520
+ # The current connection object.
521
+ #
522
+ # @return [ChefAPI::Connection]
523
+ #
524
+ def connection
525
+ Thread.current['chefapi.connection'] || ChefAPI.connection
526
+ end
519
527
  end
520
528
 
521
529
  #
@@ -546,6 +554,9 @@ module ChefAPI
546
554
  # the list of prefix options (for nested resources)
547
555
  #
548
556
  def initialize(attributes = {}, prefix = {})
557
+ @schema = self.class.schema.dup
558
+ @schema.load_flavor(self.class.connection.flavor)
559
+
549
560
  @associations = {}
550
561
  @_prefix = prefix
551
562
 
@@ -571,7 +582,7 @@ module ChefAPI
571
582
  # the primary key for this resource
572
583
  #
573
584
  def primary_key
574
- self.class.schema.primary_key
585
+ @schema.primary_key
575
586
  end
576
587
 
577
588
  #
@@ -596,7 +607,7 @@ module ChefAPI
596
607
  # @return [Hash<Symbol, Object>]
597
608
  #
598
609
  def _attributes
599
- @_attributes ||= {}.merge(self.class.schema.attributes)
610
+ @_attributes ||= {}.merge(@schema.attributes)
600
611
  end
601
612
 
602
613
  #
@@ -636,8 +647,7 @@ module ChefAPI
636
647
  # so they will be reloaded the next time they are requested. If the remote
637
648
  # record does not exist, no attributes are modified.
638
649
  #
639
- # @warn
640
- # This will remove any custom values you have set on the resource!
650
+ # @note This will remove any custom values you have set on the resource!
641
651
  #
642
652
  # @return [self]
643
653
  # the instance of the reloaded record
@@ -748,7 +758,7 @@ module ChefAPI
748
758
  # the list of validators for this resource
749
759
  #
750
760
  def validators
751
- @validators ||= self.class.schema.validators
761
+ @validators ||= @schema.validators
752
762
  end
753
763
 
754
764
  #
@@ -845,8 +855,7 @@ module ChefAPI
845
855
  # bacon.description = "My new description"
846
856
  # bacon.diff #=> { :description => { :local => "My new description", :remote => "Old description" } }
847
857
  #
848
- # @warn
849
- # This is a VERY expensive operation - use it sparringly!
858
+ # @note This is a VERY expensive operation - use it sparringly!
850
859
  #
851
860
  # @return [Hash]
852
861
  #
@@ -887,7 +896,7 @@ module ChefAPI
887
896
  # @return [Boolean]
888
897
  #
889
898
  def ignore_attribute?(key)
890
- self.class.schema.ignored_attributes.has_key?(key.to_sym)
899
+ @schema.ignored_attributes.has_key?(key.to_sym)
891
900
  end
892
901
 
893
902
  #
@@ -26,7 +26,7 @@ module ChefAPI
26
26
  # @return [Resource::Client]
27
27
  #
28
28
  def from_file(path)
29
- name, contents = Util.safe_read(path)
29
+ name, key = Util.safe_read(path)
30
30
 
31
31
  if client = fetch(name)
32
32
  client.private_key = key
@@ -63,15 +63,13 @@ module ChefAPI
63
63
  # key = client.regenerate_key
64
64
  # key #=> "-----BEGIN PRIVATE KEY-----\nMIGfMA0GCS..."
65
65
  #
66
- # @warn
67
- # For security reasons, you should perform this operation sparingly! The
68
- # resulting private key is committed to this object, meaning it is saved
69
- # to memory somewhere. You should set this resource's +private_key+ to
70
- # +nil+ after you have committed it to disk and perform a manual GC to
66
+ # @note For security reasons, you should perform this operation sparingly!
67
+ # The resulting private key is committed to this object, meaning it is
68
+ # saved to memory somewhere. You should set this resource's +private_key+
69
+ # to +nil+ after you have committed it to disk and perform a manual GC to
71
70
  # be ultra-secure.
72
71
  #
73
- # @warn
74
- # Regenerating the private key also regenerates the public key!
72
+ # @note Regenerating the private key also regenerates the public key!
75
73
  #
76
74
  # @return [self]
77
75
  # the current resource with the new public and private key attributes
@@ -157,6 +157,11 @@ module ChefAPI
157
157
  #
158
158
  # ["item_1", "item_2"]
159
159
  #
160
+ # Or if the Chef Server is feeling especially magical, it might return the
161
+ # actual objects, but prefixed with the JSON id:
162
+ #
163
+ # [{"organization" => {"_id" => "..."}}, {"organization" => {...}}]
164
+ #
160
165
  # So, this method attempts to intelligent handle these use cases. That being
161
166
  # said, I can almost guarantee that someone is going to do some crazy
162
167
  # strange edge case with this library and hit a bug here, so it will likely
@@ -165,9 +170,21 @@ module ChefAPI
165
170
  # @return [Hash]
166
171
  #
167
172
  def load_collection
168
- case response = ChefAPI.connection.get(endpoint)
173
+ case response = Resource::Base.connection.get(endpoint)
169
174
  when Array
170
- Hash[*response.map { |item| [item, klass.resource_path(item)] }.flatten]
175
+ if response.first.is_a?(Hash)
176
+ key = klass.schema.primary_key.to_s
177
+
178
+ {}.tap do |hash|
179
+ response.each do |results|
180
+ results.each do |_, info|
181
+ hash[key] = klass.resource_path(info[key])
182
+ end
183
+ end
184
+ end
185
+ else
186
+ Hash[*response.map { |item| [item, klass.resource_path(item)] }.flatten]
187
+ end
171
188
  when Hash
172
189
  response
173
190
  end
@@ -47,7 +47,7 @@ module ChefAPI
47
47
  return nil if id.nil?
48
48
 
49
49
  path = resource_path(id, prefix)
50
- response = ChefAPI.connection.get(path)
50
+ response = connection.get(path)
51
51
  new(name: id)
52
52
  rescue Error::HTTPNotFound
53
53
  nil
@@ -0,0 +1,22 @@
1
+ module ChefAPI
2
+ class Resource::Organization < Resource::Base
3
+ collection_path '/organizations'
4
+
5
+ schema do
6
+ attribute :name, type: String, primary: true, required: true
7
+ attribute :org_type, type: String
8
+ attribute :full_name, type: String
9
+ attribute :clientname, type: String
10
+ attribute :guid, type: String
11
+
12
+ ignore :_id
13
+ ignore :_rev
14
+ ignore :chargify_subscription_id
15
+ ignore :chargify_customer_id
16
+ ignore :billing_plan
17
+ ignore :requester_id
18
+ ignore :assigned_at
19
+ ignore 'couchrest-type'
20
+ end
21
+ end
22
+ end
@@ -3,9 +3,80 @@ module ChefAPI
3
3
  collection_path '/users'
4
4
 
5
5
  schema do
6
- attribute :name, type: String, primary: true, required: true
6
+ flavor :enterprise do
7
+ attribute :username, type: String, primary: true, required: true
8
+
9
+ # "Vanity" attributes
10
+ attribute :first_name, type: String
11
+ attribute :middle_name, type: String
12
+ attribute :last_name, type: String
13
+ attribute :display_name, type: String
14
+ attribute :email, type: String
15
+ attribute :city, type: String
16
+ attribute :country, type: String
17
+ attribute :twitter_account, type: String
18
+ end
19
+
20
+ flavor :open_source do
21
+ attribute :name, type: String, primary: true, required: true
22
+ end
23
+
7
24
  attribute :admin, type: Boolean, default: false
8
25
  attribute :public_key, type: String
26
+ attribute :private_key, type: [String, Boolean], default: false
27
+ end
28
+
29
+ has_many :organizations
30
+
31
+ class << self
32
+ #
33
+ # @see Base.each
34
+ #
35
+ def each(prefix = {}, &block)
36
+ users = collection(prefix)
37
+
38
+ # HEC/EC returns a slightly different response than OSC/CZ
39
+ if users.is_a?(Array)
40
+ users.each do |info|
41
+ name = URI.escape(info['user']['username'])
42
+ response = connection.get("/users/#{name}")
43
+ result = from_json(response, prefix)
44
+
45
+ block.call(result) if block
46
+ end
47
+ else
48
+ users.each do |_, path|
49
+ response = connection.get(path)
50
+ result = from_json(response, prefix)
51
+
52
+ block.call(result) if block
53
+ end
54
+ end
55
+ end
56
+
57
+ #
58
+ # Authenticate a user with the given +username+ and +password+.
59
+ #
60
+ # @note Requires Enterprise Chef
61
+ #
62
+ # @example Authenticate a user
63
+ # User.authenticate(username: 'user', password: 'pass')
64
+ # #=> { "status" => "linked", "user" => { ... } }
65
+ #
66
+ # @param [Hash] options
67
+ # the list of options to authenticate with
68
+ #
69
+ # @option options [String] username
70
+ # the username to authenticate with
71
+ # @option options [String] password
72
+ # the plain-text password to authenticate with
73
+ #
74
+ # @return [Hash]
75
+ # the parsed JSON response from the server
76
+ #
77
+ def authenticate(options = {})
78
+ connection.post('/authenticate_user', options)
79
+ end
9
80
  end
10
81
  end
11
82
  end
@@ -13,7 +13,6 @@ module ChefAPI
13
13
  attr_reader :attributes
14
14
 
15
15
  attr_reader :ignored_attributes
16
- attr_reader :transformations
17
16
 
18
17
  #
19
18
  # The list of defined validators for this schema.
@@ -28,15 +27,10 @@ module ChefAPI
28
27
  def initialize(&block)
29
28
  @attributes = {}
30
29
  @ignored_attributes = {}
31
- @transformations = {}
30
+ @flavor_attributes = {}
32
31
  @validators = []
33
32
 
34
- instance_eval(&block) if block
35
-
36
- @attributes.freeze
37
- @ignored_attributes.freeze
38
- @transformations.freeze
39
- @validators.freeze
33
+ unlock { instance_eval(&block) } if block
40
34
  end
41
35
 
42
36
  #
@@ -49,6 +43,45 @@ module ChefAPI
49
43
  @primary_key ||= @attributes.first[0]
50
44
  end
51
45
 
46
+ #
47
+ # Create a lazy-loaded block for a given flavor.
48
+ #
49
+ # @example Create a block for Enterprise Chef
50
+ # flavor :enterprise do
51
+ # attribute :custom_value
52
+ # end
53
+ #
54
+ # @param [Symbol] id
55
+ # the id of the flavor to target
56
+ # @param [Proc] block
57
+ # the block to capture
58
+ #
59
+ # @return [Proc]
60
+ # the given block
61
+ #
62
+ def flavor(id, &block)
63
+ @flavor_attributes[id] = block
64
+ block
65
+ end
66
+
67
+ #
68
+ # Load the flavor block for the given id.
69
+ #
70
+ # @param [Symbol] id
71
+ # the id of the flavor to target
72
+ #
73
+ # @return [true, false]
74
+ # true if the flavor existed and was evaluted, false otherwise
75
+ #
76
+ def load_flavor(id)
77
+ if block = @flavor_attributes[id]
78
+ unlock { instance_eval(&block) }
79
+ true
80
+ else
81
+ false
82
+ end
83
+ end
84
+
52
85
  #
53
86
  # DSL method for defining an attribute.
54
87
  #
@@ -91,22 +124,27 @@ module ChefAPI
91
124
  end
92
125
  end
93
126
 
127
+ private
128
+
94
129
  #
95
- # Transform an attribute onto another.
96
- #
97
- # @example Transform the +:bacon+ attribute onto the +:ham+ attribute
98
- # transform :bacon, ham: true
99
- #
100
- # @example Transform an attribute with a complex transformation
101
- # transform :bacon, ham: ->(value) { value.split('__', 2).last }
130
+ # @private
102
131
  #
103
- # @param [Symbol] key
104
- # the attribute to transform
105
- # @param [Hash] options
106
- # the key-value pair of the transformations to make
132
+ # Helper method to duplicate and unfreeze all the attributes in the schema,
133
+ # yield control to the user for modification in the current context, and
134
+ # then re-freeze the variables for modification.
107
135
  #
108
- def transform(key, options = {})
109
- @transformations[key.to_sym] = options
136
+ def unlock
137
+ @attributes = @attributes.dup
138
+ @ignored_attributes = @ignored_attributes.dup
139
+ @flavor_attributes = @flavor_attributes.dup
140
+ @validators = @validators.dup
141
+
142
+ yield
143
+
144
+ @attributes.freeze
145
+ @ignored_attributes.freeze
146
+ @flavor_attributes.freeze
147
+ @validators.freeze
110
148
  end
111
149
  end
112
150
  end
@@ -1,3 +1,3 @@
1
1
  module ChefAPI
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
data/lib/chef-api.rb CHANGED
@@ -1,7 +1,11 @@
1
1
  require 'json'
2
+ require 'logify'
2
3
  require 'pathname'
3
4
  require 'chef-api/version'
4
5
 
6
+ # Do not inflate JSON objects
7
+ JSON.create_id = nil
8
+
5
9
  module ChefAPI
6
10
  autoload :Boolean, 'chef-api/boolean'
7
11
  autoload :Configurable, 'chef-api/configurable'
@@ -9,7 +13,6 @@ module ChefAPI
9
13
  autoload :Defaults, 'chef-api/defaults'
10
14
  autoload :Error, 'chef-api/errors'
11
15
  autoload :ErrorCollection, 'chef-api/error_collection'
12
- autoload :Logger, 'chef-api/logger'
13
16
  autoload :Resource, 'chef-api/resource'
14
17
  autoload :Schema, 'chef-api/schema'
15
18
  autoload :Util, 'chef-api/util'
@@ -23,6 +26,28 @@ module ChefAPI
23
26
  class << self
24
27
  include ChefAPI::Configurable
25
28
 
29
+ #
30
+ # Set the log level.
31
+ #
32
+ # @example Set the log level to :info
33
+ # ChefAPI.log_level = :info
34
+ #
35
+ # @param [Symbol] level
36
+ # the log level to set
37
+ #
38
+ def log_level=(level)
39
+ Logify.level = level
40
+ end
41
+
42
+ #
43
+ # Get the current log level.
44
+ #
45
+ # @return [Symbol]
46
+ #
47
+ def log_level
48
+ Logify.level
49
+ end
50
+
26
51
  #
27
52
  # The source root of the ChefAPI gem. This is useful when requiring files
28
53
  # that are relative to the root of the project.
@@ -67,10 +92,6 @@ module ChefAPI
67
92
  end
68
93
  end
69
94
 
70
- require 'i18n'
71
- I18n.enforce_available_locales = false
72
- I18n.load_path << Dir[ChefAPI.root.join('locales', '*.yml').to_s]
73
-
74
95
  # Load the initial default values
75
96
  ChefAPI.setup
76
97
 
@@ -4,5 +4,59 @@ module ChefAPI
4
4
  describe Resource::Client do
5
5
  it_behaves_like 'a Chef API resource', :client,
6
6
  update: { validator: true }
7
+
8
+ describe '.from_file' do
9
+ let(:private_key) do
10
+ <<-EOH.strip.gsub(/^ {10}/, '')
11
+ -----BEGIN RSA PRIVATE KEY-----
12
+ MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI
13
+ w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP
14
+ kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2
15
+ hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO
16
+ Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW
17
+ yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd
18
+ ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1
19
+ Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf
20
+ TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK
21
+ iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A
22
+ sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf
23
+ 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP
24
+ cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk
25
+ EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN
26
+ CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX
27
+ 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG
28
+ YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj
29
+ 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+
30
+ dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz
31
+ 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC
32
+ P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF
33
+ llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ
34
+ kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH
35
+ +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ
36
+ NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s=
37
+ -----END RSA PRIVATE KEY-----
38
+ EOH
39
+ end
40
+
41
+ let(:client) { described_class.from_file('/path/to/bacon.pem') }
42
+
43
+ before do
44
+ File.stub(:read).and_return(private_key)
45
+ end
46
+
47
+ it 'loads the client from the server' do
48
+ chef_server.create_client('bacon', validator: true)
49
+
50
+ expect(client.name).to eq('bacon')
51
+ expect(client.private_key).to eq(private_key)
52
+ expect(client.validator).to be_true
53
+ end
54
+
55
+ it 'creates a new instance when the client does not exist' do
56
+ expect(client.name).to eq('bacon')
57
+ expect(client.validator).to be_false
58
+ expect(client.new_resource?).to be_true
59
+ end
60
+ end
7
61
  end
8
62
  end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ module ChefAPI
4
+ describe Resource::User do
5
+ it_behaves_like 'a Chef API resource', :user,
6
+ update: { admin: true }
7
+ end
8
+ end
data/spec/spec_helper.rb CHANGED
@@ -15,7 +15,7 @@ RSpec.configure do |config|
15
15
 
16
16
  #
17
17
  config.before(:each) do
18
- ChefAPI::Logger.level = :fatal
18
+ Logify.level = :fatal
19
19
  end
20
20
 
21
21
  # Run specs in random order to surface order dependencies. If you find an
@@ -70,6 +70,7 @@ module RSpec
70
70
  ['environments', 'environment'],
71
71
  ['nodes', 'node'],
72
72
  ['roles', 'role'],
73
+ ['users', 'user'],
73
74
  ].each do |plural, singular|
74
75
  define_method(plural) do
75
76
  @server.data_store.list([plural])