bolt 3.4.0 → 3.7.1

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.

Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +2 -2
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +26 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
  5. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +43 -0
  6. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +29 -0
  7. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +34 -0
  8. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +55 -0
  9. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +1 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +66 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
  16. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  17. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
  18. data/guides/guide.txt +17 -0
  19. data/guides/links.txt +13 -0
  20. data/guides/targets.txt +29 -0
  21. data/guides/transports.txt +23 -0
  22. data/lib/bolt/analytics.rb +4 -8
  23. data/lib/bolt/bolt_option_parser.rb +329 -225
  24. data/lib/bolt/cli.rb +58 -29
  25. data/lib/bolt/config.rb +11 -7
  26. data/lib/bolt/config/options.rb +41 -9
  27. data/lib/bolt/config/transport/podman.rb +33 -0
  28. data/lib/bolt/container_result.rb +105 -0
  29. data/lib/bolt/error.rb +15 -0
  30. data/lib/bolt/executor.rb +17 -13
  31. data/lib/bolt/inventory.rb +5 -4
  32. data/lib/bolt/inventory/inventory.rb +3 -2
  33. data/lib/bolt/inventory/options.rb +9 -0
  34. data/lib/bolt/inventory/target.rb +16 -0
  35. data/lib/bolt/module_installer/specs/git_spec.rb +10 -6
  36. data/lib/bolt/outputter/human.rb +242 -76
  37. data/lib/bolt/outputter/json.rb +6 -4
  38. data/lib/bolt/outputter/logger.rb +17 -0
  39. data/lib/bolt/pal/yaml_plan/step.rb +4 -2
  40. data/lib/bolt/plan_creator.rb +2 -2
  41. data/lib/bolt/plugin.rb +13 -11
  42. data/lib/bolt/puppetdb/client.rb +54 -0
  43. data/lib/bolt/result.rb +1 -4
  44. data/lib/bolt/shell/bash.rb +23 -10
  45. data/lib/bolt/transport/docker.rb +1 -1
  46. data/lib/bolt/transport/docker/connection.rb +23 -34
  47. data/lib/bolt/transport/podman.rb +19 -0
  48. data/lib/bolt/transport/podman/connection.rb +98 -0
  49. data/lib/bolt/transport/ssh/connection.rb +3 -6
  50. data/lib/bolt/util.rb +34 -0
  51. data/lib/bolt/version.rb +1 -1
  52. data/lib/bolt_server/transport_app.rb +3 -0
  53. data/lib/bolt_spec/plans/mock_executor.rb +91 -11
  54. data/modules/puppet_connect/plans/test_input_data.pp +22 -0
  55. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e66e28078af9324bb7d5668ee5079535d829913a2dcb4bce65daa4d471d4524
4
- data.tar.gz: a821d82c490030be200c0d4ab5dc4a56a1abf1aa862d69d1e4c6a4595e47952d
3
+ metadata.gz: 741f798df2b4c7a46e45c5c87a41cd4e994506cdd202d26b6acafae7774135f8
4
+ data.tar.gz: 803789a5f6872e27656a733e94998acc9dde4d325d2d920b345a1c2848683f2f
5
5
  SHA512:
6
- metadata.gz: 120dd18c9478c105387f2ad3634276cbaae05fe3638cc01d378aa9cbb1c423bf737991b7278ab4f96eb0a7cca39cd3d259cbba3d1734cea6ac071ba0695da901
7
- data.tar.gz: 61171ff383d06883e6f6ffa0cafc423a80d41ea0f2d45c3c0e4b76c7037a4626ad7a4b8b625e3a013e077b9e96cdafaab06cad6d5a490dae392af3fbb410d0fe
6
+ metadata.gz: 85cb83b0a0bf02d1ba15b14ba5e5e75c6ff290e1acee71b0de06ea716f3698e91358aa6f9961757b088afa3c98d519ba10f79a6195c767492a87603759c2fc81
7
+ data.tar.gz: 8180f0827c3576e676e6fca59af7744d3e375023b44c0ebc2e691801a25f8fc1cac2487276661f7b40503e8ab1164ae07ee5f8343be9bb5be3088851e81eafaf
data/Puppetfile CHANGED
@@ -32,9 +32,9 @@ mod 'puppetlabs-ruby_plugin_helper', '0.2.0'
32
32
  mod 'puppetlabs-stdlib', '7.0.0'
