Skip to content

Commit c1e76fe

Browse files
committed
Add Choria transport (phases 1 and 2 of 5)
Implements phases 1 and 2 of the Choria transport, enabling OpenBolt to run tasks, commands, and scripts on nodes via Choria's NATS pub/sub messaging as an alternative to SSH and WinRM. Phase 1 (bolt_tasks agent): Downloads task files to targets from an OpenVox/Puppet Server and executes them using the bolt_tasks Choria agent. Phase 2 (shell agent): Executes commands, scripts, and tasks through the Choria shell agent. This allows running tasks not available on an OpenVox/Puppet server. Everything is implemented as asynchronously as possible, aligning with Choria's model, and is built to run at scale across many thousands of nodes at once. See docs in a later commit for details on the phases of this project as well as user-facing and developer documentation.
1 parent 6903aab commit c1e76fe

16 files changed

Lines changed: 2207 additions & 1 deletion

File tree

.rubocop.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,16 @@ Layout/LineLength:
2222
Max: 150
2323

2424
Style/FetchEnvVar:
25+
Enabled: false
26+
27+
Naming/BlockForwarding:
28+
Enabled: false
29+
30+
Naming/PredicatePrefix:
31+
Enabled: false
32+
33+
Lint/NoReturnInBeginEndBlocks:
34+
Enabled: false
35+
36+
Style/MultilineTernaryOperator:
2537
Enabled: false

