chef-apply 0.3.3 → 0.4.6

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/chef-apply.gemspec +2 -1
  4. data/lib/chef_apply/action/base.rb +1 -0
  5. data/lib/chef_apply/action/converge_target.rb +11 -11
  6. data/lib/chef_apply/action/converge_target/ccr_failure_mapper.rb +100 -0
  7. data/lib/chef_apply/action/generate_local_policy.rb +1 -1
  8. data/lib/chef_apply/action/generate_temp_cookbook.rb +53 -53
  9. data/lib/chef_apply/action/generate_temp_cookbook/recipe_lookup.rb +124 -0
  10. data/lib/chef_apply/action/generate_temp_cookbook/temp_cookbook.rb +175 -0
  11. data/lib/chef_apply/action/install_chef.rb +8 -8
  12. data/lib/chef_apply/action/install_chef/minimum_chef_version.rb +85 -0
  13. data/lib/chef_apply/cli.rb +10 -9
  14. data/lib/chef_apply/cli/options.rb +2 -2
  15. data/lib/chef_apply/cli/validation.rb +2 -1
  16. data/lib/chef_apply/file_fetcher.rb +1 -1
  17. data/lib/chef_apply/log.rb +4 -6
  18. data/lib/chef_apply/startup.rb +7 -4
  19. data/lib/chef_apply/target_host.rb +10 -5
  20. data/lib/chef_apply/target_host/linux.rb +1 -1
  21. data/lib/chef_apply/target_resolver.rb +3 -1
  22. data/lib/chef_apply/telemeter.rb +1 -1
  23. data/lib/chef_apply/text/error_translation.rb +1 -1
  24. data/lib/chef_apply/text/text_wrapper.rb +2 -1
  25. data/lib/chef_apply/ui/error_printer.rb +15 -13
  26. data/lib/chef_apply/ui/plain_text_element.rb +1 -2
  27. data/lib/chef_apply/ui/plain_text_header.rb +1 -1
  28. data/lib/chef_apply/ui/terminal.rb +4 -4
  29. data/lib/chef_apply/version.rb +1 -1
  30. data/spec/spec_helper.rb +43 -3
  31. data/spec/unit/action/base_spec.rb +2 -1
  32. data/spec/unit/{errors → action/converge_target}/ccr_failure_mapper_spec.rb +12 -9
  33. data/spec/unit/action/converge_target_spec.rb +21 -22
  34. data/spec/unit/action/generate_local_policy_spec.rb +5 -5
  35. data/spec/unit/{recipe_lookup_spec.rb → action/generate_temp_cookbook/recipe_lookup_spec.rb} +10 -10
  36. data/spec/unit/{temp_cookbook_spec.rb → action/generate_temp_cookbook/temp_cookbook_spec.rb} +23 -24
  37. data/spec/unit/action/generate_temp_cookbook_spec.rb +4 -6
  38. data/spec/unit/{minimum_chef_version_spec.rb → action/install_chef/minimum_chef_version_spec.rb} +13 -13
  39. data/spec/unit/action/install_chef_spec.rb +4 -4
  40. data/spec/unit/cli/options_spec.rb +17 -17
  41. data/spec/unit/cli/validation_spec.rb +6 -3
  42. data/spec/unit/cli_spec.rb +12 -9
  43. data/spec/unit/log_spec.rb +1 -1
  44. data/spec/unit/startup_spec.rb +11 -12
  45. data/spec/unit/target_host/windows_spec.rb +1 -1
  46. data/spec/unit/target_host_spec.rb +3 -2
  47. data/spec/unit/telemeter_spec.rb +5 -5
  48. data/spec/unit/text/error_translation_spec.rb +11 -7
  49. data/spec/unit/ui/error_printer_spec.rb +6 -7
  50. metadata +24 -11
  51. data/Gemfile.lock +0 -400
  52. data/lib/chef_apply/errors/ccr_failure_mapper.rb +0 -93
  53. data/lib/chef_apply/minimum_chef_version.rb +0 -79
  54. data/lib/chef_apply/recipe_lookup.rb +0 -117
  55. data/lib/chef_apply/temp_cookbook.rb +0 -170
