chef-dk 2.3.4 → 2.4.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +22 -18
  3. data/Gemfile.lock +184 -254
  4. data/README.md +1 -1
  5. data/Rakefile +1 -1
  6. data/acceptance/Gemfile.lock +27 -32
  7. data/lib/chef-dk/chef_server_api_multi.rb +73 -0
  8. data/lib/chef-dk/command/update.rb +5 -12
  9. data/lib/chef-dk/configurable.rb +19 -0
  10. data/lib/chef-dk/cookbook_omnifetch.rb +1 -0
  11. data/lib/chef-dk/exceptions.rb +11 -0
  12. data/lib/chef-dk/generator.rb +1 -1
  13. data/lib/chef-dk/policyfile/attribute_merge_checker.rb +110 -0
  14. data/lib/chef-dk/policyfile/chef_server_cookbook_source.rb +5 -4
  15. data/lib/chef-dk/policyfile/chef_server_lock_fetcher.rb +164 -0
  16. data/lib/chef-dk/policyfile/cookbook_location_specification.rb +3 -3
  17. data/lib/chef-dk/policyfile/dsl.rb +16 -0
  18. data/lib/chef-dk/policyfile/included_policies_cookbook_source.rb +156 -0
  19. data/lib/chef-dk/policyfile/local_lock_fetcher.rb +122 -0
  20. data/lib/chef-dk/policyfile/lock_applier.rb +80 -0
  21. data/lib/chef-dk/policyfile/null_cookbook_source.rb +4 -0
  22. data/lib/chef-dk/policyfile/policyfile_location_specification.rb +122 -0
  23. data/lib/chef-dk/policyfile_compiler.rb +129 -16
  24. data/lib/chef-dk/policyfile_lock.rb +30 -0
  25. data/lib/chef-dk/policyfile_services/install.rb +7 -1
  26. data/lib/chef-dk/policyfile_services/update_attributes.rb +10 -2
  27. data/lib/chef-dk/skeletons/code_generator/templates/default/recipe_spec.rb.erb +14 -1
  28. data/lib/chef-dk/version.rb +1 -1
  29. data/omnibus_overrides.rb +6 -6
  30. data/spec/unit/chef_server_api_multi_spec.rb +120 -0
  31. data/spec/unit/command/update_spec.rb +3 -3
  32. data/spec/unit/configurable_spec.rb +27 -0
  33. data/spec/unit/policyfile/attribute_merge_checker_spec.rb +80 -0
  34. data/spec/unit/policyfile/chef_server_lock_fetcher_spec.rb +161 -0
  35. data/spec/unit/policyfile/cookbook_location_specification_spec.rb +48 -0
  36. data/spec/unit/policyfile/included_policies_cookbook_source_spec.rb +242 -0
  37. data/spec/unit/policyfile/local_lock_fetcher_spec.rb +161 -0
  38. data/spec/unit/policyfile/lock_applier_spec.rb +100 -0
  39. data/spec/unit/policyfile_demands_spec.rb +1 -1
  40. data/spec/unit/policyfile_includes_dsl_spec.rb +159 -0
  41. data/spec/unit/policyfile_includes_spec.rb +720 -0
  42. data/spec/unit/policyfile_install_with_includes_spec.rb +232 -0
  43. data/spec/unit/policyfile_lock_build_spec.rb +11 -2
  44. data/spec/unit/policyfile_services/update_attributes_spec.rb +13 -0
  45. metadata +28 -3
@@ -18,7 +18,7 @@
18
18
  require "ffi_yajl"
19
19
  require "chef-dk/exceptions"
20
20
  require "chef-dk/policyfile/source_uri"
21
- require "chef/server_api"
21
+ require "chef-dk/chef_server_api_multi"
22
22
 
23
23
  module ChefDK
24
24
  module Policyfile
@@ -82,9 +82,10 @@ module ChefDK
82
82
  private
83
83
 
84
84
  def http_connection_for(base_url)
85
- @http_connections[base_url] ||= Chef::ServerAPI.new(base_url,
86
- signing_key_filename: chef_config.client_key,
87
- client_name: chef_config.node_name)
85
+ @http_connections[base_url] ||=
86
+ ChefServerAPIMulti.new(base_url,
87
+ signing_key_filename: chef_config.client_key,
88
+ client_name: chef_config.node_name)
88
89
  end
