bolt 2.13.0 → 2.18.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 +53 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +3 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +7 -2
- 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 +36 -21
- data/lib/bolt/apply_inventory.rb +4 -0
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/apply_target.rb +4 -0
- data/lib/bolt/bolt_option_parser.rb +27 -16
- data/lib/bolt/catalog.rb +79 -69
- data/lib/bolt/cli.rb +78 -58
- data/lib/bolt/config.rb +163 -136
- 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 +78 -119
- data/lib/bolt/config/transport/winrm.rb +18 -47
- data/lib/bolt/executor.rb +14 -1
- data/lib/bolt/inventory/group.rb +1 -1
- data/lib/bolt/inventory/inventory.rb +4 -14
- data/lib/bolt/inventory/target.rb +22 -5
- data/lib/bolt/logger.rb +15 -1
- data/lib/bolt/outputter.rb +3 -0
- data/lib/bolt/outputter/rainbow.rb +84 -0
- data/lib/bolt/pal.rb +23 -9
- data/lib/bolt/project.rb +75 -55
- data/lib/bolt/resource_instance.rb +5 -1
- data/lib/bolt/shell/bash.rb +30 -42
- data/lib/bolt/shell/powershell.rb +13 -8
- data/lib/bolt/shell/powershell/snippets.rb +23 -6
- data/lib/bolt/transport/docker.rb +9 -5
- data/lib/bolt/transport/local/connection.rb +2 -1
- data/lib/bolt/transport/orch.rb +8 -0
- data/lib/bolt/transport/ssh.rb +7 -1
- data/lib/bolt/transport/ssh/connection.rb +35 -0
- data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_spec/bolt_context.rb +1 -1
- data/lib/bolt_spec/run.rb +1 -1
- metadata +22 -18
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',
|
@@ -89,8 +90,10 @@ module Bolt
|
|
89
90
|
:notice
|
90
91
|
end
|
91
92
|
|
93
|
+
# Explicitly check the log level names instead of the log level number, as levels
|
94
|
+
# that are stringified integers (e.g. "level" => "42") will return a truthy value
|
92
95
|
def self.valid_level?(level)
|
93
|
-
|
96
|
+
Logging::LEVELS.include?(Logging.levelify(level))
|
94
97
|
end
|
95
98
|
|
96
99
|
def self.levels
|
@@ -100,5 +103,16 @@ module Bolt
|
|
100
103
|
def self.reset_logging
|
101
104
|
Logging.reset
|
102
105
|
end
|
106
|
+
|
107
|
+
def self.warn_once(type, msg)
|
108
|
+
@mutex.synchronize {
|
109
|
+
@warnings ||= []
|
110
|
+
@logger ||= Logging.logger[self]
|
111
|
+
unless @warnings.include?(type)
|
112
|
+
@logger.warn(msg)
|
113
|
+
@warnings << type
|
114
|
+
end
|
115
|
+
}
|
116
|
+
end
|
103
117
|
end
|
104
118
|
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,84 @@
|
|
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
|
+
rescue LoadError
|
12
|
+
raise "The 'paint' gem is required to use the rainbow outputter."
|
13
|
+
end
|
14
|
+
super
|
15
|
+
@line_color = 0
|
16
|
+
@color = 0
|
17
|
+
@state = :normal
|
18
|
+
end
|
19
|
+
|
20
|
+
# The algorithm is from lolcat (https://github.com/busyloop/lolcat)
|
21
|
+
# lolcat is released with WTFPL
|
22
|
+
def rainbow
|
23
|
+
red = Math.sin(0.3 * @color + 0) * 127 + 128
|
24
|
+
green = Math.sin(0.3 * @color + 2 * Math::PI / 3) * 127 + 128
|
25
|
+
blue = Math.sin(0.3 * @color + 4 * Math::PI / 3) * 127 + 128
|
26
|
+
@color += 1 / 8.0
|
27
|
+
format("%<red>02X%<green>02X%<blue>02X", red: red, green: green, blue: blue)
|
28
|
+
end
|
29
|
+
|
30
|
+
def colorize(color, string)
|
31
|
+
if @color && @stream.isatty
|
32
|
+
if %i[green rainbow].include?(color)
|
33
|
+
a = string.chars.map do |c|
|
34
|
+
case @state
|
35
|
+
when :normal
|
36
|
+
if c == "\e"
|
37
|
+
@state = :ansi
|
38
|
+
elsif c == "\n"
|
39
|
+
@line_color += 1
|
40
|
+
@color = @line_color
|
41
|
+
c
|
42
|
+
else
|
43
|
+
Paint[c, rainbow]
|
44
|
+
end
|
45
|
+
when :ansi
|
46
|
+
@state = :normal if c == 'm'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
a.join('')
|
50
|
+
else
|
51
|
+
"\033[#{COLORS[color]}m#{string}\033[0m"
|
52
|
+
end
|
53
|
+
else
|
54
|
+
string
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def print_summary(results, elapsed_time = nil)
|
59
|
+
ok_set = results.ok_set
|
60
|
+
unless ok_set.empty?
|
61
|
+
@stream.puts colorize(:rainbow, format('Successful on %<size>d target%<plural>s: %<names>s',
|
62
|
+
size: ok_set.size,
|
63
|
+
plural: ok_set.size == 1 ? '' : 's',
|
64
|
+
names: ok_set.targets.map(&:safe_name).join(',')))
|
65
|
+
end
|
66
|
+
|
67
|
+
error_set = results.error_set
|
68
|
+
unless error_set.empty?
|
69
|
+
@stream.puts colorize(:red,
|
70
|
+
format('Failed on %<size>d target%<plural>s: %<names>s',
|
71
|
+
size: error_set.size,
|
72
|
+
plural: error_set.size == 1 ? '' : 's',
|
73
|
+
names: error_set.targets.map(&:safe_name).join(',')))
|
74
|
+
end
|
75
|
+
|
76
|
+
total_msg = format('Ran on %<size>d target%<plural>s',
|
77
|
+
size: results.size,
|
78
|
+
plural: results.size == 1 ? '' : 's')
|
79
|
+
total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
|
80
|
+
@stream.puts colorize(:rainbow, total_msg)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
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
|
data/lib/bolt/project.rb
CHANGED
@@ -2,32 +2,27 @@
|
|
2
2
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'bolt/pal'
|
5
|
+
require 'bolt/config'
|
5
6
|
|
6
7
|
module Bolt
|
7
8
|
class Project
|
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
|
-
attr_reader :path, :config_file, :inventory_file, :modulepath, :hiera_config,
|
16
|
-
:puppetfile, :rerunfile, :type, :resource_types
|
18
|
+
attr_reader :path, :data, :config_file, :inventory_file, :modulepath, :hiera_config,
|
19
|
+
:puppetfile, :rerunfile, :type, :resource_types, :warnings, :project_file
|
17
20
|
|
18
21
|
def self.default_project
|
19
|
-
|
22
|
+
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
|
20
23
|
# If homedir isn't defined use the system config path
|
21
24
|
rescue ArgumentError
|
22
|
-
|
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
|
25
|
+
create_project(Bolt::Config.system_path, 'system')
|
31
26
|
end
|
32
27
|
|
33
28
|
# Search recursively up the directory hierarchy for the Project. Look for a
|
@@ -36,10 +31,11 @@ module Bolt
|
|
36
31
|
# hierarchy, falling back to the default if we reach the root.
|
37
32
|
def self.find_boltdir(dir)
|
38
33
|
dir = Pathname.new(dir)
|
34
|
+
|
39
35
|
if (dir + BOLTDIR_NAME).directory?
|
40
|
-
|
36
|
+
create_project(dir + BOLTDIR_NAME, 'embedded')
|
41
37
|
elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
|
42
|
-
|
38
|
+
create_project(dir, 'local')
|
43
39
|
elsif dir.root?
|
44
40
|
default_project
|
45
41
|
else
|
@@ -47,9 +43,35 @@ module Bolt
|
|
47
43
|
end
|
48
44
|
end
|
49
45
|
|
50
|
-
def
|
46
|
+
def self.create_project(path, type = 'option')
|
47
|
+
fullpath = Pathname.new(path).expand_path
|
48
|
+
|
49
|
+
if !Bolt::Util.windows? && type != 'environment' && fullpath.world_writable?
|
50
|
+
raise Bolt::Error.new(
|
51
|
+
"Project directory '#{fullpath}' is world-writable which poses a security risk. Set "\
|
52
|
+
"BOLT_PROJECT='#{fullpath}' to force the use of this project directory.",
|
53
|
+
"bolt/world-writable-error"
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
project_file = File.join(fullpath, 'bolt-project.yaml')
|
58
|
+
data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
|
59
|
+
new(data, path, type)
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(raw_data, path, type = 'option')
|
51
63
|
@path = Pathname.new(path).expand_path
|
52
|
-
|
64
|
+
|
65
|
+
@project_file = @path + 'bolt-project.yaml'
|
66
|
+
|
67
|
+
@warnings = []
|
68
|
+
if (@path + 'bolt.yaml').file? && project_file?
|
69
|
+
msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
|
70
|
+
"Transport config should be set in inventory.yaml, all other config should be set in "\
|
71
|
+
"bolt-project.yaml."
|
72
|
+
@warnings << { msg: msg }
|
73
|
+
end
|
74
|
+
|
53
75
|
@inventory_file = @path + 'inventory.yaml'
|
54
76
|
@modulepath = [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
|
55
77
|
@hiera_config = @path + 'hiera.yaml'
|
@@ -58,9 +80,26 @@ module Bolt
|
|
58
80
|
@resource_types = @path + '.resource_types'
|
59
81
|
@type = type
|
60
82
|
|
61
|
-
|
62
|
-
|
63
|
-
|
83
|
+
tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
|
84
|
+
if tc.any?
|
85
|
+
msg = "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}"
|
86
|
+
@warnings << { msg: msg }
|
87
|
+
end
|
88
|
+
|
89
|
+
@data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_OPTIONS.include?(k) }
|
90
|
+
|
91
|
+
# Once bolt.yaml deprecation is removed, this attribute should be removed
|
92
|
+
# and replaced with .project_file in lib/bolt/config.rb
|
93
|
+
@config_file = if (Bolt::Config::BOLT_OPTIONS & @data.keys).any?
|
94
|
+
if (@path + 'bolt.yaml').file?
|
95
|
+
msg = "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
|
96
|
+
@warnings << { msg: msg }
|
97
|
+
end
|
98
|
+
@project_file
|
99
|
+
else
|
100
|
+
@path + 'bolt.yaml'
|
101
|
+
end
|
102
|
+
validate if project_file?
|
64
103
|
end
|
65
104
|
|
66
105
|
def to_s
|
@@ -78,15 +117,12 @@ module Bolt
|
|
78
117
|
end
|
79
118
|
alias == eql?
|
80
119
|
|
81
|
-
def
|
120
|
+
def project_file?
|
82
121
|
@project_file.file?
|
83
122
|
end
|
84
123
|
|
85
124
|
def name
|
86
|
-
|
87
|
-
dirname = @path.basename.to_s == 'Boltdir' ? @path.parent.basename.to_s : @path.basename.to_s
|
88
|
-
pname = @data['name'] || dirname
|
89
|
-
pname.include?('-') ? pname.split('-', 2)[1] : pname
|
125
|
+
@data['name']
|
90
126
|
end
|
91
127
|
|
92
128
|
def tasks
|
@@ -97,36 +133,20 @@ module Bolt
|
|
97
133
|
@data['plans']
|
98
134
|
end
|
99
135
|
|
100
|
-
def project_directory_name?(name)
|
101
|
-
# it must match an installed project name according to forge validator
|
102
|
-
name =~ /^[a-z][a-z0-9_]*$/
|
103
|
-
end
|
104
|
-
|
105
|
-
def project_namespaced_name?(name)
|
106
|
-
# it must match the full project name according to forge validator
|
107
|
-
name =~ /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
|
108
|
-
end
|
109
|
-
|
110
136
|
def validate
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
Configure project name in <project_dir>/bolt-project.yaml
|
125
|
-
ERROR_STRING
|
126
|
-
# If the project name is the same as one of the built-in modules raise a warning
|
127
|
-
elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
|
128
|
-
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|
129
|
-
"with a built-in Bolt module of the same name."
|
137
|
+
if name
|
138
|
+
name_regex = /^[a-z][a-z0-9_]*$/
|
139
|
+
if name !~ name_regex
|
140
|
+
raise Bolt::ValidationError, <<~ERROR_STRING
|
141
|
+
Invalid project name '#{name}' in bolt-project.yaml; project name must match #{name_regex.inspect}
|
142
|
+
ERROR_STRING
|
143
|
+
elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
|
144
|
+
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|
145
|
+
"with a built-in Bolt module of the same name."
|
146
|
+
end
|
147
|
+
else
|
148
|
+
message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
|
149
|
+
@warnings << { msg: message }
|
130
150
|
end
|
131
151
|
|
132
152
|
%w[tasks plans].each do |conf|
|
@@ -83,8 +83,12 @@ module Bolt
|
|
83
83
|
to_hash.to_json(opts)
|
84
84
|
end
|
85
85
|
|
86
|
+
def self.format_reference(type, title)
|
87
|
+
"#{type.capitalize}[#{title}]"
|
88
|
+
end
|
89
|
+
|
86
90
|
def reference
|
87
|
-
|
91
|
+
self.class.format_reference(@type, @title)
|
88
92
|
end
|
89
93
|
alias to_s reference
|
90
94
|
|
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]
|