@@ -0,0 +1,175 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 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 "tmpdir"
19
+ require "fileutils"
20
+ require "chef_apply/log"
21
+ require "chef_apply/error"
22
+ require "chef_apply/action/generate_temp_cookbook"
23
+ module ChefApply
24
+ module Action
25
+ class GenerateTempCookbook
26
+ # This class knows how to create a local cookbook in a temp file, populate
27
+ # it with various recipes, attributes, config, etc. and delete it when the
28
+ # cookbook is no longer necessary
29
+ class TempCookbook
30
+ attr_reader :path, :descriptor, :from
31
+ def initialize
32
+ @path = Dir.mktmpdir("cw")
33
+ end
34
+
35
+ def from_existing_recipe(existing_recipe_path)
36
+ ext_name = File.extname(existing_recipe_path)
37
+ raise UnsupportedExtension.new(ext_name) unless ext_name == ".rb"
38
+
39
+ cb = cookbook_for_recipe(existing_recipe_path)
40
+ if cb
41
+ # Full existing cookbook - only needs policyfile
42
+ ChefApply::Log.debug("Found full cookbook at path '#{cb[:path]}' and using recipe '#{cb[:recipe_name]}'")
43
+ @descriptor = "#{cb[:name]}::#{cb[:recipe_name]}"
44
+ @from = "#{cb[:path]}"
45
+ recipe_name = cb[:recipe_name]
46
+ cb_name = cb[:name]
47
+ FileUtils.cp_r(cb[:path], path)
48
+ # cp_r copies the whole existing cookbook into the tempdir so need to reset our path
49
+ @path = File.join(path, File.basename(cb[:path]))
50
+ else
51
+ # Cookbook from single recipe not in a cookbook. We create the full cookbook
52
+ # structure including metadata, then generate policyfile. We set the cookbook
53
+ # name to the recipe name so hopefully this gives us better reporting info
54
+ # in the future
55
+ ChefApply::Log.debug("Found single recipe at path '#{existing_recipe_path}'")
56
+ recipe = File.basename(existing_recipe_path)
57
+ recipe_name = File.basename(recipe, ext_name)
58
+ cb_name = "cw_recipe"
59
+ @descriptor = "#{recipe_name}"
60
+ @from = existing_recipe_path
61
+ recipes_dir = generate_recipes_dir
62
+ # This has the potential to break if they specify a recipe without a .rb
63
+ # extension, but lets wait to deal with that bug until we encounter it
64
+ FileUtils.cp(existing_recipe_path, File.join(recipes_dir, recipe))
65
+ generate_metadata(cb_name)
66
+ end
67
+ generate_policyfile(cb_name, recipe_name)
68
+ end
69
+
70
+ def from_resource(resource_type, resource_name, properties)
71
+ # Generate a cookbook containing a single default recipe with the specified
72
+ # resource in it. Incloud the resource type in the cookbook name so hopefully
73
+ # this gives us better reporting info in the future.
74
+ @descriptor = "#{resource_type}[#{resource_name}]"
75
+ @from = "resource"
76
+
77
+ ChefApply::Log.debug("Generating cookbook for single resource '#{resource_type}[#{resource_name}]'")
78
+ name = "cw_#{resource_type}"
79
+ recipe_name = "default"
80
+ recipes_dir = generate_recipes_dir
81
+ File.open(File.join(recipes_dir, "#{recipe_name}.rb"), "w+") do |f|
82
+ f.print(create_resource_definition(resource_type, resource_name, properties))
83
+ end
84
+ generate_metadata(name)
85
+ generate_policyfile(name, recipe_name)
86
+ end
87
+
88
+ def delete
89
+ FileUtils.remove_entry path
90
+ end
91
+
92
+ def cookbook_for_recipe(existing_recipe_path)
93
+ metadata = File.expand_path(File.join(existing_recipe_path, "../../metadata.rb"))
94
+ if File.file?(metadata)
95
+ require "chef/cookbook/metadata"
96
+ m = Chef::Cookbook::Metadata.new
97
+ m.from_file(metadata)
98
+ {
99
+ name: m.name,
100
+ recipe_name: File.basename(existing_recipe_path, File.extname(existing_recipe_path)),
101
+ path: File.expand_path(File.join(metadata, "../")),
102
+ }
103
+ else
104
+ nil
105
+ end
106
+ end
107
+
108
+ def generate_recipes_dir
109
+ recipes_path = File.join(path, "recipes")
110
+ FileUtils.mkdir_p(recipes_path)
111
+ recipes_path
112
+ end
113
+
114
+ def generate_metadata(name)
115
+ metadata_file = File.join(path, "metadata.rb")
116
+ File.open(metadata_file, "w+") do |f|
117
+ f.print("name \"#{name}\"\n")
118
+ end
119
+ metadata_file
120
+ end
121
+
122
+ def generate_policyfile(name, recipe_name)
123
+ policy_file = File.join(path, "Policyfile.rb")
124
+ if File.exist?(policy_file)
125
+ File.open(policy_file, "a") do |f|
126
+ # We override the specified run_list with the run_list we want.
127
+ # We append and put this at the end of the file so it overrides
128
+ # any specified run_list.
129
+ f.print("\n# Overriding run_list with command line specified value\n")
130
+ f.print("run_list \"#{name}::#{recipe_name}\"\n")
131
+ end
132
+ else
133
+ File.open(policy_file, "w+") do |f|
134
+ f.print("name \"#{name}_policy\"\n")
135
+ ChefApply::Config.chef.cookbook_repo_paths.each do |p|
136
+ f.print("default_source :chef_repo, \"#{p}\"\n")
137
+ end
138
+ f.print("default_source :supermarket\n")
139
+ f.print("run_list \"#{name}::#{recipe_name}\"\n")
140
+ f.print("cookbook \"#{name}\", path: \".\"\n")
141
+ end
142
+ end
143
+ policy_file
144
+ end
145
+
146
+ def create_resource_definition(resource_type, resource_name, properties)
147
+ r = "#{resource_type} '#{resource_name}'"
148
+ # lets format the properties into the correct syntax Chef expects
149
+ unless properties.empty?
150
+ r += " do\n"
151
+ properties.each do |k, v|
152
+ v = "'#{v}'" if v.is_a? String
153
+ r += " #{k} #{v}\n"
154
+ end
155
+ r += "end"
156
+ end
157
+ r += "\n"
158
+ r
159
+ end
160
+
161
+ def policyfile_lock_path
162
+ File.join(path, "Policyfile.lock.json")
163
+ end
164
+
165
+ def export_path
166
+ File.join(path, "export")
167
+ end
168
+
169
+ class UnsupportedExtension < ChefApply::ErrorNoLogs
170
+ def initialize(ext); super("CHEFVAL009", ext); end
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -16,19 +16,18 @@
16
16
  #
