bolt 2.37.0 → 2.38.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/lib/bolt/cli.rb +60 -23
- data/lib/bolt/config.rb +60 -18
- data/lib/bolt/config/options.rb +71 -77
- 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 +9 -67
- data/lib/bolt/inventory.rb +25 -0
- data/lib/bolt/inventory/options.rb +130 -0
- data/lib/bolt/inventory/target.rb +10 -11
- data/lib/bolt/module_installer.rb +21 -13
- data/lib/bolt/module_installer/resolver.rb +1 -1
- 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/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +20 -7
- 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/shell/powershell.rb +5 -3
- data/lib/bolt/target.rb +4 -0
- data/lib/bolt/transport/local.rb +13 -0
- data/lib/bolt/validator.rb +226 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +26 -26
- metadata +10 -3
- data/lib/bolt/config/validator.rb +0 -231
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'bolt/error'
|
4
4
|
require 'bolt/util'
|
5
|
-
require 'bolt/
|
5
|
+
require 'bolt/validator'
|
6
6
|
require 'bolt/config/transport/options'
|
7
7
|
|
8
8
|
module Bolt
|
@@ -125,7 +125,7 @@ module Bolt
|
|
125
125
|
|
126
126
|
# Validation defaults to just asserting the option types
|
127
127
|
private def validate
|
128
|
-
Bolt::
|
128
|
+
Bolt::Validator.new.validate(@config.compact, self.class.schema)
|
129
129
|
end
|
130
130
|
end
|
131
131
|
end
|
@@ -6,73 +6,8 @@ module Bolt
|
|
6
6
|
module Options
|
7
7
|
LOGIN_SHELLS = %w[sh bash zsh dash ksh powershell].freeze
|
8
8
|
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# are the option's definition. These options are used in multiple locations:
|
12
|
-
#
|
13
|
-
# - Automatic type validation when loading and setting configuration
|
14
|
-
# - Generating reference documentation for configuration files
|
15
|
-
# - Generating JSON schemas for configuration files
|
16
|
-
#
|
17
|
-
# Data includes keys defined by JSON Schema Draft 07 as well as some metadata used
|
18
|
-
# by Bolt to generate documentation. The following keys are used:
|
19
|
-
#
|
20
|
-
# :description String A detailed description of the option and what it does. This
|
21
|
-
# field is used in both documentation and the JSON schemas,
|
22
|
-
# and should provide as much detail as possible, including
|
23
|
-
# links to relevant documentation.
|
24
|
-
#
|
25
|
-
# :type Class The expected type of a value. These should be Ruby classes,
|
26
|
-
# as this field is used to perform automatic type validation.
|
27
|
-
# If an option can accept more than one type, this should be
|
28
|
-
# an array of types. Boolean values should set :type to
|
29
|
-
# [TrueClass, FalseClass], as Ruby does not have a single
|
30
|
-
# Boolean class.
|
31
|
-
#
|
32
|
-
# :items Hash A definition hash for items in an array. Similar to values
|
33
|
-
# for top-level options, items can have a :description, :type,
|
34
|
-
# or any other key in this list.
|
35
|
-
#
|
36
|
-
# :uniqueItems Boolean Whether or not an array should contain only unique items.
|
37
|
-
#
|
38
|
-
# :properties Hash A hash where keys are sub-options and values are definitions
|
39
|
-
# for the sub-option. Similar to values for top-level options,
|
40
|
-
# properties can have a :description, :type, or any other key
|
41
|
-
# in this list.
|
42
|
-
#
|
43
|
-
# :additionalProperties A variation of the :properties key, where the hash is a
|
44
|
-
# Hash definition for any properties not specified in :properties.
|
45
|
-
# This can be used to permit arbitrary sub-options, such as
|
46
|
-
# logs for the 'log' option.
|
47
|
-
#
|
48
|
-
# :propertyNames Hash A hash that defines the properties that an option's property
|
49
|
-
# names must adhere to.
|
50
|
-
#
|
51
|
-
# :required Array An array of properties that are required for options that
|
52
|
-
# accept Hash values.
|
53
|
-
#
|
54
|
-
# :minimum Integer The minimum integer value for an option.
|
55
|
-
#
|
56
|
-
# :enum Array An array of values that the option recognizes.
|
57
|
-
#
|
58
|
-
# :pattern String A JSON regex pattern that the option's vaue should match.
|
59
|
-
#
|
60
|
-
# :format String Requires that a string value matches a format defined by the
|
61
|
-
# JSON Schema draft.
|
62
|
-
#
|
63
|
-
# :_plugin Boolean Whether the option accepts a plugin reference. This is used
|
64
|
-
# when generating the JSON schemas to determine whether or not
|
65
|
-
# to include a reference to the _plugin definition. If :_plugin
|
66
|
-
# is set to true, the script that generates JSON schemas will
|
67
|
-
# automatically recurse through the :items and :properties keys
|
68
|
-
# and add plugin references if applicable.
|
69
|
-
#
|
70
|
-
# :_example Any An example value for the option. This is used to generate
|
71
|
-
# reference documentation for configuration files.
|
72
|
-
#
|
73
|
-
# :_default Any The documented default value for the option. This is only
|
74
|
-
# used to generate reference documentation for configuration
|
75
|
-
# files and is not used by Bolt to actually set default values.
|
9
|
+
# Definitions used to validate config options.
|
10
|
+
# https://github.com/puppetlabs/bolt/blob/main/schemas/README.md
|
76
11
|
TRANSPORT_OPTIONS = {
|
77
12
|
"basic-auth-only" => {
|
78
13
|
type: [TrueClass, FalseClass],
|
@@ -81,6 +16,13 @@ module Bolt
|
|
81
16
|
_default: false,
|
82
17
|
_example: true
|
83
18
|
},
|
19
|
+
"bundled-ruby" => {
|
20
|
+
description: "Whether to use the Ruby bundled with Bolt packages for local targets.",
|
21
|
+
type: [TrueClass, FalseClass],
|
22
|
+
_plugin: false,
|
23
|
+
_example: true,
|
24
|
+
_default: false
|
25
|
+
},
|
84
26
|
"cacert" => {
|
85
27
|
type: String,
|
86
28
|
description: "The path to the CA certificate.",
|
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,25 @@ 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
|
+
}
|
60
|
+
|
61
|
+
schema[:definitions]['config'][:properties] = Bolt::Config.transport_definitions
|
62
|
+
schema
|
63
|
+
end
|
64
|
+
|
48
65
|
def self.from_config(config, plugins)
|
49
66
|
logger = Bolt::Logger.logger(self)
|
50
67
|
|
51
68
|
if ENV.include?(ENVIRONMENT_VAR)
|
52
69
|
begin
|
70
|
+
source = ENVIRONMENT_VAR
|
53
71
|
data = YAML.safe_load(ENV[ENVIRONMENT_VAR])
|
54
72
|
raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}" unless data.is_a?(Hash)
|
55
73
|
logger.debug("Loaded inventory from environment variable #{ENVIRONMENT_VAR}")
|
@@ -57,9 +75,11 @@ module Bolt
|
|
57
75
|
raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}"
|
58
76
|
end
|
59
77
|
elsif config.inventoryfile
|
78
|
+
source = config.inventoryfile
|
60
79
|
data = Bolt::Util.read_yaml_hash(config.inventoryfile, 'inventory')
|
61
80
|
logger.debug("Loaded inventory from #{config.inventoryfile}")
|
62
81
|
else
|
82
|
+
source = config.default_inventoryfile
|
63
83
|
data = Bolt::Util.read_optional_yaml_hash(config.default_inventoryfile, 'inventory')
|
64
84
|
|
65
85
|
if config.default_inventoryfile.exist?
|
@@ -74,6 +94,11 @@ module Bolt
|
|
74
94
|
t.resolve(plugins) unless t.resolved?
|
75
95
|
end
|
76
96
|
|
97
|
+
Bolt::Validator.new.tap do |validator|
|
98
|
+
validator.validate(data, schema, source)
|
99
|
+
validator.warnings.each { |warning| logger.warn(warning) }
|
100
|
+
end
|
101
|
+
|
77
102
|
inventory = create_version(data, config.transport, config.transports, plugins)
|
78
103
|
inventory.validate
|
79
104
|
inventory
|
@@ -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
|
@@ -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")
|