chef-dk 0.5.0.rc.1 → 0.5.0

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/README.md +63 -24
  3. data/lib/chef-dk/builtin_commands.rb +2 -0
  4. data/lib/chef-dk/command/diff.rb +312 -0
  5. data/lib/chef-dk/command/push.rb +1 -1
  6. data/lib/chef-dk/command/shell_init.rb +21 -3
  7. data/lib/chef-dk/command/update.rb +28 -5
  8. data/lib/chef-dk/configurable.rb +1 -1
  9. data/lib/chef-dk/exceptions.rb +3 -0
  10. data/lib/chef-dk/pager.rb +106 -0
  11. data/lib/chef-dk/policyfile/chef_repo_cookbook_source.rb +114 -0
  12. data/lib/chef-dk/policyfile/comparison_base.rb +124 -0
  13. data/lib/chef-dk/policyfile/cookbook_sources.rb +1 -0
  14. data/lib/chef-dk/policyfile/differ.rb +266 -0
  15. data/lib/chef-dk/policyfile/dsl.rb +26 -3
  16. data/lib/chef-dk/policyfile/uploader.rb +4 -5
  17. data/lib/chef-dk/policyfile_compiler.rb +8 -0
  18. data/lib/chef-dk/policyfile_lock.rb +135 -3
  19. data/lib/chef-dk/policyfile_services/install.rb +1 -0
  20. data/lib/chef-dk/policyfile_services/update_attributes.rb +104 -0
  21. data/lib/chef-dk/service_exceptions.rb +12 -0
  22. data/lib/chef-dk/ui.rb +8 -0
  23. data/lib/chef-dk/version.rb +1 -1
  24. data/spec/spec_helper.rb +6 -0
  25. data/spec/test_helpers.rb +4 -0
  26. data/spec/unit/command/diff_spec.rb +283 -0
  27. data/spec/unit/command/shell_init_spec.rb +19 -2
  28. data/spec/unit/command/update_spec.rb +96 -0
  29. data/spec/unit/command/verify_spec.rb +0 -6
  30. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/Berksfile +3 -0
  31. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/README.md +4 -0
  32. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/chefignore +96 -0
  33. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/metadata.rb +9 -0
  34. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/recipes/default.rb +8 -0
  35. data/spec/unit/pager_spec.rb +119 -0
  36. data/spec/unit/policyfile/chef_repo_cookbook_source_spec.rb +66 -0
  37. data/spec/unit/policyfile/comparison_base_spec.rb +343 -0
  38. data/spec/unit/policyfile/differ_spec.rb +687 -0
  39. data/spec/unit/policyfile_evaluation_spec.rb +87 -0
  40. data/spec/unit/policyfile_lock_build_spec.rb +247 -8
  41. data/spec/unit/policyfile_lock_serialization_spec.rb +47 -0
  42. data/spec/unit/policyfile_services/export_repo_spec.rb +2 -0
  43. data/spec/unit/policyfile_services/push_spec.rb +2 -0
  44. data/spec/unit/policyfile_services/update_attributes_spec.rb +217 -0
  45. metadata +62 -6
@@ -104,7 +104,7 @@ E
104
104
  def apply_params!(params)
105
105
  remaining_args = parse_options(params)
106
106
  if remaining_args.size < 1 or remaining_args.size > 2
107
- ui.err(banner)
107
+ ui.err(opt_parser)
108
108
  return false
109
109
  else
110
110
  @policy_group = remaining_args[0]
@@ -22,7 +22,7 @@ module ChefDK
22
22
  module Command
23
23
  class ShellInit < ChefDK::Command::Base
24
24
 
25
- SUPPORTED_SHELLS = %w[ bash zsh sh powershell posh].map(&:freeze).freeze
25
+ SUPPORTED_SHELLS = %w[ bash fish zsh sh powershell posh].map(&:freeze).freeze
26
26
 
27
27
  banner(<<-HELP)
28
28
  Usage: chef shell-init
