bolt 2.14.0 → 2.19.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +20 -9
- data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +3 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +7 -4
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +2 -0
- data/bolt-modules/file/lib/puppet/functions/file/exists.rb +2 -1
- data/bolt-modules/file/lib/puppet/functions/file/join.rb +2 -0
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -1
- data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
- data/bolt-modules/file/lib/puppet/functions/file/write.rb +2 -0
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +2 -0
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +1 -0
- data/bolt-modules/system/lib/puppet/functions/system/env.rb +2 -0
- data/lib/bolt/applicator.rb +34 -20
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +30 -18
- data/lib/bolt/cli.rb +78 -56
- data/lib/bolt/config.rb +158 -128
- data/lib/bolt/config/options.rb +474 -0
- data/lib/bolt/config/transport/base.rb +16 -16
- data/lib/bolt/config/transport/docker.rb +9 -23
- data/lib/bolt/config/transport/local.rb +6 -44
- data/lib/bolt/config/transport/options.rb +460 -0
- data/lib/bolt/config/transport/orch.rb +9 -18
- data/lib/bolt/config/transport/remote.rb +3 -6
- data/lib/bolt/config/transport/ssh.rb +74 -154
- data/lib/bolt/config/transport/winrm.rb +18 -47
- data/lib/bolt/inventory/group.rb +1 -1
- data/lib/bolt/inventory/inventory.rb +0 -14
- data/lib/bolt/inventory/target.rb +18 -5
- data/lib/bolt/logger.rb +24 -1
- data/lib/bolt/outputter.rb +3 -0
- data/lib/bolt/outputter/rainbow.rb +90 -0
- data/lib/bolt/pal.rb +23 -9
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/plugin/module.rb +2 -4
- data/lib/bolt/project.rb +41 -52
- data/lib/bolt/shell/bash.rb +30 -42
- data/lib/bolt/shell/powershell.rb +13 -8
- data/lib/bolt/shell/powershell/snippets.rb +15 -6
- data/lib/bolt/transport/docker.rb +9 -5
- data/lib/bolt/transport/orch.rb +8 -0
- data/lib/bolt/transport/ssh.rb +7 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
- data/lib/bolt/version.rb +1 -1
- metadata +18 -15
data/lib/bolt/logger.rb
CHANGED
@@ -15,6 +15,7 @@ module Bolt
|
|
15
15
|
return if Logging.initialized?
|
16
16
|
|
17
17
|
Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
|
18
|
+
@mutex = Mutex.new
|
18
19
|
|
19
20
|
Logging.color_scheme(
|
20
21
|
'bolt',
|
@@ -66,6 +67,10 @@ module Bolt
|
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
70
|
+
def self.analytics=(analytics)
|
71
|
+
@analytics = analytics
|
72
|
+
end
|
73
|
+
|
69
74
|
def self.console_layout(color)
|
70
75
|
color_scheme = :bolt if color
|
71
76
|
Logging.layouts.pattern(
|
@@ -89,8 +94,10 @@ module Bolt
|
|
89
94
|
:notice
|
90
95
|
end
|
91
96
|
|
97
|
+
# Explicitly check the log level names instead of the log level number, as levels
|
98
|
+
# that are stringified integers (e.g. "level" => "42") will return a truthy value
|
92
99
|
def self.valid_level?(level)
|
93
|
-
|
100
|
+
Logging::LEVELS.include?(Logging.levelify(level))
|
94
101
|
end
|
95
102
|
|
96
103
|
def self.levels
|
@@ -100,5 +107,21 @@ module Bolt
|
|
100
107
|
def self.reset_logging
|
101
108
|
Logging.reset
|
102
109
|
end
|
110
|
+
|
111
|
+
def self.warn_once(type, msg)
|
112
|
+
@mutex.synchronize {
|
113
|
+
@warnings ||= []
|
114
|
+
@logger ||= Logging.logger[self]
|
115
|
+
unless @warnings.include?(type)
|
116
|
+
@logger.warn(msg)
|
117
|
+
@warnings << type
|
118
|
+
end
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.deprecation_warning(type, msg)
|
123
|
+
@analytics&.event('Warn', 'deprecation', label: type)
|
124
|
+
warn_once(type, msg)
|
125
|
+
end
|
103
126
|
end
|
104
127
|
end
|
data/lib/bolt/outputter.rb
CHANGED
@@ -8,6 +8,8 @@ module Bolt
|
|
8
8
|
Bolt::Outputter::Human.new(color, verbose, trace)
|
9
9
|
when 'json'
|
10
10
|
Bolt::Outputter::JSON.new(color, verbose, trace)
|
11
|
+
when 'rainbow'
|
12
|
+
Bolt::Outputter::Rainbow.new(color, verbose, trace)
|
11
13
|
when nil
|
12
14
|
raise "Cannot use outputter before parsing."
|
13
15
|
end
|
@@ -25,3 +27,4 @@ end
|
|
25
27
|
require 'bolt/outputter/human'
|
26
28
|
require 'bolt/outputter/json'
|
27
29
|
require 'bolt/outputter/logger'
|
30
|
+
require 'bolt/outputter/rainbow'
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/pal'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class Outputter
|
7
|
+
class Rainbow < Bolt::Outputter::Human
|
8
|
+
def initialize(color, verbose, trace, stream = $stdout)
|
9
|
+
begin
|
10
|
+
require 'paint'
|
11
|
+
if Bolt::Util.windows?
|
12
|
+
# the Paint gem thinks that windows does not support ansi colors
|
13
|
+
# but windows 10 or later does
|
14
|
+
# we can display colors if we force mode to TRUE_COLOR
|
15
|
+
Paint.mode = 0xFFFFFF
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
raise "The 'paint' gem is required to use the rainbow outputter."
|
19
|
+
end
|
20
|
+
super
|
21
|
+
@line_color = 0
|
22
|
+
@color = 0
|
23
|
+
@state = :normal
|
24
|
+
end
|
25
|
+
|
26
|
+
# The algorithm is from lolcat (https://github.com/busyloop/lolcat)
|
27
|
+
# lolcat is released with WTFPL
|
28
|
+
def rainbow
|
29
|
+
red = Math.sin(0.3 * @color + 0) * 127 + 128
|
30
|
+
green = Math.sin(0.3 * @color + 2 * Math::PI / 3) * 127 + 128
|
31
|
+
blue = Math.sin(0.3 * @color + 4 * Math::PI / 3) * 127 + 128
|
32
|
+
@color += 1 / 8.0
|
33
|
+
format("%<red>02X%<green>02X%<blue>02X", red: red, green: green, blue: blue)
|
34
|
+
end
|
35
|
+
|
36
|
+
def colorize(color, string)
|
37
|
+
if @color && @stream.isatty
|
38
|
+
if %i[green rainbow].include?(color)
|
39
|
+
a = string.chars.map do |c|
|
40
|
+
case @state
|
41
|
+
when :normal
|
42
|
+
if c == "\e"
|
43
|
+
@state = :ansi
|
44
|
+
elsif c == "\n"
|
45
|
+
@line_color += 1
|
46
|
+
@color = @line_color
|
47
|
+
c
|
48
|
+
else
|
49
|
+
Paint[c, rainbow]
|
50
|
+
end
|
51
|
+
when :ansi
|
52
|
+
@state = :normal if c == 'm'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
a.join('')
|
56
|
+
else
|
57
|
+
"\033[#{COLORS[color]}m#{string}\033[0m"
|
58
|
+
end
|
59
|
+
else
|
60
|
+
string
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def print_summary(results, elapsed_time = nil)
|
65
|
+
ok_set = results.ok_set
|
66
|
+
unless ok_set.empty?
|
67
|
+
@stream.puts colorize(:rainbow, format('Successful on %<size>d target%<plural>s: %<names>s',
|
68
|
+
size: ok_set.size,
|
69
|
+
plural: ok_set.size == 1 ? '' : 's',
|
70
|
+
names: ok_set.targets.map(&:safe_name).join(',')))
|
71
|
+
end
|
72
|
+
|
73
|
+
error_set = results.error_set
|
74
|
+
unless error_set.empty?
|
75
|
+
@stream.puts colorize(:red,
|
76
|
+
format('Failed on %<size>d target%<plural>s: %<names>s',
|
77
|
+
size: error_set.size,
|
78
|
+
plural: error_set.size == 1 ? '' : 's',
|
79
|
+
names: error_set.targets.map(&:safe_name).join(',')))
|
80
|
+
end
|
81
|
+
|
82
|
+
total_msg = format('Ran on %<size>d target%<plural>s',
|
83
|
+
size: results.size,
|
84
|
+
plural: results.size == 1 ? '' : 's')
|
85
|
+
total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
|
86
|
+
@stream.puts colorize(:rainbow, total_msg)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/bolt/pal.rb
CHANGED
@@ -15,25 +15,36 @@ module Bolt
|
|
15
15
|
# PALError is used to convert errors from executing puppet code into
|
16
16
|
# Bolt::Errors
|
17
17
|
class PALError < Bolt::Error
|
18
|
-
# Puppet sometimes rescues exceptions notes the location and reraises.
|
19
|
-
# Return the original error.
|
20
18
|
def self.from_preformatted_error(err)
|
21
19
|
if err.cause&.is_a? Bolt::Error
|
22
20
|
err.cause
|
23
21
|
else
|
24
|
-
from_error(err
|
22
|
+
from_error(err)
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
28
26
|
# Generate a Bolt::Pal::PALError for non-bolt errors
|
29
27
|
def self.from_error(err)
|
30
|
-
|
28
|
+
# Use the original error message if available
|
29
|
+
message = err.cause ? err.cause.message : err.message
|
30
|
+
|
31
|
+
# Provide the location of an error if it came from a plan
|
32
|
+
details = if defined?(err.file) && err.file
|
33
|
+
{ file: err.file,
|
34
|
+
line: err.line,
|
35
|
+
column: err.pos }.compact
|
36
|
+
else
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
|
40
|
+
e = new(message, details)
|
41
|
+
|
31
42
|
e.set_backtrace(err.backtrace)
|
32
43
|
e
|
33
44
|
end
|
34
45
|
|
35
|
-
def initialize(msg)
|
36
|
-
super(msg, 'bolt/pal-error')
|
46
|
+
def initialize(msg, details = {})
|
47
|
+
super(msg, 'bolt/pal-error', details)
|
37
48
|
end
|
38
49
|
end
|
39
50
|
|
@@ -137,7 +148,9 @@ module Bolt
|
|
137
148
|
# TODO: If we always call this inside a bolt_executor we can remove this here
|
138
149
|
setup
|
139
150
|
r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
|
140
|
-
|
151
|
+
# Only load the project if it a) exists, b) has a name it can be loaded with
|
152
|
+
bolt_project = @project if @project&.name
|
153
|
+
Puppet.override(bolt_project: bolt_project,
|
141
154
|
yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
|
142
155
|
pal.with_script_compiler do |compiler|
|
143
156
|
alias_types(compiler)
|
@@ -157,8 +170,9 @@ module Bolt
|
|
157
170
|
if e.issue_code == :UNKNOWN_VARIABLE &&
|
158
171
|
%w[facts trusted server_facts settings].include?(e.arguments[:name])
|
159
172
|
message = "Evaluation Error: Variable '#{e.arguments[:name]}' is not available in the current scope "\
|
160
|
-
|
161
|
-
|
173
|
+
"unless explicitly defined."
|
174
|
+
details = { file: e.file, line: e.line, column: e.pos }
|
175
|
+
PALError.new(message, details)
|
162
176
|
else
|
163
177
|
PALError.from_preformatted_error(e)
|
164
178
|
end
|
@@ -142,7 +142,7 @@ module Bolt
|
|
142
142
|
if plan.steps.any? { |step| step.body.key?('target') }
|
143
143
|
msg = "The 'target' parameter for YAML plan steps is deprecated and will be removed "\
|
144
144
|
"in a future version of Bolt. Use the 'targets' parameter instead."
|
145
|
-
|
145
|
+
Bolt::Logger.deprecation_warning("Using 'target' parameter for YAML plan steps, not 'targets'", msg)
|
146
146
|
end
|
147
147
|
|
148
148
|
plan_result = closure_scope.with_local_scope(args_hash) do |scope|
|
data/lib/bolt/plugin/module.rb
CHANGED
@@ -184,12 +184,10 @@ module Bolt
|
|
184
184
|
# Raises a deprecation warning if the pkcs7 plugin is using deprecated keys and
|
185
185
|
# modifies the keys so they are the correct format
|
186
186
|
def handle_deprecated_pkcs7_keys(params)
|
187
|
-
if
|
188
|
-
@deprecation_warning_issued = true
|
189
|
-
|
187
|
+
if params.key?('private-key') || params.key?('public-key')
|
190
188
|
message = "pkcs7 keys 'private-key' and 'public-key' have been deprecated and will be "\
|
191
189
|
"removed in a future version of Bolt; use 'private_key' and 'public_key' instead."
|
192
|
-
|
190
|
+
Bolt::Logger.deprecation_warning('PKCS7 keys using hyphens, not underscores', message)
|
193
191
|
end
|
194
192
|
|
195
193
|
params['private_key'] = params.delete('private-key') if params.key?('private-key')
|
data/lib/bolt/project.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'pathname'
|
4
|
+
require 'bolt/config'
|
4
5
|
require 'bolt/pal'
|
5
6
|
|
6
7
|
module Bolt
|
@@ -8,26 +9,21 @@ module Bolt
|
|
8
9
|
BOLTDIR_NAME = 'Boltdir'
|
9
10
|
PROJECT_SETTINGS = {
|
10
11
|
"name" => "The name of the project",
|
11
|
-
"plans" => "An array of plan names to
|
12
|
-
|
12
|
+
"plans" => "An array of plan names to show, if they exist in the project."\
|
13
|
+
"These plans are included in `bolt plan show` output",
|
14
|
+
"tasks" => "An array of task names to show, if they exist in the project."\
|
15
|
+
"These tasks are included in `bolt task show` output"
|
13
16
|
}.freeze
|
14
17
|
|
15
18
|
attr_reader :path, :data, :config_file, :inventory_file, :modulepath, :hiera_config,
|
16
|
-
:puppetfile, :rerunfile, :type, :resource_types, :warnings, :project_file
|
19
|
+
:puppetfile, :rerunfile, :type, :resource_types, :warnings, :project_file,
|
20
|
+
:deprecations
|
17
21
|
|
18
22
|
def self.default_project
|
19
23
|
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
|
20
24
|
# If homedir isn't defined use the system config path
|
21
25
|
rescue ArgumentError
|
22
|
-
create_project(system_path, 'system')
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.system_path
|
26
|
-
if Bolt::Util.windows?
|
27
|
-
File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'bolt', 'etc')
|
28
|
-
else
|
29
|
-
File.join('/etc', 'puppetlabs', 'bolt')
|
30
|
-
end
|
26
|
+
create_project(Bolt::Config.system_path, 'system')
|
31
27
|
end
|
32
28
|
|
33
29
|
# Search recursively up the directory hierarchy for the Project. Look for a
|
@@ -36,6 +32,7 @@ module Bolt
|
|
36
32
|
# hierarchy, falling back to the default if we reach the root.
|
37
33
|
def self.find_boltdir(dir)
|
38
34
|
dir = Pathname.new(dir)
|
35
|
+
|
39
36
|
if (dir + BOLTDIR_NAME).directory?
|
40
37
|
create_project(dir + BOLTDIR_NAME, 'embedded')
|
41
38
|
elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
|
@@ -49,6 +46,15 @@ module Bolt
|
|
49
46
|
|
50
47
|
def self.create_project(path, type = 'option')
|
51
48
|
fullpath = Pathname.new(path).expand_path
|
49
|
+
|
50
|
+
if !Bolt::Util.windows? && type != 'environment' && fullpath.world_writable?
|
51
|
+
raise Bolt::Error.new(
|
52
|
+
"Project directory '#{fullpath}' is world-writable which poses a security risk. Set "\
|
53
|
+
"BOLT_PROJECT='#{fullpath}' to force the use of this project directory.",
|
54
|
+
"bolt/world-writable-error"
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
52
58
|
project_file = File.join(fullpath, 'bolt-project.yaml')
|
53
59
|
data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
|
54
60
|
new(data, path, type)
|
@@ -56,14 +62,16 @@ module Bolt
|
|
56
62
|
|
57
63
|
def initialize(raw_data, path, type = 'option')
|
58
64
|
@path = Pathname.new(path).expand_path
|
65
|
+
|
59
66
|
@project_file = @path + 'bolt-project.yaml'
|
60
67
|
|
61
68
|
@warnings = []
|
69
|
+
@deprecations = []
|
62
70
|
if (@path + 'bolt.yaml').file? && project_file?
|
63
71
|
msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
|
64
72
|
"Transport config should be set in inventory.yaml, all other config should be set in "\
|
65
73
|
"bolt-project.yaml."
|
66
|
-
@
|
74
|
+
@deprecations << { type: 'Using bolt.yaml for project configuration', msg: msg }
|
67
75
|
end
|
68
76
|
|
69
77
|
@inventory_file = @path + 'inventory.yaml'
|
@@ -74,17 +82,17 @@ module Bolt
|
|
74
82
|
@resource_types = @path + '.resource_types'
|
75
83
|
@type = type
|
76
84
|
|
77
|
-
tc = Bolt::Config::
|
85
|
+
tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
|
78
86
|
if tc.any?
|
79
87
|
msg = "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}"
|
80
88
|
@warnings << { msg: msg }
|
81
89
|
end
|
82
90
|
|
83
|
-
@data = raw_data.reject { |k, _| Bolt::Config::
|
91
|
+
@data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_OPTIONS.include?(k) }
|
84
92
|
|
85
93
|
# Once bolt.yaml deprecation is removed, this attribute should be removed
|
86
94
|
# and replaced with .project_file in lib/bolt/config.rb
|
87
|
-
@config_file = if (Bolt::Config::
|
95
|
+
@config_file = if (Bolt::Config::BOLT_OPTIONS & @data.keys).any?
|
88
96
|
if (@path + 'bolt.yaml').file?
|
89
97
|
msg = "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
|
90
98
|
@warnings << { msg: msg }
|
@@ -103,7 +111,7 @@ module Bolt
|
|
103
111
|
# This API is used to prepend the project as a module to Puppet's internal
|
104
112
|
# module_references list. CHANGE AT YOUR OWN RISK
|
105
113
|
def to_h
|
106
|
-
{ path: @path, name: name }
|
114
|
+
{ path: @path.to_s, name: name }
|
107
115
|
end
|
108
116
|
|
109
117
|
def eql?(other)
|
@@ -116,10 +124,7 @@ module Bolt
|
|
116
124
|
end
|
117
125
|
|
118
126
|
def name
|
119
|
-
|
120
|
-
dirname = @path.basename.to_s == 'Boltdir' ? @path.parent.basename.to_s : @path.basename.to_s
|
121
|
-
pname = @data['name'] || dirname
|
122
|
-
pname.include?('-') ? pname.split('-', 2)[1] : pname
|
127
|
+
@data['name']
|
123
128
|
end
|
124
129
|
|
125
130
|
def tasks
|
@@ -130,36 +135,20 @@ module Bolt
|
|
130
135
|
@data['plans']
|
131
136
|
end
|
132
137
|
|
133
|
-
def project_directory_name?(name)
|
134
|
-
# it must match an installed project name according to forge validator
|
135
|
-
name =~ /^[a-z][a-z0-9_]*$/
|
136
|
-
end
|
137
|
-
|
138
|
-
def project_namespaced_name?(name)
|
139
|
-
# it must match the full project name according to forge validator
|
140
|
-
name =~ /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
|
141
|
-
end
|
142
|
-
|
143
138
|
def validate
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
Configure project name in <project_dir>/bolt-project.yaml
|
158
|
-
ERROR_STRING
|
159
|
-
# If the project name is the same as one of the built-in modules raise a warning
|
160
|
-
elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
|
161
|
-
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|
162
|
-
"with a built-in Bolt module of the same name."
|
139
|
+
if name
|
140
|
+
name_regex = /^[a-z][a-z0-9_]*$/
|
141
|
+
if name !~ name_regex
|
142
|
+
raise Bolt::ValidationError, <<~ERROR_STRING
|
143
|
+
Invalid project name '#{name}' in bolt-project.yaml; project name must match #{name_regex.inspect}
|
144
|
+
ERROR_STRING
|
145
|
+
elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
|
146
|
+
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|
147
|
+
"with a built-in Bolt module of the same name."
|
148
|
+
end
|
149
|
+
else
|
150
|
+
message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
|
151
|
+
@warnings << { msg: message }
|
163
152
|
end
|
164
153
|
|
165
154
|
%w[tasks plans].each do |conf|
|
@@ -171,8 +160,8 @@ module Bolt
|
|
171
160
|
|
172
161
|
def check_deprecated_file
|
173
162
|
if (@path + 'project.yaml').file?
|
174
|
-
|
175
|
-
|
163
|
+
msg = "Project configuration file 'project.yaml' is deprecated; use 'bolt-project.yaml' instead."
|
164
|
+
Bolt::Logger.deprecation_warning('Using project.yaml instead of bolt-project.yaml', msg)
|
176
165
|
end
|
177
166
|
end
|
178
167
|
end
|
data/lib/bolt/shell/bash.rb
CHANGED
@@ -23,7 +23,7 @@ module Bolt
|
|
23
23
|
|
24
24
|
def run_command(command, options = {})
|
25
25
|
running_as(options[:run_as]) do
|
26
|
-
output = execute(command, sudoable: true)
|
26
|
+
output = execute(command, environment: options[:env_vars], sudoable: true)
|
27
27
|
Bolt::Result.for_command(target,
|
28
28
|
output.stdout.string,
|
29
29
|
output.stderr.string,
|
@@ -35,7 +35,7 @@ module Bolt
|
|
35
35
|
def upload(source, destination, options = {})
|
36
36
|
running_as(options[:run_as]) do
|
37
37
|
with_tmpdir do |dir|
|
38
|
-
basename = File.basename(
|
38
|
+
basename = File.basename(source)
|
39
39
|
tmpfile = File.join(dir.to_s, basename)
|
40
40
|
conn.copy_file(source, tmpfile)
|
41
41
|
# pass over file ownership if we're using run-as to be a different user
|
@@ -58,7 +58,7 @@ module Bolt
|
|
58
58
|
with_tmpdir do |dir|
|
59
59
|
path = write_executable(dir.to_s, script)
|
60
60
|
dir.chown(run_as)
|
61
|
-
output = execute([path, *arguments], sudoable: true)
|
61
|
+
output = execute([path, *arguments], environment: options[:env_vars], sudoable: true)
|
62
62
|
Bolt::Result.for_command(target,
|
63
63
|
output.stdout.string,
|
64
64
|
output.stderr.string,
|
@@ -170,7 +170,7 @@ module Bolt
|
|
170
170
|
def check_sudo(out, inp, stdin)
|
171
171
|
buffer = out.readpartial(CHUNK_SIZE)
|
172
172
|
# Split on newlines, including the newline
|
173
|
-
lines = buffer.split(/(
|
173
|
+
lines = buffer.split(/(?<=\n)/)
|
174
174
|
# handle_sudo will return the line if it is not a sudo prompt or error
|
175
175
|
lines.map! { |line| handle_sudo(inp, line, stdin) }
|
176
176
|
lines.join("")
|
@@ -277,31 +277,8 @@ module Bolt
|
|
277
277
|
end
|
278
278
|
end
|
279
279
|
|
280
|
-
|
281
|
-
|
282
|
-
# for task input data because the sudo password has already either been
|
283
|
-
# provided on stdin or was not needed.
|
284
|
-
def prepend_sudo_success(sudo_id, command_str)
|
285
|
-
command_str = "cd; #{command_str}" if conn.reset_cwd?
|
286
|
-
"sh -c #{Shellwords.shellescape("echo #{sudo_id} 1>&2; #{command_str}")}"
|
287
|
-
end
|
288
|
-
|
289
|
-
def prepend_chdir(command_str)
|
290
|
-
"sh -c #{Shellwords.shellescape("cd; #{command_str}")}"
|
291
|
-
end
|
292
|
-
|
293
|
-
# A helper to build up a single string that contains all of the options for
|
294
|
-
# privilege escalation. A wrapper script is used to direct task input to stdin
|
295
|
-
# when a tty is allocated and thus we do not need to prepend_sudo_success when
|
296
|
-
# using the wrapper or when the task does not require stdin data.
|
297
|
-
def build_sudoable_command_str(command_str, sudo_str, sudo_id, options)
|
298
|
-
if options[:stdin] && !options[:wrapper]
|
299
|
-
"#{sudo_str} #{prepend_sudo_success(sudo_id, command_str)}"
|
300
|
-
elsif conn.reset_cwd?
|
301
|
-
"#{sudo_str} #{prepend_chdir(command_str)}"
|
302
|
-
else
|
303
|
-
"#{sudo_str} #{command_str}"
|
304
|
-
end
|
280
|
+
def sudo_success(sudo_id)
|
281
|
+
"echo #{sudo_id} 1>&2"
|
305
282
|
end
|
306
283
|
|
307
284
|
# Returns string with the interpreter conditionally prepended
|
@@ -322,27 +299,38 @@ module Bolt
|
|
322
299
|
escalate = sudoable && run_as && conn.user != run_as
|
323
300
|
use_sudo = escalate && @target.options['run-as-command'].nil?
|
324
301
|
|
325
|
-
|
302
|
+
# Depending on the transport, whether we're using sudo and whether
|
303
|
+
# there are environment variables to set, we may need to stitch
|
304
|
+
# together multiple commands into a single sh invocation
|
305
|
+
commands = [inject_interpreter(options[:interpreter], command)]
|
326
306
|
|
327
307
|
if options[:environment]
|
328
|
-
|
308
|
+
env_decl = options[:environment].map do |env, val|
|
329
309
|
"#{env}=#{Shellwords.shellescape(val)}"
|
330
|
-
end
|
331
|
-
command_str = "#{env_decls.join(' ')} #{command_str}"
|
310
|
+
end.join(' ')
|
332
311
|
end
|
333
312
|
|
334
313
|
if escalate
|
335
|
-
if use_sudo
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
314
|
+
sudo_str = if use_sudo
|
315
|
+
sudo_exec = target.options['sudo-executable'] || "sudo"
|
316
|
+
sudo_flags = [sudo_exec, "-S", "-H", "-u", run_as, "-p", sudo_prompt]
|
317
|
+
sudo_flags += ["-E"] if options[:environment]
|
318
|
+
Shellwords.shelljoin(sudo_flags)
|
319
|
+
else
|
320
|
+
Shellwords.shelljoin(@target.options['run-as-command'] + [run_as])
|
321
|
+
end
|
322
|
+
commands.unshift('cd') if conn.reset_cwd?
|
323
|
+
commands.unshift(sudo_success(@sudo_id)) if options[:stdin] && !options[:wrapper]
|
344
324
|
end
|
345
325
|
|
326
|
+
command_str = if sudo_str || env_decl
|
327
|
+
"sh -c #{Shellwords.shellescape(commands.join('; '))}"
|
328
|
+
else
|
329
|
+
commands.last
|
330
|
+
end
|
331
|
+
|
332
|
+
command_str = [sudo_str, env_decl, command_str].compact.join(' ')
|
333
|
+
|
346
334
|
@logger.debug { "Executing: #{command_str}" }
|
347
335
|
|
348
336
|
in_buffer = if !use_sudo && options[:stdin]
|