paratix 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -104,7 +104,7 @@ Signals are deferred side effects such as `service.reload(...)` or `service.rest
104
104
 
105
105
  For Podman-native services, Paratix now also includes `quadlet.container(...)`. It writes a `.container` file under `/etc/containers/systemd`, reloads systemd when the content changes, and works cleanly with `service.enabled(...)` and `service.running(...)` for the generated service.
106
106
 
107
- When you need a targeted image refresh outside the normal deploy flow, `quadlet.updateImage(...)` pulls exactly one image, reuses existing Podman registry auth on the host, optionally supports `authFile`, and only restarts the generated service when the pull actually downloaded a newer image.
107
+ When you need a targeted image refresh outside the normal deploy flow, `quadlet.updateImage(...)` pulls exactly one image, reuses existing Podman registry auth on the host, optionally supports `authFile`, and only restarts the generated service when the pull actually downloaded a newer image. Changed runs now also print the new registry digest in parentheses in the CLI output, with a local image ID fallback when no repo digest is available.
108
108
 
109
109
  ### Guards
110
110
 
@@ -2,7 +2,7 @@ import {
2
2
  CommandError,
3
3
  assertValidModuleMetaEntries,
4
4
  mergeEnvironmentFromMeta
5
- } from "./chunk-JJRF37BP.js";
5
+ } from "./chunk-NRDLYHJL.js";
6
6
 
7
7
  // src/output.ts
8
8
  import pc from "picocolors";
@@ -492,4 +492,4 @@ export {
492
492
  printSummary,
493
493
  runSignalModules
494
494
  };
495
- //# sourceMappingURL=chunk-IUY5BJHA.js.map
495
+ //# sourceMappingURL=chunk-47PTUZZR.js.map
@@ -9,7 +9,7 @@ import {
9
9
  shellQuote,
10
10
  sshdPortMeta,
11
11
  validateMode
12
- } from "./chunk-JJRF37BP.js";
12
+ } from "./chunk-NRDLYHJL.js";
13
13
 
14
14
  // src/moduleFailure.ts
15
15
  function firstNonEmptyLine(text) {
@@ -3542,6 +3542,62 @@ function shellQuoteForQuadlet(value) {
3542
3542
  return `'${value.replaceAll("'", escapedQuote)}'`;
3543
3543
  }
3544
3544
 
3545
+ // src/modules/quadletImageInspectHelpers.ts
3546
+ function shellQuoteForQuadletImageInspect(value) {
3547
+ const escapedQuote = "'\\''";
3548
+ return `'${value.replaceAll("'", escapedQuote)}'`;
3549
+ }
3550
+ function buildQuadletImageInspectCommand(image) {
3551
+ return `podman image inspect ${shellQuoteForQuadletImageInspect(image)}`;
3552
+ }
3553
+ function formatQuadletImageIdentifierDetail(imageIdentifier) {
3554
+ return `(${imageIdentifier})`;
3555
+ }
3556
+ function readQuadletImageIdentifierFromInspectOutput(image, output) {
3557
+ const parsed = parseQuadletImageInspectOutput(output);
3558
+ if (parsed == null) return null;
3559
+ const repoDigest = findQuadletRepoDigest(image, parsed.repoDigests);
3560
+ return repoDigest ?? parsed.id;
3561
+ }
3562
+ function findQuadletRepoDigest(image, repoDigests) {
3563
+ const repository = getQuadletImageRepository(image);
3564
+ for (const repoDigest of repoDigests) {
3565
+ const separatorIndex = repoDigest.indexOf("@");
3566
+ if (separatorIndex === -1) continue;
3567
+ const repo = repoDigest.slice(0, separatorIndex);
3568
+ const digest = repoDigest.slice(separatorIndex + 1);
3569
+ if (repo === repository && digest.length > 0) return digest;
3570
+ }
3571
+ return null;
3572
+ }
3573
+ function getQuadletImageRepository(image) {
3574
+ const digestSeparatorIndex = image.indexOf("@");
3575
+ if (digestSeparatorIndex !== -1) return image.slice(0, digestSeparatorIndex);
3576
+ const lastSlashIndex = image.lastIndexOf("/");
3577
+ const lastColonIndex = image.lastIndexOf(":");
3578
+ return lastColonIndex > lastSlashIndex ? image.slice(0, lastColonIndex) : image;
3579
+ }
3580
+ function parseQuadletImageInspectOutput(output) {
3581
+ try {
3582
+ const parsed = JSON.parse(output);
3583
+ if (!Array.isArray(parsed) || parsed.length === 0) return null;
3584
+ const [firstEntry] = parsed;
3585
+ if (!isQuadletInspectEntry(firstEntry)) return null;
3586
+ const repoDigests = Array.isArray(firstEntry.RepoDigests) ? firstEntry.RepoDigests : [];
3587
+ return {
3588
+ id: typeof firstEntry.Id === "string" && firstEntry.Id.length > 0 ? firstEntry.Id : null,
3589
+ repoDigests: repoDigests.filter(
3590
+ (entry) => typeof entry === "string" && entry.length > 0
3591
+ )
3592
+ };
3593
+ } catch {
3594
+ return null;
3595
+ }
3596
+ }
3597
+ function isQuadletInspectEntry(value) {
3598
+ return value != null && typeof value === "object";
3599
+ }
3600
+
3545
3601
  // src/modules/quadlet.ts
