chef-apply 0.3.3 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
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