chef-dk 2.3.4 → 2.4.17

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 (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