@alchemy.run/docker 2.0.0-beta.84 → 2.0.0-beta.86
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 +16 -0
- package/lib/Bootstrap.d.ts.map +1 -1
- package/lib/Bootstrap.js +2 -1
- package/lib/Bootstrap.js.map +1 -1
- package/lib/BuildCache.d.ts +87 -0
- package/lib/BuildCache.d.ts.map +1 -0
- package/lib/BuildCache.js +39 -0
- package/lib/BuildCache.js.map +1 -0
- package/lib/BuildHash.d.ts +20 -3
- package/lib/BuildHash.d.ts.map +1 -1
- package/lib/BuildHash.js +60 -21
- package/lib/BuildHash.js.map +1 -1
- package/lib/BuildPlan.d.ts +202 -0
- package/lib/BuildPlan.d.ts.map +1 -0
- package/lib/BuildPlan.js +514 -0
- package/lib/BuildPlan.js.map +1 -0
- package/lib/Container.d.ts +122 -1
- package/lib/Container.d.ts.map +1 -1
- package/lib/Container.js +232 -4
- package/lib/Container.js.map +1 -1
- package/lib/Context.d.ts +20 -1
- package/lib/Context.d.ts.map +1 -1
- package/lib/Context.js +15 -0
- package/lib/Context.js.map +1 -1
- package/lib/Docker.d.ts +113 -1
- package/lib/Docker.d.ts.map +1 -1
- package/lib/Docker.js +50 -8
- package/lib/Docker.js.map +1 -1
- package/lib/Dockerfile.d.ts +0 -2
- package/lib/Dockerfile.d.ts.map +1 -1
- package/lib/Dockerfile.js.map +1 -1
- package/lib/DockerfileParser.d.ts +125 -0
- package/lib/DockerfileParser.d.ts.map +1 -0
- package/lib/DockerfileParser.js +1040 -0
- package/lib/DockerfileParser.js.map +1 -0
- package/lib/HealthcheckCommand.d.ts.map +1 -1
- package/lib/HealthcheckCommand.js.map +1 -1
- package/lib/Image.d.ts +69 -13
- package/lib/Image.d.ts.map +1 -1
- package/lib/Image.js +59 -9
- package/lib/Image.js.map +1 -1
- package/lib/ImageArchive.d.ts +41 -0
- package/lib/ImageArchive.d.ts.map +1 -0
- package/lib/ImageArchive.js +54 -0
- package/lib/ImageArchive.js.map +1 -0
- package/lib/ImageSource.d.ts +151 -8
- package/lib/ImageSource.d.ts.map +1 -1
- package/lib/ImageSource.js +247 -72
- package/lib/ImageSource.js.map +1 -1
- package/lib/Network.d.ts +175 -3
- package/lib/Network.d.ts.map +1 -1
- package/lib/Network.js +296 -8
- package/lib/Network.js.map +1 -1
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Providers.js.map +1 -1
- package/lib/Registry.d.ts.map +1 -1
- package/lib/Registry.js.map +1 -1
- package/lib/RegistryError.d.ts.map +1 -1
- package/lib/RegistryError.js.map +1 -1
- package/lib/RemoteImage.d.ts.map +1 -1
- package/lib/RemoteImage.js +1 -1
- package/lib/RemoteImage.js.map +1 -1
- package/lib/Service.d.ts +24 -3
- package/lib/Service.d.ts.map +1 -1
- package/lib/Service.js +154 -12
- package/lib/Service.js.map +1 -1
- package/lib/ServiceImage.d.ts.map +1 -1
- package/lib/ServiceImage.js.map +1 -1
- package/lib/ShellWord.d.ts +28 -0
- package/lib/ShellWord.d.ts.map +1 -0
- package/lib/ShellWord.js +301 -0
- package/lib/ShellWord.js.map +1 -0
- package/lib/Swarm.d.ts.map +1 -1
- package/lib/Swarm.js.map +1 -1
- package/lib/Volume.d.ts +24 -1
- package/lib/Volume.d.ts.map +1 -1
- package/lib/Volume.js +16 -0
- package/lib/Volume.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/package.json +9 -9
- package/src/Bootstrap.ts +2 -0
- package/src/BuildCache.ts +111 -0
- package/src/BuildHash.ts +85 -26
- package/src/BuildPlan.ts +758 -0
- package/src/Container.ts +361 -5
- package/src/Context.ts +27 -1
- package/src/Docker.ts +157 -9
- package/src/Dockerfile.ts +0 -2
- package/src/DockerfileParser.ts +1152 -0
- package/src/Image.ts +75 -15
- package/src/ImageArchive.ts +77 -0
- package/src/ImageSource.ts +391 -99
- package/src/Network.ts +432 -10
- package/src/RemoteImage.ts +1 -1
- package/src/Service.ts +246 -62
- package/src/ShellWord.ts +326 -0
- package/src/Volume.ts +33 -1
package/src/Service.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "alchemy/Runtime/Process";
|
|
11
11
|
import { Stack } from "alchemy/Stack";
|
|
12
12
|
import { createInternalTags, hasAlchemyTags } from "alchemy/Tags";
|
|
13
|
+
import * as Data from "effect/Data";
|
|
13
14
|
import * as Effect from "effect/Effect";
|
|
14
15
|
import * as Equal from "effect/Equal";
|
|
15
16
|
import type { PlatformError } from "effect/PlatformError";
|
|
@@ -345,9 +346,14 @@ export interface ServiceRuntimeContext extends HostRuntimeContext {
|
|
|
345
346
|
* image on a registry every node can reach (build with `Docker.Image` +
|
|
346
347
|
* `registry` and pass the pushed ref as `image` instead).
|
|
347
348
|
*
|
|
348
|
-
* Only replicated services are supported.
|
|
349
|
-
*
|
|
350
|
-
*
|
|
349
|
+
* Only replicated services are supported. Changes to the image, replicas,
|
|
350
|
+
* environment, labels, networks, ports, mounts, secrets, configs, constraints,
|
|
351
|
+
* healthcheck, and rollout or restart policies are applied in place with
|
|
352
|
+
* `docker service update`, so swarm rolls tasks over following `updateConfig`.
|
|
353
|
+
* An update that swarm rolls back or pauses fails the deploy with
|
|
354
|
+
* `DockerServiceUpdateFailed`. Changing the name, context, command, args,
|
|
355
|
+
* endpoint mode, placement preferences, or read-only root filesystem, or
|
|
356
|
+
* removing a policy setting, replaces the service (delete-then-create).
|
|
351
357
|
* ### Creating Services
|
|
352
358
|
* **Example:** Replicated Nginx
|
|
353
359
|
* ```typescript
|
|
@@ -519,16 +525,39 @@ export const Service: Platform<Service, ServiceServices, ServiceShape, ServiceRu
|
|
|
519
525
|
const isBundledService = (props: ServiceProps): props is BundledServiceProps =>
|
|
520
526
|
typeof (props as BundledServiceProps).main === "string";
|
|
521
527
|
|
|
528
|
+
/**
|
|
529
|
+
* A `docker service update` that swarm rolled back or paused instead of
|
|
530
|
+
* completing. `--detach=false` exits 0 on a rollback and 1 on a pause, so the
|
|
531
|
+
* provider reads the service's `UpdateStatus` after the update.
|
|
532
|
+
*/
|
|
533
|
+
export class DockerServiceUpdateFailed extends Data.TaggedError("DockerServiceUpdateFailed")<{
|
|
534
|
+
readonly service: string;
|
|
535
|
+
readonly state: string;
|
|
536
|
+
readonly reason: string;
|
|
537
|
+
}> {
|
|
538
|
+
override get message() {
|
|
539
|
+
return `Update of service "${this.service}" ended in ${this.state}: ${this.reason}`;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
522
543
|
type ServiceInspect = {
|
|
523
544
|
ID: string;
|
|
524
545
|
CreatedAt?: string;
|
|
525
546
|
UpdatedAt?: string;
|
|
547
|
+
UpdateStatus?: {
|
|
548
|
+
State?: string;
|
|
549
|
+
Message?: string;
|
|
550
|
+
};
|
|
526
551
|
Spec: {
|
|
527
552
|
Name: string;
|
|
528
553
|
Labels?: Record<string, string> | null;
|
|
529
554
|
TaskTemplate?: {
|
|
530
555
|
ContainerSpec?: {
|
|
531
556
|
Image?: string;
|
|
557
|
+
Env?: string[] | null;
|
|
558
|
+
Mounts?: Array<{ Target?: string }> | null;
|
|
559
|
+
Secrets?: Array<{ SecretName?: string }> | null;
|
|
560
|
+
Configs?: Array<{ ConfigName?: string }> | null;
|
|
532
561
|
};
|
|
533
562
|
Networks?: Array<{ Target?: string }>;
|
|
534
563
|
Placement?: {
|
|
@@ -623,18 +652,20 @@ export const ServiceProvider = () =>
|
|
|
623
652
|
const newDesired = yield* normalizeDesired(id, news, instanceId);
|
|
624
653
|
|
|
625
654
|
if (!Equal.equals(oldDesired, newDesired)) {
|
|
626
|
-
return
|
|
655
|
+
return requiresReplacement(oldDesired, newDesired)
|
|
656
|
+
? { action: "replace" as const, deleteFirst: true }
|
|
657
|
+
: { action: "update" as const };
|
|
627
658
|
}
|
|
628
659
|
|
|
629
660
|
// Bundled program: the image identity is the content hash — a code
|
|
630
|
-
// or bootstrap change
|
|
661
|
+
// or bootstrap change rolls the service onto the new image.
|
|
631
662
|
if (isBundledService(news)) {
|
|
632
663
|
const hash = yield* serviceImage.hash({
|
|
633
664
|
source: news,
|
|
634
665
|
isExternal: news.isExternal,
|
|
635
666
|
});
|
|
636
667
|
if (hash !== output?.code?.hash) {
|
|
637
|
-
return { action: "
|
|
668
|
+
return { action: "update" as const };
|
|
638
669
|
}
|
|
639
670
|
}
|
|
640
671
|
|
|
@@ -673,61 +704,144 @@ export const ServiceProvider = () =>
|
|
|
673
704
|
const labelsToRemove = Object.keys(currentLabels).filter(
|
|
674
705
|
(k) => !(k in allDesiredLabels),
|
|
675
706
|
);
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
desired
|
|
722
|
-
|
|
707
|
+
// `service update` only adds and removes list entries, so the
|
|
708
|
+
// removals are computed from the observed spec rather than olds.
|
|
709
|
+
const spec = current.Spec.TaskTemplate?.ContainerSpec;
|
|
710
|
+
const envToRemove = (spec?.Env ?? [])
|
|
711
|
+
.map((entry) => entry.split("=", 1)[0]!)
|
|
712
|
+
.filter((key) => !(key in environment));
|
|
713
|
+
const currentConstraints = current.Spec.TaskTemplate?.Placement?.Constraints ?? [];
|
|
714
|
+
const constraintsToRemove = currentConstraints.filter(
|
|
715
|
+
(c) => !desired.constraints.includes(c),
|
|
716
|
+
);
|
|
717
|
+
// `--constraint-add` appends without de-duplicating.
|
|
718
|
+
const constraintsToAdd = desired.constraints.filter(
|
|
719
|
+
(c) => !currentConstraints.includes(c),
|
|
720
|
+
);
|
|
721
|
+
const desiredTargets = new Set(desired.volumes.map((v) => v.containerPath));
|
|
722
|
+
const mountsToRemove = (spec?.Mounts ?? []).flatMap((m) =>
|
|
723
|
+
m.Target && !desiredTargets.has(m.Target) ? [m.Target] : [],
|
|
724
|
+
);
|
|
725
|
+
const desiredPorts = new Set(desired.ports.map(formatPublish));
|
|
726
|
+
const portsToRemove = (current.Spec.EndpointSpec?.Ports ?? []).flatMap((p) =>
|
|
727
|
+
p.TargetPort !== undefined &&
|
|
728
|
+
!desiredPorts.has(
|
|
729
|
+
formatPublish({
|
|
730
|
+
external: p.PublishedPort,
|
|
731
|
+
internal: p.TargetPort,
|
|
732
|
+
protocol: p.Protocol,
|
|
733
|
+
mode: p.PublishMode,
|
|
734
|
+
}),
|
|
735
|
+
)
|
|
736
|
+
? // `--publish-rm` matches target, protocol and publish mode.
|
|
737
|
+
[
|
|
738
|
+
`target=${p.TargetPort},protocol=${p.Protocol ?? "tcp"},mode=${p.PublishMode ?? "ingress"}`,
|
|
739
|
+
]
|
|
740
|
+
: [],
|
|
741
|
+
);
|
|
742
|
+
// A rollback exits 0 but a paused rollout exits 1, so the
|
|
743
|
+
// outcome is read from `UpdateStatus` either way.
|
|
744
|
+
const updateError = yield* docker.service
|
|
745
|
+
.update({
|
|
746
|
+
context: desired.context,
|
|
747
|
+
id: output.id,
|
|
748
|
+
image,
|
|
749
|
+
replicas: desired.replicas,
|
|
750
|
+
"endpoint-mode": desired.endpointMode,
|
|
751
|
+
// Attaching a network the service is already on fails, so
|
|
752
|
+
// every attachment is detached and the desired set re-added.
|
|
753
|
+
"network-rm": (current.Spec.TaskTemplate?.Networks ?? []).flatMap((n) =>
|
|
754
|
+
n.Target ? [n.Target] : [],
|
|
755
|
+
),
|
|
756
|
+
"network-add": desired.networks.map(formatNetwork),
|
|
757
|
+
"constraint-rm": constraintsToRemove,
|
|
758
|
+
"constraint-add": constraintsToAdd,
|
|
759
|
+
"replicas-max-per-node": desired.maxReplicasPerNode,
|
|
760
|
+
"update-parallelism": desired.updateConfig?.parallelism,
|
|
761
|
+
"update-delay": desired.updateConfig?.delay,
|
|
762
|
+
"update-monitor": desired.updateConfig?.monitor,
|
|
763
|
+
"update-failure-action": desired.updateConfig?.failureAction,
|
|
764
|
+
"update-max-failure-ratio": desired.updateConfig?.maxFailureRatio,
|
|
765
|
+
"update-order": desired.updateConfig?.order,
|
|
766
|
+
"rollback-parallelism": desired.rollbackConfig?.parallelism,
|
|
767
|
+
"rollback-delay": desired.rollbackConfig?.delay,
|
|
768
|
+
"rollback-monitor": desired.rollbackConfig?.monitor,
|
|
769
|
+
"rollback-failure-action": desired.rollbackConfig?.failureAction,
|
|
770
|
+
"rollback-max-failure-ratio": desired.rollbackConfig?.maxFailureRatio,
|
|
771
|
+
"rollback-order": desired.rollbackConfig?.order,
|
|
772
|
+
"restart-condition": desired.restartPolicy?.condition,
|
|
773
|
+
"restart-delay": desired.restartPolicy?.delay,
|
|
774
|
+
"restart-max-attempts": desired.restartPolicy?.maxAttempts,
|
|
775
|
+
"restart-window": desired.restartPolicy?.window,
|
|
776
|
+
"health-cmd": desired.healthcheck
|
|
777
|
+
? healthcheckCommand(desired.healthcheck.cmd)
|
|
723
778
|
: undefined,
|
|
724
|
-
|
|
779
|
+
"no-healthcheck":
|
|
780
|
+
desired.healthcheck !== undefined &&
|
|
781
|
+
isHealthcheckDisabled(desired.healthcheck.cmd),
|
|
782
|
+
"health-interval": desired.healthcheck?.interval,
|
|
783
|
+
"health-timeout": desired.healthcheck?.timeout,
|
|
784
|
+
"health-retries": desired.healthcheck?.retries,
|
|
785
|
+
"health-start-period": desired.healthcheck?.startPeriod,
|
|
786
|
+
"stop-grace-period": desired.stopGracePeriod,
|
|
787
|
+
"mount-rm": mountsToRemove,
|
|
788
|
+
"mount-add": desired.volumes.map(formatMount),
|
|
789
|
+
// Re-adding a ref that is still attached conflicts on its
|
|
790
|
+
// target, so every attached ref is removed and the desired
|
|
791
|
+
// set re-added in the same update.
|
|
792
|
+
"secret-rm": (spec?.Secrets ?? []).flatMap((s) =>
|
|
793
|
+
s.SecretName ? [s.SecretName] : [],
|
|
794
|
+
),
|
|
795
|
+
"secret-add": desired.secrets.map(formatRef),
|
|
796
|
+
"config-rm": (spec?.Configs ?? []).flatMap((c) =>
|
|
797
|
+
c.ConfigName ? [c.ConfigName] : [],
|
|
798
|
+
),
|
|
799
|
+
"config-add": desired.configs.map(formatRef),
|
|
800
|
+
"read-only": desired.readOnlyRootFs || undefined,
|
|
801
|
+
"publish-rm": portsToRemove,
|
|
802
|
+
"publish-add": desired.ports.map(formatPublish),
|
|
803
|
+
"label-add": allDesiredLabels,
|
|
804
|
+
"label-rm": labelsToRemove,
|
|
805
|
+
"env-rm": envToRemove,
|
|
806
|
+
"env-add": environment,
|
|
807
|
+
args:
|
|
808
|
+
desired.command.length > 0
|
|
809
|
+
? desired.command.concat(desired.args).join(" ")
|
|
810
|
+
: undefined,
|
|
811
|
+
})
|
|
812
|
+
.pipe(
|
|
813
|
+
Effect.as(undefined),
|
|
814
|
+
Effect.catchTag("PlatformError", (error) => Effect.succeed(error)),
|
|
815
|
+
);
|
|
725
816
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
817
|
+
const updated = yield* inspectOrDie(inspect, output.id, desired.context);
|
|
818
|
+
// Swarm clears `UpdateStatus` whenever it accepts an update, so
|
|
819
|
+
// any status here belongs to this update.
|
|
820
|
+
const status = updated.UpdateStatus;
|
|
821
|
+
if (
|
|
822
|
+
status?.State === "rollback_completed" ||
|
|
823
|
+
status?.State === "rollback_paused" ||
|
|
824
|
+
status?.State === "paused"
|
|
825
|
+
) {
|
|
826
|
+
return yield* new DockerServiceUpdateFailed({
|
|
827
|
+
service: updated.Spec.Name,
|
|
828
|
+
state: status.State,
|
|
829
|
+
reason: status.Message ?? "",
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
if (updateError) {
|
|
833
|
+
return yield* updateError;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Best-effort: the service now runs the new content-addressed
|
|
837
|
+
// image, so drop the one built for the previous bundle.
|
|
838
|
+
if (output.code && output.code.hash !== resolved.codeHash) {
|
|
839
|
+
yield* docker.image
|
|
840
|
+
.remove(output.image, true, desired.context)
|
|
841
|
+
.pipe(Effect.catchCause(() => Effect.void));
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return toServiceAttributes(updated, desired.context, code);
|
|
731
845
|
}
|
|
732
846
|
}
|
|
733
847
|
|
|
@@ -745,9 +859,7 @@ export const ServiceProvider = () =>
|
|
|
745
859
|
image,
|
|
746
860
|
replicas: desired.replicas,
|
|
747
861
|
"endpoint-mode": desired.endpointMode,
|
|
748
|
-
network: desired.networks.map(
|
|
749
|
-
[`name=${n.name}`, ...(n.aliases ?? []).map((a) => `alias=${a}`)].join(","),
|
|
750
|
-
),
|
|
862
|
+
network: desired.networks.map(formatNetwork),
|
|
751
863
|
constraint: desired.constraints,
|
|
752
864
|
"replicas-max-per-node": desired.maxReplicasPerNode,
|
|
753
865
|
"placement-pref": desired.preferences,
|
|
@@ -866,6 +978,75 @@ const normalizeDesired = (id: string, props: ServiceProps, instanceId: string) =
|
|
|
866
978
|
})),
|
|
867
979
|
);
|
|
868
980
|
|
|
981
|
+
type DesiredService = Effect.Success<ReturnType<typeof normalizeDesired>>;
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Fields `docker service update` changes in place. Swarm rolls the tasks
|
|
985
|
+
* onto the new spec following `updateConfig`, so `order: "start-first"` and
|
|
986
|
+
* `failureAction: "rollback"` apply.
|
|
987
|
+
*/
|
|
988
|
+
const inPlaceFields = new Set<keyof DesiredService>([
|
|
989
|
+
"image",
|
|
990
|
+
"replicas",
|
|
991
|
+
"environment",
|
|
992
|
+
"labels",
|
|
993
|
+
"networks",
|
|
994
|
+
"constraints",
|
|
995
|
+
"maxReplicasPerNode",
|
|
996
|
+
"updateConfig",
|
|
997
|
+
"rollbackConfig",
|
|
998
|
+
"restartPolicy",
|
|
999
|
+
"healthcheck",
|
|
1000
|
+
"stopGracePeriod",
|
|
1001
|
+
"volumes",
|
|
1002
|
+
"secrets",
|
|
1003
|
+
"configs",
|
|
1004
|
+
"ports",
|
|
1005
|
+
]);
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Settings `service update` can change but not reset to the engine default:
|
|
1009
|
+
* removing one, or a field inside one, replaces the service.
|
|
1010
|
+
*/
|
|
1011
|
+
const resettableFields = new Set<keyof DesiredService>([
|
|
1012
|
+
"maxReplicasPerNode",
|
|
1013
|
+
"updateConfig",
|
|
1014
|
+
"rollbackConfig",
|
|
1015
|
+
"restartPolicy",
|
|
1016
|
+
"healthcheck",
|
|
1017
|
+
"stopGracePeriod",
|
|
1018
|
+
]);
|
|
1019
|
+
|
|
1020
|
+
const clearsSetting = (olds: unknown, news: unknown): boolean => {
|
|
1021
|
+
if (olds === undefined) return false;
|
|
1022
|
+
if (news === undefined) return true;
|
|
1023
|
+
if (typeof olds !== "object" || olds === null) return false;
|
|
1024
|
+
return Object.entries(olds).some(
|
|
1025
|
+
([key, value]) => value !== undefined && (news as Record<string, unknown>)[key] === undefined,
|
|
1026
|
+
);
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* `--publish-rm` matches target, protocol and mode, and `--publish-add` skips
|
|
1031
|
+
* a port already in the spec. Dropping one of several ports that share a
|
|
1032
|
+
* target therefore drops the kept ones too, so that change replaces.
|
|
1033
|
+
*/
|
|
1034
|
+
const dropsSharedTargetPort = (olds: DesiredService, news: DesiredService): boolean => {
|
|
1035
|
+
const kept = new Set(news.ports.map(formatPublish));
|
|
1036
|
+
const key = (port: Service.PortMapping) => `${port.internal}/${port.protocol}/${port.mode}`;
|
|
1037
|
+
const removedKeys = new Set(olds.ports.filter((p) => !kept.has(formatPublish(p))).map(key));
|
|
1038
|
+
return olds.ports.some((p) => kept.has(formatPublish(p)) && removedKeys.has(key(p)));
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
const requiresReplacement = (olds: DesiredService, news: DesiredService): boolean =>
|
|
1042
|
+
dropsSharedTargetPort(olds, news) ||
|
|
1043
|
+
(Object.keys(news) as Array<keyof DesiredService>).some(
|
|
1044
|
+
(key) =>
|
|
1045
|
+
!deepEqual(olds[key], news[key]) &&
|
|
1046
|
+
(!inPlaceFields.has(key) ||
|
|
1047
|
+
(resettableFields.has(key) && clearsSetting(olds[key], news[key]))),
|
|
1048
|
+
);
|
|
1049
|
+
|
|
869
1050
|
const normalizeImageRef = (image: Service.Image): string =>
|
|
870
1051
|
typeof image === "string" ? image : image.imageRef;
|
|
871
1052
|
|
|
@@ -928,6 +1109,9 @@ const formatMount = (volume: Service.VolumeMapping): string =>
|
|
|
928
1109
|
...(volume.readOnly ? ["readonly=true"] : []),
|
|
929
1110
|
].join(",");
|
|
930
1111
|
|
|
1112
|
+
const formatNetwork = (network: Service.NetworkAttachment): string =>
|
|
1113
|
+
[`name=${network.name}`, ...(network.aliases ?? []).map((a) => `alias=${a}`)].join(",");
|
|
1114
|
+
|
|
931
1115
|
const formatRef = (ref: Service.SecretRef | Service.ConfigRef): string =>
|
|
932
1116
|
Object.entries(ref)
|
|
933
1117
|
.filter(([, value]) => value !== undefined)
|