@@ -34,6 +34,8 @@ ruby.
34
34
 
35
35
  In sh, bash, and zsh:
36
36
  eval "$(chef shell-init SHELL_NAME)"
37
+ In fish:
38
+ eval (chef shell-init fish)
37
39
  In Powershell:
38
40
  chef shell-init powershell | Invoke-Expression
39
41
 
@@ -41,6 +43,8 @@ ruby.
41
43
 
42
44
  In sh, bash, and zsh:
43
45
  echo 'eval "$(chef shell-init SHELL_NAME)"' >> ~/.YOUR_SHELL_RC_FILE
46
+ In fish:
47
+ echo 'eval (chef shell-init SHELL_NAME)' >> ~/.config/fish/config.fish
44
48
  In Powershell
45
49
  "chef shell-init powershell | Invoke-Expression" >> $PROFILE
46
50
 
@@ -84,17 +88,31 @@ HELP
84
88
  case shell
85
89
  when 'sh', 'bash', 'zsh'
86
90
  posix_shell_export(var, val)
91
+ when 'fish'
92
+ fish_shell_export(var, val)
87
93
  when 'powershell', 'posh'
88
94
  powershell_export(var, val)
89
95
  end
90
96
  end
91
97
 
92
98
  def posix_shell_export(var, val)