89
90
 
90
91
  def full_chef_server_graph
@@ -0,0 +1,164 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2017 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "chef-dk/policyfile_lock"
19
+ require "chef-dk/exceptions"
20
+
21
+ module ChefDK
22
+ module Policyfile
23
+
24
+ # A policyfile lock fetcher that can read a lock from a chef server
25
+ class ChefServerLockFetcher
26
+
27
+ attr_accessor :name
28
+ attr_accessor :source_options
29
+ attr_accessor :chef_config
30
+
31
+ # Initialize a LocalLockFetcher
32
+ #
33
+ # @param name [String] The name of the policyfile
34
+ # @param source_options [Hash] A hash with a :server key pointing at the chef server,
35
+ # along with :policy_name and either :policy_group or :policy_revision_id. If :policy_name
36
+ # is not provided, name is used.
37
+ # @param storage_config [StorageConfig]
38
+ #
39
+ # @example ChefServerLockFetcher for a policyfile with a specific revision id
40
+ # ChefServerLockFetcher.new("foo",
41
+ # {server: "http://example.com", policy_revision_id: "abcdabcdabcd"},
42
+ # chef_config)
43
+ #
44
+ # ChefServerLockFetcher.new("foo",
45
+ # {server: "http://example.com", policy_name: "foo", policy_revision_id: "abcdabcdabcd"},
46
+ # chef_config)
47
+ #
48
+ # @example ChefServerLockFetcher for a policyfile with the latest revision_id for a policy group
49
+ # ChefServerLockFetcher.new("foo",
50
+ # {server: "http://example.com", policy_group: "dev"},
51
+ # chef_config)
52
+ #
53
+ # ChefServerLockFetcher.new("foo",
54
+ # {server: "http://example.com", policy_name: "foo", policy_group: "dev"},
55
+ # chef_config)
56
+ def initialize(name, source_options, chef_config)
57
+ @name = name
58
+ @source_options = source_options
59
+ @chef_config = chef_config
60
+
61
+ @source_options[:policy_name] ||= name
62
+ end
63
+
64
+ # @return [True] if there were no errors with the provided source_options
65
+ # @return [False] if there were errors with the provided source_options
66
+ def valid?
67
+ errors.empty?
68
+ end
69
+
70
+ # Check the options provided when craeting this class for errors
71
+ #
72
+ # @return [Array<String>] A list of errors found
73
+ def errors
74
+ error_messages = []
75
+
76
+ [:server, :policy_name].each do |key|
77
+ error_messages << "include_policy for #{name} is missing key #{key}" unless source_options[key]
78
+ end
79
+
80
+ if [:policy_revision_id, :policy_group].all? { |key| source_options[key].nil? }
81
+ error_messages << "include_policy for #{name} must specify policy_revision_id or policy_group"
82
+ end
83
+
84
+ error_messages
85
+ end
86
+
87
+ # @return [Hash] The source_options that describe how to fetch this exact lock again
88
+ def source_options_for_lock
89
+ source_options.merge({
90
+ policy_revision_id: lock_data["revision_id"],
91
+ })
92
+ end
93
+
94
+ # Applies source options from a lock file. This is used to make sure that the same
95
+ # policyfile lock is loaded that was locked
96
+ #
97
+ # @param options_from_lock [Hash] The source options loaded from a policyfile lock
98
+ def apply_locked_source_options(options_from_lock)
99
+ options = options_from_lock.inject({}) do |acc, (key, value)|
100
+ acc[key.to_sym] = value
101
+ acc
102
+ end
103
+ source_options.merge!(options)
104
+ raise ChefDK::InvalidLockfile, "Invalid source_options provided from lock data: #{options_from_lock_file.inspect}" if !valid?
105
+ end
106
+
107
+ # @return [String] of the policyfile lock data
108
+ def lock_data
109
+ @lock_data ||= fetch_lock_data.tap do |data|
110
+ data["cookbook_locks"].each do |cookbook_name, cookbook_lock|
111
+ cookbook_lock["source_options"] = {
112
+ "chef_server_artifact" => server,
113
+ "identifier" => cookbook_lock["identifier"],
114
+ }
115
+ end
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def fetch_lock_data
122
+ if revision
123
+ http_client.get("policies/#{policy_name}/revisions/#{revision}")
124
+ elsif policy_group
125
+ http_client.get("policy_groups/#{policy_group}/policies/#{policy_name}")
126
+ else
127
+ raise ChefDK::BUG.new("The source_options should have been validated: #{source_options.inspect}")
128
+ end
129
+ rescue Net::ProtocolError => e
130
+ if e.respond_to?(:response) && e.response.code.to_s == "404"
131
+ raise ChefDK::PolicyfileLockDownloadError.new("No policyfile lock named '#{policy_name}' found with revision '#{revision}' at #{http_client.url}") if revision
132
+ raise ChefDK::PolicyfileLockDownloadError.new("No policyfile lock named '#{policy_name}' found with policy group '#{policy_group}' at #{http_client.url}") if policy_group
133
+ else
134
+ raise ChefDK::PolicyfileLockDownloadError.new("HTTP error attempting to fetch policyfile lock from #{http_client.url}")
135
+ end
136
+ rescue => e
137
+ raise e
138
+ end
139
+
140
+ def policy_name
141
+ source_options[:policy_name]
142
+ end
143
+
144
+ def revision
145
+ source_options[:policy_revision_id]
146
+ end
147
+
148
+ def policy_group
149
+ source_options[:policy_group]
150
+ end
151
+
152
+ def server
153
+ source_options[:server]
154
+ end
155
+
156
+ def http_client
157
+ @http_client ||= Chef::ServerAPI.new(source_options[:server],
158
+ signing_key_filename: chef_config.client_key,
159
+ client_name: chef_config.node_name)
160
+ end
161
+
162
+ end
163
+ end
164
+ end
@@ -29,7 +29,7 @@ module ChefDK
29
29
  # API contract
