Skip to content

Commit c1c7c4e

Browse files
committed
Add jail transport
!feature * **Add transport for FreeBSD jails** ([#3160](#3170)) FreeBSD has support for jails since FreeBSD 4.0 (released in 2000). Add a new 'jail' transport to Bolt to allow management of jails on FreeBSD.
1 parent 378afc3 commit c1c7c4e

File tree

7 files changed

+490
-0
lines changed

7 files changed

+490
-0
lines changed

lib/bolt/config/options.rb

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

33
require_relative '../../bolt/config/transport/docker'
4+
require_relative '../../bolt/config/transport/jail'
45
require_relative '../../bolt/config/transport/local'
56
require_relative '../../bolt/config/transport/lxd'
67
require_relative '../../bolt/config/transport/orch'
@@ -16,6 +17,7 @@ module Options
1617
# gets passed along to the inventory.
1718
TRANSPORT_CONFIG = {
1819
'docker' => Bolt::Config::Transport::Docker,
20+
'jail' => Bolt::Config::Transport::Jail,
1921
'local' => Bolt::Config::Transport::Local,
2022
'lxd' => Bolt::Config::Transport::LXD,
2123
'pcp' => Bolt::Config::Transport::Orch,
@@ -550,6 +552,12 @@ module Options
550552
_plugin: true,
551553
_example: { "cleanup" => false, "service-url" => "https://docker.example.com" }
552554
},
555+
"jail" => {
556+
description: "A map of configuration options for the jail transport.",
557+
type: Hash,
558+
_plugin: true,
559+
_example: { cleanup: false }
560+
},
553561
"local" => {
554562
description: "A map of configuration options for the local transport. The set of available options is "\
555563
"platform dependent.",

lib/bolt/config/transport/jail.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 Jail < Base
10+
OPTIONS = %w[
11+
cleanup
12+
host
13+
interpreters
14+
service-url
15+
shell-command
16+
tmpdir
17+
user
18+
].concat(RUN_AS_OPTIONS).sort.freeze
19+
20+
DEFAULTS = {
21+
'cleanup' => true
22+
}.freeze
23+
24+
private def validate
25+
super
26+
27+
if @config['interpreters']
28+
@config['interpreters'] = normalize_interpreters(@config['interpreters'])
29+
end
30+
end
31+
end
32+
end
33+
end
34+
end

lib/bolt/executor.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
require_relative '../bolt/result_set'
1515
# Load transports
1616
require_relative '../bolt/transport/docker'
17+
require_relative '../bolt/transport/jail'
1718
require_relative '../bolt/transport/local'
1819
require_relative '../bolt/transport/lxd'
1920
require_relative '../bolt/transport/orch'
@@ -25,6 +26,7 @@
2526
module Bolt
2627
TRANSPORTS = {
2728
docker: Bolt::Transport::Docker,
29+
jail: Bolt::Transport::Jail,
2830
local: Bolt::Transport::Local,
2931
lxd: Bolt::Transport::LXD,
3032
pcp: Bolt::Transport::Orch,

lib/bolt/transport/jail.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../bolt/transport/simple'
4+
5+
module Bolt
6+
module Transport
7+
class Jail < Simple
8+
def provided_features
9+
['shell']
10+
end
11+
12+
def with_connection(target)
13+
conn = Connection.new(target)
14+
conn.connect
15+
yield conn
16+
end
17+
end
18+
end
19+
end
20+
21+
require_relative 'jail/connection'
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# frozen_string_literal: true
2+
3+
require 'logging'
4+
require_relative '../../../bolt/node/errors'
5+
6+
module Bolt
7+
module Transport
8+
class Jail < 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 = @target.user || ENV['USER'] || Etc.getlogin
16+
@logger = Bolt::Logger.logger(target.safe_name)
17+
@jail_info = {}
18+
@logger.trace("Initializing jail connection to #{target.safe_name}")
19+
end
20+
21+
def shell
22+
@shell ||= Bolt::Shell::Bash.new(target, self)
23+
end
24+
25+
def reset_cwd?
26+
true
27+
end
28+
29+
def jail_id
30+
@jail_info['jid'].to_s
31+
end
32+
33+
def jail_path
34+
@jail_info['path']
35+
end
36+
37+
def connect
38+
output = JSON.parse(`jls --libxo=json`)
39+
@jail_info = output['jail-information']['jail'].select { |jail| jail['hostname'] == target.host }.first
40+
raise "Could not find a jail with name matching #{target.host}" if @jail_info.nil?
41+
@logger.trace { "Opened session" }
42+
true
43+
rescue StandardError => e
44+
raise Bolt::Node::ConnectError.new(
45+
"Failed to connect to #{target.safe_name}: #{e.message}",
46+
'CONNECT_ERROR'
47+
)
48+
end
49+
50+
def execute(command)
51+
args = ['-lU', @user]
52+
53+
jail_command = %w[jexec] + args + [jail_id] + Shellwords.split(command)
54+
@logger.trace { "Executing #{jail_command.join(' ')}" }
55+
56+
Open3.popen3({}, *jail_command)
57+
rescue StandardError
58+
@logger.trace { "Command aborted" }
59+
raise
60+
end
61+
62+
def upload_file(source, destination)
63+
@logger.trace { "Uploading #{source} to #{destination}" }
64+
jail_destination = File.join(jail_path, destination)
65+
FileUtils.cp(source, jail_destination)
66+
rescue StandardError => e
67+
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
68+
end
69+
70+
def download_file(source, destination, _download)
71+
@logger.trace { "Downloading #{source} to #{destination}" }
72+
jail_source = File.join(jail_path, source)
73+
FileUtils.mkdir_p(destination)
74+
FileUtils.cp(jail_source, destination)
75+
rescue StandardError => e
76+
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
77+
end
78+
end
79+
end
80+
end
81+
end

schemas/bolt-defaults.schema.json

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@
101101
"docker": {
102102
"$ref": "#/definitions/docker"
103103
},
104+
"jail": {
105+
"$ref": "#/definitions/jail"
106+
},
104107
"local": {
105108
"$ref": "#/definitions/local"
106109
},
@@ -472,6 +475,7 @@
472475
"type": "string",
473476
"enum": [
474477
"docker",
478+
"jail",
475479
"local",
476480
"lxd",
477481
"pcp",
@@ -609,6 +613,129 @@
609613
}
610614
]
611615
},
616+
"jail": {
617+
"description": "A map of configuration options for the jail transport.",
618+
"oneOf": [
619+
{
620+
"type": "object",
621+
"properties": {
622+
"cleanup": {
623+
"oneOf": [
624+
{
625+
"$ref": "#/transport_definitions/cleanup"
626+
},
627+
{
628+
"$ref": "#/definitions/_plugin"
629+
}
630+
]
631+
},
632+
"host": {
633+
"oneOf": [
634+
{
635+
"$ref": "#/transport_definitions/host"
636+
},
637+
{
638+
"$ref": "#/definitions/_plugin"
639+
}
640+
]
641+
},
642+
"interpreters": {
643+
"oneOf": [
644+
{
645+
"$ref": "#/transport_definitions/interpreters"
646+
},
647+
{
648+
"$ref": "#/definitions/_plugin"
649+
}
650+
]
651+
},
652+
"run-as": {
653+
"oneOf": [
654+
{
655+
"$ref": "#/transport_definitions/run-as"
656+
},
657+
{
658+
"$ref": "#/definitions/_plugin"
659+
}
660+
]
661+
},
662+
"run-as-command": {
663+
"oneOf": [
664+
{
665+
"$ref": "#/transport_definitions/run-as-command"
666+
},
667+
{
668+
"$ref": "#/definitions/_plugin"
669+
}
670+
]
671+
},
672+
"service-url": {
673+
"oneOf": [
674+
{
675+
"$ref": "#/transport_definitions/service-url"
676+
},
677+
{
678+
"$ref": "#/definitions/_plugin"
679+
}
680+
]
681+
},
682+
"shell-command": {
683+
"oneOf": [
684+
{
685+
"$ref": "#/transport_definitions/shell-command"
686+
},
687+
{
688+
"$ref": "#/definitions/_plugin"
689+
}
690+
]
691+
},
692+
"sudo-executable": {
693+
"oneOf": [
694+
{
695+
"$ref": "#/transport_definitions/sudo-executable"
696+
},
697+
{
698+
"$ref": "#/definitions/_plugin"
699+
}
700+
]
701+
},
702+
"sudo-password": {
703+
"oneOf": [
704+
{
705+
"$ref": "#/transport_definitions/sudo-password"
706+
},
707+
{
708+
"$ref": "#/definitions/_plugin"
709+
}
710+
]
711+
},
712+
"tmpdir": {
713+
"oneOf": [
714+
{
715+
"$ref": "#/transport_definitions/tmpdir"
716+
},
717+
{
718+
"$ref": "#/definitions/_plugin"
719+
}
720+
]
721+
},
722+
"user": {
723+
"oneOf": [
724+
{
725+
"$ref": "#/transport_definitions/user"
726+
},
727+
{
728+
"$ref": "#/definitions/_plugin"
729+
}
730+
]
731+
}
732+
}
733+
},
734+
{
735+
"$ref": "#/definitions/_plugin"
736+
}
737+
]
738+
},
612739
"local": {
613740
"description": "A map of configuration options for the local transport. The set of available options is platform dependent.",
614741
"oneOf": [
@@ -635,6 +762,16 @@
635762
}
636763
]
637764
},
765+
"extensions": {
766+
"oneOf": [
767+
{
768+
"$ref": "#/transport_definitions/extensions"
769+
},
770+
{
771+
"$ref": "#/definitions/_plugin"
772+
}
773+
]
774+
},
638775
"interpreters": {
639776
"oneOf": [
640777
{
@@ -718,6 +855,16 @@
718855
}
719856
]
720857
},
858+
"interpreters": {
859+
"oneOf": [
860+
{
861+
"$ref": "#/transport_definitions/interpreters"
862+
},
863+
{
864+
"$ref": "#/definitions/_plugin"
865+
}
866+
]
867+
},
721868
"remote": {
722869
"oneOf": [
723870
{

0 commit comments

Comments
 (0)