33
33
 
34
34
  # Plugin modules
35
- mod 'puppetlabs-aws_inventory', '0.6.0'
35
+ mod 'puppetlabs-aws_inventory', '0.7.0'
36
36
  mod 'puppetlabs-azure_inventory', '0.5.0'
37
- mod 'puppetlabs-gcloud_inventory', '0.2.0'
37
+ mod 'puppetlabs-gcloud_inventory', '0.3.0'
38
38
  mod 'puppetlabs-http_request', '0.2.2'
39
39
  mod 'puppetlabs-pkcs7', '0.1.1'
40
40
  mod 'puppetlabs-secure_env_vars', '0.2.0'
@@ -1,5 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # An [apply action](applying_manifest_blocks.md#return-value-of-apply-action)
4
+ # returns an `ApplyResult`. An `ApplyResult` is part of a `ResultSet` object and
5
+ # contains information about the apply action.
6
+ #
7
+ # @param report
8
+ # The Puppet report from the apply action. Equivalent to calling `ApplyResult.value['report']`.
9
+ # The report is a hash representation of the [`Puppet::Transaction::Report`
10
+ # object](https://puppet.com/docs/puppet/latest/format_report.html), where each property
11
+ # corresponds to a key in the report hash. For more information, see [Result
12
+ # keys](applying_manifest_blocks.md#result-keys).
13
+ # @param target
14
+ # The target the result is from.
15
+ #
16
+ # @!method action
17
+ # The action performed. `ApplyResult.action` always returns the string `apply`.
18
+ # @!method error
19
+ # Returns an Error object constructed from the `_error` field of the result's value.
20
+ # @!method message
21
+ # The `_output` field of the result's value.
22
+ # @!method ok
23
+ # Whether the result was successful.
24
+ # @!method to_data
25
+ # A serialized representation of `ApplyResult`.
26
+ # @!method value
27
+ # A hash including the Puppet report from the apply action under a `report` key.
28
+ #
3
29
  Puppet::DataTypes.create_type('ApplyResult') do
4
30
  interface <<-PUPPET