30
30
  include StorageConfigDelegation
31
31
 
32
- SOURCE_TYPES = [:git, :github, :path, :artifactserver, :chef_server, :artifactory]
32
+ SOURCE_TYPES = [:git, :github, :path, :artifactserver, :chef_server, :chef_server_artifact, :artifactory]
33
33
 
34
34
  #--
35
35
  # Required by CookbookOmnifetch API contract
@@ -69,7 +69,7 @@ module ChefDK
69
69
  end
70
70
 
71
71
  def mirrors_canonical_upstream?
72
- [:git, :github, :artifactserver, :chef_server, :artifactory].include?(source_type)
72
+ [:git, :github, :artifactserver, :chef_server, :chef_server_artifact, :artifactory].include?(source_type)
73
73
  end
74
74
 
75
75
  def installed?
@@ -112,7 +112,7 @@ module ChefDK
112
112
  end
113
113
 
114
114
  def version_fixed?
115
- [:git, :github, :path].include?(@source_type)
115
+ [:git, :github, :path, :chef_server_artifact].include?(@source_type)
116
116
  end
117
117
 
118
118
  def version
@@ -18,6 +18,7 @@
18
18
  require "chef-dk/policyfile/cookbook_sources"
19
19
  require "chef-dk/policyfile/cookbook_location_specification"
20
20
  require "chef-dk/policyfile/storage_config"
21
+ require "chef-dk/policyfile/policyfile_location_specification"
21
22
 
22
23
  require "chef/node/attribute"
23
24
  require "chef/run_list/run_list_item"
@@ -36,6 +37,7 @@ module ChefDK
36
37
  attr_reader :run_list
37
38
  attr_reader :default_source
38
39
  attr_reader :cookbook_location_specs
40
+ attr_reader :included_policies
39
41
 
40
42
  attr_reader :named_run_lists
41
43
  attr_reader :node_attributes
@@ -48,6 +50,7 @@ module ChefDK
48
50
  @errors = []
49
51
  @run_list = []
50
52
  @named_run_lists = {}
53
+ @included_policies = []
51
54
  @default_source = [ NullCookbookSource.new ]
52
55
  @cookbook_location_specs = {}
53
56
  @storage_config = storage_config
@@ -121,6 +124,19 @@ module ChefDK
121
124
  end
122
125
  end
123
126
 
