chef-apply 0.1.2 → 0.1.15
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +14 -14
- data/README.md +29 -14
- data/i18n/en.yml +16 -340
- data/i18n/errors/en.yml +341 -0
- data/lib/chef_apply/action/base.rb +5 -1
- data/lib/chef_apply/action/generate_local_policy.rb +59 -0
- data/lib/chef_apply/action/generate_temp_cookbook.rb +86 -0
- data/lib/chef_apply/action/install_chef/base.rb +38 -16
- data/lib/chef_apply/cli.rb +104 -257
- data/lib/chef_apply/cli/help.rb +69 -0
- data/lib/chef_apply/cli/options.rb +147 -0
- data/lib/chef_apply/cli/validation.rb +99 -0
- data/lib/chef_apply/error.rb +4 -43
- data/lib/chef_apply/errors/standard_error_resolver.rb +45 -0
- data/lib/chef_apply/startup.rb +22 -0
- data/lib/chef_apply/temp_cookbook.rb +23 -12
- data/lib/chef_apply/text.rb +13 -45
- data/lib/chef_apply/text/text_wrapper.rb +86 -0
- data/lib/chef_apply/ui/error_printer.rb +3 -2
- data/lib/chef_apply/ui/terminal.rb +10 -8
- data/lib/chef_apply/version.rb +1 -1
- metadata +10 -3
- data/lib/chef_apply/cli_options.rb +0 -145
@@ -0,0 +1,69 @@
|
|
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
|
+
module ChefApply
|
19
|
+
class CLI
|
20
|
+
module Help
|
21
|
+
T = ChefApply::Text.cli
|
22
|
+
def show_help
|
23
|
+
UI::Terminal.output format_help
|
24
|
+
end
|
25
|
+
|
26
|
+
def format_help
|
27
|
+
help_text = banner.clone # This prevents us appending to the banner text
|
28
|
+
help_text << "\n"
|
29
|
+
help_text << format_flags
|
30
|
+
end
|
31
|
+
|
32
|
+
def format_flags
|
33
|
+
flag_text = "FLAGS:\n"
|
34
|
+
justify_length = 0
|
35
|
+
options.each_value do |spec|
|
36
|
+
justify_length = [justify_length, spec[:long].length + 4].max
|
37
|
+
end
|
38
|
+
options.sort.to_h.each_value do |flag_spec|
|
39
|
+
short = flag_spec[:short] || " "
|
40
|
+
short = short[0, 2] # We only want the flag portion, not the capture portion (if present)
|
41
|
+
if short == " "
|
42
|
+
short = " "
|
43
|
+
else
|
44
|
+
short = "#{short}, "
|
45
|
+
end
|
46
|
+
flags = "#{short}#{flag_spec[:long]}"
|
47
|
+
flag_text << " #{flags.ljust(justify_length)} "
|
48
|
+
ml_padding = " " * (justify_length + 8)
|
49
|
+
first = true
|
50
|
+
flag_spec[:description].split("\n").each do |d|
|
51
|
+
flag_text << ml_padding unless first
|
52
|
+
first = false
|
53
|
+
flag_text << "#{d}\n"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
flag_text
|
57
|
+
end
|
58
|
+
|
59
|
+
def usage
|
60
|
+
T.usage
|
61
|
+
end
|
62
|
+
|
63
|
+
def show_version
|
64
|
+
require "chef_apply/version"
|
65
|
+
UI::Terminal.output T.version.show(ChefApply::VERSION)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,147 @@
|
|
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 "chef_apply/text"
|
19
|
+
require "chef_apply/action/install_chef"
|
20
|
+
|
21
|
+
# Moving the options into here so the cli.rb file is smaller and easier to read
|
22
|
+
# For options that need to be merged back into the global ChefApply::Config object
|
23
|
+
# we do that with a proc in the option itself. We decided to do that because it is
|
24
|
+
# an easy, straight forward way to merge those options when they do not directly
|
25
|
+
# map back to keys in the Config global. IE, we cannot just do
|
26
|
+
# `ChefApply::Config.merge!(options)` because the keys do not line up, and we do
|
27
|
+
# not want all CLI params merged back into the global config object.
|
28
|
+
# We know that the config is already loaded from the file (or program defaults)
|
29
|
+
# because the `Startup` class was invoked to start the program.
|
30
|
+
module ChefApply
|
31
|
+
class CLI
|
32
|
+
module Options
|
33
|
+
|
34
|
+
T = ChefApply::Text.cli
|
35
|
+
TS = ChefApply::Text.status
|
36
|
+
|
37
|
+
def self.included(klass)
|
38
|
+
klass.banner T.description + "\n" + T.usage_full
|
39
|
+
|
40
|
+
klass.option :version,
|
41
|
+
short: "-v",
|
42
|
+
long: "--version",
|
43
|
+
description: T.version.description,
|
44
|
+
boolean: true
|
45
|
+
|
46
|
+
klass.option :help,
|
47
|
+
short: "-h",
|
48
|
+
long: "--help",
|
49
|
+
description: T.help.description,
|
50
|
+
boolean: true
|
51
|
+
|
52
|
+
# Special note:
|
53
|
+
# config_path is pre-processed in startup.rb, and is shown here only
|
54
|
+
# for purpoess of rendering help text.
|
55
|
+
klass.option :config_path,
|
56
|
+
short: "-c PATH",
|
57
|
+
long: "--config PATH",
|
58
|
+
description: T.default_config_location(ChefApply::Config.default_location),
|
59
|
+
default: ChefApply::Config.default_location,
|
60
|
+
proc: Proc.new { |path| ChefApply::Config.custom_location(path) }
|
61
|
+
|
62
|
+
klass.option :identity_file,
|
63
|
+
long: "--identity-file PATH",
|
64
|
+
short: "-i PATH",
|
65
|
+
description: T.identity_file,
|
66
|
+
proc: (Proc.new do |paths|
|
67
|
+
path = paths
|
68
|
+
unless File.exist?(path)
|
69
|
+
raise OptionValidationError.new("CHEFVAL001", self, path)
|
70
|
+
end
|
71
|
+
path
|
72
|
+
end)
|
73
|
+
|
74
|
+
klass.option :ssl,
|
75
|
+
long: "--[no-]ssl",
|
76
|
+
description: T.ssl.desc(ChefApply::Config.connection.winrm.ssl),
|
77
|
+
boolean: true,
|
78
|
+
default: ChefApply::Config.connection.winrm.ssl,
|
79
|
+
proc: Proc.new { |val| ChefApply::Config.connection.winrm.ssl(val) }
|
80
|
+
|
81
|
+
klass.option :ssl_verify,
|
82
|
+
long: "--[no-]ssl-verify",
|
83
|
+
description: T.ssl.verify_desc(ChefApply::Config.connection.winrm.ssl_verify),
|
84
|
+
boolean: true,
|
85
|
+
default: ChefApply::Config.connection.winrm.ssl_verify,
|
86
|
+
proc: Proc.new { |val| ChefApply::Config.connection.winrm.ssl_verify(val) }
|
87
|
+
|
88
|
+
klass.option :protocol,
|
89
|
+
long: "--protocol <PROTOCOL>",
|
90
|
+
short: "-p",
|
91
|
+
description: T.protocol_description(ChefApply::Config::SUPPORTED_PROTOCOLS.join(" "),
|
92
|
+
ChefApply::Config.connection.default_protocol),
|
93
|
+
default: ChefApply::Config.connection.default_protocol,
|
94
|
+
proc: Proc.new { |val| ChefApply::Config.connection.default_protocol(val) }
|
95
|
+
|
96
|
+
klass.option :user,
|
97
|
+
long: "--user <USER>",
|
98
|
+
description: T.user_description
|
99
|
+
|
100
|
+
klass.option :password,
|
101
|
+
long: "--password <PASSWORD>",
|
102
|
+
description: T.password_description
|
103
|
+
|
104
|
+
klass.option :cookbook_repo_paths,
|
105
|
+
long: "--cookbook-repo-paths PATH",
|
106
|
+
description: T.cookbook_repo_paths,
|
107
|
+
default: ChefApply::Config.chef.cookbook_repo_paths,
|
108
|
+
proc: (Proc.new do |paths|
|
109
|
+
paths = paths.split(",")
|
110
|
+
ChefApply::Config.chef.cookbook_repo_paths(paths)
|
111
|
+
paths
|
112
|
+
end)
|
113
|
+
|
114
|
+
klass.option :install,
|
115
|
+
long: "--[no-]install",
|
116
|
+
default: true,
|
117
|
+
boolean: true,
|
118
|
+
description: T.install_description
|
119
|
+
|
120
|
+
klass.option :sudo,
|
121
|
+
long: "--[no-]sudo",
|
122
|
+
description: T.sudo.flag_description.sudo,
|
123
|
+
boolean: true,
|
124
|
+
default: true
|
125
|
+
|
126
|
+
klass.option :sudo_command,
|
127
|
+
long: "--sudo-command <COMMAND>",
|
128
|
+
default: "sudo",
|
129
|
+
description: T.sudo.flag_description.command
|
130
|
+
|
131
|
+
klass.option :sudo_password,
|
132
|
+
long: "--sudo-password <PASSWORD>",
|
133
|
+
description: T.sudo.flag_description.password
|
134
|
+
|
135
|
+
klass.option :sudo_options,
|
136
|
+
long: "--sudo-options 'OPTIONS...'",
|
137
|
+
description: T.sudo.flag_description.options
|
138
|
+
end
|
139
|
+
|
140
|
+
# I really don't like that mixlib-cli refers to the parsed command line flags in
|
141
|
+
# a hash accesed via the `config` method. Thats just such an overloaded word.
|
142
|
+
def parsed_options
|
143
|
+
config
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,99 @@
|
|
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 "chef_apply/error"
|
19
|
+
|
20
|
+
module ChefApply
|
21
|
+
class CLI
|
22
|
+
module Validation
|
23
|
+
PROPERTY_MATCHER = /^([a-zA-Z0-9_]+)=(.+)$/
|
24
|
+
CB_MATCHER = '[\w\-]+'
|
25
|
+
|
26
|
+
# The first param is always hostname. Then we either have
|
27
|
+
# 1. A recipe designation
|
28
|
+
# 2. A resource type and resource name followed by any properties
|
29
|
+
def validate_params(params)
|
30
|
+
if params.size < 2
|
31
|
+
raise OptionValidationError.new("CHEFVAL002", self)
|
32
|
+
end
|
33
|
+
if params.size == 2
|
34
|
+
# Trying to specify a recipe to run remotely, no properties
|
35
|
+
cb = params[1]
|
36
|
+
if File.exist?(cb)
|
37
|
+
# This is a path specification, and we know it is valid
|
38
|
+
elsif cb =~ /^#{CB_MATCHER}$/ || cb =~ /^#{CB_MATCHER}::#{CB_MATCHER}$/
|
39
|
+
# They are specifying a cookbook as 'cb_name' or 'cb_name::recipe'
|
40
|
+
else
|
41
|
+
raise OptionValidationError.new("CHEFVAL004", self, cb)
|
42
|
+
end
|
43
|
+
elsif params.size >= 3
|
44
|
+
properties = params[3..-1]
|
45
|
+
properties.each do |property|
|
46
|
+
unless property =~ PROPERTY_MATCHER
|
47
|
+
raise OptionValidationError.new("CHEFVAL003", self, property)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Convert properties in the form k1=v1,k2=v2,kn=vn
|
54
|
+
# into a hash, while validating correct form and format
|
55
|
+
def properties_from_string(string_props)
|
56
|
+
properties = {}
|
57
|
+
string_props.each do |a|
|
58
|
+
key, value = PROPERTY_MATCHER.match(a)[1..-1]
|
59
|
+
value = transform_property_value(value)
|
60
|
+
properties[key] = value
|
61
|
+
end
|
62
|
+
properties
|
63
|
+
end
|
64
|
+
|
65
|
+
# Incoming properties are always read as a string from the command line.
|
66
|
+
# Depending on their type we should transform them so we do not try and pass
|
67
|
+
# a string to a resource property that expects an integer or boolean.
|
68
|
+
def transform_property_value(value)
|
69
|
+
case value
|
70
|
+
when /^0/
|
71
|
+
# when it is a zero leading value like "0777" don't turn
|
72
|
+
# it into a number (this is a mode flag)
|
73
|
+
value
|
74
|
+
when /^\d+$/
|
75
|
+
value.to_i
|
76
|
+
when /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
|
77
|
+
value.to_f
|
78
|
+
when /true/i
|
79
|
+
true
|
80
|
+
when /false/i
|
81
|
+
false
|
82
|
+
else
|
83
|
+
value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class OptionValidationError < ChefApply::ErrorNoLogs
|
89
|
+
attr_reader :command
|
90
|
+
def initialize(id, calling_command, *args)
|
91
|
+
super(id, *args)
|
92
|
+
# TODO - this is getting cumbersome - move them to constructor options hash in base
|
93
|
+
@decorate = false
|
94
|
+
@command = calling_command
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
data/lib/chef_apply/error.rb
CHANGED
@@ -53,7 +53,7 @@ module ChefApply
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
class MultiJobFailure <
|
56
|
+
class MultiJobFailure < ErrorNoLogs
|
57
57
|
attr_reader :jobs
|
58
58
|
def initialize(jobs)
|
59
59
|
super("CHEFMULTI001")
|
@@ -62,47 +62,8 @@ module ChefApply
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
#
|
66
|
-
#
|
67
|
-
class
|
68
|
-
|
69
|
-
def self.resolve_exception(exception)
|
70
|
-
deps
|
71
|
-
show_log = true
|
72
|
-
show_stack = true
|
73
|
-
case exception
|
74
|
-
when OpenSSL::SSL::SSLError
|
75
|
-
if exception.message =~ /SSL.*verify failed.*/
|
76
|
-
id = "CHEFNET002"
|
77
|
-
show_log = false
|
78
|
-
show_stack = false
|
79
|
-
end
|
80
|
-
when SocketError then id = "CHEFNET001"; show_log = false; show_stack = false
|
81
|
-
end
|
82
|
-
if id.nil?
|
83
|
-
exception
|
84
|
-
else
|
85
|
-
e = ChefApply::Error.new(id, exception.message)
|
86
|
-
e.show_log = show_log
|
87
|
-
e.show_stack = show_stack
|
88
|
-
e
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.wrap_exception(original, target_host = nil)
|
93
|
-
resolved_exception = resolve_exception(original)
|
94
|
-
WrappedError.new(resolved_exception, target_host)
|
95
|
-
end
|
96
|
-
|
97
|
-
def self.unwrap_exception(wrapper)
|
98
|
-
resolve_exception(wrapper.contained_exception)
|
99
|
-
end
|
100
|
-
|
101
|
-
def self.deps
|
102
|
-
# Avoid loading additional includes until they're needed
|
103
|
-
require "socket"
|
104
|
-
require "openssl"
|
105
|
-
end
|
65
|
+
# Provide a base type for internal usage errors that should not leak out
|
66
|
+
# but may anyway.
|
67
|
+
class APIError < Error
|
106
68
|
end
|
107
|
-
|
108
69
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ChefApply
|
2
|
+
module Errors
|
3
|
+
# Provides mappings of common errors that we don't explicitly
|
4
|
+
# handle, but can offer expanded help text around.
|
5
|
+
class StandardErrorResolver
|
6
|
+
def self.resolve_exception(exception)
|
7
|
+
deps
|
8
|
+
show_log = true
|
9
|
+
show_stack = true
|
10
|
+
case exception
|
11
|
+
when OpenSSL::SSL::SSLError
|
12
|
+
if exception.message =~ /SSL.*verify failed.*/
|
13
|
+
id = "CHEFNET002"
|
14
|
+
show_log = false
|
15
|
+
show_stack = false
|
16
|
+
end
|
17
|
+
when SocketError then id = "CHEFNET001"; show_log = false; show_stack = false
|
18
|
+
end
|
19
|
+
if id.nil?
|
20
|
+
exception
|
21
|
+
else
|
22
|
+
e = ChefApply::Error.new(id, exception.message)
|
23
|
+
e.show_log = show_log
|
24
|
+
e.show_stack = show_stack
|
25
|
+
e
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.wrap_exception(original, target_host = nil)
|
30
|
+
resolved_exception = resolve_exception(original)
|
31
|
+
WrappedError.new(resolved_exception, target_host)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.unwrap_exception(wrapper)
|
35
|
+
resolve_exception(wrapper.contained_exception)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.deps
|
39
|
+
# Avoid loading additional includes until they're needed
|
40
|
+
require "socket"
|
41
|
+
require "openssl"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/chef_apply/startup.rb
CHANGED
@@ -18,6 +18,8 @@ require "chef_apply/config"
|
|
18
18
|
require "chef_apply/text"
|
19
19
|
require "chef_apply/ui/terminal"
|
20
20
|
require "chef_apply/telemeter/sender"
|
21
|
+
require "chef/log"
|
22
|
+
require "chef/config"
|
21
23
|
module ChefApply
|
22
24
|
class Startup
|
23
25
|
attr_reader :argv
|
@@ -41,6 +43,9 @@ module ChefApply
|
|
41
43
|
# are required.
|
42
44
|
setup_workstation_user_directories
|
43
45
|
|
46
|
+
# Customize behavior of Ruby and any gems around error handling
|
47
|
+
setup_error_handling
|
48
|
+
|
44
49
|
# Startup tasks that may change behavior based on configuration value
|
45
50
|
# must be run after load_config
|
46
51
|
load_config
|
@@ -120,6 +125,17 @@ module ChefApply
|
|
120
125
|
FileUtils.mkdir_p(Config.telemetry_path)
|
121
126
|
end
|
122
127
|
|
128
|
+
def setup_error_handling
|
129
|
+
# In Ruby 2.5+ threads print out to stdout when they raise an exception. This is an agressive
|
130
|
+
# attempt to ensure debugging information is not lost, but in our case it is not necessary
|
131
|
+
# because we handle all the errors ourself. So we disable this to keep output clean.
|
132
|
+
# See https://ruby-doc.org/core-2.5.0/Thread.html#method-c-report_on_exception
|
133
|
+
#
|
134
|
+
# We set this globally so that it applies to all threads we create - we never want any non-UI thread
|
135
|
+
# to render error output to the terminal.
|
136
|
+
Thread.report_on_exception = false
|
137
|
+
end
|
138
|
+
|
123
139
|
def load_config
|
124
140
|
path = custom_config_path
|
125
141
|
Config.custom_location(path) unless path.nil?
|
@@ -145,6 +161,12 @@ module ChefApply
|
|
145
161
|
def setup_logging
|
146
162
|
ChefApply::Log.setup(Config.log.location, Config.log.level.to_sym)
|
147
163
|
ChefApply::Log.info("Initialized logger")
|
164
|
+
|
165
|
+
ChefConfig.logger = ChefApply::Log
|
166
|
+
# Setting the config isn't enough, we need to ensure the logger is initialized
|
167
|
+
# or automatic initialization will still go to stdout
|
168
|
+
Chef::Log.init(ChefApply::Log)
|
169
|
+
Chef::Log.level = ChefApply::Log.level
|
148
170
|
end
|
149
171
|
|
150
172
|
def start_chef_apply
|