chef-infra-api 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/chef-api.rb +96 -0
  4. data/lib/chef-api/aclable.rb +35 -0
  5. data/lib/chef-api/authentication.rb +300 -0
  6. data/lib/chef-api/boolean.rb +6 -0
  7. data/lib/chef-api/configurable.rb +80 -0
  8. data/lib/chef-api/connection.rb +507 -0
  9. data/lib/chef-api/defaults.rb +197 -0
  10. data/lib/chef-api/error_collection.rb +44 -0
  11. data/lib/chef-api/errors.rb +64 -0
  12. data/lib/chef-api/multipart.rb +164 -0
  13. data/lib/chef-api/resource.rb +21 -0
  14. data/lib/chef-api/resources/base.rb +960 -0
  15. data/lib/chef-api/resources/client.rb +84 -0
  16. data/lib/chef-api/resources/collection_proxy.rb +234 -0
  17. data/lib/chef-api/resources/cookbook.rb +24 -0
  18. data/lib/chef-api/resources/cookbook_version.rb +23 -0
  19. data/lib/chef-api/resources/data_bag.rb +136 -0
  20. data/lib/chef-api/resources/data_bag_item.rb +53 -0
  21. data/lib/chef-api/resources/environment.rb +16 -0
  22. data/lib/chef-api/resources/group.rb +16 -0
  23. data/lib/chef-api/resources/node.rb +20 -0
  24. data/lib/chef-api/resources/organization.rb +22 -0
  25. data/lib/chef-api/resources/partial_search.rb +44 -0
  26. data/lib/chef-api/resources/principal.rb +11 -0
  27. data/lib/chef-api/resources/role.rb +18 -0
  28. data/lib/chef-api/resources/search.rb +47 -0
  29. data/lib/chef-api/resources/user.rb +82 -0
  30. data/lib/chef-api/schema.rb +150 -0
  31. data/lib/chef-api/util.rb +119 -0
  32. data/lib/chef-api/validator.rb +16 -0
  33. data/lib/chef-api/validators/base.rb +82 -0
  34. data/lib/chef-api/validators/required.rb +11 -0
  35. data/lib/chef-api/validators/type.rb +23 -0
  36. data/lib/chef-api/version.rb +3 -0
  37. data/templates/errors/abstract_method.erb +5 -0
  38. data/templates/errors/cannot_regenerate_key.erb +1 -0
  39. data/templates/errors/chef_api_error.erb +1 -0
  40. data/templates/errors/file_not_found.erb +1 -0
  41. data/templates/errors/http_bad_request.erb +3 -0
  42. data/templates/errors/http_forbidden_request.erb +3 -0
  43. data/templates/errors/http_gateway_timeout.erb +3 -0
  44. data/templates/errors/http_method_not_allowed.erb +3 -0
  45. data/templates/errors/http_not_acceptable.erb +3 -0
  46. data/templates/errors/http_not_found.erb +3 -0
  47. data/templates/errors/http_server_unavailable.erb +1 -0
  48. data/templates/errors/http_unauthorized_request.erb +3 -0
  49. data/templates/errors/insufficient_file_permissions.erb +1 -0
  50. data/templates/errors/invalid_resource.erb +1 -0
  51. data/templates/errors/invalid_validator.erb +1 -0
  52. data/templates/errors/missing_url_parameter.erb +1 -0
  53. data/templates/errors/not_a_directory.erb +1 -0
  54. data/templates/errors/resource_already_exists.erb +1 -0
  55. data/templates/errors/resource_not_found.erb +1 -0
  56. data/templates/errors/resource_not_mutable.erb +1 -0
  57. data/templates/errors/unknown_attribute.erb +1 -0
  58. metadata +130 -0