17
17
 
18
18
  require "chef_apply/action/base"
19
- require "chef_apply/minimum_chef_version"
19
+ require "chef_apply/action/install_chef/minimum_chef_version"
20
20
  require "fileutils"
21
21
 
22
22
  module ChefApply
23
23
  module Action
24
- class InstallChef < ChefApply::Action::Base
25
-
24
+ class InstallChef < Base
26
25
  def initialize(opts = { check_only: false })
27
26
  super
28
27
  end
29
28
 
30
29
  def perform_action
31
- if ChefApply::MinimumChefVersion.check!(target_host, config[:check_only]) == :minimum_version_met
30
+ if InstallChef::MinimumChefVersion.check!(target_host, config[:check_only]) == :minimum_version_met
32
31
  notify(:already_installed)
33
32
  else
34
33
  perform_local_install
@@ -40,7 +39,7 @@ module ChefApply
40
39
  end
41
40
 
42
41
  def perform_local_install
43
- package = lookup_artifact()
42
+ package = lookup_artifact
44
43
  notify(:downloading)
45
44
  local_path = download_to_workstation(package.url)
46
45
  notify(:uploading)
@@ -54,18 +53,19 @@ module ChefApply
54
53
  # TODO BOOTSTRAP - we'll need to implement this for both platforms
55
54
  # require "mixlib/install"
56
55
  # installer = Mixlib::Install.new({
57
- # platform: "windows",
56
+ # platform: "windows",/etc -
58
57
  # product_name: "chef",
59
58
  # channel: :stable,
60
59
  # shell_type: :ps1,
61
60
  # version: "13",
62
61
  # })
63
- target_host.run_command! installer.install_command
62
+ # target_host.run_command! installer.install_command
64
63
  raise NotImplementedError
65
64
  end
66
65
 
67
66
  def lookup_artifact
68
67
  return @artifact_info if @artifact_info
68
+
69
69
  require "mixlib/install"
