@microagi/alchemy-gcp 0.2.1 → 0.3.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/CHANGELOG.md +65 -0
- package/README.md +54 -2
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Providers.js +5 -1
- package/lib/Providers.js.map +1 -1
- package/lib/Run/IamMember.d.ts +70 -0
- package/lib/Run/IamMember.d.ts.map +1 -0
- package/lib/Run/IamMember.js +65 -0
- package/lib/Run/IamMember.js.map +1 -0
- package/lib/Run/IamSync.d.ts +80 -0
- package/lib/Run/IamSync.d.ts.map +1 -0
- package/lib/Run/IamSync.js +73 -0
- package/lib/Run/IamSync.js.map +1 -0
- package/lib/Run/Job.d.ts +167 -0
- package/lib/Run/Job.d.ts.map +1 -0
- package/lib/Run/Job.js +199 -0
- package/lib/Run/Job.js.map +1 -0
- package/lib/Run/Operations.d.ts +47 -0
- package/lib/Run/Operations.d.ts.map +1 -0
- package/lib/Run/Operations.js +46 -0
- package/lib/Run/Operations.js.map +1 -0
- package/lib/Run/Service.d.ts +193 -0
- package/lib/Run/Service.d.ts.map +1 -0
- package/lib/Run/Service.js +247 -0
- package/lib/Run/Service.js.map +1 -0
- package/lib/Run/Validation.d.ts +54 -0
- package/lib/Run/Validation.d.ts.map +1 -0
- package/lib/Run/Validation.js +129 -0
- package/lib/Run/Validation.js.map +1 -0
- package/lib/Run/index.d.ts +51 -0
- package/lib/Run/index.d.ts.map +1 -0
- package/lib/Run/index.js +4 -0
- package/lib/Run/index.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/package.json +5 -4
- package/src/Providers.ts +6 -0
- package/src/Run/IamMember.ts +79 -0
- package/src/Run/IamSync.ts +134 -0
- package/src/Run/Job.ts +453 -0
- package/src/Run/Operations.ts +77 -0
- package/src/Run/Service.ts +541 -0
- package/src/Run/Validation.ts +153 -0
- package/src/Run/index.ts +52 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ConfigError } from "@distilled.cloud/gcp";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Plan-time validation for Cloud Run resource props. Failing fast with
|
|
6
|
+
* a typed `ConfigError` is much friendlier than letting the user wait
|
|
7
|
+
* 30 s for the server to reject the request — and unlike server-side
|
|
8
|
+
* errors, these surface before any state mutation happens.
|
|
9
|
+
*
|
|
10
|
+
* Used by both `Service` and `Job` since the constraints are shared:
|
|
11
|
+
* resource naming, label namespacing, and the "at least one container"
|
|
12
|
+
* rule on the template.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cloud Run resource names must match the
|
|
17
|
+
* [RFC 1123](https://datatracker.ietf.org/doc/html/rfc1123) label
|
|
18
|
+
* subset:
|
|
19
|
+
* - lowercase letters, digits, and hyphens
|
|
20
|
+
* - begin with a letter, not end with a hyphen
|
|
21
|
+
* - <50 chars (server hard cap, distinct from the wider 63-char label
|
|
22
|
+
* limit other GCP resources use)
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
const RUN_NAME_RE = /^[a-z]([-a-z0-9]{0,47}[a-z0-9])?$/;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Cloud Run rejects labels and annotations with keys in any of these
|
|
30
|
+
* namespaces. Listed in `vendor/distilled/packages/gcp/src/services/run-v2.ts`
|
|
31
|
+
* doc comments next to every `labels` / `annotations` field. Failing
|
|
32
|
+
* fast here saves a 400 round-trip from the server.
|
|
33
|
+
*/
|
|
34
|
+
const RESERVED_LABEL_NAMESPACES = [
|
|
35
|
+
"run.googleapis.com/",
|
|
36
|
+
"cloud.googleapis.com/",
|
|
37
|
+
"serving.knative.dev/",
|
|
38
|
+
"autoscaling.knative.dev/",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const reservedNamespaceFor = (key: string): string | undefined =>
|
|
42
|
+
RESERVED_LABEL_NAMESPACES.find((ns) => key.startsWith(ns));
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validate a Cloud Run resource name (Service or Job). Returns a
|
|
46
|
+
* `ConfigError` Effect on the failure channel if the name is invalid,
|
|
47
|
+
* otherwise succeeds.
|
|
48
|
+
*
|
|
49
|
+
* The name comes from `props.name` (when the user supplied one) or
|
|
50
|
+
* `createPhysicalName({ id, maxLength: 49 })` (when defaulted). The
|
|
51
|
+
* defaulted path is already capped at 49; this guard mostly protects
|
|
52
|
+
* the user-supplied path against a server-side reject after a long
|
|
53
|
+
* deploy.
|
|
54
|
+
*/
|
|
55
|
+
export const validateRunName = (
|
|
56
|
+
kind: "Service" | "Job",
|
|
57
|
+
name: string,
|
|
58
|
+
): Effect.Effect<void, ConfigError> => {
|
|
59
|
+
if (RUN_NAME_RE.test(name) && name.length < 50) return Effect.void;
|
|
60
|
+
return Effect.fail(
|
|
61
|
+
new ConfigError({
|
|
62
|
+
message: `Cloud Run ${kind} name ${JSON.stringify(name)} is invalid: must match /^[a-z]([-a-z0-9]{0,47}[a-z0-9])?$/ (lowercase letters/digits/hyphens, starts with letter, no trailing hyphen, <50 chars).`,
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Reject user labels in any of the Cloud Run reserved namespaces — the
|
|
69
|
+
* server's 400 message ("Labels with run.googleapis.com namespace are
|
|
70
|
+
* not allowed") is clear enough, but failing at plan time means the
|
|
71
|
+
* stack never starts a deploy that's guaranteed to fail.
|
|
72
|
+
*
|
|
73
|
+
* Alchemy internals are merged *on top* of user labels and are NOT
|
|
74
|
+
* subject to this check — they use the un-namespaced `alchemy_*` keys.
|
|
75
|
+
*/
|
|
76
|
+
export const validateLabels = (
|
|
77
|
+
labels: Record<string, string> | undefined,
|
|
78
|
+
): Effect.Effect<void, ConfigError> => {
|
|
79
|
+
if (!labels) return Effect.void;
|
|
80
|
+
for (const key of Object.keys(labels)) {
|
|
81
|
+
const ns = reservedNamespaceFor(key);
|
|
82
|
+
if (ns) {
|
|
83
|
+
return Effect.fail(
|
|
84
|
+
new ConfigError({
|
|
85
|
+
message: `Label key ${JSON.stringify(key)} uses reserved namespace ${JSON.stringify(ns)} — Cloud Run will reject this on create/patch. Strip the prefix or pick a different key.`,
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return Effect.void;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Same check, applied to annotations (server has the same reservation). */
|
|
94
|
+
export const validateAnnotations = (
|
|
95
|
+
annotations: Record<string, string> | undefined,
|
|
96
|
+
): Effect.Effect<void, ConfigError> => validateLabels(annotations);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Cloud Run requires every Service template and Job task template to
|
|
100
|
+
* declare at least one container. A `containers: []` body sails through
|
|
101
|
+
* client-side type checks but the server rejects with a generic 400.
|
|
102
|
+
*/
|
|
103
|
+
export const validateContainers = (
|
|
104
|
+
kind: "Service" | "Job",
|
|
105
|
+
containers: ReadonlyArray<unknown> | undefined,
|
|
106
|
+
): Effect.Effect<void, ConfigError> => {
|
|
107
|
+
if (containers && containers.length > 0) return Effect.void;
|
|
108
|
+
return Effect.fail(
|
|
109
|
+
new ConfigError({
|
|
110
|
+
message: `Cloud Run ${kind} template must declare at least one container. Did you forget \`template.containers: [{ image: "…" }]\`?`,
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Reshape a `BadRequest` from a Cloud Run create or patch into a
|
|
117
|
+
* `ConfigError` with a remediation hint for the top failure modes:
|
|
118
|
+
*
|
|
119
|
+
* 1. **Billing not enabled** — the project's billing-account attach
|
|
120
|
+
* was missing or detached. Common after a fresh project + ApiEnable
|
|
121
|
+
* before billing reconciles.
|
|
122
|
+
* 2. **Image not in an approved registry** — Cloud Run defaults reject
|
|
123
|
+
* Docker Hub and most non-Google registries when Binary Authorization
|
|
124
|
+
* is on. Surface the registry-allow-list pointer.
|
|
125
|
+
* 3. **Reserved label/annotation namespace** — we catch user-supplied
|
|
126
|
+
* cases in `validateLabels`, but server-side checks also fire for
|
|
127
|
+
* fields nested inside `template` (we don't recurse) so leave this
|
|
128
|
+
* as a passthrough hint.
|
|
129
|
+
*
|
|
130
|
+
* Anything else propagates verbatim — the underlying GCP message is
|
|
131
|
+
* usually clear ("invalid image reference", "memory limit too low").
|
|
132
|
+
*/
|
|
133
|
+
export const reshapeBadRequest =
|
|
134
|
+
(kind: "Service" | "Job", op: "create" | "patch") =>
|
|
135
|
+
(e: { message?: string }): Effect.Effect<never, ConfigError> => {
|
|
136
|
+
const underlying = e.message ?? `unknown 400 from ${kind} ${op}`;
|
|
137
|
+
let hint = "";
|
|
138
|
+
if (/billing/i.test(underlying)) {
|
|
139
|
+
hint =
|
|
140
|
+
" Cloud Run requires billing to be enabled on the project. Attach a billing account (see `GCP.Project({ billingAccount })`) and retry.";
|
|
141
|
+
} else if (/registry|image.*not.*allowed|disallowed.*image/i.test(underlying)) {
|
|
142
|
+
hint =
|
|
143
|
+
" Cloud Run rejects images from non-Google registries when Binary Authorization is on. Push to Artifact Registry (`*-docker.pkg.dev`) or relax the binary-auth policy.";
|
|
144
|
+
} else if (/run\.googleapis\.com|knative\.dev|cloud\.googleapis\.com/i.test(underlying)) {
|
|
145
|
+
hint =
|
|
146
|
+
" A reserved label/annotation namespace is in use. Cloud Run forbids `run.googleapis.com/*`, `cloud.googleapis.com/*`, `serving.knative.dev/*`, and `autoscaling.knative.dev/*` on user fields.";
|
|
147
|
+
}
|
|
148
|
+
return Effect.fail(
|
|
149
|
+
new ConfigError({
|
|
150
|
+
message: `Cloud Run ${kind} ${op} rejected: ${underlying}.${hint}`,
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
};
|
package/src/Run/index.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type * as run from "@distilled.cloud/gcp/run-v2";
|
|
2
|
+
|
|
3
|
+
export { Job, JobProvider } from "./Job.ts";
|
|
4
|
+
export type { JobAttributes, JobProps } from "./Job.ts";
|
|
5
|
+
export { jobIamMember, serviceIamMember } from "./IamMember.ts";
|
|
6
|
+
export type { RunIamBinding, RunIamBindingContract } from "./IamSync.ts";
|
|
7
|
+
export { Service, ServiceProvider } from "./Service.ts";
|
|
8
|
+
export type { ServiceAttributes, ServiceProps } from "./Service.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Clean aliases for the most commonly-used Cloud Run v2 SDK types,
|
|
12
|
+
* re-exported from `@distilled.cloud/gcp/run-v2` so consumers don't
|
|
13
|
+
* need to import from the underlying SDK directly. The aliases are
|
|
14
|
+
* type-only — they share identity with the distilled types, so values
|
|
15
|
+
* are interchangeable.
|
|
16
|
+
*
|
|
17
|
+
* The `GoogleCloudRunV2*` prefix on the SDK types is generated from
|
|
18
|
+
* the GCP Discovery Document and is awkward to consume; these aliases
|
|
19
|
+
* mirror the casing the GCP REST docs use ("RevisionTemplate",
|
|
20
|
+
* "Container", "TrafficTarget"), which is what most users will
|
|
21
|
+
* already have in their heads.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import * as GCP from "@microagi/alchemy-gcp";
|
|
26
|
+
*
|
|
27
|
+
* const template: GCP.RevisionTemplate = {
|
|
28
|
+
* containers: [{ image: "..." }],
|
|
29
|
+
* };
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export type RevisionTemplate = run.GoogleCloudRunV2RevisionTemplate;
|
|
33
|
+
export type ExecutionTemplate = run.GoogleCloudRunV2ExecutionTemplate;
|
|
34
|
+
export type TaskTemplate = run.GoogleCloudRunV2TaskTemplate;
|
|
35
|
+
export type Container = run.GoogleCloudRunV2Container;
|
|
36
|
+
export type ContainerPort = run.GoogleCloudRunV2ContainerPort;
|
|
37
|
+
export type EnvVar = run.GoogleCloudRunV2EnvVar;
|
|
38
|
+
export type EnvVarSource = run.GoogleCloudRunV2EnvVarSource;
|
|
39
|
+
export type ResourceRequirements = run.GoogleCloudRunV2ResourceRequirements;
|
|
40
|
+
export type Probe = run.GoogleCloudRunV2Probe;
|
|
41
|
+
export type VpcAccess = run.GoogleCloudRunV2VpcAccess;
|
|
42
|
+
export type NetworkInterface = run.GoogleCloudRunV2NetworkInterface;
|
|
43
|
+
export type NodeSelector = run.GoogleCloudRunV2NodeSelector;
|
|
44
|
+
export type TrafficTarget = run.GoogleCloudRunV2TrafficTarget;
|
|
45
|
+
export type TrafficTargetStatus = run.GoogleCloudRunV2TrafficTargetStatus;
|
|
46
|
+
export type ServiceScaling = run.GoogleCloudRunV2ServiceScaling;
|
|
47
|
+
export type BinaryAuthorization = run.GoogleCloudRunV2BinaryAuthorization;
|
|
48
|
+
export type Volume = run.GoogleCloudRunV2Volume;
|
|
49
|
+
export type VolumeMount = run.GoogleCloudRunV2VolumeMount;
|
|
50
|
+
export type Condition = run.GoogleCloudRunV2Condition;
|
|
51
|
+
export type ExecutionReference = run.GoogleCloudRunV2ExecutionReference;
|
|
52
|
+
export type SecretKeySelector = run.GoogleCloudRunV2SecretKeySelector;
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from "./CloudResourceManager/index.ts";
|
|
|
11
11
|
export * from "./Compute/index.ts";
|
|
12
12
|
export * from "./Container/index.ts";
|
|
13
13
|
export * from "./ManagedLustre/index.ts";
|
|
14
|
+
export * from "./Run/index.ts";
|
|
14
15
|
export * from "./ServiceNetworking/index.ts";
|
|
15
16
|
export * from "./ServiceUsage/index.ts";
|
|
16
17
|
export * from "./Providers.ts";
|