127
+ def include_policy(name, source_options = {})
128
+ if existing = included_policies.find { |p| p.name == name }
129
+ err = "Included policy '#{name}' assigned conflicting locations or was already specified\n\n"
130
+ err << "Previous source: #{existing.source_options.inspect}\n"
131
+ err << "Conflicts with: #{source_options.inspect}\n"
132
+ @errors << err
133
+ else
134
+ spec = PolicyfileLocationSpecification.new(name, source_options, storage_config, chef_config)
135
+ included_policies << spec
136
+ @errors += spec.errors
137
+ end
138
+ end
139
+
124
140
  def default
125
141
  @node_attributes.default
126
142
  end
@@ -0,0 +1,156 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2014 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "chef-dk/ui"
19
+
20
+ module ChefDK
21
+ module Policyfile
22
+ class IncludedPoliciesCookbookSource
23
+
24
+ class ConflictingCookbookVersions < StandardError
25
+ end
26
+
27
+ class ConflictingCookbookDependencies < StandardError
28
+ end
29
+
30
+ class ConflictingCookbookSources < StandardError
31
+ end
32
+
33
+ # Why do we need this class?
34
+ # If we rely on default sources, we may not have the the universe of cookbooks
35
+ # provided in the included policies
36
+ #
37
+ # This is not meant to be used from the DSL
38
+
39
+ # A list of included policies
40
+ attr_reader :included_policy_location_specs
41
+ # UI object for output
42
+ attr_accessor :ui
43
+
44
+ # Constructor
45
+ #
46
+ def initialize(included_policy_location_specs)
47
+ @included_policy_location_specs = included_policy_location_specs
48
+ @ui = UI.new
49
+ @preferred_cookbooks = []
50
+ yield self if block_given?
51
+ end
52
+
53
+ def default_source_args
54
+ [:included_policies, []]
55
+ end
56
+
57
+ def check_for_conflicts!
58
+ source_options
59
+ universe_graph
60
+ end
61
+
62
+ # All are preferred here
63
+ def preferred_source_for?(cookbook_name)
64
+ universe_graph.include?(cookbook_name)
65
+ end
66
+
67
+ def preferred_cookbooks
68
+ universe_graph.keys
69
+ end
70
+
71
+ def ==(other)
72
+ other.kind_of?(self.class) && other.included_policy_location_specs == included_policy_location_specs
73
+ end
74
+
75
+ # Calls the slurp_metadata! helper once to calculate the @universe_graph
76
+ # and @cookbook_version_paths metadata. Returns the @universe_graph.
77
+ #
78
+ # @return [Hash] universe_graph
79
+ def universe_graph
80
+ @universe_graph ||= build_universe
81
+ end
82
+
83
+ # Returns the metadata (path and version) for an individual cookbook
84
+ #
85
+ # @return [Hash] metadata for a single cookbook version
86
+ def source_options_for(cookbook_name, cookbook_version)
87
+ source_options[cookbook_name][cookbook_version]
88
+ end
89
+
90
+ def null?
91
+ false
92
+ end
93
+
94
+ def desc
95
+ "included_policies()"
96
+ end
97
+
98
+ private
99
+
100
+ def build_universe
101
+ included_policy_location_specs.inject({}) do |acc, policy_spec|
102
+ lock = policy_spec.policyfile_lock
103
+ cookbook_dependencies = lock.solution_dependencies.cookbook_dependencies
104
+ cookbook_dependencies.each do |(cookbook, deps)|
105
+ name = cookbook.name
106
+ version = cookbook.version
107
+ mapped_deps = deps.map do |dep|
108
+ [dep[0], dep[1].to_s]
109
+ end
110
+ if acc[name]
111
+ if acc[name][version]
112
+ if acc[name][version] != mapped_deps
113
+ raise ConflictingCookbookDependencies.new("Conflicting dependencies provided for cookbook #{name}")
114
+ end
115
+ else
116
+ raise ConflictingCookbookVersions.new("Multiple versions provided for cookbook #{name}")
117
+ end
118
+ else
119
+ acc[name] = {}
120
+ acc[name][version] = mapped_deps
121
+ end
122
+ end
123
+ acc
124
+ end
125
+ end
126
+
127
+ def source_options
128
+ @source_options ||= build_source_options
129
+ end
130
+
131
+ ## Collect all the source options
132
+ def build_source_options
133
+ included_policy_location_specs.inject({}) do |acc, policy_spec|
134
+ lock = policy_spec.policyfile_lock
135
+ lock.cookbook_locks.each do |(name, cookbook_lock)|
136
+ version = cookbook_lock.version
137
+ if acc[name]
138
+ if acc[name][version]
139
+ if acc[name][version] != cookbook_lock.source_options
140
+ raise ConflictingCookbookSources.new("Conflicting sources provided for cookbook #{name}")
141
+ end
142
+ else
143
+ raise ConflictingCookbookVersions.new("Multiple sources provided for cookbook #{name}")
144
+ end
145
+ else
146
+ acc[name] = {}
147
+ acc[name][version] = cookbook_lock.source_options
148
+ end
149
+ end
150
+ acc
151
+ end
152
+ end
153
+
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,122 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2017 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "chef-dk/policyfile_lock"
19
+ require "chef-dk/exceptions"
20
+
21
+ module ChefDK
22
+ module Policyfile
23
+
24
+ # A policyfile lock fetcher that can read a lock from a local disk
25
+ class LocalLockFetcher
26
+
27
+ attr_reader :name
28
+ attr_reader :source_options
29
+ attr_reader :storage_config
30
+
31
+ # Initialize a LocalLockFetcher
32
+ #
33
+ # @param name [String] The name of the policyfile
34
+ # @param source_options [Hash] A hash with a :path key pointing at the location
35
+ # of the lock
36
+ # @param storage_config [StorageConfig]
37
+ def initialize(name, source_options, storage_config)
38
+ @name = name
39
+ @source_options = source_options
40
+ @storage_config = storage_config
41
+ end
42
+
43
+ # @return [True] if there were no errors with the provided source_options
44
+ # @return [False] if there were errors with the provided source_options
45
+ def valid?
46
+ errors.empty?
47
+ end
48
+
49
+ # Check the options provided when craeting this class for errors
50
+ #
51
+ # @return [Array<String>] A list of errors found
52
+ def errors
53
+ error_messages = []
54
+
55
+ [:path].each do |key|
56
+ error_messages << "include_policy for #{name} is missing key #{key}" unless source_options[key]
57
+ end
58
+
59
+ error_messages
60
+ end
61
+
62
+ # @return [Hash] The source_options that describe how to fetch this exact lock again
63
+ def source_options_for_lock
64
+ source_options
65
+ end
66
+
67
+ # Applies source options from a lock file. This is used to make sure that the same
68
+ # policyfile lock is loaded that was locked
69
+ #
70
+ # @param options_from_lock [Hash] The source options loaded from a policyfile lock
71
+ def apply_locked_source_options(options_from_lock)
72
+ # There are no options the lock could provide
73
+ end
74
+
75
+ # @return [String] of the policyfile lock data
76
+ def lock_data
77
+ FFI_Yajl::Parser.new.parse(content).tap do |data|
78
+ data["cookbook_locks"].each do |cookbook_name, cookbook_lock|
79
+ cookbook_path = cookbook_lock["source_options"]["path"]
80
+ if !cookbook_path.nil?
81
+ cookbook_lock["source_options"]["path"] = transform_path(cookbook_path)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def transform_path(path_to_transform)
90
+ cur_path = Pathname.new(storage_config.relative_paths_root)
91
+ include_path = Pathname.new(path).dirname
92
+ include_path.relative_path_from(cur_path).join(path_to_transform).to_s
93
+ end
94
+
95
+ def content
96
+ IO.read(path)
97
+ end
98
+
99
+ def path
100
+ @path ||= begin
101
+ path = Pathname.new(source_options[:path])
102
+ if path.directory?
103
+ path = path.join("#{name}.lock.json")
104
+ if !path.file?
105
+ raise ChefDK::LocalPolicyfileLockNotFound.new(
106
+ "Expected to find file #{name}.lock.json inside #{source_options[:path]}. If the file name is different than this, provide the file name as part of the path.")
107
+ end
108
+ else
109
+ if !path.file?
110
+ raise ChefDK::LocalPolicyfileLockNotFound.new(
111
+ "The provided path #{source_options[:path]} does not exist.")
112
+ end
113
+ end
114
+ if !path.absolute?
115
+ path = Pathname.new(storage_config.relative_paths_root).join(path)
116
+ end
117
+ path
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end