chef-api 0.2.0 → 0.2.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 (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])