:xSalt-ssh can deploy a relenv environment

This commit is contained in:
Tyler Levy Conde 2024-09-10 12:06:49 -06:00 committed by Daniel Wozniak
parent 15573a8f20
commit 5263353262
2 changed files with 136 additions and 6 deletions

View file

@ -13,6 +13,7 @@ import os
import pathlib
import queue
import re
import requests
import shlex
import shutil
import subprocess
@ -59,7 +60,7 @@ try:
except ImportError:
HAS_WINSHELL = False
# The directory where salt thin is deployed
# The directory where salt thin/relenv is deployed
DEFAULT_THIN_DIR = "/var/tmp/.%%USER%%_%%FQDNUUID%%_salt"
# RSTR is just a delimiter to distinguish the beginning of salt STDOUT
@ -1141,14 +1142,130 @@ class Single:
return False
return True
def detect_os_arch(self):
"""
Detect the OS and architecture of the target machine.
Returns a tuple of (kernel, architecture) or raises an error if detection fails.
"""
os_arch_cmd = 'echo "$OSTYPE|$MACHTYPE|$env:PROCESSOR_ARCHITECTURE"'
# Execute the command on the target
stdout, stderr, retcode = self.shell.exec_cmd(os_arch_cmd)
if retcode != 0:
log.error(f"Failed to detect OS and architecture on target: {stderr}")
raise ValueError("OS and architecture detection failed")
# Parse the output
try:
kernel, arch, winarch = stdout.lower().strip().split("|", maxsplit=2)
except ValueError as e:
log.error(f"Error parsing OS/arch detection result: {e}")
raise ValueError("Failed to parse OS and architecture data")
# Set architecture
if "64" in winarch or "64" in arch:
os_arch = "amd64"
else:
os_arch = "i386"
# Set kernel based on OS type
if "linux" in kernel:
kernel = "linux"
elif "darwin" in kernel:
kernel = "macos"
elif "processor_architecture" not in winarch:
kernel = "windows"
else:
log.error(f"Could not determine OS from kernel: {kernel}, arch: {arch}, winarch: {winarch}")
log.info(f'Detected kernel "{kernel}" and architecture "{os_arch}" on target')
return kernel, os_arch
def get_relenv_tarball(self, kernel, arch):
"""
Get the latest Salt onedir tarball URL for the specified kernel and architecture.
:param kernel: The detected OS (e.g., 'linux', 'darwin', 'windows')
:param arch: The detected architecture (e.g., 'amd64', 'x86_64', 'arm64')
:return: The URL of the latest tarball
"""
# TODO actually add this as an option
base_url = self.opts.get("salt_repo_url", "https://repo.saltproject.io/salt/py3/onedir/latest/")
try:
# Request the page listing
response = requests.get(base_url)
response.raise_for_status()
except requests.RequestException as e:
log.error(f"Failed to retrieve tarball listing: {e}")
raise ValueError("Unable to fetch tarball list from repository")
# Search for tarball filenames that match the kernel and arch
pattern = re.compile(rf'href="(salt-.*-onedir-{kernel}-{arch}\.tar\.xz)"')
# Find all matches in the HTML content
matches = pattern.findall(response.text)
if not matches:
log.error(f"No tarballs found for {kernel} and {arch}")
raise ValueError(f"No tarball found for {kernel} {arch}")
# Assume that the latest tarball is the last one in the sorted list
matches.sort()
latest_tarball = matches[-1]
# Construct the full URL
latest_url = base_url + latest_tarball
log.info(f"Latest relenv tarball URL: {latest_url}")
return latest_url
def relenv(self):
"""
Deploy salt-relenv
"""
try:
# Detect OS and architecture
kernel, os_arch = self.detect_os_arch()
except ValueError as e:
log.error(f"Error in OS and architecture detection: {e}")
return False
# Construct the relenv URL based on the detected OS and architecture
relenv_url = self.get_relenv_tarball(kernel, os_arch)
# Path to cache the downloaded relenv tarball
tarball_path = os.path.join(self.opts["cachedir"], f"salt-relenv-{kernel}-{os_arch}.tgz")
# Download the relenv tarball if not already cached
if not os.path.exists(tarball_path):
log.info(f"Downloading relenv tarball from {relenv_url} to {tarball_path}")
result = salt.utils.http.query(relenv_url, stream=True, local_file=tarball_path)
if result.get("status") != 200:
log.error(f"Failed to download relenv tarball from {relenv_url}")
return False
# Send the tarball to the target machine
self.shell.send(tarball_path, os.path.join(self.thin_dir, "salt-relenv.tar.xz"))
return True
def deploy(self):
"""
Deploy salt-thin
Deploy salt-thin/relenv
"""
self.shell.send(
self.thin,
os.path.join(self.thin_dir, "salt-thin.tgz"),
)
if self.opts.get("relenv"):
self.relenv()
else:
self.shell.send(
self.thin,
os.path.join(self.thin_dir, "salt-thin.tgz"),
)
self.deploy_ext()
return True

View file

@ -3227,6 +3227,16 @@ class SaltSSHOptionParser(
"custom grains/modules/states have been added or updated."
),
)
self.add_option(
"--relenv",
dest="relenv",
default=False,
action="store_true",
help=(
"Deploy and use a relenv (Salt+Python bundled) environment on the "
"SSH target."
),
),
self.add_option(
"--python2-bin",
default="python2",
@ -3410,6 +3420,9 @@ class SaltSSHOptionParser(
option.explicit = True
break
if self.options.regen_thin and self.options.relenv:
self.error("--thin and --relenv are mutually exclusive")
def setup_config(self):
return config.master_config(self.get_config_file_path())