Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transport for FreeBSD jails #3170

Merged
merged 2 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions documentation/experimental_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,30 @@ targets:
tmpdir: /root/tmp
```

## FreeBSD jails support

Bolt now has experimental support for [FreeBSD
jails](https://docs.freebsd.org/en/books/handbook/jails/), a lightweight virtualization solution
that allow for the creation of isolated environments within a single FreeBSD system.
The jail transport supports connecting to jails running on the local system.
The jail transport accepts many of the same configuration options as the Docker transport. You can
see the full list of supported configuration options [on the transport reference
page](bolt_transports_reference.md). The jail transport doesn't support the `service-url`
configuration options as the transport doesn't support remote connections. If this is a feature
you're interested in, let us know [in Slack](https://slack.puppet.com) or submit a [Github
issue](https://github.com/puppetlabs/bolt/issues).

The example inventory file below demonstrates connecting to a jail container target named
`postgres_db`.

```
targets:
- uri: jail://postgres_db
config:
jail:
user: postgres
```

## Streaming output

This feature was introduced in [Bolt 3.2.0](https://github.com/puppetlabs/bolt/blob/main/CHANGELOG.md#bolt-320-2021-3-08).
Expand Down
8 changes: 8 additions & 0 deletions lib/bolt/config/options.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require_relative '../../bolt/config/transport/docker'
require_relative '../../bolt/config/transport/jail'
require_relative '../../bolt/config/transport/local'
require_relative '../../bolt/config/transport/lxd'
require_relative '../../bolt/config/transport/orch'
Expand All @@ -16,6 +17,7 @@ module Options
# gets passed along to the inventory.
TRANSPORT_CONFIG = {
'docker' => Bolt::Config::Transport::Docker,
'jail' => Bolt::Config::Transport::Jail,
'local' => Bolt::Config::Transport::Local,
'lxd' => Bolt::Config::Transport::LXD,
'pcp' => Bolt::Config::Transport::Orch,
Expand Down Expand Up @@ -550,6 +552,12 @@ module Options
_plugin: true,
_example: { "cleanup" => false, "service-url" => "https://docker.example.com" }
},
"jail" => {
description: "A map of configuration options for the jail transport.",
type: Hash,
_plugin: true,
_example: { cleanup: false }
},
"local" => {
description: "A map of configuration options for the local transport. The set of available options is "\
"platform dependent.",
Expand Down
33 changes: 33 additions & 0 deletions lib/bolt/config/transport/jail.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

require_relative '../../../bolt/error'
require_relative '../../../bolt/config/transport/base'

module Bolt
class Config
module Transport
class Jail < Base
OPTIONS = %w[
cleanup
host
interpreters
shell-command
tmpdir
user
].concat(RUN_AS_OPTIONS).sort.freeze

DEFAULTS = {
'cleanup' => true
}.freeze

private def validate
super

if @config['interpreters']
@config['interpreters'] = normalize_interpreters(@config['interpreters'])
end
end
end
end
end
end
2 changes: 2 additions & 0 deletions lib/bolt/executor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
require_relative '../bolt/result_set'
# Load transports
require_relative '../bolt/transport/docker'
require_relative '../bolt/transport/jail'
require_relative '../bolt/transport/local'
require_relative '../bolt/transport/lxd'
require_relative '../bolt/transport/orch'
Expand All @@ -25,6 +26,7 @@
module Bolt
TRANSPORTS = {
docker: Bolt::Transport::Docker,
jail: Bolt::Transport::Jail,
local: Bolt::Transport::Local,
lxd: Bolt::Transport::LXD,
pcp: Bolt::Transport::Orch,
Expand Down
2 changes: 1 addition & 1 deletion lib/bolt/shell/bash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ def execute(command, sudoable: false, **options)
if defined? conn.add_env_vars
conn.add_env_vars(options[:environment])
else
env_decl = options[:environment].map do |env, val|
env_decl = '/usr/bin/env ' + options[:environment].map do |env, val|
"#{env}=#{Shellwords.shellescape(val)}"
end.join(' ')
end
Expand Down
21 changes: 21 additions & 0 deletions lib/bolt/transport/jail.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

require_relative '../../bolt/transport/simple'

module Bolt
module Transport
class Jail < Simple
def provided_features
['shell']
end

def with_connection(target)
conn = Connection.new(target)
conn.connect
yield conn
end
end
end
end

require_relative 'jail/connection'
81 changes: 81 additions & 0 deletions lib/bolt/transport/jail/connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# frozen_string_literal: true

require 'logging'
require_relative '../../../bolt/node/errors'

module Bolt
module Transport
class Jail < Simple
class Connection
attr_reader :user, :target

def initialize(target)
raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
@target = target
@user = @target.user || ENV['USER'] || Etc.getlogin
@logger = Bolt::Logger.logger(target.safe_name)
@jail_info = {}
@logger.trace("Initializing jail connection to #{target.safe_name}")
end

def shell
@shell ||= Bolt::Shell::Bash.new(target, self)
end

def reset_cwd?
true
end

def jail_id
@jail_info['jid'].to_s
end

def jail_path
@jail_info['path']
end

def connect
output = JSON.parse(`jls --libxo=json`)
@jail_info = output['jail-information']['jail'].select { |jail| jail['hostname'] == target.host }.first
raise "Could not find a jail with name matching #{target.host}" if @jail_info.nil?
@logger.trace { "Opened session" }
true
rescue StandardError => e
raise Bolt::Node::ConnectError.new(
"Failed to connect to #{target.safe_name}: #{e.message}",
'CONNECT_ERROR'
)
end

def execute(command)
args = ['-lU', @user]

jail_command = %w[jexec] + args + [jail_id] + Shellwords.split(command)
@logger.trace { "Executing #{jail_command.join(' ')}" }

Open3.popen3({}, *jail_command)
rescue StandardError
@logger.trace { "Command aborted" }
raise
end

def upload_file(source, destination)
@logger.trace { "Uploading #{source} to #{destination}" }
jail_destination = File.join(jail_path, destination)
FileUtils.cp(source, jail_destination)
rescue StandardError => e
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
end

def download_file(source, destination, _download)
@logger.trace { "Downloading #{source} to #{destination}" }
jail_source = File.join(jail_path, source)
FileUtils.mkdir_p(destination)
FileUtils.cp(jail_source, destination)
rescue StandardError => e
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
end
end
end
end
end
137 changes: 137 additions & 0 deletions schemas/bolt-defaults.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
"docker": {
"$ref": "#/definitions/docker"
},
"jail": {
"$ref": "#/definitions/jail"
},
"local": {
"$ref": "#/definitions/local"
},
Expand Down Expand Up @@ -472,6 +475,7 @@
"type": "string",
"enum": [
"docker",
"jail",
"local",
"lxd",
"pcp",
Expand Down Expand Up @@ -609,6 +613,119 @@
}
]
},
"jail": {
"description": "A map of configuration options for the jail transport.",
"oneOf": [
{
"type": "object",
"properties": {
"cleanup": {
"oneOf": [
{
"$ref": "#/transport_definitions/cleanup"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"host": {
"oneOf": [
{
"$ref": "#/transport_definitions/host"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"interpreters": {
"oneOf": [
{
"$ref": "#/transport_definitions/interpreters"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"run-as": {
"oneOf": [
{
"$ref": "#/transport_definitions/run-as"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"run-as-command": {
"oneOf": [
{
"$ref": "#/transport_definitions/run-as-command"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"shell-command": {
"oneOf": [
{
"$ref": "#/transport_definitions/shell-command"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"sudo-executable": {
"oneOf": [
{
"$ref": "#/transport_definitions/sudo-executable"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"sudo-password": {
"oneOf": [
{
"$ref": "#/transport_definitions/sudo-password"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"tmpdir": {
"oneOf": [
{
"$ref": "#/transport_definitions/tmpdir"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"user": {
"oneOf": [
{
"$ref": "#/transport_definitions/user"
},
{
"$ref": "#/definitions/_plugin"
}
]
}
}
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"local": {
"description": "A map of configuration options for the local transport. The set of available options is platform dependent.",
"oneOf": [
Expand All @@ -635,6 +752,16 @@
}
]
},
"extensions": {
"oneOf": [
{
"$ref": "#/transport_definitions/extensions"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"interpreters": {
"oneOf": [
{
Expand Down Expand Up @@ -718,6 +845,16 @@
}
]
},
"interpreters": {
"oneOf": [
{
"$ref": "#/transport_definitions/interpreters"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"remote": {
"oneOf": [
{
Expand Down
Loading