bolt 2.36.0 → 2.42.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 +8 -8
- data/lib/bolt/bolt_option_parser.rb +7 -3
- data/lib/bolt/cli.rb +67 -23
- data/lib/bolt/config.rb +70 -45
- data/lib/bolt/config/options.rb +104 -79
- data/lib/bolt/config/transport/base.rb +2 -2
- data/lib/bolt/config/transport/local.rb +1 -0
- data/lib/bolt/config/transport/options.rb +11 -68
- data/lib/bolt/config/transport/ssh.rb +0 -5
- data/lib/bolt/inventory.rb +26 -0
- data/lib/bolt/inventory/group.rb +29 -9
- data/lib/bolt/inventory/inventory.rb +1 -1
- data/lib/bolt/inventory/options.rb +130 -0
- data/lib/bolt/inventory/target.rb +10 -11
- data/lib/bolt/module.rb +10 -2
- data/lib/bolt/module_installer.rb +21 -13
- data/lib/bolt/module_installer/resolver.rb +13 -5
- data/lib/bolt/outputter.rb +19 -5
- data/lib/bolt/outputter/human.rb +20 -1
- data/lib/bolt/outputter/json.rb +1 -1
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/outputter/rainbow.rb +12 -1
- data/lib/bolt/pal/yaml_plan/transpiler.rb +5 -1
- data/lib/bolt/plugin.rb +42 -6
- data/lib/bolt/plugin/cache.rb +76 -0
- data/lib/bolt/plugin/module.rb +4 -4
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +38 -13
- data/lib/bolt/project_manager.rb +2 -0
- data/lib/bolt/project_manager/config_migrator.rb +9 -1
- data/lib/bolt/project_manager/module_migrator.rb +2 -0
- data/lib/bolt/puppetdb/client.rb +8 -0
- data/lib/bolt/rerun.rb +1 -5
- data/lib/bolt/shell/bash.rb +7 -1
- data/lib/bolt/shell/powershell.rb +21 -3
- data/lib/bolt/target.rb +4 -0
- data/lib/bolt/transport/local.rb +13 -0
- data/lib/bolt/util.rb +22 -0
- data/lib/bolt/validator.rb +227 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/plugin.rb +13 -0
- data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
- data/lib/bolt_server/schemas/connect-data.json +22 -0
- data/lib/bolt_server/schemas/partials/task.json +1 -1
- data/lib/bolt_server/transport_app.rb +64 -36
- metadata +24 -5
- data/lib/bolt/config/validator.rb +0 -231
@@ -111,11 +111,6 @@ module Bolt
|
|
111
111
|
@config['interpreters'] = normalize_interpreters(@config['interpreters'])
|
112
112
|
end
|
113
113
|
|
114
|
-
if @config['login-shell'] && !LOGIN_SHELLS.include?(@config['login-shell'])
|
115
|
-
raise Bolt::ValidationError,
|
116
|
-
"Unsupported login-shell #{@config['login-shell']}. Supported shells are #{LOGIN_SHELLS.join(', ')}"
|
117
|
-
end
|
118
|
-
|
119
114
|
if @config['login-shell'] == 'powershell'
|
120
115
|
%w[tty run-as].each do |key|
|
121
116
|
if @config[key]
|
data/lib/bolt/inventory.rb
CHANGED
@@ -4,13 +4,17 @@ require 'set'
|
|
4
4
|
require 'bolt/config'
|
5
5
|
require 'bolt/inventory/group'
|
6
6
|
require 'bolt/inventory/inventory'
|
7
|
+
require 'bolt/inventory/options'
|
7
8
|
require 'bolt/target'
|
8
9
|
require 'bolt/util'
|
9
10
|
require 'bolt/plugin'
|
11
|
+
require 'bolt/validator'
|
10
12
|
require 'yaml'
|
11
13
|
|
12
14
|
module Bolt
|
13
15
|
class Inventory
|
16
|
+
include Bolt::Inventory::Options
|
17
|
+
|
14
18
|
ENVIRONMENT_VAR = 'BOLT_INVENTORY'
|
15
19
|
|
16
20
|
class ValidationError < Bolt::Error
|
@@ -45,11 +49,26 @@ module Bolt
|
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
52
|
+
# Builds the schema used by the validator.
|
53
|
+
#
|
54
|
+
def self.schema
|
55
|
+
schema = {
|
56
|
+
type: Hash,
|
57
|
+
properties: OPTIONS.map { |opt| [opt, _ref: opt] }.to_h,
|
58
|
+
definitions: DEFINITIONS,
|
59
|
+
_plugin: true
|
60
|
+
}
|
61
|
+
|
62
|
+
schema[:definitions]['config'][:properties] = Bolt::Config.transport_definitions
|
63
|
+
schema
|
64
|
+
end
|
65
|
+
|
48
66
|
def self.from_config(config, plugins)
|
49
67
|
logger = Bolt::Logger.logger(self)
|
50
68
|
|
51
69
|
if ENV.include?(ENVIRONMENT_VAR)
|
52
70
|
begin
|
71
|
+
source = ENVIRONMENT_VAR
|
53
72
|
data = YAML.safe_load(ENV[ENVIRONMENT_VAR])
|
54
73
|
raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}" unless data.is_a?(Hash)
|
55
74
|
logger.debug("Loaded inventory from environment variable #{ENVIRONMENT_VAR}")
|
@@ -57,9 +76,11 @@ module Bolt
|
|
57
76
|
raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}"
|
58
77
|
end
|
59
78
|
elsif config.inventoryfile
|
79
|
+
source = config.inventoryfile
|
60
80
|
data = Bolt::Util.read_yaml_hash(config.inventoryfile, 'inventory')
|
61
81
|
logger.debug("Loaded inventory from #{config.inventoryfile}")
|
62
82
|
else
|
83
|
+
source = config.default_inventoryfile
|
63
84
|
data = Bolt::Util.read_optional_yaml_hash(config.default_inventoryfile, 'inventory')
|
64
85
|
|
65
86
|
if config.default_inventoryfile.exist?
|
@@ -74,6 +95,11 @@ module Bolt
|
|
74
95
|
t.resolve(plugins) unless t.resolved?
|
75
96
|
end
|
76
97
|
|
98
|
+
Bolt::Validator.new.tap do |validator|
|
99
|
+
validator.validate(data, schema, source)
|
100
|
+
validator.warnings.each { |warning| logger.warn(warning) }
|
101
|
+
end
|
102
|
+
|
77
103
|
inventory = create_version(data, config.transport, config.transports, plugins)
|
78
104
|
inventory.validate
|
79
105
|
inventory
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bolt/config/options'
|
3
4
|
require 'bolt/inventory/group'
|
4
5
|
require 'bolt/inventory/inventory'
|
5
6
|
require 'bolt/inventory/target'
|
@@ -18,12 +19,20 @@ module Bolt
|
|
18
19
|
GROUP_KEYS = DATA_KEYS + %w[name groups targets]
|
19
20
|
CONFIG_KEYS = Bolt::Config::INVENTORY_OPTIONS.keys
|
20
21
|
|
21
|
-
def initialize(input, plugins)
|
22
|
+
def initialize(input, plugins, all_group: false)
|
22
23
|
@logger = Bolt::Logger.logger(self)
|
23
24
|
@plugins = plugins
|
24
25
|
|
25
26
|
input = @plugins.resolve_top_level_references(input) if @plugins.reference?(input)
|
26
27
|
|
28
|
+
if all_group
|
29
|
+
if input.key?('name') && input['name'] != 'all'
|
30
|
+
@logger.warn("Top-level group '#{input['name']}' cannot specify a name, using 'all' instead.")
|
31
|
+
end
|
32
|
+
|
33
|
+
input = input.merge('name' => 'all')
|
34
|
+
end
|
35
|
+
|
27
36
|
raise ValidationError.new("Group does not have a name", nil) unless input.key?('name')
|
28
37
|
|
29
38
|
@name = @plugins.resolve_references(input['name'])
|
@@ -254,14 +263,6 @@ module Bolt
|
|
254
263
|
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in group #{@name}"
|
255
264
|
@logger.warn(msg)
|
256
265
|
end
|
257
|
-
|
258
|
-
Bolt::Util.walk_keys(input) do |key|
|
259
|
-
if @plugins.reference?(key)
|
260
|
-
raise ValidationError.new("Group keys cannot be specified as _plugin references", @name)
|
261
|
-
else
|
262
|
-
key
|
263
|
-
end
|
264
|
-
end
|
265
266
|
end
|
266
267
|
|
267
268
|
def validate(used_group_names = Set.new, used_target_names = Set.new, used_aliases = {})
|
@@ -323,7 +324,26 @@ module Bolt
|
|
323
324
|
'features' => @plugins.resolve_references(data.fetch('features', [])),
|
324
325
|
'plugin_hooks' => @plugins.resolve_references(data.fetch('plugin_hooks', {}))
|
325
326
|
}
|
327
|
+
|
326
328
|
validate_data_keys(result, target)
|
329
|
+
|
330
|
+
Bolt::Config::Options::TRANSPORT_CONFIG.each_key do |transport|
|
331
|
+
next unless result['config'].key?(transport)
|
332
|
+
transport_config = result['config'][transport]
|
333
|
+
next unless transport_config.is_a?(Hash)
|
334
|
+
transport_config = Bolt::Util.postwalk_vals(transport_config) do |val|
|
335
|
+
if val.is_a?(Hash)
|
336
|
+
val = val.compact
|
337
|
+
val = nil if val.empty?
|
338
|
+
end
|
339
|
+
val
|
340
|
+
end
|
341
|
+
# the transport config is user-specified data so we
|
342
|
+
# still want to preserve it even if it exclusively
|
343
|
+
# contains nil-resolved keys
|
344
|
+
result['config'][transport] = transport_config || {}
|
345
|
+
end
|
346
|
+
|
327
347
|
result['features'] = Set.new(result['features'].flatten)
|
328
348
|
result
|
329
349
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/config/options'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class Inventory
|
7
|
+
module Options
|
8
|
+
# Top-level options available in the inventory.
|
9
|
+
OPTIONS = %w[
|
10
|
+
config
|
11
|
+
facts
|
12
|
+
features
|
13
|
+
groups
|
14
|
+
targets
|
15
|
+
vars
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
# Definitions used to validate the data.
|
19
|
+
# https://github.com/puppetlabs/bolt/blob/main/schemas/README.md
|
20
|
+
DEFINITIONS = {
|
21
|
+
"alias" => {
|
22
|
+
description: "A unique alias to refer to the target. Aliases cannot conflict "\
|
23
|
+
"with the name of a group, the name of a target, or another alias.",
|
24
|
+
type: [String, Array],
|
25
|
+
uniqueItems: true,
|
26
|
+
items: {
|
27
|
+
type: String,
|
28
|
+
_plugin: true
|
29
|
+
},
|
30
|
+
_plugin: true
|
31
|
+
},
|
32
|
+
"config" => {
|
33
|
+
description: "A map of configuration options.",
|
34
|
+
type: Hash,
|
35
|
+
# These properties are populated as part of Bolt::Inventory.schema
|
36
|
+
properties: {},
|
37
|
+
_plugin: true
|
38
|
+
},
|
39
|
+
"facts" => {
|
40
|
+
description: "A map of system information, also known as facts, for the target.",
|
41
|
+
type: Hash,
|
42
|
+
_plugin: true
|
43
|
+
},
|
44
|
+
"features" => {
|
45
|
+
description: "A list of available features for the target.",
|
46
|
+
type: Array,
|
47
|
+
uniqueItems: true,
|
48
|
+
items: {
|
49
|
+
type: String,
|
50
|
+
_plugin: true
|
51
|
+
},
|
52
|
+
_plugin: true
|
53
|
+
},
|
54
|
+
"groups" => {
|
55
|
+
description: "A list of groups and their associated configuration.",
|
56
|
+
type: Array,
|
57
|
+
items: {
|
58
|
+
type: Hash,
|
59
|
+
required: ["name"],
|
60
|
+
properties: {
|
61
|
+
"config" => { _ref: "config" },
|
62
|
+
"facts" => { _ref: "facts" },
|
63
|
+
"features" => { _ref: "features" },
|
64
|
+
"groups" => { _ref: "groups" },
|
65
|
+
"name" => { _ref: "name" },
|
66
|
+
"plugin_hooks" => { _ref: "plugin_hooks" },
|
67
|
+
"targets" => { _ref: "targets" },
|
68
|
+
"vars" => { _ref: "vars" }
|
69
|
+
},
|
70
|
+
_plugin: true
|
71
|
+
},
|
72
|
+
_plugin: true
|
73
|
+
},
|
74
|
+
"name" => {
|
75
|
+
description: "A human-readable name to refer to the group or target. Names "\
|
76
|
+
"cannot conflict with the name of a group, the name of a target, "\
|
77
|
+
"or the alias of a target. A name is required for a group and is "\
|
78
|
+
"required for a target unless the uri option is set.",
|
79
|
+
type: String,
|
80
|
+
_plugin: true
|
81
|
+
},
|
82
|
+
"plugin_hooks" => {
|
83
|
+
description: "Configuration for the Puppet library plugin used to install the "\
|
84
|
+
"Puppet agent on the target. For more information, see "\
|
85
|
+
"https://pup.pt/bolt-plugin-hooks",
|
86
|
+
type: Hash,
|
87
|
+
properties: {
|
88
|
+
"puppet_library" => {
|
89
|
+
description: "Configuration for the Puppet library plugin.",
|
90
|
+
type: Hash,
|
91
|
+
_plugin: true
|
92
|
+
}
|
93
|
+
},
|
94
|
+
_plugin: true
|
95
|
+
},
|
96
|
+
"targets" => {
|
97
|
+
description: "A list of targets and their associated configuration.",
|
98
|
+
type: Array,
|
99
|
+
items: {
|
100
|
+
type: [String, Hash],
|
101
|
+
properties: {
|
102
|
+
"alias" => { _ref: "alias" },
|
103
|
+
"config" => { _ref: "config" },
|
104
|
+
"facts" => { _ref: "facts" },
|
105
|
+
"features" => { _ref: "features" },
|
106
|
+
"name" => { _ref: "name" },
|
107
|
+
"plugin_hooks" => { _ref: "plugin_hooks" },
|
108
|
+
"uri" => { _ref: "uri" },
|
109
|
+
"vars" => { _ref: "vars" }
|
110
|
+
},
|
111
|
+
_plugin: true
|
112
|
+
},
|
113
|
+
_plugin: true
|
114
|
+
},
|
115
|
+
"uri" => {
|
116
|
+
description: "The URI of the target. This option is required unless the name "\
|
117
|
+
"option is set.",
|
118
|
+
type: String,
|
119
|
+
format: "uri",
|
120
|
+
_plugin: true
|
121
|
+
},
|
122
|
+
"vars" => {
|
123
|
+
description: "A map of variables for the group or target.",
|
124
|
+
type: Hash,
|
125
|
+
_plugin: true
|
126
|
+
}
|
127
|
+
}.freeze
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -31,7 +31,8 @@ module Bolt
|
|
31
31
|
end
|
32
32
|
|
33
33
|
if @name == 'localhost'
|
34
|
-
|
34
|
+
default = { 'config' => { 'transport' => 'local' } }
|
35
|
+
target_data = Bolt::Util.deep_merge(default, target_data)
|
35
36
|
end
|
36
37
|
|
37
38
|
@config = target_data['config'] || {}
|
@@ -49,18 +50,16 @@ module Bolt
|
|
49
50
|
validate
|
50
51
|
end
|
51
52
|
|
52
|
-
def
|
53
|
+
def set_local_defaults
|
54
|
+
return if @set_local_default
|
53
55
|
defaults = {
|
54
|
-
'
|
55
|
-
'transport' => 'local',
|
56
|
-
'local' => { 'interpreters' => { '.rb' => RbConfig.ruby } }
|
57
|
-
},
|
58
|
-
'features' => ['puppet-agent']
|
56
|
+
'local' => { 'interpreters' => { '.rb' => RbConfig.ruby } }
|
59
57
|
}
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
58
|
+
old_config = @config
|
59
|
+
@config = Bolt::Util.deep_merge(defaults, @config)
|
60
|
+
invalidate_config_cache! if old_config != @config
|
61
|
+
set_feature('puppet-agent')
|
62
|
+
@set_local_default = true
|
64
63
|
end
|
65
64
|
|
66
65
|
# rubocop:disable Naming/AccessorMethodName
|
data/lib/bolt/module.rb
CHANGED
@@ -6,8 +6,14 @@ module Bolt
|
|
6
6
|
CONTENT_NAME_REGEX = /\A[a-z][a-z0-9_]*(::[a-z][a-z0-9_]*)*\z/.freeze
|
7
7
|
MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
|
8
8
|
|
9
|
-
def self.discover(modulepath)
|
10
|
-
|
9
|
+
def self.discover(modulepath, project)
|
10
|
+
mods = {}
|
11
|
+
|
12
|
+
if project.load_as_module?
|
13
|
+
mods[project.name] = Bolt::Module.new(project.name, project.path.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
modulepath.each do |path|
|
11
17
|
next unless File.exist?(path) && File.directory?(path)
|
12
18
|
Dir.children(path)
|
13
19
|
.map { |dir| File.join(path, dir) }
|
@@ -20,6 +26,8 @@ module Bolt
|
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
29
|
+
|
30
|
+
mods
|
23
31
|
end
|
24
32
|
|
25
33
|
attr_reader :name, :path
|
@@ -17,14 +17,14 @@ module Bolt
|
|
17
17
|
|
18
18
|
# Adds a single module to the project.
|
19
19
|
#
|
20
|
-
def add(name, specs, puppetfile_path, moduledir,
|
20
|
+
def add(name, specs, puppetfile_path, moduledir, project_file, config)
|
21
21
|
project_specs = Specs.new(specs)
|
22
22
|
|
23
23
|
# Exit early if project config already includes a spec with this name.
|
24
24
|
if project_specs.include?(name)
|
25
25
|
@outputter.print_message(
|
26
|
-
"Project configuration file #{
|
27
|
-
"#{name}. Nothing to do."
|
26
|
+
"Project configuration file #{project_file} already includes specification "\
|
27
|
+
"with name #{name}. Nothing to do."
|
28
28
|
)
|
29
29
|
return true
|
30
30
|
end
|
@@ -47,30 +47,32 @@ module Bolt
|
|
47
47
|
# a version conflict.
|
48
48
|
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
49
49
|
|
50
|
+
@outputter.start_spin
|
50
51
|
begin
|
51
52
|
resolve_specs.add_specs('name' => name)
|
52
|
-
puppetfile = Resolver.new.resolve(resolve_specs)
|
53
|
+
puppetfile = Resolver.new.resolve(resolve_specs, config)
|
53
54
|
rescue Bolt::Error
|
54
55
|
project_specs.add_specs('name' => name)
|
55
|
-
puppetfile = Resolver.new.resolve(project_specs)
|
56
|
+
puppetfile = Resolver.new.resolve(project_specs, config)
|
56
57
|
end
|
58
|
+
@outputter.stop_spin
|
57
59
|
|
58
60
|
# Display the diff between the existing Puppetfile and the new Puppetfile.
|
59
61
|
print_puppetfile_diff(existing_puppetfile, puppetfile)
|
60
62
|
|
61
63
|
# Add the module to the project configuration.
|
62
|
-
@outputter.print_action_step("Updating project configuration file at #{
|
64
|
+
@outputter.print_action_step("Updating project configuration file at #{project_file}")
|
63
65
|
|
64
|
-
data = Bolt::Util.read_yaml_hash(
|
66
|
+
data = Bolt::Util.read_yaml_hash(project_file, 'project')
|
65
67
|
data['modules'] ||= []
|
66
68
|
data['modules'] << name.tr('-', '/')
|
67
69
|
|
68
70
|
begin
|
69
|
-
File.write(
|
71
|
+
File.write(project_file, data.to_yaml)
|
70
72
|
rescue SystemCallError => e
|
71
73
|
raise Bolt::FileError.new(
|
72
74
|
"Unable to update project configuration file: #{e.message}",
|
73
|
-
|
75
|
+
project_file
|
74
76
|
)
|
75
77
|
end
|
76
78
|
|
@@ -79,7 +81,7 @@ module Bolt
|
|
79
81
|
puppetfile.write(puppetfile_path, moduledir)
|
80
82
|
|
81
83
|
# Install the modules.
|
82
|
-
install_puppetfile(puppetfile_path, moduledir)
|
84
|
+
install_puppetfile(puppetfile_path, moduledir, config)
|
83
85
|
end
|
84
86
|
|
85
87
|
# Outputs a diff of an old Puppetfile and a new Puppetfile.
|
@@ -145,7 +147,7 @@ module Bolt
|
|
145
147
|
|
146
148
|
# Installs a project's module dependencies.
|
147
149
|
#
|
148
|
-
def install(specs, path, moduledir, force: false, resolve: true)
|
150
|
+
def install(specs, path, moduledir, config = {}, force: false, resolve: true)
|
149
151
|
@outputter.print_message("Installing project modules\n\n")
|
150
152
|
|
151
153
|
if resolve != false
|
@@ -155,7 +157,11 @@ module Bolt
|
|
155
157
|
# and write a Puppetfile.
|
156
158
|
if force || !path.exist?
|
157
159
|
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
158
|
-
|
160
|
+
|
161
|
+
# This doesn't use the block as it's more testable to just mock *_spin
|
162
|
+
@outputter.start_spin
|
163
|
+
puppetfile = Resolver.new.resolve(specs, config)
|
164
|
+
@outputter.stop_spin
|
159
165
|
|
160
166
|
# We get here either through 'bolt module install' which uses the
|
161
167
|
# managed modulepath (which isn't configurable) or through bolt
|
@@ -177,14 +183,16 @@ module Bolt
|
|
177
183
|
end
|
178
184
|
|
179
185
|
# Install the modules.
|
180
|
-
install_puppetfile(path, moduledir)
|
186
|
+
install_puppetfile(path, moduledir, config)
|
181
187
|
end
|
182
188
|
|
183
189
|
# Installs the Puppetfile and generates types.
|
184
190
|
#
|
185
191
|
def install_puppetfile(path, moduledir, config = {})
|
186
192
|
@outputter.print_action_step("Syncing modules from #{path} to #{moduledir}")
|
193
|
+
@outputter.start_spin
|
187
194
|
ok = Installer.new(config).install(path, moduledir)
|
195
|
+
@outputter.stop_spin
|
188
196
|
|
189
197
|
# Automatically generate types after installing modules
|
190
198
|
@outputter.print_action_step("Generating type references")
|