@@ -0,0 +1,197 @@
1
+ require 'chef-api/version'
2
+ require 'pathname'
3
+ require 'json'
4
+
5
+ module ChefAPI
6
+ module Defaults
7
+ # Default API endpoint
8
+ ENDPOINT = 'https://api.opscode.com/'.freeze
9
+
10
+ # Default User Agent header string
11
+ USER_AGENT = "ChefAPI Ruby Gem #{ChefAPI::VERSION}".freeze
12
+
13
+ class << self
14
+ #
15
+ # The list of calculated default options for the configuration.
16
+ #
17
+ # @return [Hash]
18
+ #
19
+ def options
20
+ Hash[Configurable.keys.map { |key| [key, send(key)] }]
21
+ end
22
+
23
+ #
24
+ # The Chef API configuration
25
+ #
26
+ # @return [Hash]
27
+ def config
28
+ path = config_path
29
+ @config ||= path.exist? ? JSON.parse(path.read) : {}
30
+ end
31
+
32
+ #
33
+ # Pathname to configuration file, or a blank Pathname.
34
+ #
35
+ # @return [Pathname] an expanded Pathname or a non-existent Pathname
36
+ def config_path
37
+ if result = chef_api_config_path
38
+ Pathname(result).expand_path
39
+ else
40
+ Pathname('')
41
+ end
42
+ end
43
+
44
+ #
45
+ # String representation of path to configuration file
46
+ #
47
+ # @return [String, nil] Path to config file, or nil
48
+ def chef_api_config_path
49
+ ENV['CHEF_API_CONFIG'] || if ENV.key?('HOME')
50
+ '~/.chef-api'
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ #
57
+ # The endpoint where the Chef Server lives. This is equivalent to the
58
+ # +chef_server_url+ in Chef terminology. If you are using Enterprise
59
+ # Hosted Chef or Enterprise Chef on premise, this endpoint should include
60
+ # your organization name. For example:
61
+ #
62
+ # https://api.opscode.com/organizations/bacon
63
+ #
64
+ # If you are running Open Source Chef Server or Chef Zero, this is the
65
+ # full URL to your Chef Server instance, including the server port and
66
+ # FQDN.
67
+ #
68
+ # https://chef.server.local:4567/
69
+ #
70
+ # @return [String] (default: +https://api.opscode.com/+)
71
+ #
72
+ def endpoint
73
+ ENV['CHEF_API_ENDPOINT'] || config['CHEF_API_ENDPOINT'] || ENDPOINT
74
+ end
75
+
76
+ #
77
+ # The flavor of the target Chef Server. There are two possible values:
78
+ #
79
+ # - enterprise
80
+ # - open_source
81
+ #
82
+ # "Enterprise" covers both Hosted Chef and Enterprise Chef. "Open Source"
83
+ # covers both Chef Zero and Open Source Chef Server.
84
+ #
85
+ # @return [true, false]
86
+ #
87
+ def flavor
88
+ if ENV['CHEF_API_FLAVOR']
89
+ ENV['CHEF_API_FLAVOR'].to_sym
90
+ elsif config['CHEF_API_FLAVOR']
91
+ config['CHEF_API_FLAVOR']
92
+ else
93
+ endpoint.include?('/organizations') ? :enterprise : :open_source
94
+ end
95
+ end
96
+
97
+ #
98
+ # The User Agent header to send along.
99
+ #
100
+ # @return [String]
101
+ #
102
+ def user_agent
103
+ ENV['CHEF_API_USER_AGENT'] || config['CHEF_API_USER_AGENT'] || USER_AGENT
104
+ end
105
+
106
+ #
107
+ # The name of the Chef API client. This is the equivalent of
108
+ # +client_name+ in Chef terminology. In most cases, this is your Chef
109
+ # username.
110
+ #
111
+ # @return [String, nil]
112
+ #
113
+ def client
114
+ ENV['CHEF_API_CLIENT'] || config['CHEF_API_CLIENT']
115
+ end
116
+
117
+ #
118
+ # The private key to authentication against the Chef Server. This is
119
+ # equivalent to the +client_key+ in Chef terminology. This value can
120
+ # be the client key in plain text or a path to the key on disk.
121
+ #
122
+ # @return [String, nil]
123
+ #
124
+ def key
125
+ ENV['CHEF_API_KEY'] || config['CHEF_API_KEY']
126
+ end
127
+ #
128
+ # The HTTP Proxy server address as a string
129
+ #
130
+ # @return [String, nil]
131
+ #
132
+ def proxy_address
133
+ ENV['CHEF_API_PROXY_ADDRESS'] || config['CHEF_API_PROXY_ADDRESS']
134
+ end
135
+
136
+ #
137
+ # The HTTP Proxy user password as a string
138
+ #
139
+ # @return [String, nil]
140
+ #
141
+ def proxy_password
142
+ ENV['CHEF_API_PROXY_PASSWORD'] || config['CHEF_API_PROXY_PASSWORD']
143
+ end
144
+
145
+ #
146
+ # The HTTP Proxy server port as a string
147
+ #
148
+ # @return [String, nil]
149
+ #
150
+ def proxy_port
151
+ ENV['CHEF_API_PROXY_PORT'] || config['CHEF_API_PROXY_PORT']
152
+ end
153
+
154
+ #
155
+ # The HTTP Proxy server username as a string
156
+ #
157
+ # @return [String, nil]
158
+ #
159
+ def proxy_username
160
+ ENV['CHEF_API_PROXY_USERNAME'] || config['CHEF_API_PROXY_USERNAME']
161
+ end
162
+
163
+ #
164
+ # The path to a pem file on disk for use with a custom SSL verification
165
+ #
166
+ # @return [String, nil]
167
+ #
168
+ def ssl_pem_file
169
+ ENV['CHEF_API_SSL_PEM_FILE'] || config['CHEF_API_SSL_PEM_FILE']
170
+ end
171
+
172
+ #
173
+ # Verify SSL requests (default: true)
174
+ #
175
+ # @return [true, false]
176
+ #
177
+ def ssl_verify
178
+ if ENV['CHEF_API_SSL_VERIFY'].nil? && config['CHEF_API_SSL_VERIFY'].nil?
179
+ true
180
+ else
181
+ %w[t y].include?(ENV['CHEF_API_SSL_VERIFY'].downcase[0]) || config['CHEF_API_SSL_VERIFY']
182
+ end
183
+ end
184
+
185
+ #
186
+ # Network request read timeout in seconds (default: 60)
187
+ #
188
+ # @return [Integer, nil]
189
+ #
190
+ def read_timeout
191
+ timeout_from_env = ENV['CHEF_API_READ_TIMEOUT'] || config['CHEF_API_READ_TIMEOUT']
192
+
193
+ Integer(timeout_from_env) unless timeout_from_env.nil?
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,44 @@
1
+ module ChefAPI
2
+ #
3
+ # Private internal class for managing the error collection.
4
+ #
5
+ class ErrorCollection < Hash
6
+ #
7
+ # The default proc for the hash needs to be an empty Array.
8
+ #
9
+ # @return [Proc]
10
+ #
11
+ def initialize
12
+ super { |h, k| h[k] = [] }
13
+ end
14
+
15
+ #
16
+ # Add a new error to the hash.
17
+ #
18
+ # @param [Symbol] key
19
+ # the attribute key
20
+ # @param [String] error
21
+ # the error message to push
22
+ #
23
+ # @return [self]
24
+ #
25
+ def add(key, error)
26
+ self[key].push(error)
27
+ self
28
+ end
29
+
30
+ #
31
+ # Output the full messages for each error. This is useful for displaying
32
+ # information about validation to the user when something goes wrong.
33
+ #
34
+ # @return [Array<String>]
35
+ #
36
+ def full_messages
37
+ self.map do |key, errors|
38
+ errors.map do |error|
39
+ "`#{key}' #{error}"
40
+ end
41
+ end.flatten
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,64 @@
1
+ require 'erb'
2
+
3
+ module ChefAPI
4
+ module Error
5
+ class ErrorBinding
6
+ def initialize(options = {})
7
+ options.each do |key, value|
8
+ instance_variable_set(:"@#{key}", value)
9
+ end
10
+ end
11
+
12
+ def get_binding
13
+ binding
14
+ end
15
+ end
16
+
17
+ class ChefAPIError < StandardError
18
+ def initialize(options = {})
19
+ @options = options
20
+ @filename = options.delete(:_template)
21
+
22
+ super()
23
+ end
24
+
25
+ def message
26
+ erb = ERB.new(File.read(template))
27
+ erb.result(ErrorBinding.new(@options).get_binding)
28
+ end
29
+ alias_method :to_s, :message
30
+
31
+ private
32
+
33
+ def template
34
+ class_name = self.class.to_s.split('::').last
35
+ filename = @filename || Util.underscore(class_name)
36
+ ChefAPI.root.join('templates', 'errors', "#{filename}.erb")
37
+ end
38
+ end
39
+
40
+ class AbstractMethod < ChefAPIError; end
41
+ class CannotRegenerateKey < ChefAPIError; end
42
+ class FileNotFound < ChefAPIError; end
43
+
44
+ class HTTPError < ChefAPIError; end
45
+ class HTTPBadRequest < HTTPError; end
46
+ class HTTPForbiddenRequest < HTTPError; end
47
+ class HTTPGatewayTimeout < HTTPError; end
48
+ class HTTPNotAcceptable < HTTPError; end
49
+ class HTTPNotFound < HTTPError; end
50
+ class HTTPMethodNotAllowed < HTTPError; end
51
+ class HTTPServerUnavailable < HTTPError; end
52
+
53
+ class HTTPUnauthorizedRequest < ChefAPIError; end
54
+ class InsufficientFilePermissions < ChefAPIError; end
55
+ class InvalidResource < ChefAPIError; end
56
+ class InvalidValidator < ChefAPIError; end
57
+ class MissingURLParameter < ChefAPIError; end
58
+ class NotADirectory < ChefAPIError; end
59
+ class ResourceAlreadyExists < ChefAPIError; end
60
+ class ResourceNotFound < ChefAPIError; end
61
+ class ResourceNotMutable < ChefAPIError; end
62
+ class UnknownAttribute < ChefAPIError; end
63
+ end
64
+ end
@@ -0,0 +1,164 @@
1
+ require 'cgi'
2
+ require 'mime/types'
3
+
4
+ module ChefAPI
5
+ module Multipart
6
+ BOUNDARY = '------ChefAPIMultipartBoundary'.freeze
7
+
8
+ class Body
9
+ def initialize(params = {})
10
+ params.each do |key, value|
11
+ if value.respond_to?(:read)
12
+ parts << FilePart.new(key, value)
13
+ else
14
+ parts << ParamPart.new(key, value)
15
+ end
16
+ end
17
+
18
+ parts << EndingPart.new
19
+ end
20
+
21
+ def stream
22
+ MultiIO.new(*parts.map(&:io))
23
+ end
24
+
25
+ def content_type
26
+ "multipart/form-data; boundary=#{BOUNDARY}"
27
+ end
28
+
29
+ def content_length
30
+ parts.map(&:size).inject(:+)
31
+ end
32
+
33
+ private
34
+
35
+ def parts
36
+ @parts ||= []
37
+ end
38
+ end
39
+
40
+ class MultiIO
41
+ attr_reader :ios
42
+
43
+ def initialize(*ios)
44
+ @ios = ios
45
+ @index = 0
46
+ end
47
+
48
+ # Read from IOs in order until `length` bytes have been received.
49
+ def read(length = nil, outbuf = nil)
50
+ got_result = false
51
+ outbuf = outbuf ? outbuf.replace('') : ''
52
+
53
+ while io = current_io
54
+ if result = io.read(length)
55
+ got_result ||= !result.nil?
56
+ result.force_encoding('BINARY') if result.respond_to?(:force_encoding)
57
+ outbuf << result
58
+ length -= result.length if length
59
+ break if length == 0
60
+ end
61
+ advance_io
62
+ end
63
+
64
+ (!got_result && length) ? nil : outbuf
65
+ end
66
+
67
+ def rewind
68
+ @ios.each { |io| io.rewind }
69
+ @index = 0
70
+ end
71
+
72
+ private
73
+
74
+ def current_io
75
+ @ios[@index]
76
+ end
77
+
78
+ def advance_io
79
+ @index += 1
80
+ end
81
+ end
82
+
83
+ #
84
+ # A generic key => value part.
85
+ #
86
+ class ParamPart
87
+ def initialize(name, value)
88
+ @part = build(name, value)
89
+ end
90
+
91
+ def io
92
+ @io ||= StringIO.new(@part)
93
+ end
94
+
95
+ def size
96
+ @part.bytesize
97
+ end
98
+
99
+ private
100
+
101
+ def build(name, value)
102
+ part = %|--#{BOUNDARY}\r\n|
103
+ part << %|Content-Disposition: form-data; name="#{CGI.escape(name)}"\r\n\r\n|
104
+ part << %|#{value}\r\n|
105
+ part
106
+ end
107
+ end
108
+
109
+ #
110
+ # A File part
111
+ #
112
+ class FilePart
113
+ def initialize(name, file)
114
+ @file = file
115
+ @head = build(name, file)
116
+ @foot = "\r\n"
117
+ end
118
+
119
+ def io
120
+ @io ||= MultiIO.new(
121
+ StringIO.new(@head),
122
+ @file,
123
+ StringIO.new(@foot)
124
+ )
125
+ end
126
+
127
+ def size
128
+ @head.bytesize + @file.size + @foot.bytesize
129
+ end
130
+
131
+ private
132
+
133
+ def build(name, file)
134
+ filename = File.basename(file.path)
135
+ mime_type = MIME::Types.type_for(filename)[0] || MIME::Types['application/octet-stream'][0]
136
+
137
+ part = %|--#{BOUNDARY}\r\n|
138
+ part << %|Content-Disposition: form-data; name="#{CGI.escape(name)}"; filename="#{filename}"\r\n|
139
+ part << %|Content-Length: #{file.size}\r\n|
140
+ part << %|Content-Type: #{mime_type.simplified}\r\n|
141
+ part << %|Content-Transfer-Encoding: binary\r\n|
142
+ part << %|\r\n|
143
+ part
144
+ end
145
+ end
146
+
147
+ #
148
+ # The end of the entire request
149
+ #
150
+ class EndingPart
151
+ def initialize
152
+ @part = "--#{BOUNDARY}--\r\n\r\n"
153
+ end
154
+
155
+ def io
156
+ @io ||= StringIO.new(@part)
157
+ end
158
+
159
+ def size
160
+ @part.bytesize
161
+ end
162
+ end
163
+ end
164
+ end