93
- msg("export #{var}=\"#{val}\"")
99
+ msg(%Q(export #{var}="#{val}"))
100
+ end
101
+
102
+ def fish_shell_export(var, val)
103
+ # Fish's syntax for setting PATH is special. Path elements are
104
+ # divided by spaces (instead of colons). We also send STDERR to
105
+ # /dev/null to avoid Fish's helpful warnings about nonexistent
106
+ # PATH elements.
107
+ if var == 'PATH'
108
+ msg(%Q(set -gx #{var} "#{val.split(':').join('" "')}" 2>/dev/null;))
109
+ else
110
+ msg(%Q(set -gx #{var} "#{val}";))
111
+ end
94
112
  end
95
113
 
96
114
  def powershell_export(var, val)
97
- msg("$env:#{var}=\"#{val}\"")
115
+ msg(%Q($env:#{var}="#{val}"))
98
116
  end
99
117
  end
100
118
  end
@@ -18,6 +18,7 @@
18
18
  require 'chef-dk/command/base'
19
19
  require 'chef-dk/ui'
20
20
  require 'chef-dk/policyfile_services/install'
21
+ require 'chef-dk/policyfile_services/update_attributes'
21
22
 
22
23
  module ChefDK
23
24
  module Command
@@ -46,10 +47,18 @@ Options:
46
47
  BANNER
47
48
 
48
49
  option :debug,
49
- short: "-D",
50
- long: "--debug",
51
- description: "Enable stacktraces and other debug output",
52
- default: false
50
+ short: "-D",
51
+ long: "--debug",
52
+ description: "Enable stacktraces and other debug output",
53
+ default: false,
54
+ boolean: true
55
+
56
+ option :update_attributes,
57
+ short: "-a",
58
+ long: "--attributes",
59
+ description: "Update attributes",
60
+ default: false,
61
+ boolean: true
53
62
 
54
63
  attr_reader :policyfile_relative_path
55
64
 
@@ -61,11 +70,16 @@ BANNER
61
70
 
62
71
  @policyfile_relative_path = nil
63
72
  @installer = nil
73
+ @attributes_updater = nil
64
74
  end
65
75
 
66
76
  def run(params = [])
67
77
  apply_params!(params)
68
- installer.run
78
+ if update_attributes?
79
+ attributes_updater.run
80
+ else
81
+ installer.run
82
+ end
69
83
  0
70
84
  rescue PolicyfileServiceError => e
71
85
  handle_error(e)
@@ -76,10 +90,19 @@ BANNER
76
90
  @installer ||= PolicyfileServices::Install.new(policyfile: policyfile_relative_path, ui: ui, root_dir: Dir.pwd, overwrite: true)
77
91
  end
78
92
 
93
+ def attributes_updater
94
+ @attributes_updater ||=
95
+ PolicyfileServices::UpdateAttributes.new(policyfile: policyfile_relative_path, ui: ui, root_dir: Dir.pwd)
96
+ end
97
+
79
98
  def debug?
80
99
  !!config[:debug]
81
100
  end
82
101
 
102
+ def update_attributes?
103
+ !!config[:update_attributes]
104
+ end
105
+
83
106
  def handle_error(error)
84
107
  ui.err("Error: #{error.message}")
85
108
  if error.respond_to?(:reason)
@@ -21,7 +21,7 @@ require 'chef/workstation_config_loader'
21
21
  # Define a config context for ChefDK
22
22
  class Chef::Config
23
23
 
24
- default(:policy_document_native_api, false)
24
+ default(:policy_document_native_api, true)
25
25
 
26
26
  config_context(:chefdk) do
27
27
 
@@ -38,6 +38,9 @@ module ChefDK
38
38
  class CachedCookbookModified < StandardError
39
39
  end
40
40
 
41
+ class InvalidPolicyfileAttribute < StandardError
42
+ end
43
+
41
44
  class MissingComponentError < RuntimeError
42
45
  def initialize(component_name, path_checked)
43
46
  super("Component #{component_name} is missing. \nReason: Could not find #{path_checked}.")
@@ -0,0 +1,106 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2015 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
+ class Pager
22
+
23
+ attr_reader :pager_pid
24
+
25
+ def initialize(enable_pager: true)
26
+ @enable_pager = enable_pager
27
+ @pipe = nil
28
+ @pager_pid
29
+ end
30
+
31
+ def pager_enabled?
32
+ !!(@enable_pager && have_tty? && env["PAGER"])
33
+ end
34
+
35
+ def ui
36
+ @ui ||=
37
+ if pager_enabled?
38
+ UI.new(out: parent_stdout)
39
+ else
40
+ UI.new
41
+ end
42
+ end
43
+
44
+ def with_pager
45
+ start
46
+ begin
47
+ yield self
48
+ ensure
49
+ wait
50
+ end
51
+ end
52
+
53
+ def start
54
+ return false unless pager_enabled?
55
+
56
+ # Ignore CTRL-C because it can cause the parent to die before the
57
+ # pager which causes wonky behavior in the terminal
58
+ Kernel.trap(:INT, "IGNORE")
59
+
60
+ @pager_pid = Process.spawn(pager_env, env["PAGER"], in: child_stdin)
61
+
62
+ child_stdin.close
63
+ end
64
+
65
+ def wait
66
+ return false unless pager_enabled?
67
+
68
+ # Sends EOF to the PAGER
69
+ parent_stdout.close
70
+ # wait or else we'd kill the pager when we exit
71
+ Process.waitpid(pager_pid)
72
+ end
73
+
74
+ # @api private
75
+ # This is just public so we can stub it for testing
76
+ def env
77
+ ENV
78
+ end
79
+
80
+ # @api private
81
+ # This is just public so we can stub it for testing
82
+ def have_tty?
83
+ $stdout.tty?
84
+ end
85
+
86
+ private
87
+
88
+ def child_stdin
89
+ pipe[0]
90
+ end
91
+
92
+ def parent_stdout
93
+ pipe[1]
94
+ end
95
+
96
+ def pipe
97
+ @pipe ||= IO.pipe
98
+ end
99
+
100
+ def pager_env
101
+ {"LESS" => "-FRX", "LV" => "-c"}
102
+ end
103
+
104
+ end
105
+ end
106
+
@@ -0,0 +1,114 @@
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
+ require 'chef/cookbook_loader'
20
+ require 'chef/cookbook/file_system_file_vendor'
21
+ require 'chef-dk/ui'
22
+
23
+ module ChefDK
24
+ module Policyfile
25
+ class ChefRepoCookbookSource
26
+
27
+ # path to a chef-repo or the cookbook path under it
28
+ attr_reader :path
29
+ # UI object for output
30
+ attr_accessor :ui
31
+
32
+ # Constructor
33
+ #
34
+ # @param path [String] path to a chef-repo or the cookbook path under it
35
+ def initialize(path)
36
+ self.path = path
37
+ @ui = UI.new
38
+ end
39
+
40
+ def ==(other)
41
+ other.kind_of?(self.class) && other.path == path
42
+ end
43
+
44
+ # Calls the slurp_metadata! helper once to calculate the @universe_graph
45
+ # and @cookbook_version_paths metadata. Returns the @universe_graph.
46
+ #
47
+ # @return [Hash] universe_graph
48
+ def universe_graph
49
+ slurp_metadata! if @universe_graph.nil?
50
+ @universe_graph
51
+ end
52
+
53
+ # Returns the metadata (path and version) for an individual cookbook
54
+ #
55
+ # @return [Hash] metadata for a single cookbook version
56
+ def source_options_for(cookbook_name, cookbook_version)
57
+ { path: cookbook_version_paths[cookbook_name][cookbook_version], version: cookbook_version }
58
+ end
59
+
60
+ private
61
+
62
+ # Setter for setting the path. It may either be a full chef-repo with
63
+ # a cookbooks directory in it, or only a path to the cookbooks directory,
64
+ # and it autodetects which it is passed.
65
+ #
66
+ # @param path [String] path to a chef-repo or the cookbook path under it
67
+ def path=(path)
68
+ cookbooks_path = "#{path}/cookbooks"
69
+ if Dir.exist?(cookbooks_path)
70
+ @path = cookbooks_path
71
+ else
72
+ @path = path
73
+ end
74
+ end
75
+
76
+ # Calls the slurp_metadata! helper once to calculate the @universe_graph
77
+ # and @cookbook_version_paths metadata. Returns the @cookbook_version_paths.
78
+ #
79
+ # @return [Hash] cookbook_version_paths
80
+ def cookbook_version_paths
81
+ slurp_metadata! if @cookbook_version_paths.nil?
82
+ @cookbook_version_paths
83
+ end
84
+
85
+ # Helper to compute the @universe_graph and @cookbook_version_paths once
86
+ # from the Chef::CookbookLoader on-disk cookbook repo.
87
+ def slurp_metadata!
88
+ @universe_graph = {}
89
+ @cookbook_version_paths = {}
90
+ cookbook_repo.load_cookbooks
91
+ cookbook_repo.each do |cookbook_name, cookbook_version|
92
+ metadata = cookbook_version.metadata
93
+ if metadata.name.nil?
94
+ ui.err("WARN: #{cookbook_name} cookbook missing metadata or no name field, skipping")
95
+ next
96
+ end
97
+ @universe_graph[metadata.name] ||= {}
98
+ @universe_graph[metadata.name][metadata.version] = metadata.dependencies.to_a
99
+ @cookbook_version_paths[metadata.name] ||= {}
100
+ @cookbook_version_paths[metadata.name][metadata.version] = cookbook_version.root_dir
101
+ end
102
+ end
103
+
104
+ # @return [Chef::CookbookLoader] cookbook loader using on disk FileVendor
105
+ def cookbook_repo
106
+ @cookbook_repo ||= begin
107
+ Chef::Cookbook::FileVendor.fetch_from_disk(path)
108
+ Chef::CookbookLoader.new(path)
109
+ end
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,124 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2015 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 'ffi_yajl'
19
+ require 'mixlib/shellout'
20
+ require 'chef-dk/service_exceptions'
21
+
22
+ module ChefDK
23
+ module Policyfile
24
+ module ComparisonBase
25
+
26
+ class Local
27
+
28
+ attr_reader :policyfile_lock_relpath
29
+
30
+ def initialize(policyfile_lock_relpath)
31
+ @policyfile_lock_relpath = policyfile_lock_relpath
32
+ end
33
+
34
+ def name
35
+ "local:#{policyfile_lock_relpath}"
36
+ end
37
+
38
+ def lock
39
+ raise LockfileNotFound, "Expected lockfile at #{policyfile_lock_relpath} does not exist" unless File.exist?(policyfile_lock_relpath)
40
+ raise LockfileNotFound, "Expected lockfile at #{policyfile_lock_relpath} cannot be read" unless File.readable?(policyfile_lock_relpath)
41
+ FFI_Yajl::Parser.parse(IO.read(policyfile_lock_relpath))
42
+ rescue FFI_Yajl::ParseError => e
43
+ raise MalformedLockfile, "Invalid JSON in lockfile at #{policyfile_lock_relpath}:\n #{e.message}"
44
+ end
45
+
46
+ end
47
+
48
+ class Git
49
+
50
+ attr_reader :ref
51
+ attr_reader :policyfile_lock_relpath
52
+
53
+ def initialize(ref, policyfile_lock_relpath)
54
+ @ref = ref
55
+ @policyfile_lock_relpath = policyfile_lock_relpath
56
+ end
57
+
58
+ def name
59
+ "git:#{ref}"
60
+ end
61
+
62
+ def lock
63
+ git_cmd.run_command
64
+ git_cmd.error!
65
+ FFI_Yajl::Parser.parse(git_cmd.stdout)
66
+ rescue Mixlib::ShellOut::ShellCommandFailed
67
+ raise GitError, "Git command `#{git_cmd_string}` failed with message: #{git_cmd.stderr.chomp}"
68
+ rescue FFI_Yajl::ParseError => e
69
+ raise MalformedLockfile, "Invalid JSON in lockfile at git ref '#{ref}' at path '#{policyfile_lock_relpath}':\n #{e.message}"
70
+ end
71
+
72
+ def git_cmd
73
+ @git_cmd ||= Mixlib::ShellOut.new(git_cmd_string)
74
+ end
75
+
76
+ def git_cmd_string
77
+ # Git is a little picky about how we specify the paths, but it looks
78
+ # like we don't need to worry about the relative path to the root of
79
+ # the repo if we give git a leading dot:
80
+ #
81
+ # git show 6644e6cb2ade90b8aff2ebb44728958fbc939ebf:zero.rb
82
+ # fatal: Path 'etc/zero.rb' exists, but not 'zero.rb'.
83
+ # Did you mean '6644e6cb2ade90b8aff2ebb44728958fbc939ebf:etc/zero.rb' aka '6644e6cb2ade90b8aff2ebb44728958fbc939ebf:./zero.rb'?
84
+ # git show 6644e6cb2ade90b8aff2ebb44728958fbc939ebf:./zero.rb
85
+ # (works)
86
+ "git show #{ref}:./#{policyfile_lock_relpath}"
87
+ end
88
+
89
+ end
90
+
91
+ class PolicyGroup
92
+
93
+ attr_reader :group
94
+ attr_reader :policy_name
95
+ attr_reader :http_client
96
+
97
+ def initialize(group, policy_name, http_client)
98
+ @group = group
99
+ @policy_name = policy_name
100
+ @http_client = http_client
101
+ end
102
+
103
+ def name
104
+ "policy_group:#{group}"
105
+ end
106
+
107
+ def lock
108
+ http_client.get("policy_groups/#{group}/policies/#{policy_name}")
109
+ rescue Net::ProtocolError => e
110
+ if e.respond_to?(:response) && e.response.code.to_s == "404"
111
+ raise PolicyfileDownloadError.new("No policyfile lock named '#{policy_name}' found in policy_group '#{group}' at #{http_client.url}", e)
112
+ else
113
+ raise PolicyfileDownloadError.new("HTTP error attempting to fetch policyfile lock from #{http_client.url}", e)
114
+ end
115
+ rescue => e
116
+ raise PolicyfileDownloadError.new("Failed to fetch policyfile lock from #{http_client.url}", e)
117
+ end
118
+
119
+ end
120
+
121
+ end
122
+ end
123
+ end
124
+