openbolt 5.0.0.rc1
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 +7 -0
- data/Puppetfile +52 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +60 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +71 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +55 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +65 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +93 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +33 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +38 -0
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +208 -0
- data/bolt-modules/boltlib/lib/puppet/functions/background.rb +62 -0
- data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +57 -0
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +130 -0
- data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +31 -0
- data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +52 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +87 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +34 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +35 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +74 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +97 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +47 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +52 -0
- data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +40 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +42 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +53 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +106 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +291 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +145 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +164 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +211 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +48 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +43 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +145 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +38 -0
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +101 -0
- data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +29 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +131 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +59 -0
- data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +39 -0
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +50 -0
- data/bolt-modules/boltlib/types/planresult.pp +18 -0
- data/bolt-modules/boltlib/types/targetspec.pp +7 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +42 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +20 -0
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
- data/bolt-modules/file/lib/puppet/functions/file/delete.rb +21 -0
- data/bolt-modules/file/lib/puppet/functions/file/exists.rb +28 -0
- data/bolt-modules/file/lib/puppet/functions/file/join.rb +20 -0
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +33 -0
- data/bolt-modules/file/lib/puppet/functions/file/readable.rb +28 -0
- data/bolt-modules/file/lib/puppet/functions/file/write.rb +24 -0
- data/bolt-modules/log/lib/puppet/functions/log/debug.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/error.rb +40 -0
- data/bolt-modules/log/lib/puppet/functions/log/fatal.rb +40 -0
- data/bolt-modules/log/lib/puppet/functions/log/info.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/trace.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/warn.rb +41 -0
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +36 -0
- data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +35 -0
- data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +65 -0
- data/bolt-modules/system/lib/puppet/functions/system/env.rb +20 -0
- data/exe/bolt +17 -0
- data/guides/debugging.yaml +27 -0
- data/guides/inventory.yaml +23 -0
- data/guides/links.yaml +12 -0
- data/guides/logging.yaml +17 -0
- data/guides/module.yaml +18 -0
- data/guides/modulepath.yaml +24 -0
- data/guides/project.yaml +21 -0
- data/guides/targets.yaml +28 -0
- data/guides/transports.yaml +22 -0
- data/lib/bolt/analytics.rb +233 -0
- data/lib/bolt/application.rb +806 -0
- data/lib/bolt/applicator.rb +368 -0
- data/lib/bolt/apply_inventory.rb +93 -0
- data/lib/bolt/apply_result.rb +154 -0
- data/lib/bolt/apply_target.rb +90 -0
- data/lib/bolt/bolt_option_parser.rb +1226 -0
- data/lib/bolt/catalog/logging.rb +15 -0
- data/lib/bolt/catalog.rb +144 -0
- data/lib/bolt/cli.rb +949 -0
- data/lib/bolt/config/modulepath.rb +30 -0
- data/lib/bolt/config/options.rb +673 -0
- data/lib/bolt/config/transport/base.rb +133 -0
- data/lib/bolt/config/transport/docker.rb +34 -0
- data/lib/bolt/config/transport/jail.rb +33 -0
- data/lib/bolt/config/transport/local.rb +39 -0
- data/lib/bolt/config/transport/lxd.rb +34 -0
- data/lib/bolt/config/transport/options.rb +431 -0
- data/lib/bolt/config/transport/orch.rb +41 -0
- data/lib/bolt/config/transport/podman.rb +33 -0
- data/lib/bolt/config/transport/remote.rb +24 -0
- data/lib/bolt/config/transport/ssh.rb +138 -0
- data/lib/bolt/config/transport/winrm.rb +63 -0
- data/lib/bolt/config.rb +515 -0
- data/lib/bolt/container_result.rb +105 -0
- data/lib/bolt/error.rb +194 -0
- data/lib/bolt/executor.rb +539 -0
- data/lib/bolt/fiber_executor.rb +190 -0
- data/lib/bolt/inventory/group.rb +446 -0
- data/lib/bolt/inventory/inventory.rb +391 -0
- data/lib/bolt/inventory/options.rb +139 -0
- data/lib/bolt/inventory/target.rb +293 -0
- data/lib/bolt/inventory.rb +120 -0
- data/lib/bolt/logger.rb +252 -0
- data/lib/bolt/module.rb +54 -0
- data/lib/bolt/module_installer/installer.rb +44 -0
- data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
- data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
- data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
- data/lib/bolt/module_installer/puppetfile.rb +131 -0
- data/lib/bolt/module_installer/resolver.rb +129 -0
- data/lib/bolt/module_installer/specs/forge_spec.rb +91 -0
- data/lib/bolt/module_installer/specs/git_spec.rb +150 -0
- data/lib/bolt/module_installer/specs/id/base.rb +116 -0
- data/lib/bolt/module_installer/specs/id/gitclone.rb +120 -0
- data/lib/bolt/module_installer/specs/id/github.rb +90 -0
- data/lib/bolt/module_installer/specs/id/gitlab.rb +92 -0
- data/lib/bolt/module_installer/specs.rb +95 -0
- data/lib/bolt/module_installer.rb +208 -0
- data/lib/bolt/node/errors.rb +55 -0
- data/lib/bolt/node/output.rb +29 -0
- data/lib/bolt/outputter/human.rb +958 -0
- data/lib/bolt/outputter/json.rb +205 -0
- data/lib/bolt/outputter/logger.rb +76 -0
- data/lib/bolt/outputter/rainbow.rb +118 -0
- data/lib/bolt/outputter.rb +57 -0
- data/lib/bolt/pal/issues.rb +19 -0
- data/lib/bolt/pal/logging.rb +17 -0
- data/lib/bolt/pal/yaml_plan/evaluator.rb +83 -0
- data/lib/bolt/pal/yaml_plan/loader.rb +94 -0
- data/lib/bolt/pal/yaml_plan/parameter.rb +63 -0
- data/lib/bolt/pal/yaml_plan/step/command.rb +45 -0
- data/lib/bolt/pal/yaml_plan/step/download.rb +37 -0
- data/lib/bolt/pal/yaml_plan/step/eval.rb +42 -0
- data/lib/bolt/pal/yaml_plan/step/message.rb +31 -0
- data/lib/bolt/pal/yaml_plan/step/plan.rb +42 -0
- data/lib/bolt/pal/yaml_plan/step/resources.rb +170 -0
- data/lib/bolt/pal/yaml_plan/step/script.rb +62 -0
- data/lib/bolt/pal/yaml_plan/step/task.rb +42 -0
- data/lib/bolt/pal/yaml_plan/step/upload.rb +37 -0
- data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
- data/lib/bolt/pal/yaml_plan/step.rb +223 -0
- data/lib/bolt/pal/yaml_plan/transpiler.rb +90 -0
- data/lib/bolt/pal/yaml_plan.rb +172 -0
- data/lib/bolt/pal.rb +847 -0
- data/lib/bolt/plan_creator.rb +219 -0
- data/lib/bolt/plan_future.rb +86 -0
- data/lib/bolt/plan_result.rb +44 -0
- data/lib/bolt/plugin/cache.rb +76 -0
- data/lib/bolt/plugin/env_var.rb +54 -0
- data/lib/bolt/plugin/module.rb +276 -0
- data/lib/bolt/plugin/prompt.rb +36 -0
- data/lib/bolt/plugin/puppet_connect_data.rb +84 -0
- data/lib/bolt/plugin/puppetdb.rb +124 -0
- data/lib/bolt/plugin/task.rb +72 -0
- data/lib/bolt/plugin.rb +380 -0
- data/lib/bolt/project.rb +219 -0
- data/lib/bolt/project_manager/config_migrator.rb +113 -0
- data/lib/bolt/project_manager/inventory_migrator.rb +67 -0
- data/lib/bolt/project_manager/migrator.rb +39 -0
- data/lib/bolt/project_manager/module_migrator.rb +203 -0
- data/lib/bolt/project_manager.rb +221 -0
- data/lib/bolt/puppetdb/client.rb +153 -0
- data/lib/bolt/puppetdb/config.rb +176 -0
- data/lib/bolt/puppetdb/instance.rb +146 -0
- data/lib/bolt/puppetdb.rb +15 -0
- data/lib/bolt/r10k_log_proxy.rb +30 -0
- data/lib/bolt/rerun.rb +55 -0
- data/lib/bolt/resource_instance.rb +133 -0
- data/lib/bolt/result.rb +247 -0
- data/lib/bolt/result_set.rb +128 -0
- data/lib/bolt/shell/bash/tmpdir.rb +62 -0
- data/lib/bolt/shell/bash.rb +516 -0
- data/lib/bolt/shell/powershell/snippets.rb +181 -0
- data/lib/bolt/shell/powershell.rb +365 -0
- data/lib/bolt/shell.rb +105 -0
- data/lib/bolt/target.rb +174 -0
- data/lib/bolt/task/puppet_server.rb +27 -0
- data/lib/bolt/task/run.rb +55 -0
- data/lib/bolt/task.rb +163 -0
- data/lib/bolt/transport/base.rb +252 -0
- data/lib/bolt/transport/docker/connection.rb +150 -0
- data/lib/bolt/transport/docker.rb +23 -0
- data/lib/bolt/transport/jail/connection.rb +81 -0
- data/lib/bolt/transport/jail.rb +21 -0
- data/lib/bolt/transport/local/connection.rb +106 -0
- data/lib/bolt/transport/local.rb +20 -0
- data/lib/bolt/transport/lxd/connection.rb +115 -0
- data/lib/bolt/transport/lxd.rb +26 -0
- data/lib/bolt/transport/orch/connection.rb +111 -0
- data/lib/bolt/transport/orch.rb +271 -0
- data/lib/bolt/transport/podman/connection.rb +102 -0
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/remote.rb +41 -0
- data/lib/bolt/transport/simple.rb +54 -0
- data/lib/bolt/transport/ssh/connection.rb +321 -0
- data/lib/bolt/transport/ssh/exec_connection.rb +140 -0
- data/lib/bolt/transport/ssh.rb +48 -0
- data/lib/bolt/transport/winrm/connection.rb +378 -0
- data/lib/bolt/transport/winrm.rb +33 -0
- data/lib/bolt/util/format.rb +68 -0
- data/lib/bolt/util/puppet_log_level.rb +21 -0
- data/lib/bolt/util.rb +465 -0
- data/lib/bolt/validator.rb +227 -0
- data/lib/bolt/version.rb +5 -0
- data/lib/bolt.rb +8 -0
- data/lib/bolt_server/acl.rb +39 -0
- data/lib/bolt_server/base_config.rb +112 -0
- data/lib/bolt_server/config.rb +64 -0
- data/lib/bolt_server/file_cache.rb +200 -0
- data/lib/bolt_server/request_error.rb +11 -0
- data/lib/bolt_server/schemas/action-check_node_connections.json +14 -0
- data/lib/bolt_server/schemas/action-run_command.json +12 -0
- data/lib/bolt_server/schemas/action-run_script.json +47 -0
- data/lib/bolt_server/schemas/action-run_task.json +20 -0
- data/lib/bolt_server/schemas/action-upload_file.json +47 -0
- data/lib/bolt_server/schemas/partials/target-any.json +10 -0
- data/lib/bolt_server/schemas/partials/target-ssh.json +88 -0
- data/lib/bolt_server/schemas/partials/target-winrm.json +67 -0
- data/lib/bolt_server/schemas/partials/task.json +94 -0
- data/lib/bolt_server/schemas/transport-ssh.json +25 -0
- data/lib/bolt_server/schemas/transport-winrm.json +19 -0
- data/lib/bolt_server/transport_app.rb +554 -0
- data/lib/bolt_spec/bolt_context.rb +226 -0
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +51 -0
- data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
- data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +55 -0
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +59 -0
- data/lib/bolt_spec/plans/action_stubs/task_stub.rb +57 -0
- data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +65 -0
- data/lib/bolt_spec/plans/action_stubs.rb +196 -0
- data/lib/bolt_spec/plans/mock_executor.rb +361 -0
- data/lib/bolt_spec/plans/publish_stub.rb +49 -0
- data/lib/bolt_spec/plans.rb +190 -0
- data/lib/bolt_spec/run.rb +246 -0
- data/lib/logging_extensions/logging.rb +13 -0
- data/libexec/apply_catalog.rb +130 -0
- data/libexec/bolt_catalog +68 -0
- data/libexec/custom_facts.rb +63 -0
- data/libexec/query_resources.rb +75 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/count.rb +21 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/nodes.rb +22 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/targets.rb +21 -0
- data/modules/aggregate/plans/count.pp +56 -0
- data/modules/aggregate/plans/targets.pp +56 -0
- data/modules/canary/lib/puppet/functions/canary/merge.rb +13 -0
- data/modules/canary/lib/puppet/functions/canary/random_split.rb +22 -0
- data/modules/canary/lib/puppet/functions/canary/skip.rb +25 -0
- data/modules/canary/plans/init.pp +100 -0
- data/modules/puppet_connect/plans/test_input_data.pp +94 -0
- data/modules/puppetdb_fact/plans/init.pp +20 -0
- data/resources/bolt_bash_completion.sh +214 -0
- metadata +735 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
class Task
|
5
|
+
module Run
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# TODO: we should probably use a Bolt::Task for this
|
9
|
+
def validate_params(task_signature, params)
|
10
|
+
task_signature.runnable_with?(params) do |mismatch_message|
|
11
|
+
raise Bolt::ValidationError, mismatch_message
|
12
|
+
end || (raise Bolt::ValidationError, 'Task parameters do not match')
|
13
|
+
|
14
|
+
unless Puppet::Pops::Types::TypeFactory.data.instance?(params)
|
15
|
+
# generate a helpful error message about the type-mismatch between the type Data
|
16
|
+
# and the actual type of use_args
|
17
|
+
use_args_t = Puppet::Pops::Types::TypeCalculator.infer_set(params)
|
18
|
+
desc = Puppet::Pops::Types::TypeMismatchDescriber.singleton.describe_mismatch(
|
19
|
+
'Task parameters are not of type Data. run_task()',
|
20
|
+
Puppet::Pops::Types::TypeFactory.data, use_args_t
|
21
|
+
)
|
22
|
+
raise Bolt::ValidationError, desc
|
23
|
+
end
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def wrap_sensitive(task, params)
|
28
|
+
if (spec = task.metadata['parameters'])
|
29
|
+
params.each_with_object({}) do |(param, val), wrapped|
|
30
|
+
wrapped[param] = if spec.dig(param, 'sensitive')
|
31
|
+
Puppet::Pops::Types::PSensitiveType::Sensitive.new(val)
|
32
|
+
else
|
33
|
+
val
|
34
|
+
end
|
35
|
+
end
|
36
|
+
else
|
37
|
+
params
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_task(task, targets, params, options, executor)
|
42
|
+
if targets.empty?
|
43
|
+
Bolt::ResultSet.new([])
|
44
|
+
else
|
45
|
+
result = executor.run_task(targets, task, params, options, [], :trace)
|
46
|
+
|
47
|
+
if !result.ok && !options[:catch_errors]
|
48
|
+
raise Bolt::RunFailure.new(result, 'run_task', task.name)
|
49
|
+
end
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/bolt/task.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
class NoImplementationError < Bolt::Error
|
5
|
+
def initialize(target, task)
|
6
|
+
msg = "No suitable implementation of #{task.name} for #{target.name}"
|
7
|
+
super(msg, 'bolt/no-implementation')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Task
|
12
|
+
STDIN_METHODS = %w[both stdin].freeze
|
13
|
+
ENVIRONMENT_METHODS = %w[both environment].freeze
|
14
|
+
|
15
|
+
METADATA_KEYS = %w[description extensions files implementations
|
16
|
+
input_method parameters private puppet_task_version
|
17
|
+
remote supports_noop].freeze
|
18
|
+
|
19
|
+
attr_reader :name, :files, :metadata, :remote
|
20
|
+
attr_accessor :mtime
|
21
|
+
|
22
|
+
# name [String] name of the task
|
23
|
+
# files [Array<Hash>] where each entry includes `name` and `path`
|
24
|
+
# metadata [Hash] task metadata
|
25
|
+
def initialize(name, metadata = {}, files = [], remote = false)
|
26
|
+
@name = name
|
27
|
+
@metadata = metadata
|
28
|
+
@files = files
|
29
|
+
@remote = remote
|
30
|
+
@logger = Bolt::Logger.logger(self)
|
31
|
+
|
32
|
+
validate_metadata
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.from_task_signature(task_sig)
|
36
|
+
hash = task_sig.task_hash
|
37
|
+
new(hash['name'], hash.fetch('metadata', {}), hash.fetch('files', []))
|
38
|
+
end
|
39
|
+
|
40
|
+
def remote_instance
|
41
|
+
self.class.new(@name, @metadata, @files, true)
|
42
|
+
end
|
43
|
+
|
44
|
+
def description
|
45
|
+
metadata['description']
|
46
|
+
end
|
47
|
+
|
48
|
+
def parameters
|
49
|
+
metadata['parameters']
|
50
|
+
end
|
51
|
+
|
52
|
+
def parameter_defaults
|
53
|
+
(parameters || {}).each_with_object({}) do |(name, param_spec), defaults|
|
54
|
+
defaults[name] = param_spec['default'] if param_spec.key?('default')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def supports_noop
|
59
|
+
metadata['supports_noop']
|
60
|
+
end
|
61
|
+
|
62
|
+
def module_name
|
63
|
+
name.split('::').first
|
64
|
+
end
|
65
|
+
|
66
|
+
def tasks_dir
|
67
|
+
File.join(module_name, 'tasks')
|
68
|
+
end
|
69
|
+
|
70
|
+
def file_map
|
71
|
+
@file_map ||= files.each_with_object({}) { |file, hsh| hsh[file['name']] = file }
|
72
|
+
end
|
73
|
+
private :file_map
|
74
|
+
|
75
|
+
# This provides a method we can override in subclasses if the 'path' needs
|
76
|
+
# to be fetched or computed.
|
77
|
+
def file_path(file_name)
|
78
|
+
file_map[file_name]['path']
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_mtimes
|
82
|
+
@files.each do |f|
|
83
|
+
f['mtime'] = File.mtime(f['path']) if File.exist?(f['path'])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def implementations
|
88
|
+
metadata['implementations']
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns a hash of implementation name, path to executable, input method (if defined),
|
92
|
+
# and any additional files (name and path)
|
93
|
+
def select_implementation(target, provided_features = [])
|
94
|
+
impl = if (impls = implementations)
|
95
|
+
available_features = target.feature_set + provided_features
|
96
|
+
impl = impls.find do |imp|
|
97
|
+
remote_impl = imp['remote']
|
98
|
+
remote_impl = metadata['remote'] if remote_impl.nil?
|
99
|
+
Set.new(imp['requirements']).subset?(available_features) && !!remote_impl == @remote
|
100
|
+
end
|
101
|
+
raise NoImplementationError.new(target, self) unless impl
|
102
|
+
impl = impl.dup
|
103
|
+
impl['path'] = file_path(impl['name'])
|
104
|
+
impl.delete('requirements')
|
105
|
+
impl
|
106
|
+
else
|
107
|
+
raise NoImplementationError.new(target, self) unless !!metadata['remote'] == @remote
|
108
|
+
name = files.first['name']
|
109
|
+
{ 'name' => name, 'path' => file_path(name) }
|
110
|
+
end
|
111
|
+
|
112
|
+
inmethod = impl['input_method'] || metadata['input_method']
|
113
|
+
impl['input_method'] = inmethod unless inmethod.nil?
|
114
|
+
|
115
|
+
mfiles = impl.fetch('files', []) + metadata.fetch('files', [])
|
116
|
+
dirnames, filenames = mfiles.partition { |file| file.end_with?('/') }
|
117
|
+
impl['files'] = filenames.map do |file|
|
118
|
+
path = file_path(file)
|
119
|
+
raise "No file found for reference #{file}" if path.nil?
|
120
|
+
{ 'name' => file, 'path' => path }
|
121
|
+
end
|
122
|
+
|
123
|
+
unless dirnames.empty?
|
124
|
+
files.each do |file|
|
125
|
+
name = file['name']
|
126
|
+
if dirnames.any? { |dirname| name.start_with?(dirname) }
|
127
|
+
impl['files'] << { 'name' => name, 'path' => file_path(name) }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
impl
|
133
|
+
end
|
134
|
+
|
135
|
+
def eql?(other)
|
136
|
+
self.class == other.class &&
|
137
|
+
@name == other.name &&
|
138
|
+
@metadata == other.metadata &&
|
139
|
+
@files == other.files &&
|
140
|
+
@remote == other.remote
|
141
|
+
end
|
142
|
+
|
143
|
+
alias == :eql?
|
144
|
+
|
145
|
+
def to_h
|
146
|
+
{
|
147
|
+
name: @name,
|
148
|
+
files: @files,
|
149
|
+
metadata: @metadata
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def validate_metadata
|
154
|
+
unknown_keys = metadata.keys - METADATA_KEYS
|
155
|
+
|
156
|
+
if unknown_keys.any?
|
157
|
+
msg = "Metadata for task '#{@name}' contains unknown keys: #{unknown_keys.join(', ')}."
|
158
|
+
msg += " This could be a typo in the task metadata or might result in incorrect behavior."
|
159
|
+
Bolt::Logger.warn("unknown_task_metadata_keys", msg)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logging'
|
4
|
+
require_relative '../../bolt/result'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
module Transport
|
8
|
+
# This class provides the default behavior for Transports. A Transport is
|
9
|
+
# responsible for uploading files and running commands, scripts, and tasks
|
10
|
+
# on Targets.
|
11
|
+
#
|
12
|
+
# Bolt executes work on the Transport in "batches". To do that, it calls
|
13
|
+
# the batches() method, which is responsible for dividing the list of
|
14
|
+
# Targets into batches according to how it wants to handle them. It will
|
15
|
+
# then call Transport#batch_task, or the corresponding method for another
|
16
|
+
# operation, passing a list of Targets. The Transport returns a list of
|
17
|
+
# Bolt::Result objects, one per Target. Each batch is executed on a
|
18
|
+
# separate thread, controlled by the `concurrency` setting, so many batches
|
19
|
+
# may be running in parallel.
|
20
|
+
#
|
21
|
+
# The default batch implementation splits the list of Targets into batches
|
22
|
+
# of 1. It then calls run_task(), or a corresponding method for other
|
23
|
+
# operations, passing in the single Target.
|
24
|
+
#
|
25
|
+
# Most Transport implementations, like the SSH and WinRM transports, don't
|
26
|
+
# need to do their own batching, since they only operate on a single Target
|
27
|
+
# at a time. Those Transports can implement the run_task() and related
|
28
|
+
# methods, which will automatically handle running many Targets in
|
29
|
+
# parallel, and will handle publishing start and finish events for each
|
30
|
+
# Target.
|
31
|
+
#
|
32
|
+
# Transports that need their own batching, like the Orch transport, can
|
33
|
+
# instead override the batches() method to split Targets into sets that can
|
34
|
+
# be executed together, and override the batch_task() and related methods
|
35
|
+
# to execute a batch of targets. In that case, those Transports should accept
|
36
|
+
# a block argument and call it with a :node_start event for each Target
|
37
|
+
# before executing, and a :node_result event for each Target after
|
38
|
+
# execution.
|
39
|
+
class Base
|
40
|
+
attr_reader :logger
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@logger = Bolt::Logger.logger(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def with_events(target, callback, action, position)
|
47
|
+
callback&.call(type: :node_start, target: target)
|
48
|
+
|
49
|
+
result = begin
|
50
|
+
yield
|
51
|
+
rescue StandardError, NotImplementedError => e
|
52
|
+
Bolt::Result.from_exception(target, e, action: action, position: position)
|
53
|
+
end
|
54
|
+
|
55
|
+
callback&.call(type: :node_result, result: result)
|
56
|
+
result
|
57
|
+
end
|
58
|
+
|
59
|
+
def provided_features
|
60
|
+
[]
|
61
|
+
end
|
62
|
+
|
63
|
+
def default_input_method(_executable)
|
64
|
+
'both'
|
65
|
+
end
|
66
|
+
|
67
|
+
def select_implementation(target, task)
|
68
|
+
impl = task.select_implementation(target, provided_features)
|
69
|
+
impl['input_method'] ||= default_input_method(impl['path'])
|
70
|
+
impl
|
71
|
+
end
|
72
|
+
|
73
|
+
def select_interpreter(executable, interpreters)
|
74
|
+
interpreters[Pathname(executable).extname] if interpreters
|
75
|
+
end
|
76
|
+
|
77
|
+
# Raises an error if more than one target was given in the batch.
|
78
|
+
#
|
79
|
+
# The default implementations of batch_* strictly assume the transport is
|
80
|
+
# using the default batch size of 1. This method ensures that is the
|
81
|
+
# case and raises an error if it's not.
|
82
|
+
def assert_batch_size_one(method, targets)
|
83
|
+
if targets.length > 1
|
84
|
+
message = "#{self.class.name} must implement #{method} to support batches (got #{targets.length} targets)"
|
85
|
+
raise NotImplementedError, message
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Runs the given task on a batch of targets.
|
90
|
+
#
|
91
|
+
# The default implementation only supports batches of size 1 and will fail otherwise.
|
92
|
+
#
|
93
|
+
# Transports may override this method to implement their own batch processing.
|
94
|
+
def batch_task(targets, task, arguments, options = {}, position = [], &callback)
|
95
|
+
assert_batch_size_one("batch_task()", targets)
|
96
|
+
target = targets.first
|
97
|
+
with_events(target, callback, 'task', position) do
|
98
|
+
@logger.debug { "Running task '#{task.name}' on #{target.safe_name}" }
|
99
|
+
run_task(target, task, arguments, options, position)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Runs the given task on a batch of targets with variable parameters.
|
104
|
+
#
|
105
|
+
# The default implementation only supports batches of size 1 and will fail otherwise.
|
106
|
+
#
|
107
|
+
# Transports may override this method to implment their own batch processing.
|
108
|
+
def batch_task_with(targets, task, target_mapping, options = {}, position = [], &callback)
|
109
|
+
assert_batch_size_one("batch_task_with()", targets)
|
110
|
+
target = targets.first
|
111
|
+
arguments = target_mapping[target]
|
112
|
+
|
113
|
+
with_events(target, callback, 'task', position) do
|
114
|
+
@logger.debug { "Running task '#{task.name}' on #{target.safe_name} with '#{arguments.to_json}'" }
|
115
|
+
run_task(target, task, arguments, options, position)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Runs the given command on a batch of targets.
|
120
|
+
#
|
121
|
+
# The default implementation only supports batches of size 1 and will fail otherwise.
|
122
|
+
#
|
123
|
+
# Transports may override this method to implement their own batch processing.
|
124
|
+
def batch_command(targets, command, options = {}, position = [], &callback)
|
125
|
+
assert_batch_size_one("batch_command()", targets)
|
126
|
+
target = targets.first
|
127
|
+
with_events(target, callback, 'command', position) do
|
128
|
+
@logger.debug("Running command '#{command}' on #{target.safe_name}")
|
129
|
+
run_command(target, command, options, position)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Runs the given script on a batch of targets.
|
134
|
+
#
|
135
|
+
# The default implementation only supports batches of size 1 and will fail otherwise.
|
136
|
+
#
|
137
|
+
# Transports may override this method to implement their own batch processing.
|
138
|
+
def batch_script(targets, script, arguments, options = {}, position = [], &callback)
|
139
|
+
assert_batch_size_one("batch_script()", targets)
|
140
|
+
target = targets.first
|
141
|
+
with_events(target, callback, 'script', position) do
|
142
|
+
@logger.debug { "Running script '#{script}' on #{target.safe_name}" }
|
143
|
+
run_script(target, script, arguments, options, position)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Uploads the given source file to the destination location on a batch of targets.
|
148
|
+
#
|
149
|
+
# The default implementation only supports batches of size 1 and will fail otherwise.
|
150
|
+
#
|
151
|
+
# Transports may override this method to implement their own batch processing.
|
152
|
+
def batch_upload(targets, source, destination, options = {}, position = [], &callback)
|
153
|
+
assert_batch_size_one("batch_upload()", targets)
|
154
|
+
target = targets.first
|
155
|
+
with_events(target, callback, 'upload', position) do
|
156
|
+
@logger.debug { "Uploading: '#{source}' to #{destination} on #{target.safe_name}" }
|
157
|
+
upload(target, source, destination, options)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Downloads the given source file from a batch of targets to the destination location
|
162
|
+
# on the host.
|
163
|
+
#
|
164
|
+
# The default implementation only supports batches of size 1 and will fail otherwise.
|
165
|
+
#
|
166
|
+
# Transports may override this method to implement their own batch processing.
|
167
|
+
def batch_download(targets, source, destination, options = {}, position = [], &callback)
|
168
|
+
require 'erb'
|
169
|
+
|
170
|
+
assert_batch_size_one("batch_download()", targets)
|
171
|
+
target = targets.first
|
172
|
+
with_events(target, callback, 'download', position) do
|
173
|
+
escaped_name = ERB::Util.url_encode(target.safe_name)
|
174
|
+
target_destination = File.expand_path(escaped_name, destination)
|
175
|
+
@logger.debug { "Downloading: '#{source}' on #{target.safe_name} to #{target_destination}" }
|
176
|
+
download(target, source, target_destination, options)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def batch_connected?(targets)
|
181
|
+
assert_batch_size_one("connected?()", targets)
|
182
|
+
connected?(targets.first)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Split the given list of targets into a list of batches. The default
|
186
|
+
# implementation returns single-target batches.
|
187
|
+
#
|
188
|
+
# Transports may override this method, and the corresponding batch_*
|
189
|
+
# methods, to implement their own batch processing.
|
190
|
+
def batches(targets)
|
191
|
+
targets.map { |target| [target] }
|
192
|
+
end
|
193
|
+
|
194
|
+
# Transports should override this method with their own implementation of running a command.
|
195
|
+
def run_command(*_args)
|
196
|
+
raise NotImplementedError, "run_command() must be implemented by the transport class"
|
197
|
+
end
|
198
|
+
|
199
|
+
# Transports should override this method with their own implementation of running a script.
|
200
|
+
def run_script(*_args)
|
201
|
+
raise NotImplementedError, "run_script() must be implemented by the transport class"
|
202
|
+
end
|
203
|
+
|
204
|
+
# Transports should override this method with their own implementation of running a task.
|
205
|
+
def run_task(*_args)
|
206
|
+
raise NotImplementedError, "run_task() must be implemented by the transport class"
|
207
|
+
end
|
208
|
+
|
209
|
+
# Transports should override this method with their own implementation of file upload.
|
210
|
+
def upload(*_args)
|
211
|
+
raise NotImplementedError, "upload() must be implemented by the transport class"
|
212
|
+
end
|
213
|
+
|
214
|
+
# Transports should override this method with their own implementation of file download.
|
215
|
+
def download(*_args)
|
216
|
+
raise NotImplementedError, "download() must be implemented by the transport class"
|
217
|
+
end
|
218
|
+
|
219
|
+
# Transports should override this method with their own implementation of a connection test.
|
220
|
+
def connected?(_targets)
|
221
|
+
raise NotImplementedError, "connected?() must be implemented by the transport class"
|
222
|
+
end
|
223
|
+
|
224
|
+
# Unwraps any Sensitive data in an arguments Hash, so the plain-text is passed
|
225
|
+
# to the Task/Script.
|
226
|
+
#
|
227
|
+
# This works on deeply nested data structures composed of Hashes, Arrays, and
|
228
|
+
# and plain-old data types (int, string, etc).
|
229
|
+
def unwrap_sensitive_args(arguments)
|
230
|
+
# Skip this if Puppet isn't loaded
|
231
|
+
return arguments unless defined?(Puppet::Pops::Types::PSensitiveType::Sensitive)
|
232
|
+
|
233
|
+
case arguments
|
234
|
+
when Array
|
235
|
+
# iterate over the array, unwrapping all elements
|
236
|
+
arguments.map { |x| unwrap_sensitive_args(x) }
|
237
|
+
when Hash
|
238
|
+
# iterate over the arguments hash and unwrap all keys and values
|
239
|
+
arguments.each_with_object({}) { |(k, v), h|
|
240
|
+
h[unwrap_sensitive_args(k)] = unwrap_sensitive_args(v)
|
241
|
+
}
|
242
|
+
when Puppet::Pops::Types::PSensitiveType::Sensitive
|
243
|
+
# this value is Sensitive, unwrap it
|
244
|
+
unwrap_sensitive_args(arguments.unwrap)
|
245
|
+
else
|
246
|
+
# unknown data type, just return it
|
247
|
+
arguments
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logging'
|
4
|
+
require_relative '../../../bolt/node/errors'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
module Transport
|
8
|
+
class Docker < Simple
|
9
|
+
class Connection
|
10
|
+
attr_reader :user, :target
|
11
|
+
|
12
|
+
def initialize(target)
|
13
|
+
raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
|
14
|
+
@target = target
|
15
|
+
@user = ENV['USER'] || Etc.getlogin
|
16
|
+
@logger = Bolt::Logger.logger(target.safe_name)
|
17
|
+
@container_info = {}
|
18
|
+
@docker_host = target.options['service-url']
|
19
|
+
@logger.trace("Initializing docker connection to #{target.safe_name}")
|
20
|
+
end
|
21
|
+
|
22
|
+
def shell
|
23
|
+
@shell ||= if Bolt::Util.windows?
|
24
|
+
Bolt::Shell::Powershell.new(target, self)
|
25
|
+
else
|
26
|
+
Bolt::Shell::Bash.new(target, self)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_cwd?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
# The full ID of the target container
|
35
|
+
#
|
36
|
+
# @return [String] The full ID of the target container
|
37
|
+
def container_id
|
38
|
+
@container_info["Id"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_cmd(cmd, env_vars)
|
42
|
+
Bolt::Util.exec_docker(cmd, env_vars)
|
43
|
+
end
|
44
|
+
|
45
|
+
private def env_hash
|
46
|
+
# Set the DOCKER_HOST if we are using a non-default service-url
|
47
|
+
@docker_host.nil? ? {} : { 'DOCKER_HOST' => @docker_host }
|
48
|
+
end
|
49
|
+
|
50
|
+
def connect
|
51
|
+
# We don't actually have a connection, but we do need to
|
52
|
+
# check that the container exists and is running.
|
53
|
+
output = execute_local_json_command('ps', ['--no-trunc'])
|
54
|
+
index = output.find_index { |item| item["ID"].start_with?(target.host) || item["Names"] == target.host }
|
55
|
+
raise "Could not find a container with name or ID matching '#{target.host}'" if index.nil?
|
56
|
+
# Now find the indepth container information
|
57
|
+
output = execute_local_json_command('inspect', [output[index]["ID"]])
|
58
|
+
# Store the container information for later
|
59
|
+
@container_info = output[0]
|
60
|
+
@logger.trace { "Opened session" }
|
61
|
+
true
|
62
|
+
rescue StandardError => e
|
63
|
+
raise Bolt::Node::ConnectError.new(
|
64
|
+
"Failed to connect to #{target.safe_name}: #{e.message}",
|
65
|
+
'CONNECT_ERROR'
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_env_vars(env_vars)
|
70
|
+
@env_vars = Bolt::Util.format_env_vars_for_cli(env_vars)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Executes a command inside the target container. This is called from the shell class.
|
74
|
+
#
|
75
|
+
# @param command [string] The command to run
|
76
|
+
def execute(command)
|
77
|
+
args = []
|
78
|
+
# CODEREVIEW: Is it always safe to pass --interactive?
|
79
|
+
args += %w[--interactive]
|
80
|
+
args += %w[--tty] if target.options['tty']
|
81
|
+
args += @env_vars if @env_vars
|
82
|
+
|
83
|
+
if target.options['shell-command'] && !target.options['shell-command'].empty?
|
84
|
+
# escape any double quotes in command
|
85
|
+
command = command.gsub('"', '\"')
|
86
|
+
command = "#{target.options['shell-command']} \"#{command}\""
|
87
|
+
end
|
88
|
+
|
89
|
+
docker_command = %w[docker exec] + args + [container_id] + Shellwords.split(command)
|
90
|
+
@logger.trace { "Executing: #{docker_command.join(' ')}" }
|
91
|
+
|
92
|
+
Open3.popen3(env_hash, *docker_command)
|
93
|
+
rescue StandardError
|
94
|
+
@logger.trace { "Command aborted" }
|
95
|
+
raise
|
96
|
+
end
|
97
|
+
|
98
|
+
def upload_file(source, destination)
|
99
|
+
@logger.trace { "Uploading #{source} to #{destination}" }
|
100
|
+
_out, err, stat = run_cmd(['cp', source, "#{container_id}:#{destination}"], env_hash)
|
101
|
+
unless stat.exitstatus.zero?
|
102
|
+
raise "Error writing to container #{container_id}: #{err}"
|
103
|
+
end
|
104
|
+
rescue StandardError => e
|
105
|
+
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
106
|
+
end
|
107
|
+
|
108
|
+
def download_file(source, destination, _download)
|
109
|
+
@logger.trace { "Downloading #{source} to #{destination}" }
|
110
|
+
# Create the destination directory, otherwise copying a source directory with Docker will
|
111
|
+
# copy the *contents* of the directory.
|
112
|
+
# https://docs.docker.com/engine/reference/commandline/cp/
|
113
|
+
FileUtils.mkdir_p(destination)
|
114
|
+
_out, err, stat = run_cmd(['cp', "#{container_id}:#{source}", destination], env_hash)
|
115
|
+
unless stat.exitstatus.zero?
|
116
|
+
raise "Error downloading content from container #{container_id}: #{err}"
|
117
|
+
end
|
118
|
+
rescue StandardError => e
|
119
|
+
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
120
|
+
end
|
121
|
+
|
122
|
+
# Executes a Docker CLI command and parses the output in JSON format
|
123
|
+
#
|
124
|
+
# @param subcommand [String] The docker subcommand to run
|
125
|
+
# e.g. 'inspect' for `docker inspect`
|
126
|
+
# @param arguments [Array] Arguments to pass to the docker command
|
127
|
+
# e.g. 'src' and 'dest' for `docker cp <src> <dest>
|
128
|
+
# @return [Object] Ruby object representation of the JSON string
|
129
|
+
def execute_local_json_command(subcommand, arguments = [])
|
130
|
+
cmd = [subcommand, '--format', '{{json .}}'].concat(arguments)
|
131
|
+
out, _err, _stat = run_cmd(cmd, env_hash)
|
132
|
+
extract_json(out)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Converts the JSON encoded STDOUT string from the docker cli into ruby objects
|
136
|
+
#
|
137
|
+
# @param stdout [String] The string to convert
|
138
|
+
# @return [Object] Ruby object representation of the JSON string
|
139
|
+
private def extract_json(stdout)
|
140
|
+
# The output from the docker format command is a JSON string per line.
|
141
|
+
# We can't do a direct convert but this helper method will convert it into
|
142
|
+
# an array of Objects
|
143
|
+
stdout.split("\n")
|
144
|
+
.reject { |str| str.strip.empty? }
|
145
|
+
.map { |str| JSON.parse(str) }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'shellwords'
|
5
|
+
require_relative '../../bolt/transport/simple'
|
6
|
+
|
7
|
+
module Bolt
|
8
|
+
module Transport
|
9
|
+
class Docker < Simple
|
10
|
+
def provided_features
|
11
|
+
['shell']
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_connection(target)
|
15
|
+
conn = Connection.new(target)
|
16
|
+
conn.connect
|
17
|
+
yield conn
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require_relative 'docker/connection'
|