5
31
  attributes => {
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The [run_container](plan_functions.md#run_container) plan function returns a
4
+ # `ContainerResult` object. A `ContainerResult` is a standalone object (not part
5
+ # of a `ResultSet`) that includes either the `stdout` and `stderr` values from
6
+ # running the container, or an `_error` object if the container exited with a
7
+ # nonzero exit code.
8
+ #
9
+ # @param value
10
+ # A hash including the `stdout`, `stderr`, and `exit_code` received from the
11
+ # container.
12
+ #
13
+ # @!method []
14
+ # Accesses the value hash directly and returns the value for the key. This
15
+ # function does not use dot notation. Call the function directly on the
16
+ # `ContainerResult`. For example, `$result[key]`.
17
+ # @!method error
18
+ # An object constructed from the `_error` field of the result's value.
19
+ # @!method ok
20
+ # Whether the result was successful.
21
+ # @!method status
22
+ # Either `success` if the result was successful or `failure`.
23
+ # @!method stdout
24
+ # The value of 'stdout' output by the container.
25
+ # @!method stderr
26
+ # The value of 'stderr' output by the container.
27
+ # @!method to_data
28
+ # A serialized representation of `ContainerResult`.
29
+ #
30
+ Puppet::DataTypes.create_type('ContainerResult') do
31
+ interface <<-PUPPET
32
+ attributes => {
33
+ 'value' => Hash[String[1], Data],
34
+ },
35
+ functions => {
36
+ '[]' => Callable[[String[1]], Data],
37
+ error => Callable[[], Optional[Error]],
38
+ ok => Callable[[], Boolean],
39
+ status => Callable[[], String],
40
+ stdout => Callable[[], String],
41
+ stderr => Callable[[], String],
42
+ to_data => Callable[[], Hash]
43
+ }
44
+ PUPPET
45
+
46
+ load_file('bolt/container_result')
47
+
48
+ # Needed for Puppet to recognize Bolt::ContainerResult as a Puppet object when deserializing
49
+ Bolt::ContainerResult.include(Puppet::Pops::Types::PuppetObject)
50
+ implementation_class Bolt::ContainerResult
51
+ end
@@ -1,5 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # `ResourceInstance` objects are used to store the observed and desired state of a
4
+ # target's resource and to track events for the resource. These objects do not
5
+ # modify or interact with a target's resources.
6
+ #
7
+ # > The `ResourceInstance` data type is experimental and might change in a future
8
+ # > release. You can learn more about this data type and how to use it in the
9
+ # > [experimental features
10
+ # > documentation](experimental_features.md#resourceinstance-data-type).
11
+ #
12
+ # @param events
13
+ # Events for the resource.
14
+ # @param desired_state
15
+ # [Attributes](https://puppet.com/docs/puppet/latest/lang_resources.html#attributes) describing
16
+ # the desired state of the resource.
17
+ # @param state
18
+ # [Attributes](https://puppet.com/docs/puppet/latest/lang_resources.html#attributes) describing
19
+ # the observed state of the resource.
20
+ # @param target
21
+ # The resource's target.
22
+ # @param title
23
+ # The [resource title](https://puppet.com/docs/puppet/latest/lang_resources.html#title).
24
+ # @param type
25
+ # The [resource type](https://puppet.com/docs/puppet/latest/lang_resources.html#resource-types).
26
+ #
27
+ # @!method []
28
+ # Accesses the `state` hash directly and returns the value for the specified
29
+ # attribute. This function does not use dot noation. Call the function directly
30
+ # on the `ResourceInstance`. For example, `$resource['ensure']`.
31
+ # @!method add_event(event)
32
+ # Add an event for the resource.
33
+ # @!method overwrite_desired_state(desired_state)
34
+ # Overwrites the desired state of the resource.
35
+ # @!method overwrite_state(state)
36
+ # Overwrites the observed state of the resource.
37
+ # @!method set_desired_state(desired_state)
38
+ # Sets attributes describing the desired state of the resource. Performs a shallow
39
+ # merge with existing desired state.
40
+ # @!method set_state(state)
41
+ # Sets attributes describing the observed state of the resource. Performs a shallow
42
+ # merge with existing state.
43
+ # @!method reference
44
+ # The resources reference string. For example, `File[/etc/puppetlabs]`.
45
+ #
3
46
  Puppet::DataTypes.create_type('ResourceInstance') do
4
47
  interface <<-PUPPET
5
48
  attributes => {
@@ -1,5 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # For each target that you execute an action on, Bolt returns a `Result` object
4
+ # and adds the `Result` to a `ResultSet` object. A `Result` object contains
5
+ # information about the action you executed on the target.
6
+ #
7
+ # @param target
8
+ # The target the result is from.
9
+ # @param value
10
+ # The output or return of executing on the target.
11
+ #
12
+ # @!method []
13
+ # Accesses the `value` hash directly and returns the value for the key. This
14
+ # function does not use dot nation. Call the function directly on the `Result`.
15
+ # For example, `$result['key']`.
16
+ # @!method action
17
+ # The type of result. For example, `task` or `command`.
18
+ # @!method error
19
+ # An object constructed from the `_error` field of the result's `value`.
20
+ # @!method message
21
+ # The `_output` field of the result's value.
22
+ # @!method ok
23
+ # Whether the result was successful.
24
+ # @!method sensitive
25
+ # The `_sensitive` field of the result's value, wrapped in a `Sensitive` object.
26
+ # Call `unwrap()` to extract the value.
27
+ # @!method status
28
+ # Either `success` if the result was successful or `failure`.
29
+ # @!method to_data
30
+ # A serialized representation of `Result`.
31
+ #
3
32
  Puppet::DataTypes.create_type('Result') do
4
33
  interface <<-PUPPET
5
34
  attributes => {
@@ -1,5 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # For each target that you execute an action on, Bolt returns a `Result` object
4
+ # and adds the `Result` to a `ResultSet` object. In the case of [apply
5
+ # actions](applying_manifest_blocks.md), Bolt returns a `ResultSet` with one or
6
+ # more `ApplyResult` objects.
7
+ #
8
+ # @param results
9
+ # All results in the set.
10
+ #
11
+ # @!method []
12
+ # The accessed results. This function does not use dot notation. Call the
13
+ # function directly on the `ResultSet`. For example, `$results[0]`.
14
+ # @!method count
15
+ # The number of results in the set.
16
+ # @!method empty
17
+ # Whether the set is empty.
18
+ # @!method error_set
19
+ # The set of failing results.
20
+ # @!method filter_set
21
+ # Filters a set of results by the contents of the block.
22
+ # @!method find(target_name)
23
+ # Retrieves a result for a specified target.
24
+ # @!method first
25
+ # The first result in the set. Useful for unwrapping single results.
26
+ # @!method names
27
+ # The names of all targets that have a `Result` in the set.
28
+ # @!method ok
29
+ # Whether all results were successful. Equivalent to `$results.error_set.empty`.
30
+ # @!method ok_set
31
+ # The set of successful results.
32
+ # @!method targets
33
+ # The list of targets that have results in the set.
34
+ # @!method to_data
35
+ # An array of serialized representations of each result in the set.
36
+ #
3
37
  Puppet::DataTypes.create_type('ResultSet') do
4
38
  interface <<-PUPPET
5
39
  attributes => {
@@ -1,5 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # The `Target` object represents a target and its specific connection options.
4
+ #
5
+ # @param config
6
+ # The inventory configuration for the target. This function returns the
7
+ # configuration set directly on the target in `inventory.yaml` or set in
8
+ # a plan using `Target.new` or `set_config()`. It does not return default
9
+ # configuration values or configuration set in Bolt configuration files.
10
+ # @param facts
11
+ # The target's facts. This function does not look up facts for a target and
12
+ # only returns the facts specified in an `inventory.yaml` file or set on a
13
+ # target during a plan run. To retrieve facts for a target and set them in
14
+ # inventory, run the [facts](writing_plans.md#collect-facts-from-targets)
15
+ # plan or [puppetdb_fact](writing_plans.md#collect-facts-from-puppetdb)
16
+ # plan.
17
+ # @param features
18
+ # The target's features.
19
+ # @param name
20
+ # The target's human-readable name, or its URI if a name was not given.
21
+ # @param plugin_hooks
22
+ # The target's `plugin_hooks` [configuration
23
+ # options](bolt_inventory_reference.md#plugin-hooks-1).
24
+ # @param resources
25
+ # The target's resources. This function does not look up resources for a
26
+ # target and only returns resources set on a target during a plan run.
27
+ # @param safe_name
28
+ # The target's safe name. Equivalent to `name` if a name was given, or the
29
+ # target's `uri` with any password omitted.
30
+ # @param target_alias
31
+ # The target's aliases.
32
+ # @param uri
33
+ # The target's URI.
34
+ # @param vars
35
+ # The target's variables.
36
+ #
37
+ # @!method host
38
+ # The target's hostname.
39
+ # @!method password
40
+ # The password to use when connecting to the target.
41
+ # @!method port
42
+ # The target's connection port.
43
+ # @!method protocol
44
+ # The protocol used to connect to the target. This is equivalent to the target's
45
+ # `transport`, expect for targets using the `remote` transport. For example,
46
+ # a target with the URI `http://example.com` using the `remote` transport would
47
+ # return `http` for the `protocol`.
48
+ # @!method transport
49
+ # The transport used to connect to the target.
50
+ # @!method transport_config
51
+ # The merged configuration for the target's `transport`. This function returns
52
+ # configuration that includes defaults set by Bolt, configuration set in
53
+ # `inventory.yaml`, configuration set in `bolt-defaults.yaml`, and configuration
54
+ # set in a plan using `set_config()`.
55
+ # @!method user
56
+ # The user to connect to the target.
57
+ #
3
58
  Puppet::DataTypes.create_type('Target') do
4
59
  begin
5
60
  inventory = Puppet.lookup(:bolt_inventory)
@@ -8,6 +8,7 @@ require 'bolt/error'
8
8
  Puppet::Functions.create_function(:add_to_group) do
9
9
  # @param targets A pattern or array of patterns identifying a set of targets.
10
10
  # @param group The name of the group to add targets to.
11
+ # @return [Array[Target]] The targets.
11
12
  # @example Add new Target to group.
12
13
  # Target.new('foo@example.com', 'password' => 'secret').add_to_group('group1')
13
14
  # @example Add new target to group by name.
@@ -16,6 +16,7 @@ Puppet::Functions.create_function(:apply_prep) do
16
16
  # @param targets A pattern or array of patterns identifying a set of targets.
17
17
  # @param options Options hash.
18
18
  # @option options [Array] _required_modules An array of modules to sync to the target.
19
+ # @return [nil]
19
20
  # @example Prepare targets by name.
20
21
  # apply_prep('target1,target2')
21
22
  dispatch :apply_prep do
@@ -10,6 +10,7 @@ Puppet::Functions.create_function(:parallelize, Puppet::Functions::InternalFunct
10
10
  # Map a block onto an array, where each array element executes in parallel.
11
11
  # This function is experimental.
12
12
  # @param data The array to apply the block to.
13
+ # @param block The code block to execute for each array element.
13
14
  # @return [Array] An array of PlanResult objects. Each input from the input
14
15
  # array returns a corresponding PlanResult object.
15
16
  # @example Execute two tasks on two targets.
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # Send a command with a payload to PuppetDB.
6
+ #
7
+ # The `pdb_command` function only supports version 5 of the `replace_facts`
8
+ # command. Other commands might also work, but are not tested or supported
9
+ # by Bolt.
10
+ #
11
+ # See the [commands endpoint](https://puppet.com/docs/puppetdb/latest/api/command/v1/commands.html)
12
+ # documentation for more information about available commands and payload
13
+ # format.
14
+ #
15
+ # _This function is experimental and subject to change._
16
+ #
17
+ # > **Note:** Not available in apply block
18
+ #
19
+ Puppet::Functions.create_function(:puppetdb_command) do
20
+ # @param command The command to invoke.
21
+ # @param version The version of the command to invoke.
22
+ # @param payload The payload to the command.
23
+ # @return The UUID identifying the response sent by PuppetDB.
24
+ # @example Replace facts for a target
25
+ # $payload = {
26
+ # 'certname' => 'localhost',
27
+ # 'environment' => 'dev',
28
+ # 'producer' => 'bolt',
29
+ # 'producer_timestamp' => '1970-01-01',
30
+ # 'values' => { 'orchestrator' => 'bolt' }
31
+ # }
32
+ #
33
+ # puppetdb_command('replace_facts', 5, $payload)
34
+ dispatch :puppetdb_command do
35
+ param 'String[1]', :command
36
+ param 'Integer', :version
37
+ param 'Hash[Data, Data]', :payload
38
+ return_type 'String'
39
+ end
40
+
41
+ def puppetdb_command(command, version, payload)
42
+ # Disallow in apply blocks.
43
+ unless Puppet[:tasks]
44
+ raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
45
+ Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
46
+ action: 'puppetdb_command'
47
+ )
48
+ end
49
+
50
+ # Send analytics report.
51
+ Puppet.lookup(:bolt_executor).report_function_call(self.class.name)
52
+
53
+ puppetdb_client = Puppet.lookup(:bolt_pdb_client)
54
+
55
+ # Error if the PDB client does not implement :send_command
56
+ unless puppetdb_client.respond_to?(:send_command)
57
+ raise Bolt::Error.new(
58
+ "PuppetDB client #{puppetdb_client.class} does not implement :send_command, "\
59
+ "unable to invoke command.",
60
+ 'bolt/pdb-command'
61
+ )
62
+ end
63
+
64
+ puppetdb_client.send_command(command, version, payload)
65
+ end
66
+ end
@@ -11,6 +11,7 @@ require 'bolt/error'
11
11
  Puppet::Functions.create_function(:remove_from_group) do
12
12
  # @param target A pattern identifying a single target.
13
13
  # @param group The name of the group to remove the target from.
14
+ # @return [nil]
14
15
  # @example Remove Target from group.
15
16
  # remove_from_group('foo@example.com', 'group1')
16
17
  # @example Remove failing Targets from the rest of a plan
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/container_result'
4
+ require 'bolt/error'
5
+ require 'bolt/util'
6
+
7
+ # Run a container and return its output to stdout and stderr.
8
+ #
9
+ # > **Note:** Not available in apply block
10
+ Puppet::Functions.create_function(:run_container) do
11
+ # Run a container.
12
+ # @param image The name of the image to run.
13
+ # @param options A hash of additional options.
14
+ # @option options [Boolean] _catch_errors Whether to catch raised errors.
15
+ # @option options [String] cmd A command to run in the container.
16
+ # @option options [Hash[String, Data]] env_vars Map of environment variables to set.
17
+ # @option options [Hash[Integer, Integer]] ports A map of container ports to
18
+ # publish. Keys are the host port, values are the corresponding container
19
+ # port.
20
+ # @option options [Boolean] rm Whether to remove the container once it exits.
21
+ # @option options [Hash[String, String]] volumes A map of absolute paths on
22
+ # the host to absolute paths on the remote to mount.
23
+ # @option options [String] workdir The working directory within the container.
24
+ # @return Output from the container.
25
+ # @example Run Nginx proxy manager
26
+ # run_container('jc21/nginx-proxy-manager', 'ports' => { 80 => 80, 81 => 81, 443 => 443 })
27
+ dispatch :run_container do
28
+ param 'String[1]', :image
29
+ optional_param 'Hash[String[1], Any]', :options
30
+ return_type 'ContainerResult'
31
+ end
32
+
33
+ def run_container(image, options = {})
34
+ unless Puppet[:tasks]
35
+ raise Puppet::ParseErrorWithIssue
36
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'run_container')
37
+ end
38
+
39
+ # Send Analytics Report
40
+ executor = Puppet.lookup(:bolt_executor)
41
+ executor.report_function_call(self.class.name)
42
+
43
+ options = options.transform_keys { |k| k.sub(/^_/, '').to_sym }
44
+ validate_options(options)
45
+
46
+ if options.key?(:env_vars)
47
+ options[:env_vars] = options[:env_vars].transform_values do |val|
48
+ [Array, Hash].include?(val.class) ? val.to_json : val
49
+ end
50
+ end
51
+
52
+ if options[:ports]
53
+ ports = options[:ports].each_with_object([]) do |(host_port, container_port), acc|
54
+ acc << "-p"
55
+ acc << "#{host_port}:#{container_port}"
56
+ end
57
+ end
58
+
59
+ if options[:volumes]
60
+ volumes = options[:volumes].each_with_object([]) do |(host_path, remote_path), acc|
61
+ begin
62
+ FileUtils.mkdir_p(host_path)
63
+ rescue StandardError => e
64
+ message = "Unable to create host volume directory #{host_path}: #{e.message}"
65
+ raise Bolt::Error.new(message, 'bolt/file-error')
66
+ end
67
+ acc << "-v"
68
+ acc << "#{host_path}:#{remote_path}"
69
+ end
70
+ end
71
+
72
+ # Run the container
73
+ # `docker run` will automatically pull the image if it isn't already downloaded
74
+ cmd = %w[run]
75
+ cmd += Bolt::Util.format_env_vars_for_cli(options[:env_vars]) if options[:env_vars]
76
+ cmd += volumes if volumes
77
+ cmd += ports if ports
78
+ cmd << "--rm" if options[:rm]
79
+ cmd += %W[-w #{options[:workdir]}] if options[:workdir]
80
+ cmd << image
81
+ cmd += Shellwords.shellsplit(options[:cmd]) if options[:cmd]
82
+
83
+ executor.publish_event(type: :container_start, image: image)
84
+ out, err, status = Bolt::Util.exec_docker(cmd)
85
+
86
+ o = out.is_a?(String) ? out.dup.force_encoding('utf-8') : out
87
+ e = err.is_a?(String) ? err.dup.force_encoding('utf-8') : err
88
+
89
+ unless status.exitstatus.zero?
90
+ result = Bolt::ContainerResult.from_exception(e,
91
+ status.exitstatus,
92
+ image,
93
+ position: Puppet::Pops::PuppetStack.top_of_stack)
94
+ executor.publish_event(type: :container_finish, result: result)
95
+ if options[:catch_errors]
96
+ return result
97
+ else
98
+ raise Bolt::ContainerFailure, result
99
+ end
100
+ end
101
+
102
+ value = { 'stdout' => o, 'stderr' => e, 'exit_code' => status.exitstatus }
103
+ result = Bolt::ContainerResult.new(value, object: image)
104
+ executor.publish_event(type: :container_finish, result: result)
105
+ result
106
+ end
107
+
108
+ def validate_options(options)
109
+ if options.key?(:env_vars)
110
+ ev = options[:env_vars]
111
+ unless ev.is_a?(Hash)
112
+ msg = "Option 'env_vars' must be a hash. Received #{ev} which is a #{ev.class}"
113
+ raise Bolt::ValidationError, msg
114
+ end
115
+
116
+ if (bad_keys = ev.keys.reject { |k| k.is_a?(String) }).any?
117
+ msg = "Keys for option 'env_vars' must be strings: #{bad_keys.map(&:inspect).join(', ')}"
118
+ raise Bolt::ValidationError, msg
119
+ end
120
+ end
121
+
122
+ if options.key?(:volumes)
123
+ volumes = options[:volumes]
124
+ unless volumes.is_a?(Hash)
125
+ msg = "Option 'volumes' must be a hash. Received #{volumes} which is a #{volumes.class}"
126
+ raise Bolt::ValidationError, msg
127
+ end
128
+
129
+ if (bad_vs = volumes.reject { |k, v| k.is_a?(String) && v.is_a?(String) }).any?
130
+ msg = "Option 'volumes' only accepts strings for keys and values. "\
131
+ "Received: #{bad_vs.map(&:inspect).join(', ')}"
132
+ raise Bolt::ValidationError, msg
133
+ end
134
+ end
135
+
136
+ if options.key?(:cmd) && !options[:cmd].is_a?(String)
137
+ cmd = options[:cmd]
138
+ msg = "Option 'cmd' must be a string. Received #{cmd} which is a #{cmd.class}"
139
+ raise Bolt::ValidationError, msg
140
+ end
141
+
142
+ if options.key?(:workdir) && !options[:workdir].is_a?(String)
143
+ wd = options[:workdir]
144
+ msg = "Option 'workdir' must be a string. Received #{wd} which is a #{wd.class}"
145
+ raise Bolt::ValidationError, msg
146
+ end
147
+
148
+ if options.key?(:ports)
149
+ ports = options[:ports]
150
+ unless ports.is_a?(Hash)
151
+ msg = "Option 'ports' must be a hash. Received #{ports} which is a #{ports.class}"
152
+ raise Bolt::ValidationError, msg
153
+ end
154
+
155
+ if (bad_ps = ports.reject { |k, v| k.is_a?(Integer) && v.is_a?(Integer) }).any?
156
+ msg = "Option 'ports' only accepts integers for keys and values. "\
157
+ "Received: #{bad_ps.map(&:inspect).join(', ')}"
158
+ raise Bolt::ValidationError, msg
159
+ end
160
+ end
161
+ end
162
+ end