chef-dk 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +192 -0
  3. data/LICENSE +201 -0
  4. data/README.md +198 -0
  5. data/Rakefile +18 -0
  6. data/bin/chef +25 -0
  7. data/lib/chef-dk.rb +19 -0
  8. data/lib/chef-dk/builtin_commands.rb +31 -0
  9. data/lib/chef-dk/chef_runner.rb +83 -0
  10. data/lib/chef-dk/cli.rb +135 -0
  11. data/lib/chef-dk/command/base.rb +71 -0
  12. data/lib/chef-dk/command/exec.rb +33 -0
  13. data/lib/chef-dk/command/gem.rb +47 -0
  14. data/lib/chef-dk/command/generate.rb +97 -0
  15. data/lib/chef-dk/command/generator_commands.rb +417 -0
  16. data/lib/chef-dk/command/shell_init.rb +80 -0
  17. data/lib/chef-dk/command/verify.rb +226 -0
  18. data/lib/chef-dk/commands_map.rb +115 -0
  19. data/lib/chef-dk/component_test.rb +142 -0
  20. data/lib/chef-dk/cookbook_metadata.rb +36 -0
  21. data/lib/chef-dk/cookbook_omnifetch.rb +29 -0
  22. data/lib/chef-dk/cookbook_profiler/git.rb +95 -0
  23. data/lib/chef-dk/cookbook_profiler/identifiers.rb +79 -0
  24. data/lib/chef-dk/cookbook_profiler/null_scm.rb +32 -0
  25. data/lib/chef-dk/exceptions.rb +46 -0
  26. data/lib/chef-dk/generator.rb +70 -0
  27. data/lib/chef-dk/helpers.rb +95 -0
  28. data/lib/chef-dk/policyfile/chef_server_cookbook_source.rb +46 -0
  29. data/lib/chef-dk/policyfile/community_cookbook_source.rb +84 -0
  30. data/lib/chef-dk/policyfile/cookbook_sources.rb +20 -0
  31. data/lib/chef-dk/policyfile/cookbook_spec.rb +96 -0
  32. data/lib/chef-dk/policyfile/dsl.rb +148 -0
  33. data/lib/chef-dk/policyfile/null_cookbook_source.rb +37 -0
  34. data/lib/chef-dk/policyfile_compiler.rb +217 -0
  35. data/lib/chef-dk/policyfile_lock.rb +243 -0
  36. data/lib/chef-dk/shell_out.rb +36 -0
  37. data/lib/chef-dk/skeletons/code_generator/files/default/Berksfile +3 -0
  38. data/lib/chef-dk/skeletons/code_generator/files/default/chefignore +96 -0
  39. data/lib/chef-dk/skeletons/code_generator/files/default/converge_spec.rb +9 -0
  40. data/lib/chef-dk/skeletons/code_generator/files/default/gitignore +16 -0
  41. data/lib/chef-dk/skeletons/code_generator/files/default/spec_helper.rb +8 -0
  42. data/lib/chef-dk/skeletons/code_generator/metadata.rb +8 -0
  43. data/lib/chef-dk/skeletons/code_generator/recipes/app.rb +65 -0
  44. data/lib/chef-dk/skeletons/code_generator/recipes/attribute.rb +12 -0
  45. data/lib/chef-dk/skeletons/code_generator/recipes/cookbook.rb +50 -0
  46. data/lib/chef-dk/skeletons/code_generator/recipes/cookbook_file.rb +24 -0
  47. data/lib/chef-dk/skeletons/code_generator/recipes/lwrp.rb +23 -0
  48. data/lib/chef-dk/skeletons/code_generator/recipes/recipe.rb +9 -0
  49. data/lib/chef-dk/skeletons/code_generator/recipes/template.rb +32 -0
  50. data/lib/chef-dk/skeletons/code_generator/templates/default/README.md.erb +4 -0
  51. data/lib/chef-dk/skeletons/code_generator/templates/default/attribute.rb.erb +0 -0
  52. data/lib/chef-dk/skeletons/code_generator/templates/default/cookbook_file.erb +0 -0
  53. data/lib/chef-dk/skeletons/code_generator/templates/default/default_recipe.rb.erb +8 -0
  54. data/lib/chef-dk/skeletons/code_generator/templates/default/kitchen.yml.erb +16 -0
  55. data/lib/chef-dk/skeletons/code_generator/templates/default/metadata.rb.erb +8 -0
  56. data/lib/chef-dk/skeletons/code_generator/templates/default/provider.rb.erb +0 -0
  57. data/lib/chef-dk/skeletons/code_generator/templates/default/recipe.rb.erb +0 -0
  58. data/lib/chef-dk/skeletons/code_generator/templates/default/resource.rb.erb +0 -0
  59. data/lib/chef-dk/skeletons/code_generator/templates/default/template.erb +0 -0
  60. data/lib/chef-dk/version.rb +20 -0
  61. data/spec/shared/setup_git_cookbooks.rb +53 -0
  62. data/spec/spec_helper.rb +32 -0
  63. data/spec/test_helpers.rb +59 -0
  64. data/spec/unit/chef_runner_spec.rb +70 -0
  65. data/spec/unit/cli_spec.rb +151 -0
  66. data/spec/unit/command/base_spec.rb +88 -0
  67. data/spec/unit/command/exec_spec.rb +123 -0
  68. data/spec/unit/command/generate_spec.rb +102 -0
  69. data/spec/unit/command/generator_commands_spec.rb +504 -0
  70. data/spec/unit/command/shell_init_spec.rb +109 -0
  71. data/spec/unit/command/verify_spec.rb +311 -0
  72. data/spec/unit/commands_map_spec.rb +57 -0
  73. data/spec/unit/component_test_spec.rb +126 -0
  74. data/spec/unit/cookbook_metadata_spec.rb +62 -0
  75. data/spec/unit/cookbook_profiler/git_spec.rb +127 -0
  76. data/spec/unit/cookbook_profiler/identifiers_spec.rb +79 -0
  77. data/spec/unit/fixtures/chef-runner-cookbooks/test_cookbook/recipes/recipe_one.rb +9 -0
  78. data/spec/unit/fixtures/chef-runner-cookbooks/test_cookbook/recipes/recipe_two.rb +9 -0
  79. data/spec/unit/fixtures/command/cli_test_command.rb +26 -0
  80. data/spec/unit/fixtures/command/explicit_path_example.rb +7 -0
  81. data/spec/unit/fixtures/cookbook_cache/baz-f59ee7a5bca6a4e606b67f7f856b768d847c39bb/.kitchen.yml +16 -0
  82. data/spec/unit/fixtures/cookbook_cache/baz-f59ee7a5bca6a4e606b67f7f856b768d847c39bb/Berksfile +3 -0
  83. data/spec/unit/fixtures/cookbook_cache/baz-f59ee7a5bca6a4e606b67f7f856b768d847c39bb/README.md +4 -0
  84. data/spec/unit/fixtures/cookbook_cache/baz-f59ee7a5bca6a4e606b67f7f856b768d847c39bb/chefignore +96 -0
  85. data/spec/unit/fixtures/cookbook_cache/baz-f59ee7a5bca6a4e606b67f7f856b768d847c39bb/metadata.rb +8 -0
  86. data/spec/unit/fixtures/cookbook_cache/baz-f59ee7a5bca6a4e606b67f7f856b768d847c39bb/recipes/default.rb +8 -0
  87. data/spec/unit/fixtures/cookbook_cache/dep_of_bar-1.2.3/.kitchen.yml +16 -0
  88. data/spec/unit/fixtures/cookbook_cache/dep_of_bar-1.2.3/Berksfile +3 -0
  89. data/spec/unit/fixtures/cookbook_cache/dep_of_bar-1.2.3/README.md +4 -0
  90. data/spec/unit/fixtures/cookbook_cache/dep_of_bar-1.2.3/chefignore +96 -0
  91. data/spec/unit/fixtures/cookbook_cache/dep_of_bar-1.2.3/metadata.rb +8 -0
  92. data/spec/unit/fixtures/cookbook_cache/dep_of_bar-1.2.3/recipes/default.rb +8 -0
  93. data/spec/unit/fixtures/cookbook_cache/foo-1.0.0/.kitchen.yml +16 -0
  94. data/spec/unit/fixtures/cookbook_cache/foo-1.0.0/Berksfile +3 -0
  95. data/spec/unit/fixtures/cookbook_cache/foo-1.0.0/README.md +4 -0
  96. data/spec/unit/fixtures/cookbook_cache/foo-1.0.0/chefignore +96 -0
  97. data/spec/unit/fixtures/cookbook_cache/foo-1.0.0/metadata.rb +8 -0
  98. data/spec/unit/fixtures/cookbook_cache/foo-1.0.0/recipes/default.rb +8 -0
  99. data/spec/unit/fixtures/cookbooks_api/pruned_small_universe.json +1322 -0
  100. data/spec/unit/fixtures/cookbooks_api/small_universe.json +2987 -0
  101. data/spec/unit/fixtures/cookbooks_api/universe.json +1 -0
  102. data/spec/unit/fixtures/cookbooks_api/update_fixtures.rb +36 -0
  103. data/spec/unit/fixtures/dev_cookbooks/README.md +16 -0
  104. data/spec/unit/fixtures/dev_cookbooks/bar-cookbook.gitbundle +0 -0
  105. data/spec/unit/fixtures/eg_omnibus_dir/missing_apps/bin/.keep +0 -0
  106. data/spec/unit/fixtures/eg_omnibus_dir/missing_apps/embedded/.keep +0 -0
  107. data/spec/unit/fixtures/eg_omnibus_dir/missing_apps/embedded/bin/.keep +0 -0
  108. data/spec/unit/fixtures/eg_omnibus_dir/missing_component/bin/.keep +0 -0
  109. data/spec/unit/fixtures/eg_omnibus_dir/missing_component/embedded/apps/berkshelf/.keep +0 -0
  110. data/spec/unit/fixtures/eg_omnibus_dir/missing_component/embedded/apps/test-kitchen/.keep +0 -0
  111. data/spec/unit/fixtures/eg_omnibus_dir/missing_component/embedded/bin/.keep +0 -0
  112. data/spec/unit/fixtures/eg_omnibus_dir/valid/bin/.keep +0 -0
  113. data/spec/unit/fixtures/eg_omnibus_dir/valid/embedded/apps/berkshelf/integration_test +2 -0
  114. data/spec/unit/fixtures/eg_omnibus_dir/valid/embedded/apps/berkshelf/verify_me +5 -0
  115. data/spec/unit/fixtures/eg_omnibus_dir/valid/embedded/apps/chef-dk/.keep +0 -0
  116. data/spec/unit/fixtures/eg_omnibus_dir/valid/embedded/apps/chef/verify_me +3 -0
  117. data/spec/unit/fixtures/eg_omnibus_dir/valid/embedded/apps/test-kitchen/verify_me +2 -0
  118. data/spec/unit/fixtures/eg_omnibus_dir/valid/embedded/bin/.keep +0 -0
  119. data/spec/unit/fixtures/example_app/Policyfile.rb +0 -0
  120. data/spec/unit/fixtures/example_cookbook/.gitignore +17 -0
  121. data/spec/unit/fixtures/example_cookbook/.kitchen.yml +16 -0
  122. data/spec/unit/fixtures/example_cookbook/Berksfile +3 -0
  123. data/spec/unit/fixtures/example_cookbook/README.md +4 -0
  124. data/spec/unit/fixtures/example_cookbook/chefignore +96 -0
  125. data/spec/unit/fixtures/example_cookbook/metadata.rb +8 -0
  126. data/spec/unit/fixtures/example_cookbook/recipes/default.rb +8 -0
  127. data/spec/unit/fixtures/local_path_cookbooks/local-cookbook/.kitchen.yml +16 -0
  128. data/spec/unit/fixtures/local_path_cookbooks/local-cookbook/Berksfile +3 -0
  129. data/spec/unit/fixtures/local_path_cookbooks/local-cookbook/README.md +4 -0
  130. data/spec/unit/fixtures/local_path_cookbooks/local-cookbook/chefignore +96 -0
  131. data/spec/unit/fixtures/local_path_cookbooks/local-cookbook/metadata.rb +8 -0
  132. data/spec/unit/fixtures/local_path_cookbooks/local-cookbook/recipes/default.rb +8 -0
  133. data/spec/unit/fixtures/local_path_cookbooks/metadata-missing/README.md +2 -0
  134. data/spec/unit/policyfile/chef_server_cookbook_source_spec.rb +34 -0
  135. data/spec/unit/policyfile/community_cookbook_source_spec.rb +51 -0
  136. data/spec/unit/policyfile/cookbook_spec_spec.rb +200 -0
  137. data/spec/unit/policyfile/null_cookbook_source_spec.rb +35 -0
  138. data/spec/unit/policyfile_builder_spec.rb +489 -0
  139. data/spec/unit/policyfile_demands_spec.rb +484 -0
  140. data/spec/unit/policyfile_evaluation_spec.rb +284 -0
  141. data/spec/unit/shell_out_spec.rb +34 -0
  142. metadata +422 -0