lib/bolt/bolt_option_parser.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def get_help_text(subcommand, action = nil)
168168
when 'task'
169169
case action
170170
when 'run'
171-
{ flags: ACTION_OPTS + %w[params tmpdir noop],
171+
{ flags: ACTION_OPTS + %w[params tmpdir noop choria-agent],
172172
banner: TASK_RUN_HELP }
173173
when 'show'
174174
{ flags: OPTIONS[:global] + OPTIONS[:global_config_setters] + %w[filter format],
@@ -1095,6 +1095,11 @@ def initialize(options)
10951095
define('--tmpdir DIR', 'The directory to upload and execute temporary files on the target.') do |tmpdir|
10961096
@options[:tmpdir] = tmpdir
10971097
end
1098+
define('--choria-agent AGENT', %w[bolt_tasks shell],
1099+
"Which Choria agent to use for task execution (bolt_tasks, shell).",
1100+
"Defaults to 'bolt_tasks'. Set to 'shell' for tasks not on the Puppet Server.") do |agent|
1101+
@options[:'choria-agent'] = agent
1102+
end
10981103

10991104
separator "\n#{self.class.colorize(:cyan, 'Module options')}"
11001105
define('--[no-]resolve',

lib/bolt/config/options.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22

3+
require_relative '../../bolt/config/transport/choria'
34
require_relative '../../bolt/config/transport/docker'
45
require_relative '../../bolt/config/transport/jail'
56
require_relative '../../bolt/config/transport/local'
@@ -15,6 +16,7 @@ module Options
1516
# Transport config classes. Used to load default transport config which
1617
# gets passed along to the inventory.
1718
TRANSPORT_CONFIG = {
19+
'choria' => Bolt::Config::Transport::Choria,
1820
'docker' => Bolt::Config::Transport::Docker,
1921
'jail' => Bolt::Config::Transport::Jail,
2022
'local' => Bolt::Config::Transport::Local,
@@ -551,6 +553,12 @@ module Options
551553
_example: "winrm",
552554
_default: "ssh"
553555
},
556+
"choria" => {
557+
description: "A map of configuration options for the choria transport.",
558+
type: Hash,
559+
_plugin: true,
560+
_example: { "config-file" => "/etc/choria/client.conf" }
561+
},
554562
"docker" => {
555563
description: "A map of configuration options for the docker transport.",
556564
type: Hash,
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../../bolt/error'
4+
require_relative '../../../bolt/config/transport/base'
5+
6+
module Bolt
7+
class Config
8+
module Transport
9+
class Choria < Base
10+
OPTIONS = %w[
11+
choria-agent
12+
cleanup
13+
collective
14+
command-timeout
15+
config-file
16+
host
17+
interpreters
18+
nats-connection-timeout
19+
nats-servers
20+
puppet-environment
21+
rpc-timeout
22+
ssl-ca
23+
ssl-cert
24+
ssl-key
25+
task-timeout
26+
tmpdir
27+
].sort.freeze
28+
29+
DEFAULTS = {
30+
'cleanup' => true,
31+
'command-timeout' => 60,
32+
'nats-connection-timeout' => 30,
33+
'puppet-environment' => 'production',
34+
'rpc-timeout' => 30,
35+
'task-timeout' => 300,
36+
'tmpdir' => '/tmp'
37+
}.freeze
38+
39+
VALID_AGENTS = %w[bolt_tasks shell].freeze
40+
41+
private def validate
42+
super
43+
44+
if @config['choria-agent'] && !VALID_AGENTS.include?(@config['choria-agent'])
45+
raise Bolt::ValidationError,
46+
"choria-agent must be one of #{VALID_AGENTS.join(', ')}, got '#{@config['choria-agent']}'"
47+
end
48+
49+
if @config['tmpdir'] && !absolute_path?(@config['tmpdir'])
50+
raise Bolt::ValidationError,
51+
"Choria tmpdir must be an absolute path, got '#{@config['tmpdir']}'"
52+
end
53+
54+
ssl_keys = %w[ssl-ca ssl-cert ssl-key]
55+
provided_ssl = ssl_keys.select { |k| @config[k] }
56+
if provided_ssl.any? && provided_ssl.length < ssl_keys.length
57+
missing = ssl_keys - provided_ssl
58+
raise Bolt::ValidationError,
59+
"When overriding Choria SSL settings, all three options must be provided " \
60+
"(ssl-ca, ssl-cert, ssl-key). Missing: #{missing.join(', ')}"
61+
end
62+
63+
@config['interpreters'] = normalize_interpreters(@config['interpreters']) if @config['interpreters']
64+
end
65+
66+
# Accept both POSIX absolute paths (/tmp) and Windows absolute paths (C:\temp).
67+
def absolute_path?(path)
68+
path.start_with?('/') || path.match?(Bolt::Transport::Choria::WINDOWS_PATH_REGEX)
69+
end
70+
end
71+
end
72+
end
73+
end

lib/bolt/config/transport/options.rb

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,56 @@ module Options
5151
_default: true,
5252
_example: false
5353
},
54+
"choria-agent" => {
55+
type: String,
56+
description: "Which Choria agent to use for task execution. Defaults to 'bolt_tasks' " \
57+
"(downloads task files from a Puppet Server). Set to 'shell' for tasks " \
58+
"not available on the Puppet Server.",
59+
_plugin: true,
60+
_example: "shell"
61+
},
62+
"collective" => {
63+
type: String,
64+
description: "The Choria collective to target. Overrides the main_collective from the Choria " \
65+
"client configuration file.",
66+
_plugin: true,
67+
_example: "production"
68+
},
69+
"command-timeout" => {
70+
type: Integer,
71+
description: "How long to wait in seconds for commands and scripts to complete when using the " \
72+
"Choria transport.",
73+
minimum: 1,
74+
_plugin: true,
75+
_default: 60,
76+
_example: 120
77+
},
78+
"config-file" => {
79+
type: String,
80+
description: "The path to the Choria or MCollective client configuration file.",
81+
_plugin: true,
82+
_example: "/etc/choria/client.conf"
83+
},
84+
"nats-connection-timeout" => {
85+
type: Integer,
86+
description: "How long to wait in seconds for the initial TCP connection to the NATS broker. " \
87+
"If the connection cannot be made within this time, the operation fails.",
88+
minimum: 1,
89+
_plugin: true,
90+
_default: 30,
91+
_example: 60
92+
},
93+
"rpc-timeout" => {
94+
type: Integer,
95+
description: "How long to wait in seconds for nodes to respond to an RPC request. " \
96+
"Used for lightweight operations like agent discovery, shell.start, and " \
97+
"shell.list polling. Distinct from command-timeout and task-timeout which " \
98+
"govern the overall duration of commands and tasks.",
99+
minimum: 1,
100+
_plugin: true,
101+
_default: 30,
102+
_example: 60
103+
},
54104
"connect-timeout" => {
55105
type: Integer,
56106
description: "How long to wait in seconds when establishing connections. Set this value higher if you " \
@@ -225,6 +275,16 @@ module Options
225275
_plugin: true,
226276
_example: %w[defaults hmac-md5]
227277
},
278+
"nats-servers" => {
279+
type: [String, Array],
280+
description: "One or more NATS server addresses for the Choria transport. Overrides the middleware " \
281+
"hosts from the Choria client configuration file. Can be a single string or an array.",
282+
items: {
283+
type: String
284+
},
285+
_plugin: true,
286+
_example: ["nats://broker1:4222", "nats://broker2:4222"]
287+
},
228288
"native-ssh" => {
229289
type: [TrueClass, FalseClass],
230290
description: "This enables the native SSH transport, which shells out to SSH instead of using the " \
@@ -267,6 +327,14 @@ module Options
267327
_plugin: true,
268328
_example: "jump.example.com"
269329
},
330+
"puppet-environment" => {
331+
type: String,
332+
description: "The Puppet environment to use when constructing task file URIs for the Choria " \
333+
"bolt_tasks agent.",
334+
_plugin: true,
335+
_default: "production",
336+
_example: "staging"
337+
},
270338
"read-timeout" => {
271339
type: Integer,
272340
description: "How long to wait in seconds when making requests to the Orchestrator.",
@@ -343,6 +411,27 @@ module Options
343411
_plugin: true,
344412
_example: 445
345413
},
414+
"ssl-ca" => {
415+
type: String,
416+
description: "The path to the CA certificate for Choria TLS connections. Overrides the CA " \
417+
"from the Choria client configuration file.",
418+
_plugin: true,
419+
_example: "/etc/choria/ssl/ca.pem"
420+
},
421+
"ssl-cert" => {
422+
type: String,
423+
description: "The path to the client certificate for Choria TLS connections. Overrides the " \
424+
"certificate from the Choria client configuration file.",
425+
_plugin: true,
426+
_example: "/etc/choria/ssl/client.pem"
427+
},
428+
"ssl-key" => {
429+
type: String,
430+
description: "The path to the client private key for Choria TLS connections. Overrides the " \
431+
"key from the Choria client configuration file.",
432+
_plugin: true,
433+
_example: "/etc/choria/ssl/client-key.pem"
434+
},
346435
"ssh-command" => {
347436
type: [Array, String],
348437
description: "The command and options to use when SSHing. This option is used when you need support for " \
@@ -393,6 +482,14 @@ module Options
393482
_default: "production",
394483
_example: "development"
395484
},
485+
"task-timeout" => {
486+
type: Integer,
487+
description: "How long to wait in seconds for tasks to complete when using the Choria transport.",
488+
minimum: 1,
489+
_plugin: true,
490+
_default: 300,
491+
_example: 300
492+
},
396493
"tmpdir" => {
397494
type: String,
398495
description: "The directory to upload and execute temporary files on the target.",

lib/bolt/executor.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require_relative '../bolt/result'
1414
require_relative '../bolt/result_set'
1515
# Load transports
16+
require_relative '../bolt/transport/choria'
1617
require_relative '../bolt/transport/docker'
1718
require_relative '../bolt/transport/jail'
1819
require_relative '../bolt/transport/local'
@@ -24,6 +25,7 @@
2425

2526
module Bolt
2627
TRANSPORTS = {
28+
choria: Bolt::Transport::Choria,
2729
docker: Bolt::Transport::Docker,
2830
jail: Bolt::Transport::Jail,
2931
local: Bolt::Transport::Local,

0 commit comments

Comments
 (0)