3546
3602
  var CONTAINERS_SYSTEMD_DIRECTORY_COMMAND = "mkdir -p '/etc/containers/systemd'";
3547
3603
  var QUADLET_FILE_MODE = "0644";
@@ -3588,6 +3644,61 @@ async function checkQuadletFile(parameters) {
3588
3644
  const remoteContent = await parameters.ssh.readFile(parameters.filePath);
3589
3645
  return remoteContent.trim() === parameters.content.trim() ? "ok" : NEEDS_APPLY;
3590
3646
  }
3647
+ async function inspectQuadletImageId(parameters) {
3648
+ const inspectResult = await parameters.ssh.exec(parameters.inspectCommand, {
3649
+ ignoreExitCode: true,
3650
+ silent: true
3651
+ });
3652
+ if (inspectResult.code !== 0) {
3653
+ return failedCommand(
3654
+ `[quadlet.updateImage: ${parameters.name}] podman image inspect failed`,
3655
+ inspectResult
3656
+ );
3657
+ }
3658
+ const imageId = readQuadletImageIdentifierFromInspectOutput(
3659
+ parameters.image,
3660
+ inspectResult.stdout
3661
+ );
3662
+ if (imageId == null) {
3663
+ return failed(
3664
+ `[quadlet.updateImage: ${parameters.name}] podman image inspect returned no digest or image ID`
3665
+ );
3666
+ }
3667
+ return imageId;
3668
+ }
3669
+ async function restartQuadletService(parameters) {
3670
+ const restartResult = await parameters.ssh.exec(
3671
+ `${SYSTEMCTL} restart ${shellQuote(parameters.serviceName)}`,
3672
+ {
3673
+ ignoreExitCode: true,
3674
+ silent: true
3675
+ }
3676
+ );
3677
+ return restartResult.code === 0 ? { detail: formatQuadletImageIdentifierDetail(parameters.imageId), status: "changed" } : failedCommand(
3678
+ `[quadlet.updateImage: ${parameters.name}] systemctl restart failed`,
3679
+ restartResult
3680
+ );
3681
+ }
3682
+ async function applyQuadletImageUpdate(parameters) {
3683
+ const pullResult = await parameters.ssh.exec(parameters.pullCommand, {
3684
+ ignoreExitCode: true,
3685
+ silent: true
3686
+ });
3687
+ if (pullResult.code !== 0) {
3688
+ return failedCommand(`[quadlet.updateImage: ${parameters.name}] podman pull failed`, pullResult);
3689
+ }
3690
+ if (!quadletPullOutputIndicatesChange(pullResult.stdout)) {
3691
+ return { status: "ok" };
3692
+ }
3693
+ const imageId = await inspectQuadletImageId(parameters);
3694
+ if (typeof imageId !== "string") return imageId;
3695
+ return restartQuadletService({
3696
+ imageId,
3697
+ name: parameters.name,
3698
+ serviceName: parameters.serviceName,
3699
+ ssh: parameters.ssh
3700
+ });
3701
+ }
3591
3702
  var quadlet = {
3592
3703
  /**
3593
3704
  * Write a Podman Quadlet `.container` definition and reload systemd when it changes.
@@ -3628,31 +3739,19 @@ var quadlet = {
3628
3739
  validateQuadletName(options.name);
3629
3740
  if (options.serviceName != null) validateQuadletName(options.serviceName);
3630
3741
  const pullCommand = buildQuadletImagePullCommand(options);
3742
+ const inspectCommand = buildQuadletImageInspectCommand(options.image);
3631
3743
  const serviceName = getQuadletContainerServiceName(options);
3632
3744
  return {
3633
3745
  async apply(ssh2) {
3634
3746
  if (!ssh2) return failed(`[quadlet.updateImage: ${options.name}] SSH connection is required`);
3635
- const pullResult = await ssh2.exec(pullCommand, {
3636
- ignoreExitCode: true,
3637
- silent: true
3638
- });
3639
- if (pullResult.code !== 0) {
3640
- return failedCommand(
3641
- `[quadlet.updateImage: ${options.name}] podman pull failed`,
3642
- pullResult
3643
- );
3644
- }
3645
- if (!quadletPullOutputIndicatesChange(pullResult.stdout)) {
3646
- return { status: "ok" };
3647
- }
3648
- const restartResult = await ssh2.exec(`${SYSTEMCTL} restart ${shellQuote(serviceName)}`, {
3649
- ignoreExitCode: true,
3650
- silent: true
3747
+ return applyQuadletImageUpdate({
3748
+ image: options.image,
3749
+ inspectCommand,
3750
+ name: options.name,
3751
+ pullCommand,
3752
+ serviceName,
3753
+ ssh: ssh2
3651
3754
  });
3652
- return restartResult.code === 0 ? { status: "changed" } : failedCommand(
3653
- `[quadlet.updateImage: ${options.name}] systemctl restart failed`,
3654
- restartResult
3655
- );
3656
3755
  },
3657
3756
  // eslint-disable-next-line @typescript-eslint/require-await -- Signal-style module
3658
3757
  async check() {
@@ -5742,4 +5841,4 @@ export {
5742
5841
  ufw,
5743
5842
  user
5744
5843
  };
5745
- //# sourceMappingURL=chunk-ENWMSERJ.js.map
5844
+ //# sourceMappingURL=chunk-FFQ6FR4N.js.map