chef-infra-api 0.9.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 (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,53 @@
1
+ module ChefAPI
2
+ class Resource::DataBagItem < Resource::Base
3
+ collection_path '/data/:bag'
4
+
5
+ schema do
6
+ attribute :id, type: String, primary: true, required: true
7
+ attribute :data, type: Hash, default: {}
8
+ end
9
+
10
+ class << self
11
+ def from_file(path, bag = File.basename(File.dirname(path)))
12
+ id, contents = Util.safe_read(path)
13
+ data = JSON.parse(contents)
14
+ data[:id] = id
15
+
16
+ bag = bag.is_a?(Resource::DataBag) ? bag : Resource::DataBag.new(name: bag)
17
+
18
+ new(data, { bag: bag.name }, bag)
19
+ end
20
+ end
21
+
22
+ attr_reader :bag
23
+
24
+ #
25
+ # Override the initialize method to move any attributes into the +data+
26
+ # hash.
27
+ #
28
+ def initialize(attributes = {}, prefix = {}, bag = nil)
29
+ @bag = bag || Resource::DataBag.fetch(prefix[:bag])
30
+
31
+ id = attributes.delete(:id) || attributes.delete('id')
32
+ super({ id: id, data: attributes }, prefix)
33
+ end
34
+
35
+
36
+ #
37
+ # Override the to_hash method to move data to the upper scope.
38
+ #
39
+ # @see (Resource::Base#to_hash)
40
+ #
41
+ def to_hash
42
+ {}.tap do |hash|
43
+ _attributes.each do |key, value|
44
+ if key == :data
45
+ hash.merge!(value)
46
+ else
47
+ hash[key] = value.respond_to?(:to_hash) ? value.to_hash : value
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,16 @@
1
+ module ChefAPI
2
+ class Resource::Environment < Resource::Base
3
+ collection_path '/environments'
4
+
5
+ schema do
6
+ attribute :name, type: String, primary: true, required: true
7
+ attribute :description, type: String
8
+ attribute :default_attributes, type: Hash, default: {}
9
+ attribute :override_attributes, type: Hash, default: {}
10
+ attribute :cookbook_versions, type: Hash, default: {}
11
+ end
12
+
13
+ has_many :cookbooks
14
+ has_many :nodes
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module ChefAPI
2
+ class Resource::Group < Resource::Base
3
+ collection_path '/groups'
4
+
5
+ schema do
6
+ attribute :groupname, type: String, primary: true, required: true
7
+ attribute :name, type: String
8
+ attribute :orgname, type: String
9
+ attribute :actors, type: Array, default: []
10
+ attribute :users, type: Array, default: []
11
+ attribute :clients, type: Array, default: []
12
+ attribute :groups, type: Array, default: []
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,20 @@
1
+ module ChefAPI
2
+ class Resource::Node < Resource::Base
3
+ include ChefAPI::AclAble
4
+ collection_path '/nodes'
5
+
6
+ schema do
7
+ attribute :name, type: String, primary: true, required: true
8
+ attribute :automatic, type: Hash, default: {}
9
+ attribute :default, type: Hash, default: {}
10
+ attribute :normal, type: Hash, default: {}
11
+ attribute :override, type: Hash, default: {}
12
+ attribute :run_list, type: Array, default: []
13
+ attribute :policy_name, type: String
14
+ attribute :policy_group, type: String
15
+
16
+ # Enterprise Chef attributes
17
+ attribute :chef_environment, type: String, default: '_default'
18
+ end
19
+ end
20
+ end
@@ -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
@@ -0,0 +1,44 @@
1
+ module ChefAPI
2
+ class Resource::PartialSearch < Resource::Base
3
+ collection_path '/search/:index'
4
+
5
+ schema do
6
+ attribute :total, type: Integer
7
+ attribute :start, type: Integer
8
+ attribute :rows, type: Array
9
+ end
10
+
11
+ class << self
12
+ #
13
+ # About search : https://docs.chef.io/chef_search.html
14
+ #
15
+ # @param [String] index
16
+ # the name of the index to search
17
+ # @param [Hash] keys
18
+ # key paths for the attributes to be returned
19
+ # @param [String] query
20
+ # the query string
21
+ # @param [Hash] options
22
+ # the query string
23
+ #
24
+ # @return [self]
25
+ # the current resource
26
+ #
27
+ def query(index, keys, query = '*:*', options = {})
28
+ return nil if index.nil?
29
+
30
+ params = {}.tap do |o|
31
+ o[:q] = query
32
+ o[:rows] = options[:rows] || 1000
33
+ o[:sort] = options[:sort] || 'X_CHEF_id_CHEF_X'
34
+ o[:start] = options[:start] || 0
35
+ end
36
+
37
+ path = expanded_collection_path(index: index.to_s)
38
+ response = connection.post(path, keys.to_json, params)
39
+ response['rows'].map! { |row| row['data'] }
40
+ from_json(response, index: index.to_s)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ module ChefAPI
2
+ class Resource::Principal < Resource::Base
3
+ collection_path '/principals'
4
+
5
+ schema do
6
+ attribute :name, type: String, primary: true, required: true
7
+ attribute :type, type: String
8
+ attribute :public_key, type: String
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module ChefAPI
2
+ class Resource::Role < Resource::Base
3
+ include ChefAPI::AclAble
4
+ collection_path '/roles'
5
+
6
+ schema do
7
+ attribute :name, type: String, primary: true, required: true
8
+ attribute :json_class, type: String, default: "Chef::Role"
9
+ attribute :description, type: String
10
+ attribute :default_attributes, type: Hash, default: {}
11
+ attribute :override_attributes, type: Hash, default: {}
12
+ attribute :run_list, type: Array, default: []
13
+ attribute :env_run_lists, type: Hash, default: {}
14
+ end
15
+
16
+ has_many :environments
17
+ end
18
+ end
@@ -0,0 +1,47 @@
1
+ module ChefAPI
2
+ class Resource::Search < Resource::Base
3
+ collection_path '/search/:index'
4
+
5
+ schema do
6
+ attribute :total, type: Integer
7
+ attribute :start, type: Integer
8
+ attribute :rows, type: Array
9
+ end
10
+
11
+ class << self
12
+ #
13
+ # About search : https://docs.chef.io/chef_search.html
14
+ #
15
+ # @param [String] index
16
+ # the name of the index to search
17
+ # @param [String] query
18
+ # the query string
19
+ # @param [Hash] options
20
+ # the query string
21
+ #
22
+ # @return [self]
23
+ # the current resource
24
+ #
25
+ def query(index, query = '*:*', options = {})
26
+ return nil if index.nil?
27
+
28
+ params = {}.tap do |o|
29
+ o[:q] = query
30
+ o[:rows] = options[:rows] || 1000
31
+ o[:sort] = options[:sort] || 'X_CHEF_id_CHEF_X'
32
+ o[:start] = options[:start] || 0
33
+ end
34
+
35
+ path = expanded_collection_path(index: index.to_s)
36
+
37
+ response = if filter_result = options[:filter_result]
38
+ connection.post(path, filter_result.to_json, params)
39
+ else
40
+ connection.get(path, params)
41
+ end
42
+
43
+ from_json(response, index: index.to_s)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,82 @@
1
+ module ChefAPI
2
+ class Resource::User < Resource::Base
3
+ collection_path '/users'
4
+
5
+ schema do
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
+
24
+ attribute :admin, type: Boolean, default: false
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.to_json)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,150 @@
1
+ module ChefAPI
2
+ #
3
+ # A wrapper class that describes a remote schema (such as the Chef Server
4
+ # API layer), with validation and other magic spinkled on top.
5
+ #
6
+ class Schema
7
+
8
+ #
9
+ # The full list of attributes defined on this schema.
10
+ #
11
+ # @return [Hash]
12
+ #
13
+ attr_reader :attributes
14
+
15
+ attr_reader :ignored_attributes
16
+
17
+ #
18
+ # The list of defined validators for this schema.
19
+ #
20
+ # @return [Array]
21
+ #
22
+ attr_reader :validators
23
+
24
+ #
25
+ # Create a new schema and evaulte the block contents in a clean room.
26
+ #
27
+ def initialize(&block)
28
+ @attributes = {}
29
+ @ignored_attributes = {}
30
+ @flavor_attributes = {}
31
+ @validators = []
32
+
33
+ unlock { instance_eval(&block) } if block
34
+ end
35
+
36
+ #
37
+ # The defined primary key for this schema. If no primary key is given, it
38
+ # is assumed to be the first item in the list.
39
+ #
40
+ # @return [Symbol]
41
+ #
42
+ def primary_key
43
+ @primary_key ||= @attributes.first[0]
44
+ end
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
+
85
+ #
86
+ # DSL method for defining an attribute.
87
+ #
88
+ # @param [Symbol] key
89
+ # the key to use
90
+ # @param [Hash] options
91
+ # a list of options to create the attribute with
92
+ #
93
+ # @return [Symbol]
94
+ # the attribute
95
+ #
96
+ def attribute(key, options = {})
97
+ if primary_key = options.delete(:primary)
98
+ @primary_key = key.to_sym
99
+ end
100
+
101
+ @attributes[key] = options.delete(:default)
102
+
103
+ # All remaining options are assumed to be validations
104
+ options.each do |validation, options|
105
+ if options
106
+ @validators << Validator.find(validation).new(key, options)
107
+ end
108
+ end
109
+
110
+ key
111
+ end
112
+
113
+ #
114
+ # Ignore an attribute. This is handy if you know there's an attribute that
115
+ # the remote server will return, but you don't want that information
116
+ # exposed to the user (or the data is sensitive).
117
+ #
118
+ # @param [Array<Symbol>] keys
119
+ # the list of attributes to ignore
120
+ #
121
+ def ignore(*keys)
122
+ keys.each do |key|
123
+ @ignored_attributes[key.to_sym] = true
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ #
130
+ # @private
131
+ #
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.
135
+ #
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
148
+ end
149
+ end
150
+ end