chef-dk 0.5.0.rc.1 → 0.5.0

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/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
+