@@ -0,0 +1,46 @@
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/exceptions'
19
+
20
+ module ChefDK
21
+ module Policyfile
22
+ class ChefServerCookbookSource
23
+
24
+ attr_reader :uri
25
+
26
+ def initialize(uri)
27
+ @uri = uri
28
+ end
29
+
30
+ def ==(other)
31
+ other.kind_of?(self.class) && other.uri == uri
32
+ end
33
+
34
+ def universe_graph
35
+ raise UnsupportedFeature, 'ChefDK does not support chef-server cookbook default sources at this time'
36
+ end
37
+
38
+ def source_options_for(cookbook_name, cookbook_version)
39
+ raise UnsupportedFeature, 'ChefDK does not support chef-server cookbook default sources at this time'
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+
46
+
@@ -0,0 +1,84 @@
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 'json'
19
+ require 'chef-dk/cookbook_omnifetch'
20
+ require 'chef-dk/exceptions'
21
+ # TODO: chef bug. Chef::HTTP::Simple needs to require this itself.
22
+ # Fixed in 2ed829e661f9a357fc9a8cdf316c84f077dad7f9 waiting for that to be
23
+ # released...
24
+ require 'tempfile'
25
+ require 'chef/platform/query_helpers' # should be handled by http/simple
26
+ require 'chef/http/cookie_manager' # should be handled by http/simple
27
+ require 'chef/http/validate_content_length' # should be handled by http/simple
28
+ require 'chef/http/simple'
29
+
30
+ # TODO: fix hardcoding
31
+ Chef::Config.ssl_verify_mode = :verify_peer
32
+
33
+ module ChefDK
34
+ module Policyfile
35
+
36
+ class CommunityCookbookSource
37
+
38
+ attr_reader :uri
39
+
40
+ def initialize(uri = nil)
41
+ @uri = uri || "https://supermarket.getchef.com"
42
+ @http_connections = {}
43
+ end
44
+
45
+ def ==(other)
46
+ other.kind_of?(self.class) && other.uri == uri
47
+ end
48
+
49
+ def universe_graph
50
+ @universe_graph ||= begin
51
+ full_community_graph.inject({}) do |normalized_graph, (cookbook_name, metadata_by_version)|
52
+ normalized_graph[cookbook_name] = metadata_by_version.inject({}) do |deps_by_version, (version, metadata)|
53
+ deps_by_version[version] = metadata["dependencies"]
54
+ deps_by_version
55
+ end
56
+ normalized_graph
57
+ end
58
+ end
59
+ end
60
+
61
+ def source_options_for(cookbook_name, cookbook_version)
62
+ base_uri = full_community_graph[cookbook_name][cookbook_version]["download_url"]
63
+ { artifactserver: base_uri, version: cookbook_version }
64
+ end
65
+
66
+ private
67
+
68
+ def http_connection_for(base_url)
69
+ @http_connections[base_url] ||= Chef::HTTP::Simple.new(base_url)
70
+ end
71
+
72
+
73
+ def full_community_graph
74
+ @full_community_graph ||=
75
+ begin
76
+ graph_json = http_connection_for(uri).get("/universe")
77
+ JSON.parse(graph_json)
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+
@@ -0,0 +1,20 @@
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/policyfile/null_cookbook_source'
19
+ require 'chef-dk/policyfile/community_cookbook_source'
20
+ require 'chef-dk/policyfile/chef_server_cookbook_source'
@@ -0,0 +1,96 @@
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 'semverse'
19
+ require 'cookbook-omnifetch'
20
+
21
+ module ChefDK
22
+ module Policyfile
23
+
24
+ class CookbookSpec
25
+
26
+ SOURCE_TYPES = [:git, :github, :path, :artifactserver]
27
+
28
+ attr_reader :version_constraint
29
+ attr_reader :name
30
+ attr_reader :source_options
31
+ attr_reader :source_type
32
+
33
+ def initialize(name, version_constraint, source_options, policyfile_filename)
34
+ @name = name
35
+ @version_constraint = Semverse::Constraint.new(version_constraint)
36
+ @source_options = source_options
37
+ @policyfile_filename = policyfile_filename
38
+ @source_type = SOURCE_TYPES.find { |type| source_options.key?(type) }
39
+ end
40
+
41
+ def ==(other)
42
+ other.kind_of?(self.class) and
43
+ other.name == name and
44
+ other.version_constraint == version_constraint and
45
+ other.source_options == source_options
46
+ end
47
+
48
+ def mirrors_canonical_upstream?
49
+ [:git, :github, :artifactserver].include?(source_type)
50
+ end
51
+
52
+ def ensure_cached
53
+ unless installer.installed?
54
+ installer.install
55
+ end
56
+ end
57
+
58
+ def installer
59
+ @installer ||= CookbookOmnifetch.init(self, source_options)
60
+ end
61
+
62
+ def cache_key
63
+ installer.cache_key
64
+ end
65
+
66
+ def relative_path
67
+ installer.relative_path.to_s
68
+ end
69
+
70
+ def uri
71
+ installer.uri
72
+ end
73
+
74
+ def version_fixed?
75
+ [:git, :github, :path].include?(@source_type)
76
+ end
77
+
78
+ def version
79
+ cached_cookbook.version
80
+ end
81
+
82
+ def dependencies
83
+ cached_cookbook.dependencies
84
+ end
85
+
86
+ def cached_cookbook
87
+ installer.cached_cookbook
88
+ end
89
+
90
+ def relative_paths_root
91
+ File.dirname(@policyfile_filename)
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,148 @@
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/policyfile/cookbook_sources'
19
+ require 'chef-dk/policyfile/cookbook_spec'
20
+
21
+ module ChefDK
22
+ module Policyfile
23
+ class DSL
24
+
25
+ attr_reader :errors
26
+ attr_reader :run_list
27
+ attr_reader :default_source
28
+ attr_reader :policyfile_cookbook_specs
29
+
30
+ attr_accessor :policyfile_filename
31
+
32
+ def initialize
33
+ @errors = []
34
+ @run_list = []
35
+ @default_source = NullCookbookSource.new
36
+ @policyfile_cookbook_specs = {}
37
+ @policyfile_filename = nil
38
+ end
39
+
40
+ def run_list(*run_list_items)
41
+ run_list_items = run_list_items.flatten
42
+ @run_list = run_list_items unless run_list_items.empty?
43
+ @run_list
44
+ end
45
+
46
+ def default_source(source_type = nil, source_uri = nil)
47
+ return @default_source if source_type.nil?
48
+ case source_type
49
+ when :community
50
+ set_default_community_source(source_uri)
51
+ when :chef_server
52
+ set_default_chef_server_source(source_uri)
53
+ else
54
+ @errors << "Invalid default_source type '#{source_type.inspect}'"
55
+ end
56
+ end
57
+
58
+ def cookbook(name, *version_and_source_opts)
59
+ source_options =
60
+ if version_and_source_opts.last.is_a?(Hash)
61
+ version_and_source_opts.pop
62
+ else
63
+ {}
64
+ end
65
+
66
+ constraint = version_and_source_opts.first || ">= 0.0.0"
67
+ spec = CookbookSpec.new(name, constraint, source_options, policyfile_filename)
68
+
69
+
70
+ if existing_source = @policyfile_cookbook_specs[name]
71
+ err = "Cookbook '#{name}' assigned to conflicting sources\n\n"
72
+ err << "Previous source: #{existing_source.source_options.inspect}\n"
73
+ err << "Conflicts with: #{source_options.inspect}\n"
74
+ @errors << err
75
+ else
76
+ @policyfile_cookbook_specs[name] = spec
77
+ end
78
+ end
79
+
80
+ def eval_policyfile(policyfile_string, policyfile_filename)
81
+ @policyfile_filename = policyfile_filename
82
+ instance_eval(policyfile_string, policyfile_filename)
83
+ validate!
84
+ self
85
+ rescue SyntaxError => e
86
+ @errors << "Invalid ruby syntax in policyfile '#{policyfile_filename}':\n\n#{e.message}"
87
+ rescue SignalException, SystemExit
88
+ # allow signal from kill, ctrl-C, etc. to bubble up:
89
+ raise
90
+ rescue Exception => e
91
+ error_message = "Evaluation of policyfile '#{policyfile_filename}' raised an exception\n"
92
+ error_message << " Exception: #{e.class.name} \"#{e.to_s}\"\n\n"
93
+ trace = filtered_bt(policyfile_filename, e)
94
+ error_message << " Relevant Code:\n"
95
+ error_message << " #{error_context(policyfile_string, policyfile_filename, e)}\n\n"
96
+ unless trace.empty?
97
+ error_message << " Backtrace:\n"
98
+ error_message << filtered_bt(policyfile_filename, e).inject("") { |formatted_trace, line| formatted_trace << " #{line}" }
99
+ error_message << "\n"
100
+ end
101
+ @errors << error_message
102
+ end
103
+
104
+ private
105
+
106
+ def set_default_community_source(source_uri)
107
+ @default_source = CommunityCookbookSource.new(source_uri)
108
+ end
109
+
110
+ def set_default_chef_server_source(source_uri)
111
+ if source_uri.nil?
112
+ @errors << "You must specify the server's URI when using a default_source :chef_server"
113
+ else
114
+ @default_source = ChefServerCookbookSource.new(source_uri)
115
+ end
116
+ end
117
+
118
+ def validate!
119
+ if @run_list.empty?
120
+ @errors << "Invalid run_list. run_list cannot be empty"
121
+ end
122
+ end
123
+
124
+ def error_context(policyfile_string, policyfile_filename, exception)
125
+ if line_number_to_show = culprit_line_number(policyfile_filename, exception)
126
+ code = policyfile_string.lines.to_a[line_number_to_show - 1].strip
127
+ "#{line_number_to_show}: #{code}"
128
+ else
129
+ "Could not find relevant code from backtrace"
130
+ end
131
+ end
132
+
133
+ def culprit_line_number(policyfile_filename, exception)
134
+ if most_proximate_backtrace_line = filtered_bt(policyfile_filename, exception).first
135
+ most_proximate_backtrace_line[/^(?:.\:)?[^:]+:([\d]+)/,1].to_i
136
+ else
137
+ nil
138
+ end
139
+ end
140
+
141
+ def filtered_bt(policyfile_filename, exception)
142
+ policyfile_filename_matcher = /^#{Regexp.escape(policyfile_filename)}/
143
+ exception.backtrace.select {|line| line =~ policyfile_filename_matcher }
144
+ end
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,37 @@
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/exceptions'
19
+
20
+ module ChefDK
21
+ module Policyfile
22
+ class NullCookbookSource
23
+
24
+ def initialize(_uri=nil)
25
+ end
26
+
27
+ def universe_graph
28
+ {}
29
+ end
30
+
31
+ def source_options_for(cookbook_name, cookbook_version)
32
+ raise UnsupportedFeature, 'You must set a default_source in your Policyfile to download cookbooks without explicit sources'
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,217 @@
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 'forwardable'
19
+
20
+ require 'solve'
21
+ require 'chef/run_list/run_list_item'
22
+
23
+ require 'chef-dk/policyfile/dsl'
24
+ require 'chef-dk/policyfile_lock'
25
+
26
+ module ChefDK
27
+
28
+ class PolicyfileCompiler
29
+
30
+ extend Forwardable
31
+
32
+ DEFAULT_DEMAND_CONSTRAINT = '>= 0.0.0'.freeze
33
+
34
+ # Cookbooks from these sources lock that cookbook to exactly one version
35
+ SOURCE_TYPES_WITH_FIXED_VERSIONS = [:git, :path].freeze
36
+
37
+ def self.evaluate(policyfile_string, policyfile_filename)
38
+ compiler = new
39
+ compiler.evaluate_policyfile(policyfile_string, policyfile_filename)
40
+ compiler
41
+ end
42
+
43
+ def_delegator :@dsl, :run_list
44
+ def_delegator :@dsl, :errors
45
+ def_delegator :@dsl, :default_source
46
+ def_delegator :@dsl, :policyfile_cookbook_specs
47
+
48
+ attr_reader :dsl
49
+
50
+ def initialize
51
+ @dsl = Policyfile::DSL.new
52
+ @artifact_server_cookbook_specs = {}
53
+ end
54
+
55
+ def error!
56
+ unless errors.empty?
57
+ raise PolicyfileError, errors.join("\n")
58
+ end
59
+ end
60
+
61
+ def cookbook_spec_for(cookbook_name)
62
+ policyfile_cookbook_specs[cookbook_name]
63
+ end
64
+
65
+ def expanded_run_list
66
+ run_list
67
+ end
68
+
69
+ def lock
70
+ @policyfile_lock ||= PolicyfileLock.build_from_compiler(self, cache_path: cache_path)
71
+ end
72
+
73
+ def install
74
+ ensure_cache_dir_exists
75
+
76
+ graph_solution.each do |cookbook_name, version|
77
+ spec = cookbook_spec_for(cookbook_name)
78
+ if spec.nil? or !spec.version_fixed?
79
+ spec = create_spec_for_cookbook(cookbook_name, version)
80
+ spec.ensure_cached
81
+ end
82
+ end
83
+ end
84
+
85
+ def create_spec_for_cookbook(cookbook_name, version)
86
+ source_options = default_source.source_options_for(cookbook_name, version)
87
+ spec = Policyfile::CookbookSpec.new(cookbook_name, "= #{version}", source_options, dsl)
88
+ @artifact_server_cookbook_specs[cookbook_name] = spec
89
+ end
90
+
91
+ def all_cookbook_specs
92
+ # in the installation proces, we create "artifact_server_cookbook_specs"
93
+ # for any cookbook that isn't sourced from a single-version source (e.g.,
94
+ # path and git only support one version at a time), but we might have
95
+ # specs for them to track additional version constraint demands. Merging
96
+ # in this order ensures the artifact_server_cookbook_specs "win".
97
+ policyfile_cookbook_specs.merge(@artifact_server_cookbook_specs)
98
+ end
99
+
100
+ ##
101
+ # Compilation Methods
102
+ ##
103
+
104
+ def graph_solution
105
+ return @solution if @solution
106
+ cache_fixed_version_cookbooks
107
+ @solution = Solve.it!(graph, graph_demands)
108
+ end
109
+
110
+ def graph
111
+ @graph ||= Solve::Graph.new.tap do |g|
112
+ artifacts_graph.each do |name, dependencies_by_version|
113
+ dependencies_by_version.each do |version, dependencies|
114
+ artifact = g.artifact(name, version)
115
+ dependencies.each do |dep_name, constraint|
116
+ artifact.dependency(dep_name, constraint)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ def graph_demands
124
+ cookbooks_for_demands.map do |cookbook_name|
125
+ spec = cookbook_spec_for(cookbook_name)
126
+ if spec.nil?
127
+ [ cookbook_name, DEFAULT_DEMAND_CONSTRAINT ]
128
+ elsif spec.version_fixed?
129
+ [ cookbook_name, "= #{spec.version}" ]
130
+ else
131
+ [ cookbook_name, spec.version_constraint.to_s ]
132
+ end
133
+ end
134
+ end
135
+
136
+ def artifacts_graph
137
+ remote_artifacts_graph.merge(local_artifacts_graph)
138
+ end
139
+
140
+ # Gives a dependency graph for cookbooks that are source from an alternate
141
+ # location. These cookbooks could have a different set of dependencies
142
+ # compared to an unmodified copy upstream. For example, the community site
143
+ # may have a cookbook "apache2" at version "1.10.4", which the user has
144
+ # forked on github and modified the dependencies without changing the
145
+ # version number. To accomodate this, the local_artifacts_graph should be
146
+ # merged over the upstream's artifacts graph.
147
+ def local_artifacts_graph
148
+ policyfile_cookbook_specs.inject({}) do |local_artifacts, (cookbook_name, cookbook_spec)|
149
+ if cookbook_spec.version_fixed?
150
+ local_artifacts[cookbook_name] = { cookbook_spec.version => cookbook_spec.dependencies }
151
+ end
152
+ local_artifacts
153
+ end
154
+ end
155
+
156
+ def remote_artifacts_graph
157
+ default_source.universe_graph
158
+ end
159
+
160
+ def version_constraint_for(cookbook_name)
161
+ if (cookbook_spec = cookbook_spec_for(cookbook_name)) and cookbook_spec.version_fixed?
162
+ version = cookbook_spec.version
163
+ "= #{version}"
164
+ else
165
+ DEFAULT_DEMAND_CONSTRAINT
166
+ end
167
+ end
168
+
169
+ def cookbook_version_fixed?(cookbook_name)
170
+ if cookbook_spec = cookbook_spec_for(cookbook_name)
171
+ cookbook_spec.version_fixed?
172
+ else
173
+ false
174
+ end
175
+ end
176
+
177
+ def cookbooks_in_run_list
178
+ run_list.map {|item_spec| Chef::RunList::RunListItem.new(item_spec).name }
179
+ end
180
+
181
+ def build
182
+ yield @dsl
183
+ self
184
+ end
185
+
186
+ def evaluate_policyfile(policyfile_string, policyfile_filename)
187
+ @dsl.eval_policyfile(policyfile_string, policyfile_filename)
188
+ self
189
+ end
190
+
191
+ private
192
+
193
+ def cookbooks_for_demands
194
+ (cookbooks_in_run_list + policyfile_cookbook_specs.keys).uniq
195
+ end
196
+
197
+ def cache_fixed_version_cookbooks
198
+ ensure_cache_dir_exists
199
+
200
+ policyfile_cookbook_specs.each do |_cookbook_name, cookbook_spec|
201
+ cookbook_spec.ensure_cached if cookbook_spec.version_fixed?
202
+ end
203
+ end
204
+
205
+ def ensure_cache_dir_exists
206
+ unless File.exist?(cache_path)
207
+ FileUtils.mkdir_p(cache_path)
208
+ end
209
+ end
210
+
211
+ def cache_path
212
+ CookbookOmnifetch.storage_path
213
+ end
214
+
215
+
216
+ end
217
+ end