using GLib; namespace Spry.Cli { /** * `spry deploy` — packages and deploys the application as a container * image through USM, delegating to `usm manifest deploy` in the * application directory. * * USM resolves the whole graph inside the image build: the system * package manager provides the platform libraries and toolchain, the * configured (or `--repository`-named) USM repositories provide the * Web-Stack, and the application's own `MANIFEST.usm`/`usm-scripts/` * (scaffolded by `spry new`) drive its build and install. Flags pass * through to `usm manifest deploy` verbatim; `--exec` defaults to * ` 8080` so the container serves on the documented port. * `--system`/`--spm` map to usm's `--base`/`--spm` (see * {@link Deploy.resolve_system}). */ public class Deploy : GLib.Object { /** * The default USM repository used when no `--repository` is given: * the hosted web-stack repository on packages.astrologue.nz. */ public const string DEFAULT_REPOSITORY = "https://packages.astrologue.nz/repo/web-stack/repo.usmr"; /** `--system fedora` base image ({@link Deploy.resolve_system}). */ public const string SYSTEM_FEDORA_BASE = "registry.fedoraproject.org/fedora:latest"; /** `--system debian` base image ({@link Deploy.resolve_system}). */ public const string SYSTEM_DEBIAN_BASE = "docker.io/library/debian:latest"; /** `--system ubuntu` base image ({@link Deploy.resolve_system}). */ public const string SYSTEM_UBUNTU_BASE = "docker.io/library/ubuntu:latest"; /** `--system alpine` base image ({@link Deploy.resolve_system}). */ public const string SYSTEM_ALPINE_BASE = "docker.io/library/alpine:latest"; /** `--system gentoo` base image ({@link Deploy.resolve_system}). */ public const string SYSTEM_GENTOO_BASE = "docker.io/gentoo/stage3:latest"; /** * Resolves `--system`/`--spm`/`--base` into the base image and SPM * handed to `usm manifest deploy`. * * `--system` is mutually exclusive with `--spm` and an explicit * `--base` (it expands to both); a combined invocation is reported * as an error listing the conflicting flags. When none of the three * is given, spry keeps today's behaviour via the fedora-equivalent * default: only `--spm dnf` is set and NO base image, so usm's own * fedora base constant keeps deciding the image and its identity * is unchanged. * * @param system the `--system` value or null * @param base_image null, or the explicitly passed `--base` value; * set to the expanded base image on success * @param spm null, or the explicitly passed `--spm` value; set to * the expanded SPM name on success * @return false (after printing the problem) on conflicting or * unknown flags, true otherwise */ public static bool resolve_system(string? system, ref string? base_image, ref string? spm) { if (system != null) { var conflicts = new string[0]; if (spm != null) { conflicts += "--spm"; } if (base_image != null) { conflicts += "--base"; } if (conflicts.length > 0) { stderr.printf("Error: --system %s cannot be combined with %s — --system already expands to a base image and an SPM. Pass either --system, or --spm/--base directly.\n", system, string.joinv("/", conflicts)); return false; } switch (system) { case "fedora": base_image = SYSTEM_FEDORA_BASE; spm = "dnf"; return true; case "debian": base_image = SYSTEM_DEBIAN_BASE; spm = "apt"; return true; case "ubuntu": base_image = SYSTEM_UBUNTU_BASE; spm = "apt"; return true; case "alpine": base_image = SYSTEM_ALPINE_BASE; spm = "apk"; return true; case "gentoo": base_image = SYSTEM_GENTOO_BASE; spm = "emerge"; return true; default: stderr.printf("Error: '%s' is not a valid --system value (expected fedora, debian, ubuntu, alpine or gentoo).\n", system); return false; } } if (base_image == null && spm == null) { // The fedora-equivalent default deliberately passes no // --base: usm's own fedora base constant keeps deciding // the image, so default deploys stay byte-identical spm = "dnf"; } return true; } /** * Runs `usm manifest deploy` in {@link app_dir} with the collected * flags. The USM binary is resolved from `--usm` or PATH; a missing * binary is reported with install guidance rather than a raw spawn * error. Returns the delegate process's exit status. * * @param spm the `--spm` value resolved by {@link resolve_system}, * or null to let usm default (repo-only resolution) */ public static int run(string app_dir, string app_name, string? usm_path, string? exec_command, string? base_image, string? spm, string[] repositories, string? installer_url, bool no_build, bool verbose = false) throws Error { var usm = usm_path != null ? (!)usm_path : Environment.find_program_in_path("usm"); if (usm == null) { stderr.printf("Error: usm not found on PATH — install USM (or pass --usm ) to deploy.\n"); return 1; } string[] argv = { (!)usm, "manifest", "deploy", "--exec", exec_command ?? @"$app_name 8080" }; if (base_image != null) { argv += "--base"; argv += base_image; } if (spm != null) { argv += "--spm"; argv += spm; } if (repositories.length > 0) { foreach (var repository in repositories) { argv += "--repository"; argv += repository; } } else { // No explicit repositories: default to the hosted web-stack // repository so stack dependencies resolve out of the box argv += "--repository"; argv += DEFAULT_REPOSITORY; } if (installer_url != null) { argv += "--installer-url"; argv += installer_url; } if (no_build) { argv += "--no-build"; } if (verbose) { argv += "--verbose"; } var shown = ""; foreach (var word in argv[1:argv.length]) { shown += (shown.length > 0 ? " " : "") + (word.contains(" ") ? "\"" + word + "\"" : word); } stdout.printf("Running: %s\n", shown); var launcher = new SubprocessLauncher(SubprocessFlags.INHERIT_FDS); launcher.set_cwd(app_dir); var process = launcher.spawnv(argv); try { process.wait_check(); return 0; } catch(Error e) { return process.get_exit_status(); } } } }