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

Fixed --output-dir to properly handle relative and user paths like ./ and ~/ #105

Merged
merged 3 commits into from
Dec 19, 2024
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
39 changes: 27 additions & 12 deletions cf_remote/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,9 @@ def install(
return errors


def _iterate_over_packages(tags=None, version=None, edition=None, download=False, output_dir=None):
def _iterate_over_packages(
tags=None, version=None, edition=None, download=False, output_dir=None
):
releases = Releases(edition)
print("Available releases: {}".format(releases))

Expand All @@ -298,17 +300,25 @@ def _iterate_over_packages(tags=None, version=None, edition=None, download=False
else:
for artifact in artifacts:
if download:
package_path= download_package(artifact.url, checksum=artifact.checksum)
if output_dir :
output_dir = os.path.normpath(output_dir)
package_path = download_package(
artifact.url, checksum=artifact.checksum
)
if output_dir:
output_dir = os.path.abspath(os.path.expanduser(output_dir))
parent = os.path.dirname(output_dir)
if not os.path.exists(parent):
user_error("'{}' doesn't exist. Make sure this path is correct and exists.".format(parent))
user_error(
"'{}' doesn't exist. Make sure this path is correct and exists.".format(
parent
)
)
mkdir(output_dir)

filename = os.path.basename(artifact.url)
output_path = os.path.join(output_dir, filename)
assert artifact.checksum
copy_file(package_path, output_path)
print("Copied to '{}' (Checksum OK).".format(output_path))
olehermanse marked this conversation as resolved.
Show resolved Hide resolved
else:
print(artifact.url)
return 0
Expand Down Expand Up @@ -574,8 +584,12 @@ def destroy(group_name=None):

def list_platforms():
print()
print("Platform images are queried based on the platform name, version and architecture.")
print("The form of platform specified is: <platform_name>[-<version>][-<architecture>]. e.g. debian, debian-12 or debian-12-x64")
print(
"Platform images are queried based on the platform name, version and architecture."
)
print(
"The form of platform specified is: <platform_name>[-<version>][-<architecture>]. e.g. debian, debian-12 or debian-12-x64"
)
print("Ubuntu version can be just major (20) or major+minor (20-04)")
print("Architecture can either be x64 or arm64")
print()
Expand Down Expand Up @@ -748,7 +762,7 @@ def show(ansible_inventory):
del group["meta"]
if "region" in meta and "provider" in meta:
extra = " in {}, {}".format(meta["region"], meta["provider"])
if "date" in meta :
if "date" in meta:
added = "saved" if "saved" in meta and meta["saved"] else "spawned"
extra += ", {} {}".format(added, meta["date"])
print(
Expand Down Expand Up @@ -899,20 +913,21 @@ def deploy(hubs, masterfiles):
os.system("tar -czf %s -C %s masterfiles" % (tarball, above))
return deploy_tarball(hubs, tarball)

def agent(hosts, bootstrap=None) :

def agent(hosts, bootstrap=None):

command = "cf-agent"
if bootstrap :
if bootstrap:
command += " --bootstrap {}".format(bootstrap)

for host in hosts:
data = get_info(host)

if not data["agent_version"] :
if not data["agent_version"]:
user_error("CFEngine not installed on {}".format(host))

output = run_command(host, command, sudo=True)
if output :
if output:
print(output)

return 0
23 changes: 14 additions & 9 deletions cf_remote/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,7 @@ def _get_arg_parser():
)
sp.add_argument("tags", metavar="TAG", nargs="*")

sp.add_argument("--output-dir",
"-o",
help="Where to download",
type=str
)
sp.add_argument("--output-dir", "-o", help="Where to download", type=str)

sp = subp.add_parser(
"run", help="Run the command given as arguments on the given hosts"
Expand Down Expand Up @@ -270,7 +266,11 @@ def _get_arg_parser():
)
sp = subp.add_parser("agent", help="Run cf-agent")
sp.add_argument(
"--hosts", "-H", help="Which hosts to run cf-agent from", type=str, required=True
"--hosts",
"-H",
help="Which hosts to run cf-agent from",
type=str,
required=True,
)
sp.add_argument("--bootstrap", "-B", help="Which hub to bootstrap to", type=str)

Expand All @@ -292,8 +292,10 @@ def run_command_with_args(command, args):
else:
trust_keys = None

if not args.bootstrap :
log.warning("You did not specify --bootstrap in the install command, so CFEngine has been installed, but not started.\nTo fix this, run:\ncf-remote agent --hosts HOSTS --bootstrap BOOTSTRAP")
if not args.bootstrap:
log.warning(
"You did not specify --bootstrap in the install command, so CFEngine has been installed, but not started.\nTo fix this, run:\ncf-remote agent --hosts HOSTS --bootstrap BOOTSTRAP"
)

return commands.install(
args.hub,
Expand Down Expand Up @@ -325,7 +327,10 @@ def run_command_with_args(command, args):
)
elif command == "download":
return commands.download(
tags=args.tags, version=args.version, edition=args.edition, output_dir=args.output_dir
tags=args.tags,
version=args.version,
edition=args.edition,
output_dir=args.output_dir,
)
elif command == "run":
return commands.run(hosts=args.hosts, raw=args.raw, command=args.remote_command)
Expand Down
1 change: 0 additions & 1 deletion cf_remote/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ def add_tags_from_filename(self, filename):
self.add_tag("opensuse-leap15")
self.add_tag("opensuse-leap")


# We don't build for Fedora, so we need to map the distro to the correct packages
if "el9" in self.tags:
self.add_tag("fedora40")
Expand Down
6 changes: 5 additions & 1 deletion cf_remote/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ def cfengine_dir(subdir=None):
parent = os.path.dirname(override_dir)

if not os.path.exists(parent):
user_error("'{}' doesn't exist. Make sure this path is correct and exists.".format(parent))
user_error(
"'{}' doesn't exist. Make sure this path is correct and exists.".format(
parent
)
)

return path_append(override_dir, subdir)

Expand Down
16 changes: 11 additions & 5 deletions cf_remote/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,13 @@ def print_info(data):
output["CFEngine"] = agent_version

policy_server = data.get("policy_server")
if policy_server :
if policy_server:
output["Policy server"] = policy_server
else :
else:
output["Policy server"] = "None (not bootstrapped yet)"
else:
output["CFEngine"] = "Not installed"


binaries = []
if "bin" in data:
for key in data["bin"]:
Expand Down Expand Up @@ -330,8 +329,15 @@ def uninstall_cfengine(host, data, *, connection=None, purge=False):
host, "rm -rf /var/cfengine /opt/cfengine", connection=connection, sudo=True
)
if purge:
run_command(host, "rm -rf /var/log/CFEngine-Install*", connection=connection, sudo=True)
run_command(host, "rm -rf /etc/systemd/system/cf-php-fpm.service", connection=connection, sudo=True)
run_command(
host, "rm -rf /var/log/CFEngine-Install*", connection=connection, sudo=True
)
run_command(
host,
"rm -rf /etc/systemd/system/cf-php-fpm.service",
connection=connection,
sudo=True,
)


@auto_connect
Expand Down
18 changes: 13 additions & 5 deletions cf_remote/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,25 @@ def __init__(self, host, user, connect_kwargs=None):
# Create an SSH Control Master process (man:ssh_config(5)) so that
# commands run on this host can reuse the same SSH connection.
self._control_path = os.path.join(paths.cf_remote_dir(), "%C")
control_master_args = ["ssh", "-M", "-N",
"-oControlPath=%s" % self._control_path,
]
control_master_args = [
"ssh",
"-M",
"-N",
"-oControlPath=%s" % self._control_path,
]
control_master_args.extend(aramid.DEFAULT_SSH_ARGS)
control_master_args.append("%s@%s" % (user, host))

self._ssh_control_master = subprocess.Popen(control_master_args) # stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
self._ssh_control_master = subprocess.Popen(
control_master_args
) # stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def __del__(self):
# If we have an SSH Control Master running, signal it to terminate.
if self._ssh_control_master is not None and self._ssh_control_master.poll() is None:
if (
self._ssh_control_master is not None
and self._ssh_control_master.poll() is None
):
self._ssh_control_master.send_signal(signal.SIGTERM)

def run(self, command, hide=False):
Expand Down
12 changes: 6 additions & 6 deletions cf_remote/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def read_file(path):


def save_file(path, data):
try :
try:
if "/" in path:
mkdir("/".join(path.split("/")[0:-1]))
with open(path, "w") as f:
Expand Down Expand Up @@ -205,19 +205,19 @@ def print_progress_dot(*args):
print(".", end="")
sys.stdout.flush() # STDOUT is line-buffered

def copy_file(input_path, output_path) :

def copy_file(input_path, output_path):
filename = os.path.basename(input_path)
output_dir = os.path.dirname(output_path)

tmp_filename = '.{}.tmp'.format(filename)
tmp_filename = ".{}.tmp".format(filename)
tmp_output_path = os.path.join(output_dir, tmp_filename)

shutil.copyfile(input_path, tmp_output_path)
shutil.copyfile(input_path, tmp_output_path)
os.rename(tmp_output_path, output_path)


def is_different_checksum(checksum, content) :
def is_different_checksum(checksum, content):
assert type(content) == bytes

digest = hashlib.sha256(content).digest().hex()
Expand Down
26 changes: 21 additions & 5 deletions cf_remote/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
import urllib.request
import json
from collections import OrderedDict
from cf_remote.utils import is_different_checksum, user_error, write_json, mkdir, parse_json
from cf_remote.utils import (
is_different_checksum,
user_error,
write_json,
mkdir,
parse_json,
)
from cf_remote import log
from cf_remote.paths import cf_remote_dir, cf_remote_packages_dir

SHA256_RE = re.compile(r"^[0-9a-f]{64}$")


def get_json(url):
with urllib.request.urlopen(url) as r:
assert r.status >= 200 and r.status < 300
Expand All @@ -27,9 +34,10 @@ def get_json(url):

def download_package(url, path=None, checksum=None):


if checksum and not SHA256_RE.match(checksum):
user_error("Invalid checksum or unsupported checksum algorithm: '%s'" % checksum)
user_error(
"Invalid checksum or unsupported checksum algorithm: '%s'" % checksum
)

if not path:
filename = os.path.basename(url)
Expand All @@ -50,14 +58,22 @@ def download_package(url, path=None, checksum=None):
f.seek(0)
content = f.read()
if checksum and is_different_checksum(checksum, content):
user_error("Downloaded file '{}' does not match expected checksum '{}'. Please delete the file.".format(filename, checksum))
user_error(
"Downloaded file '{}' does not match expected checksum '{}'. Please delete the file.".format(
filename, checksum
)
)

else:
print("Downloading package: '{}'".format(path))

answer = urllib.request.urlopen(url).read()
if checksum and is_different_checksum(checksum, answer):
user_error("Downloaded file '{}' does not match expected checksum '{}'".format(filename, checksum))
user_error(
"Downloaded file '{}' does not match expected checksum '{}'. Please delete the file.".format(
filename, checksum
)
)

f.write(answer)
f.flush()
Expand Down
Loading