70
70
  c = train_to_mixlib(target_host.platform)
71
71
  Mixlib::Install.new(c).artifact_info
@@ -109,7 +109,7 @@ module ChefApply
109
109
  end
110
110
 
111
111
  def upload_to_target(local_path)
112
- installer_dir = target_host.temp_dir()
112
+ installer_dir = target_host.temp_dir
113
113
  remote_path = File.join(installer_dir, File.basename(local_path))
114
114
  target_host.upload_file(local_path, remote_path)
115
115
  remote_path
@@ -0,0 +1,85 @@
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_apply/error"
19
+ require "chef_apply/action/install_chef/minimum_chef_version"
20
+
21
+ module ChefApply
22
+ module Action
23
+ class InstallChef < Base
24
+ class MinimumChefVersion
25
+
26
+ CONSTRAINTS = {
27
+ windows: {
28
+ 13 => Gem::Version.new("13.10.4"),
29
+ 14 => Gem::Version.new("14.4.22"),
30
+ },
31
+ linux: {
32
+ 13 => Gem::Version.new("13.10.4"),
33
+ 14 => Gem::Version.new("14.1.1"),
34
+ },
35
+ }.freeze
36
+
37
+ def self.check!(target, check_only)
38
+ begin
39
+ installed_version = target.installed_chef_version
40
+ rescue ChefApply::TargetHost::ChefNotInstalled
41
+ if check_only
42
+ raise ClientNotInstalled.new
43
+ end
44
+
45
+ return :client_not_installed
46
+ end
47
+
48
+ os_constraints = CONSTRAINTS[target.base_os]
49
+ min_14_version = os_constraints[14]
50
+ min_13_version = os_constraints[13]
51
+
52
+ case
53
+ when installed_version >= Gem::Version.new("14.0.0") && installed_version < min_14_version
54
+ raise Client14Outdated.new(installed_version, min_14_version)
55
+ when installed_version >= Gem::Version.new("13.0.0") && installed_version < min_13_version
56
+ raise Client13Outdated.new(installed_version, min_13_version, min_14_version)
57
+ when installed_version < Gem::Version.new("13.0.0")
58
+ # If they have Chef < 13.0.0 installed we want to show them the easiest upgrade path -
59
+ # Chef 13 first and then Chef 14 since most customers cannot make the leap directly
60
+ # to 14.
61
+ raise Client13Outdated.new(installed_version, min_13_version, min_14_version)
62
+ end
63
+
64
+ :minimum_version_met
65
+ end
66
+
67
+ class ClientNotInstalled < ChefApply::ErrorNoLogs
68
+ def initialize(); super("CHEFINS002"); end
69
+ end
70
+
71
+ class Client13Outdated < ChefApply::ErrorNoLogs
72
+ def initialize(current_version, min_13_version, min_14_version)
73
+ super("CHEFINS003", current_version, min_13_version, min_14_version)
74
+ end
75
+ end
76
+
77
+ class Client14Outdated < ChefApply::ErrorNoLogs
78
+ def initialize(current_version, target_version)
79
+ super("CHEFINS004", current_version, target_version)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -24,10 +24,6 @@ require "chef-config/logger"
24
24
  require "chef_apply/cli/validation"
25
25
  require "chef_apply/cli/options"
26
26
  require "chef_apply/cli/help"
27
- require "chef_apply/action/converge_target"
28
- require "chef_apply/action/generate_local_policy"
29
- require "chef_apply/action/generate_temp_cookbook"
30
- require "chef_apply/action/install_chef"
31
27
  require "chef_apply/error"
32
28
  require "chef_apply/log"
33
29
  require "chef_apply/target_host"
@@ -39,6 +35,10 @@ require "chef_apply/ui/terminal/job"
39
35
  require "license_acceptance/cli_flags/mixlib_cli"
40
36
  require "license_acceptance/acceptor"
41
37
 
38
+ require "chef_apply/action/generate_temp_cookbook"
39
+ require "chef_apply/action/generate_local_policy"
40
+ require "chef_apply/action/converge_target"
41
+
42
42
  module ChefApply
43
43
  class CLI
44
44
  attr_reader :temp_cookbook, :archive_file_location, :target_hosts
@@ -118,9 +118,8 @@ module ChefApply
118
118
  rescue OptionParser::InvalidOption => e # from parse_options
119
119
  # Using nil here is a bit gross but it prevents usage from printing.
120
120
  ove = OptionValidationError.new("CHEFVAL010", nil,
121
- e.message.split(":")[1].strip, # only want the flag
122
- format_flags.lines[1..-1].join # remove 'FLAGS:' header
123
- )
121
+ e.message.split(":")[1].strip, # only want the flag
122
+ format_flags.lines[1..-1].join) # remove 'FLAGS:' header
124
123
  handle_perform_error(ove)
125
124
  rescue => e
126
125
  handle_perform_error(e)
@@ -140,8 +139,8 @@ module ChefApply
140
139
 
141
140
  def resolve_targets(host_spec, opts)
142
141
  @target_hosts = TargetResolver.new(host_spec,
143
- opts.delete(:protocol),
144
- opts).targets
142
+ opts.delete(:protocol),
143
+ opts).targets
145
144
  end
146
145
 
147
146
  def render_cookbook_setup(arguments)
@@ -182,6 +181,7 @@ module ChefApply
182
181
  end
183
182
 
184
183
  def install(target_host, reporter)
184
+ require "chef_apply/action/install_chef"
185
185
  context = TS.install_chef
186
186
  reporter.update(context.verifying)
187
187
  installer = Action::InstallChef.new(target_host: target_host, check_only: !parsed_options[:install])
@@ -310,6 +310,7 @@ module ChefApply
310
310
  # message when there was only one job.
311
311
  raise jobs.first.exception
312
312
  end
313
+
313
314
  raise ChefApply::MultiJobFailure.new(failed_jobs)
314
315
  end
315
316
 
@@ -16,7 +16,6 @@
16
16
  #
17
17
 
18
18
  require "chef_apply/text"
19
- require "chef_apply/action/install_chef"
20
19
 
21
20
  # Moving the options into here so the cli.rb file is smaller and easier to read
22
21
  # For options that need to be merged back into the global ChefApply::Config object
@@ -68,6 +67,7 @@ module ChefApply
68
67
  unless File.exist?(path)
69
68
  raise OptionValidationError.new("CHEFVAL001", self, path)
70
69
  end
70
+
71
71
  path
72
72
  end)
73
73
 
@@ -89,7 +89,7 @@ module ChefApply
89
89
  long: "--protocol <PROTOCOL>",
90
90
  short: "-p",
91
91
  description: T.protocol_description(ChefApply::Config::SUPPORTED_PROTOCOLS.join(" "),
92
- ChefApply::Config.connection.default_protocol),
92
+ ChefApply::Config.connection.default_protocol),
93
93
  default: ChefApply::Config.connection.default_protocol,
94
94
  proc: Proc.new { |val| ChefApply::Config.connection.default_protocol(val) }
95
95
 
@@ -30,6 +30,7 @@ module ChefApply
30
30
  if params.size < 2
31
31
  raise OptionValidationError.new("CHEFVAL002", self)
32
32
  end
33
+
33
34
  if params.size == 2
34
35
  # Trying to specify a recipe to run remotely, no properties
35
36
  cb = params[1]
@@ -73,7 +74,7 @@ module ChefApply
73
74
  value
74
75
  when /^\d+$/
75
76
  value.to_i
76
- when /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
77
+ when /^\d+\.\d*$/, /^\d*\.\d+$/
77
78
  value.to_f
78
79
  when /true/i
79
80
  true
@@ -54,7 +54,7 @@ module ChefApply
54
54
  @error = true
55
55
  raise
56
56
  ensure
57
- file.close()
57
+ file.close
58
58
  # If any failures occurred, don't risk keeping
59
59
  # an incomplete download that we'll see as 'cached'
60
60
  if @error
@@ -24,19 +24,17 @@ module ChefApply
24
24
  def self.setup(location, log_level)
25
25
  if location.is_a?(String)
26
26
  if location.casecmp("stdout") == 0
27
- location = $stdout
27
+ @stream = $stdout
28
28
  else
29
- location = File.open(location, "w+")
29
+ @stream = File.open(location, "w+")
30
30
  end
31
31
  end
32
- @location = location
33
32
  init(location)
34
33
  Log.level = log_level
35
34
  end
36
35
 
37
- def self.location
38
- @location
36
+ def self.stream
37
+ @stream
39
38
  end
40
-
41
39
  end
42
40
  end