@microagi/alchemy-gcp 0.8.0 → 0.9.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/lib/Container/NodePool.d.ts +10 -0
- package/lib/Container/NodePool.d.ts.map +1 -1
- package/lib/Container/NodePool.js +4 -0
- package/lib/Container/NodePool.js.map +1 -1
- package/lib/Iam/ServiceAccount.d.ts.map +1 -1
- package/lib/Iam/ServiceAccount.js +21 -2
- package/lib/Iam/ServiceAccount.js.map +1 -1
- package/lib/Iam/ServiceAccountKey.d.ts +72 -0
- package/lib/Iam/ServiceAccountKey.d.ts.map +1 -0
- package/lib/Iam/ServiceAccountKey.js +164 -0
- package/lib/Iam/ServiceAccountKey.js.map +1 -0
- package/lib/Iam/index.d.ts +2 -0
- package/lib/Iam/index.d.ts.map +1 -1
- package/lib/Iam/index.js +1 -0
- package/lib/Iam/index.js.map +1 -1
- package/lib/Kubernetes/Helm.d.ts +132 -0
- package/lib/Kubernetes/Helm.d.ts.map +1 -0
- package/lib/Kubernetes/Helm.js +314 -0
- package/lib/Kubernetes/Helm.js.map +1 -0
- package/lib/Kubernetes/index.d.ts +1 -0
- package/lib/Kubernetes/index.d.ts.map +1 -1
- package/lib/Kubernetes/index.js +1 -0
- package/lib/Kubernetes/index.js.map +1 -1
- package/lib/Providers.d.ts +1 -1
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Providers.js +7 -1
- package/lib/Providers.js.map +1 -1
- package/lib/Storage/Bucket.d.ts +117 -0
- package/lib/Storage/Bucket.d.ts.map +1 -0
- package/lib/Storage/Bucket.js +239 -0
- package/lib/Storage/Bucket.js.map +1 -0
- package/lib/Storage/BucketIamMember.d.ts +29 -0
- package/lib/Storage/BucketIamMember.d.ts.map +1 -0
- package/lib/Storage/BucketIamMember.js +26 -0
- package/lib/Storage/BucketIamMember.js.map +1 -0
- package/lib/Storage/index.d.ts +4 -0
- package/lib/Storage/index.d.ts.map +1 -0
- package/lib/Storage/index.js +3 -0
- package/lib/Storage/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 +2 -2
- package/src/Container/NodePool.ts +12 -0
- package/src/Iam/ServiceAccount.ts +21 -2
- package/src/Iam/ServiceAccountKey.ts +284 -0
- package/src/Iam/index.ts +5 -0
- package/src/Kubernetes/Helm.ts +536 -0
- package/src/Kubernetes/index.ts +1 -0
- package/src/Providers.ts +18 -0
- package/src/Storage/Bucket.ts +439 -0
- package/src/Storage/BucketIamMember.ts +33 -0
- package/src/Storage/index.ts +10 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as storage from "@distilled.cloud/gcp/storage-v1";
|
|
2
|
+
import { Resource } from "alchemy";
|
|
3
|
+
import * as Provider from "alchemy/Provider";
|
|
4
|
+
import type * as GCP from "../Providers.ts";
|
|
5
|
+
/**
|
|
6
|
+
* A GCS bucket. Lives under a
|
|
7
|
+
* {@link import("../CloudResourceManager/Project.ts").Project} and serves
|
|
8
|
+
* as the target for {@link storageBucketIamMember} bindings (bucket-level
|
|
9
|
+
* IAM grants merged into a single `setIamPolicy` call).
|
|
10
|
+
*
|
|
11
|
+
* Adoption is gated on alchemy internal labels (`alchemy_app` /
|
|
12
|
+
* `alchemy_stage` / `alchemy_id`) — GCS buckets support labels, so the
|
|
13
|
+
* same label-based adoption gate as `Project` applies. The labels are
|
|
14
|
+
* merged into the user-supplied label set on create and never cleared.
|
|
15
|
+
*
|
|
16
|
+
* All lifecycle operations (`insertBuckets`, `getBuckets`, `patchBuckets`,
|
|
17
|
+
* `deleteBuckets`) are synchronous — no LRO polling required.
|
|
18
|
+
*
|
|
19
|
+
* @section Creating a StorageBucket
|
|
20
|
+
* @example Bucket with a 48-hour object TTL
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const bucket = yield* GCP.StorageBucket("TempBucket", {
|
|
23
|
+
* project: project.projectId,
|
|
24
|
+
* name: "microagi-shared-temp",
|
|
25
|
+
* location: "europe-west4",
|
|
26
|
+
* storageClass: "STANDARD",
|
|
27
|
+
* lifecycle: {
|
|
28
|
+
* rule: [{
|
|
29
|
+
* action: { type: "Delete" },
|
|
30
|
+
* condition: { age: 2 }, // 2 days = 48 hours
|
|
31
|
+
* }],
|
|
32
|
+
* },
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export type StorageBucketProps = {
|
|
37
|
+
/**
|
|
38
|
+
* GCP project id (e.g. `micro-research-shared`) the bucket lives
|
|
39
|
+
* under. Immutable — changing this orphans the bucket and its IAM
|
|
40
|
+
* bindings, so `diff` triggers a replacement.
|
|
41
|
+
*/
|
|
42
|
+
project: string;
|
|
43
|
+
/**
|
|
44
|
+
* The bucket name. Must be globally unique, 3-222 chars, lowercase
|
|
45
|
+
* letters, digits, hyphens, underscores, and dots. Immutable —
|
|
46
|
+
* replaces.
|
|
47
|
+
*/
|
|
48
|
+
name: string;
|
|
49
|
+
/**
|
|
50
|
+
* Bucket location, e.g. `europe-west4` (single region) or `EU`
|
|
51
|
+
* (multi-region). Immutable — replaces.
|
|
52
|
+
*/
|
|
53
|
+
location: string;
|
|
54
|
+
/**
|
|
55
|
+
* Storage class, e.g. `STANDARD`, `NEARLINE`, `COLDLINE`,
|
|
56
|
+
* `ARCHIVE`. Defaults to `STANDARD` server-side. Mutable.
|
|
57
|
+
*/
|
|
58
|
+
storageClass?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Bucket lifecycle configuration. Used for object TTL (e.g.
|
|
61
|
+
* `{ rule: [{ action: { type: "Delete" }, condition: { age: 2 } }] }`
|
|
62
|
+
* deletes objects older than 2 days). Mutable.
|
|
63
|
+
*/
|
|
64
|
+
lifecycle?: storage.Bucket["lifecycle"];
|
|
65
|
+
/**
|
|
66
|
+
* User-supplied labels. The alchemy internal labels are merged
|
|
67
|
+
* automatically (see `gcpInternalLabels`). Alchemy keys are reserved
|
|
68
|
+
* and overwrite user-supplied collisions.
|
|
69
|
+
*/
|
|
70
|
+
labels?: Record<string, string>;
|
|
71
|
+
/**
|
|
72
|
+
* Whether uniform bucket-level access is enabled. Defaults to
|
|
73
|
+
* `true` (recommended — ACLs are disabled, IAM is the sole authz
|
|
74
|
+
* path). Mutable.
|
|
75
|
+
*/
|
|
76
|
+
uniformBucketLevelAccess?: boolean;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Single (role, members) entry on a bucket's IAM policy. Targets
|
|
80
|
+
* declare this contract so {@link storageBucketIamMember} can `.bind`
|
|
81
|
+
* IAM grants onto the bucket — the provider's `reconcile` merges all
|
|
82
|
+
* bound entries by role into a single `setIamPolicy` call.
|
|
83
|
+
*/
|
|
84
|
+
export type StorageBucketIamBinding = {
|
|
85
|
+
/** IAM role, e.g. `"roles/storage.objectUser"`. */
|
|
86
|
+
role: string;
|
|
87
|
+
/** Principals, e.g. `["serviceAccount:sa@project.iam.gserviceaccount.com"]`. */
|
|
88
|
+
members: ReadonlyArray<string>;
|
|
89
|
+
};
|
|
90
|
+
export type StorageBucketBindingContract = {
|
|
91
|
+
iamBindings: ReadonlyArray<StorageBucketIamBinding>;
|
|
92
|
+
};
|
|
93
|
+
export type StorageBucket = Resource<"GCP.StorageBucket", StorageBucketProps, {
|
|
94
|
+
/** Bucket name (same as the prop). */
|
|
95
|
+
name: string;
|
|
96
|
+
/** Bucket ID (server-assigned, same as name for buckets). */
|
|
97
|
+
id: string;
|
|
98
|
+
/** Self-link URL. */
|
|
99
|
+
selfLink: string;
|
|
100
|
+
/** Bucket location. */
|
|
101
|
+
location: string;
|
|
102
|
+
/** Storage class. */
|
|
103
|
+
storageClass: string | undefined;
|
|
104
|
+
/** Lifecycle configuration. */
|
|
105
|
+
lifecycle: storage.Bucket["lifecycle"];
|
|
106
|
+
/** Labels (including alchemy internal labels). */
|
|
107
|
+
labels: Record<string, string> | undefined;
|
|
108
|
+
/** Metageneration (changes on metadata updates). */
|
|
109
|
+
metageneration: string;
|
|
110
|
+
/** Creation time (RFC 3339). */
|
|
111
|
+
timeCreated: string;
|
|
112
|
+
/** Last update time (RFC 3339). */
|
|
113
|
+
updated: string;
|
|
114
|
+
}, StorageBucketBindingContract, GCP.Providers>;
|
|
115
|
+
export declare const StorageBucket: import("alchemy").ResourceClass<StorageBucket>;
|
|
116
|
+
export declare const StorageBucketProvider: () => import("effect/Layer").Layer<Provider.Provider<StorageBucket>, never, import("alchemy").Stack | import("alchemy").Stage | import("@distilled.cloud/gcp").Credentials | import("effect/unstable/http/HttpClient").HttpClient>;
|
|
117
|
+
//# sourceMappingURL=Bucket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Bucket.d.ts","sourceRoot":"","sources":["../../src/Storage/Bucket.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,OAAO,MAAM,iCAAiC,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAS7C,OAAO,KAAK,KAAK,GAAG,MAAM,iBAAiB,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACxC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,WAAW,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,QAAQ,CAClC,mBAAmB,EACnB,kBAAkB,EAClB;IACE,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB;IACrB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,+BAA+B;IAC/B,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACvC,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC3C,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB,EACD,4BAA4B,EAC5B,GAAG,CAAC,SAAS,CACd,CAAC;AAEF,eAAO,MAAM,aAAa,gDAA+C,CAAC;AAe1E,eAAO,MAAM,qBAAqB,oOA+R/B,CAAC"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { ConfigError } from "@distilled.cloud/gcp";
|
|
2
|
+
import * as storage from "@distilled.cloud/gcp/storage-v1";
|
|
3
|
+
import { Resource } from "alchemy";
|
|
4
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
5
|
+
import { isResolved } from "alchemy/Diff";
|
|
6
|
+
import * as Provider from "alchemy/Provider";
|
|
7
|
+
import * as Duration from "effect/Duration";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Schedule from "effect/Schedule";
|
|
10
|
+
import { gcpInternalLabels, hasAlchemyLabels, } from "../Tags.js";
|
|
11
|
+
export const StorageBucket = Resource("GCP.StorageBucket");
|
|
12
|
+
const toAttributes = (b) => ({
|
|
13
|
+
name: b.name ?? "",
|
|
14
|
+
id: b.id ?? b.name ?? "",
|
|
15
|
+
selfLink: b.selfLink ?? "",
|
|
16
|
+
location: b.location ?? "",
|
|
17
|
+
storageClass: b.storageClass,
|
|
18
|
+
lifecycle: b.lifecycle,
|
|
19
|
+
labels: b.labels,
|
|
20
|
+
metageneration: b.metageneration ?? "",
|
|
21
|
+
timeCreated: b.timeCreated ?? "",
|
|
22
|
+
updated: b.updated ?? "",
|
|
23
|
+
});
|
|
24
|
+
export const StorageBucketProvider = () => Provider.effect(StorageBucket, Effect.gen(function* () {
|
|
25
|
+
const getBuckets = yield* storage.getBuckets;
|
|
26
|
+
const insertBuckets = yield* storage.insertBuckets;
|
|
27
|
+
const patchBuckets = yield* storage.patchBuckets;
|
|
28
|
+
const deleteBuckets = yield* storage.deleteBuckets;
|
|
29
|
+
const listBuckets = yield* storage.listBuckets;
|
|
30
|
+
const getIamPolicyBuckets = yield* storage.getIamPolicyBuckets;
|
|
31
|
+
const setIamPolicyBuckets = yield* storage.setIamPolicyBuckets;
|
|
32
|
+
const observeBucket = (name) => getBuckets({ bucket: name }).pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)), Effect.catchTag("Forbidden", () => Effect.succeed(undefined)));
|
|
33
|
+
const requireBucket = (name, context) => observeBucket(name).pipe(Effect.flatMap((b) => b
|
|
34
|
+
? Effect.succeed(b)
|
|
35
|
+
: Effect.fail(new ConfigError({
|
|
36
|
+
message: `Bucket ${name} ${context}.`,
|
|
37
|
+
}))));
|
|
38
|
+
// List-scan for cold recovery. Alchemy labels on the bucket
|
|
39
|
+
// identify our resources; the prefix narrows the scan.
|
|
40
|
+
const findByAlchemyLabels = Effect.fn(function* (id, project, name) {
|
|
41
|
+
let pageToken;
|
|
42
|
+
for (let i = 0; i < 100; i++) {
|
|
43
|
+
const page = yield* listBuckets({
|
|
44
|
+
project,
|
|
45
|
+
prefix: name.slice(0, Math.min(name.length, 10)),
|
|
46
|
+
maxResults: 100,
|
|
47
|
+
...(pageToken ? { pageToken } : {}),
|
|
48
|
+
});
|
|
49
|
+
for (const b of page.items ?? []) {
|
|
50
|
+
if (b.name === name && (yield* hasAlchemyLabels(id, b.labels))) {
|
|
51
|
+
return b;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
pageToken = page.nextPageToken;
|
|
55
|
+
if (!pageToken)
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
});
|
|
60
|
+
// Sync mutable fields: storageClass, lifecycle, labels,
|
|
61
|
+
// uniformBucketLevelAccess. Uses PATCH with the full bucket body
|
|
62
|
+
// (GCS patch semantics merge supplied fields). Diff against
|
|
63
|
+
// OBSERVED state, not `olds`, so adoption converges correctly.
|
|
64
|
+
const syncMutable = Effect.fn(function* (observed, desired) {
|
|
65
|
+
const needsUpdate = desired.storageClass !== observed.storageClass ||
|
|
66
|
+
JSON.stringify(desired.lifecycle) !==
|
|
67
|
+
JSON.stringify(observed.lifecycle) ||
|
|
68
|
+
JSON.stringify(desired.labels) !==
|
|
69
|
+
JSON.stringify(observed.labels ?? {}) ||
|
|
70
|
+
desired.uniformBucketLevelAccess !==
|
|
71
|
+
(observed.iamConfiguration?.uniformBucketLevelAccess?.enabled ?? false);
|
|
72
|
+
if (!needsUpdate)
|
|
73
|
+
return observed;
|
|
74
|
+
return yield* patchBuckets({
|
|
75
|
+
bucket: observed.name,
|
|
76
|
+
body: {
|
|
77
|
+
storageClass: desired.storageClass,
|
|
78
|
+
lifecycle: desired.lifecycle,
|
|
79
|
+
labels: desired.labels,
|
|
80
|
+
iamConfiguration: {
|
|
81
|
+
uniformBucketLevelAccess: {
|
|
82
|
+
enabled: desired.uniformBucketLevelAccess,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
// Apply merged IAM bindings as a single setIamPolicy. Same
|
|
89
|
+
// foreign-binding preservation + etag-retry pattern as
|
|
90
|
+
// ServiceAccount's `syncIam`.
|
|
91
|
+
const syncIam = (args) => Effect.gen(function* () {
|
|
92
|
+
const desiredByRole = new Map();
|
|
93
|
+
for (const b of args.bindings) {
|
|
94
|
+
for (const ib of b.data.iamBindings) {
|
|
95
|
+
const set = desiredByRole.get(ib.role) ?? new Set();
|
|
96
|
+
for (const m of ib.members)
|
|
97
|
+
set.add(m);
|
|
98
|
+
desiredByRole.set(ib.role, set);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (desiredByRole.size === 0)
|
|
102
|
+
return;
|
|
103
|
+
const current = yield* getIamPolicyBuckets({
|
|
104
|
+
bucket: args.bucketName,
|
|
105
|
+
optionsRequestedPolicyVersion: 3,
|
|
106
|
+
});
|
|
107
|
+
const existingBindings = (current.bindings ?? []).map((b) => ({
|
|
108
|
+
...b,
|
|
109
|
+
members: [...(b.members ?? [])],
|
|
110
|
+
}));
|
|
111
|
+
let mutated = false;
|
|
112
|
+
for (const [role, members] of desiredByRole) {
|
|
113
|
+
let existing = existingBindings.find((b) => b.role === role && !b.condition);
|
|
114
|
+
if (!existing) {
|
|
115
|
+
existing = { role, members: [] };
|
|
116
|
+
existingBindings.push(existing);
|
|
117
|
+
}
|
|
118
|
+
const merged = new Set([...(existing.members ?? []), ...members]);
|
|
119
|
+
if (merged.size !== (existing.members?.length ?? 0))
|
|
120
|
+
mutated = true;
|
|
121
|
+
existing.members = [...merged];
|
|
122
|
+
}
|
|
123
|
+
if (!mutated)
|
|
124
|
+
return;
|
|
125
|
+
yield* setIamPolicyBuckets({
|
|
126
|
+
bucket: args.bucketName,
|
|
127
|
+
body: {
|
|
128
|
+
...current,
|
|
129
|
+
bindings: existingBindings,
|
|
130
|
+
version: 3,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}).pipe(Effect.retry({
|
|
134
|
+
schedule: Schedule.exponential(Duration.seconds(2)).pipe(Schedule.both(Schedule.recurs(8))),
|
|
135
|
+
}));
|
|
136
|
+
return {
|
|
137
|
+
stables: ["name", "id", "selfLink", "location", "timeCreated"],
|
|
138
|
+
diff: Effect.fn(function* ({ news, olds = {}, output }) {
|
|
139
|
+
if (!isResolved(news))
|
|
140
|
+
return undefined;
|
|
141
|
+
const priorName = output?.name || olds.name;
|
|
142
|
+
const priorProject = olds.project;
|
|
143
|
+
const priorLocation = olds.location;
|
|
144
|
+
if ((priorName && news.name !== priorName) ||
|
|
145
|
+
(priorProject && news.project !== priorProject) ||
|
|
146
|
+
(priorLocation && news.location !== priorLocation)) {
|
|
147
|
+
return { action: "replace" };
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}),
|
|
151
|
+
reconcile: Effect.fn(function* ({ id, news, bindings, output }) {
|
|
152
|
+
const internalLabels = yield* gcpInternalLabels(id);
|
|
153
|
+
const mergedLabels = { ...(news.labels ?? {}), ...internalLabels };
|
|
154
|
+
let created = false;
|
|
155
|
+
// 1. Observe — cloud state is authoritative. Fall back to
|
|
156
|
+
// label-scan for cold recovery.
|
|
157
|
+
let observed = yield* observeBucket(news.name);
|
|
158
|
+
if (!observed) {
|
|
159
|
+
observed = yield* findByAlchemyLabels(id, news.project, news.name);
|
|
160
|
+
}
|
|
161
|
+
// 2. Ensure — create if missing. insertBuckets is
|
|
162
|
+
// synchronous (returns the bucket, not an LRO). Conflict
|
|
163
|
+
// covers a peer reconciler race — re-observe.
|
|
164
|
+
if (!observed) {
|
|
165
|
+
observed = yield* insertBuckets({
|
|
166
|
+
project: news.project,
|
|
167
|
+
body: {
|
|
168
|
+
name: news.name,
|
|
169
|
+
location: news.location,
|
|
170
|
+
storageClass: news.storageClass,
|
|
171
|
+
lifecycle: news.lifecycle,
|
|
172
|
+
labels: mergedLabels,
|
|
173
|
+
iamConfiguration: {
|
|
174
|
+
uniformBucketLevelAccess: {
|
|
175
|
+
enabled: news.uniformBucketLevelAccess ?? true,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
}).pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
|
|
180
|
+
created = !!observed;
|
|
181
|
+
if (!observed) {
|
|
182
|
+
observed = yield* requireBucket(news.name, "did not appear after create");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Refuse to mutate an existing unowned bucket on greenfield
|
|
186
|
+
// create. The `read` handler returns Unowned(attrs) for
|
|
187
|
+
// buckets without our labels, which gates adoption behind
|
|
188
|
+
// --adopt. But reconcile runs before read on a first deploy,
|
|
189
|
+
// so without this guard we'd sync mutable fields (labels,
|
|
190
|
+
// lifecycle, UBLA) on a foreign bucket — bypassing the
|
|
191
|
+
// adoption gate. Skip the check when we just created the
|
|
192
|
+
// bucket (it has our labels) or when the engine already
|
|
193
|
+
// adopted it (output is defined).
|
|
194
|
+
if (!created &&
|
|
195
|
+
!output &&
|
|
196
|
+
!(yield* hasAlchemyLabels(id, observed.labels))) {
|
|
197
|
+
return yield* Effect.fail(new ConfigError({
|
|
198
|
+
message: `Bucket ${news.name} already exists without matching alchemy labels; use --adopt to take it over.`,
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
// 3. Sync mutable fields against OBSERVED state.
|
|
202
|
+
const synced = yield* syncMutable(observed, {
|
|
203
|
+
storageClass: news.storageClass ?? observed.storageClass,
|
|
204
|
+
lifecycle: news.lifecycle ?? observed.lifecycle,
|
|
205
|
+
labels: mergedLabels,
|
|
206
|
+
uniformBucketLevelAccess: news.uniformBucketLevelAccess ?? true,
|
|
207
|
+
});
|
|
208
|
+
// 4. Sync IAM bindings (storage.objectUser, etc.).
|
|
209
|
+
yield* syncIam({
|
|
210
|
+
bucketName: synced.name,
|
|
211
|
+
bindings,
|
|
212
|
+
});
|
|
213
|
+
return toAttributes(synced);
|
|
214
|
+
}),
|
|
215
|
+
delete: Effect.fn(function* ({ output }) {
|
|
216
|
+
yield* deleteBuckets({
|
|
217
|
+
bucket: output.name,
|
|
218
|
+
}).pipe(Effect.catchTag("NotFound", () => Effect.void), Effect.catchTag("Forbidden", () => Effect.void));
|
|
219
|
+
}),
|
|
220
|
+
read: Effect.fn(function* ({ id, output, olds }) {
|
|
221
|
+
const name = output?.name ?? olds?.name;
|
|
222
|
+
const project = olds?.project;
|
|
223
|
+
let observed;
|
|
224
|
+
if (name) {
|
|
225
|
+
observed = yield* observeBucket(name);
|
|
226
|
+
}
|
|
227
|
+
if (!observed && project) {
|
|
228
|
+
observed = yield* findByAlchemyLabels(id, project, name ?? "");
|
|
229
|
+
}
|
|
230
|
+
if (!observed)
|
|
231
|
+
return undefined;
|
|
232
|
+
const attrs = toAttributes(observed);
|
|
233
|
+
return (yield* hasAlchemyLabels(id, observed.labels))
|
|
234
|
+
? attrs
|
|
235
|
+
: Unowned(attrs);
|
|
236
|
+
}),
|
|
237
|
+
};
|
|
238
|
+
}));
|
|
239
|
+
//# sourceMappingURL=Bucket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Bucket.js","sourceRoot":"","sources":["../../src/Storage/Bucket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,OAAO,MAAM,iCAAiC,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACL,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AA2HpB,MAAM,CAAC,MAAM,aAAa,GAAG,QAAQ,CAAgB,mBAAmB,CAAC,CAAC;AAE1E,MAAM,YAAY,GAAG,CAAC,CAAiB,EAA+B,EAAE,CAAC,CAAC;IACxE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE;IAClB,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE;IACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;IAC1B,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;IAC1B,YAAY,EAAE,CAAC,CAAC,YAAY;IAC5B,SAAS,EAAE,CAAC,CAAC,SAAS;IACtB,MAAM,EAAE,CAAC,CAAC,MAA4C;IACtD,cAAc,EAAE,CAAC,CAAC,cAAc,IAAI,EAAE;IACtC,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE;IAChC,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,EAAE;CACzB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,EAAE,CACxC,QAAQ,CAAC,MAAM,CACb,aAAa,EACb,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;IAC7C,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;IACnD,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;IACjD,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;IACnD,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;IAC/C,MAAM,mBAAmB,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC;IAC/D,MAAM,mBAAmB,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC;IAE/D,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,EAAE,CACrC,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAC/B,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE,CAC/B,MAAM,CAAC,OAAO,CAAC,SAAuC,CAAC,CACxD,EACD,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE,CAChC,MAAM,CAAC,OAAO,CAAC,SAAuC,CAAC,CACxD,CACF,CAAC;IAEJ,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE,CACtD,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CACtB,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACnB,CAAC;QACC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QACnB,CAAC,CAAC,MAAM,CAAC,IAAI,CACT,IAAI,WAAW,CAAC;YACd,OAAO,EAAE,UAAU,IAAI,IAAI,OAAO,GAAG;SACtC,CAAC,CACH,CACN,CACF,CAAC;IAEJ,4DAA4D;IAC5D,uDAAuD;IACvD,MAAM,mBAAmB,GAAG,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAC7C,EAAU,EACV,OAAe,EACf,IAAY;QAEZ,IAAI,SAA6B,CAAC;QAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC;gBAC9B,OAAO;gBACP,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAChD,UAAU,EAAE,GAAG;gBACf,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpC,CAAC,CAAC;YACH,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;gBACjC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC,CAAC,MAA4C,CAAC,CAAC,EAAE,CAAC;oBACrG,OAAO,CAAC,CAAC;gBACX,CAAC;YACH,CAAC;YACD,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC;YAC/B,IAAI,CAAC,SAAS;gBAAE,OAAO,SAAS,CAAC;QACnC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,wDAAwD;IACxD,iEAAiE;IACjE,4DAA4D;IAC5D,+DAA+D;IAC/D,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EACrC,QAAwB,EACxB,OAKC;QAED,MAAM,WAAW,GACf,OAAO,CAAC,YAAY,KAAK,QAAQ,CAAC,YAAY;YAC9C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;gBAC/B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC;YACpC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC;gBAC5B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;YACvC,OAAO,CAAC,wBAAwB;gBAC9B,CAAC,QAAQ,CAAC,gBAAgB,EAAE,wBAAwB,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC;QAE5E,IAAI,CAAC,WAAW;YAAE,OAAO,QAAQ,CAAC;QAElC,OAAO,KAAK,CAAC,CAAC,YAAY,CAAC;YACzB,MAAM,EAAE,QAAQ,CAAC,IAAK;YACtB,IAAI,EAAE;gBACJ,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,gBAAgB,EAAE;oBAChB,wBAAwB,EAAE;wBACxB,OAAO,EAAE,OAAO,CAAC,wBAAwB;qBAC1C;iBACF;aACF;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,2DAA2D;IAC3D,uDAAuD;IACvD,8BAA8B;IAC9B,MAAM,OAAO,GAAG,CAAC,IAKhB,EAAE,EAAE,CACH,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,aAAa,GAAG,IAAI,GAAG,EAAuB,CAAC;QACrD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACpC,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;gBAC5D,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO;oBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACvC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QACD,IAAI,aAAa,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAErC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC;YACzC,MAAM,EAAE,IAAI,CAAC,UAAU;YACvB,6BAA6B,EAAE,CAAC;SACjC,CAAC,CAAC;QAEH,MAAM,gBAAgB,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5D,GAAG,CAAC;YACJ,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;SAChC,CAAC,CAAC,CAAC;QACJ,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,aAAa,EAAE,CAAC;YAC5C,IAAI,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAClC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CACvC,CAAC;YACF,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;gBACjC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAClC,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;YAClE,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC;gBAAE,OAAO,GAAG,IAAI,CAAC;YACpE,QAAQ,CAAC,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;QACjC,CAAC;QACD,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,KAAK,CAAC,CAAC,mBAAmB,CAAC;YACzB,MAAM,EAAE,IAAI,CAAC,UAAU;YACvB,IAAI,EAAE;gBACJ,GAAG,OAAO;gBACV,QAAQ,EAAE,gBAAgB;gBAC1B,OAAO,EAAE,CAAC;aACX;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC,IAAI,CACL,MAAM,CAAC,KAAK,CAAC;QACX,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CACtD,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAClC;KACF,CAAC,CACH,CAAC;IAEJ,OAAO;QACL,OAAO,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,CAAC;QAC9D,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,MAAM,EAAE;YACpD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO,SAAS,CAAC;YACxC,MAAM,SAAS,GAAG,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC;YAClC,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC;YACpC,IACE,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;gBACtC,CAAC,YAAY,IAAI,IAAI,CAAC,OAAO,KAAK,YAAY,CAAC;gBAC/C,CAAC,aAAa,IAAI,IAAI,CAAC,QAAQ,KAAK,aAAa,CAAC,EAClD,CAAC;gBACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAW,CAAC;YACxC,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC,CAAC;QACF,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE;YAC5D,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;YACpD,MAAM,YAAY,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,GAAG,cAAc,EAAE,CAAC;YACnE,IAAI,OAAO,GAAG,KAAK,CAAC;YAEpB,0DAA0D;YAC1D,mCAAmC;YACnC,IAAI,QAAQ,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/C,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACrE,CAAC;YAED,kDAAkD;YAClD,4DAA4D;YAC5D,iDAAiD;YACjD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC;oBAC9B,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,IAAI,EAAE;wBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,YAAY,EAAE,IAAI,CAAC,YAAY;wBAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;wBACzB,MAAM,EAAE,YAAY;wBACpB,gBAAgB,EAAE;4BAChB,wBAAwB,EAAE;gCACxB,OAAO,EAAE,IAAI,CAAC,wBAAwB,IAAI,IAAI;6BAC/C;yBACF;qBACF;iBACF,CAAC,CAAC,IAAI,CACL,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE,CAC/B,MAAM,CAAC,OAAO,CAAC,SAAuC,CAAC,CACxD,CACF,CAAC;gBACF,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC;gBAErB,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,QAAQ,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,6BAA6B,CAAC,CAAC;gBAC5E,CAAC;YACH,CAAC;YAED,4DAA4D;YAC5D,wDAAwD;YACxD,0DAA0D;YAC1D,6DAA6D;YAC7D,0DAA0D;YAC1D,uDAAuD;YACvD,yDAAyD;YACzD,wDAAwD;YACxD,kCAAkC;YAClC,IACE,CAAC,OAAO;gBACR,CAAC,MAAM;gBACP,CAAC,CAAC,KAAK,CAAC,CAAC,gBAAgB,CACvB,EAAE,EACF,QAAQ,CAAC,MAA4C,CACtD,CAAC,EACF,CAAC;gBACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACvB,IAAI,WAAW,CAAC;oBACd,OAAO,EACL,UAAU,IAAI,CAAC,IAAI,+EAA+E;iBACrG,CAAC,CACH,CAAC;YACJ,CAAC;YAED,iDAAiD;YACjD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE;gBAC1C,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC,YAAY;gBACxD,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS;gBAC/C,MAAM,EAAE,YAAY;gBACpB,wBAAwB,EAAE,IAAI,CAAC,wBAAwB,IAAI,IAAI;aAChE,CAAC,CAAC;YAEH,mDAAmD;YACnD,KAAK,CAAC,CAAC,OAAO,CAAC;gBACb,UAAU,EAAE,MAAM,CAAC,IAAK;gBACxB,QAAQ;aACT,CAAC,CAAC;YAEH,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC,CAAC;QACF,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE;YACrC,KAAK,CAAC,CAAC,aAAa,CAAC;gBACnB,MAAM,EAAE,MAAM,CAAC,IAAI;aACpB,CAAC,CAAC,IAAI,CACL,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAC9C,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAChD,CAAC;QACJ,CAAC,CAAC;QACF,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;YAC7C,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,IAAI,IAAI,EAAE,IAAI,CAAC;YACxC,MAAM,OAAO,GAAG,IAAI,EAAE,OAA6B,CAAC;YAEpD,IAAI,QAAoC,CAAC;YACzC,IAAI,IAAI,EAAE,CAAC;gBACT,QAAQ,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC;YACD,IAAI,CAAC,QAAQ,IAAI,OAAO,EAAE,CAAC;gBACzB,QAAQ,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;YACjE,CAAC;YACD,IAAI,CAAC,QAAQ;gBAAE,OAAO,SAAS,CAAC;YAEhC,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACrC,OAAO,CAAC,KAAK,CAAC,CAAC,gBAAgB,CAAC,EAAE,EAAE,QAAQ,CAAC,MAA4C,CAAC,CAAC;gBACzF,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC,CAAC;KACH,CAAC;AACJ,CAAC,CAAC,CACH,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type { StorageBucket } from "./Bucket.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Bind a single `(role, member)` IAM grant onto a
|
|
5
|
+
* {@link StorageBucket}.
|
|
6
|
+
*
|
|
7
|
+
* Target-side binding — same pattern as
|
|
8
|
+
* {@link import("../CloudResourceManager/ProjectIamMember.ts").projectIamMember}
|
|
9
|
+
* / `serviceAccountIamMember`. The bucket's `reconcile` merges all
|
|
10
|
+
* bindings into a single `setIamPolicy` call on the bucket, preserving
|
|
11
|
+
* foreign roles + members.
|
|
12
|
+
*
|
|
13
|
+
* Identity for dedup: the rendered SID is `IamMember(<bucket>, <key>)`.
|
|
14
|
+
* The engine collapses binds by SID, so callers MUST supply a
|
|
15
|
+
* stack-unique `key` per (bucket, role, member) triple.
|
|
16
|
+
*
|
|
17
|
+
* @example Granting a service account read+write on a bucket
|
|
18
|
+
* ```typescript
|
|
19
|
+
* yield* GCP.storageBucketIamMember(bucket, "TempBucketRw", {
|
|
20
|
+
* role: "roles/storage.objectUser",
|
|
21
|
+
* member: `serviceAccount:${sa.email}`,
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare const storageBucketIamMember: (bucket: StorageBucket, key: string, args: {
|
|
26
|
+
role: string;
|
|
27
|
+
member: string;
|
|
28
|
+
}) => Effect.Effect<void>;
|
|
29
|
+
//# sourceMappingURL=BucketIamMember.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BucketIamMember.d.ts","sourceRoot":"","sources":["../../src/Storage/BucketIamMember.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,aAAa,EACrB,KAAK,MAAM,EACX,MAAM;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACrC,MAAM,CAAC,MAAM,CAAC,IAAI,CAGiB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bind a single `(role, member)` IAM grant onto a
|
|
3
|
+
* {@link StorageBucket}.
|
|
4
|
+
*
|
|
5
|
+
* Target-side binding — same pattern as
|
|
6
|
+
* {@link import("../CloudResourceManager/ProjectIamMember.ts").projectIamMember}
|
|
7
|
+
* / `serviceAccountIamMember`. The bucket's `reconcile` merges all
|
|
8
|
+
* bindings into a single `setIamPolicy` call on the bucket, preserving
|
|
9
|
+
* foreign roles + members.
|
|
10
|
+
*
|
|
11
|
+
* Identity for dedup: the rendered SID is `IamMember(<bucket>, <key>)`.
|
|
12
|
+
* The engine collapses binds by SID, so callers MUST supply a
|
|
13
|
+
* stack-unique `key` per (bucket, role, member) triple.
|
|
14
|
+
*
|
|
15
|
+
* @example Granting a service account read+write on a bucket
|
|
16
|
+
* ```typescript
|
|
17
|
+
* yield* GCP.storageBucketIamMember(bucket, "TempBucketRw", {
|
|
18
|
+
* role: "roles/storage.objectUser",
|
|
19
|
+
* member: `serviceAccount:${sa.email}`,
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const storageBucketIamMember = (bucket, key, args) => bucket.bind `IamMember(${bucket}, ${key})`({
|
|
24
|
+
iamBindings: [{ role: args.role, members: [args.member] }],
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=BucketIamMember.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BucketIamMember.js","sourceRoot":"","sources":["../../src/Storage/BucketIamMember.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,MAAqB,EACrB,GAAW,EACX,IAAsC,EACjB,EAAE,CACvB,MAAM,CAAC,IAAI,CAAA,aAAa,MAAM,KAAK,GAAG,GAAG,CAAC;IACxC,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;CAC3D,CAAmC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/Storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAMrB,cAAc,sBAAsB,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -12,5 +12,6 @@ export * from "./Run/index.ts";
|
|
|
12
12
|
export * from "./ServiceNetworking/index.ts";
|
|
13
13
|
export * from "./Sqladmin/index.ts";
|
|
14
14
|
export * from "./ServiceUsage/index.ts";
|
|
15
|
+
export * from "./Storage/index.ts";
|
|
15
16
|
export * from "./Providers.ts";
|
|
16
17
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EACL,sBAAsB,EACtB,OAAO,GACR,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,aAAa,EACb,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAChC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EACL,sBAAsB,EACtB,OAAO,GACR,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,aAAa,EACb,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAChC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -11,5 +11,6 @@ export * from "./Run/index.js";
|
|
|
11
11
|
export * from "./ServiceNetworking/index.js";
|
|
12
12
|
export * from "./Sqladmin/index.js";
|
|
13
13
|
export * from "./ServiceUsage/index.js";
|
|
14
|
+
export * from "./Storage/index.js";
|
|
14
15
|
export * from "./Providers.js";
|
|
15
16
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EACL,sBAAsB,EACtB,OAAO,GACR,MAAM,wBAAwB,CAAC;AAKhC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EACL,sBAAsB,EACtB,OAAO,GACR,MAAM,wBAAwB,CAAC;AAKhC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microagi/alchemy-gcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "GCP provider for Alchemy v2 (Effect-based IaC). Resources for Project, ServiceAccount, Cluster, NodePool, Compute, ManagedLustre, ServiceUsage, ServiceNetworking, Cloud Run (Service + Job + IAM), Cloud SQL (Instance + Database + User with IAM database authn), Artifact Registry (Docker repositories, standard + remote pull-through caches), Kubernetes (typed Secret + generic SSA Manifest), with ADC-backed credentials and target-side IAM bindings.",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "GCP provider for Alchemy v2 (Effect-based IaC). Resources for Project, ServiceAccount, Cluster, NodePool, Compute, ManagedLustre, ServiceUsage, ServiceNetworking, Cloud Run (Service + Job + IAM), Cloud SQL (Instance + Database + User with IAM database authn), Artifact Registry (Docker repositories, standard + remote pull-through caches), Kubernetes (typed Secret + generic SSA Manifest + Helm release), with ADC-backed credentials and target-side IAM bindings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alchemy",
|
|
7
7
|
"alchemy-effect",
|
|
@@ -144,6 +144,14 @@ export type NodePoolProps = {
|
|
|
144
144
|
gvnic?: { enabled: boolean };
|
|
145
145
|
/** Confidential VM. Mutable via `update`. */
|
|
146
146
|
confidentialNodes?: { enabled: boolean };
|
|
147
|
+
/**
|
|
148
|
+
* GCFS (Image Streaming) — stream container image data from Artifact
|
|
149
|
+
* Registry so containers start before the full image is pulled,
|
|
150
|
+
* cutting cold start for large images. Only accelerates images stored
|
|
151
|
+
* in Artifact Registry. Mutable via `update`. Enabling/disabling on an
|
|
152
|
+
* existing pool re-creates its nodes (rolling node recreation).
|
|
153
|
+
*/
|
|
154
|
+
gcfsConfig?: { enabled: boolean };
|
|
147
155
|
};
|
|
148
156
|
/** Cluster autoscaler. Mutable via `setAutoscaling`. */
|
|
149
157
|
autoscaling?: {
|
|
@@ -295,6 +303,7 @@ export const toNodeConfigCreateBody = (
|
|
|
295
303
|
: {}),
|
|
296
304
|
...(config.gvnic ? { gvnic: config.gvnic } : {}),
|
|
297
305
|
...(config.confidentialNodes ? { confidentialNodes: config.confidentialNodes } : {}),
|
|
306
|
+
...(config.gcfsConfig ? { gcfsConfig: config.gcfsConfig } : {}),
|
|
298
307
|
};
|
|
299
308
|
};
|
|
300
309
|
|
|
@@ -374,6 +383,9 @@ const toNodePoolUpdateBody = (
|
|
|
374
383
|
if (nc.confidentialNodes && !deepEqual(oc.confidentialNodes, nc.confidentialNodes)) {
|
|
375
384
|
body.confidentialNodes = nc.confidentialNodes;
|
|
376
385
|
}
|
|
386
|
+
if (nc.gcfsConfig && !deepEqual(oc.gcfsConfig, nc.gcfsConfig)) {
|
|
387
|
+
body.gcfsConfig = nc.gcfsConfig;
|
|
388
|
+
}
|
|
377
389
|
if (
|
|
378
390
|
nc.workloadMetadataConfig &&
|
|
379
391
|
!deepEqual(oc.workloadMetadataConfig, nc.workloadMetadataConfig)
|
|
@@ -321,9 +321,28 @@ export const ServiceAccountProvider = () =>
|
|
|
321
321
|
stables: ["accountId", "projectId", "uniqueId", "name", "email"],
|
|
322
322
|
diff: Effect.fn(function* ({ id, news, olds = {}, output }) {
|
|
323
323
|
if (!isResolved(news)) return undefined;
|
|
324
|
+
// Replace ONLY when a prior identity is actually known AND differs.
|
|
325
|
+
// A partial/adopted state can lack the persisted accountId/projectId
|
|
326
|
+
// (an adoption that never persisted full Attributes leaves
|
|
327
|
+
// output.projectId === "" / undefined). Treating that "unknown" as a
|
|
328
|
+
// replace spuriously deletes+recreates a live, correctly-named GSA
|
|
329
|
+
// (and churns its uniqueId → breaks Workload-Identity bindings).
|
|
330
|
+
// When the prior identity is absent, fall through to reconcile, which
|
|
331
|
+
// observes the live resource and re-persists its Attributes. The
|
|
332
|
+
// truthiness guard covers both `undefined` and the `""` toAttributes
|
|
333
|
+
// default; a genuine accountId/project change still has a non-empty
|
|
334
|
+
// prior value to compare against, so real replacements are unaffected.
|
|
335
|
+
// `||` (not `??`): toAttributes persists `accountId`/`projectId` as
|
|
336
|
+
// `""` when absent, and an empty string must fall through to `olds`
|
|
337
|
+
// (the previous Props) rather than mask it — otherwise a genuine
|
|
338
|
+
// identity change with an empty `output` but a populated `olds` would
|
|
339
|
+
// skip the replace and adopt/create at the new identity without
|
|
340
|
+
// deleting the old GSA.
|
|
341
|
+
const priorAccount = output?.accountId || olds.accountId;
|
|
342
|
+
const priorProject = output?.projectId || olds.project;
|
|
324
343
|
if (
|
|
325
|
-
news.accountId !==
|
|
326
|
-
news.project !==
|
|
344
|
+
(priorAccount && news.accountId !== priorAccount) ||
|
|
345
|
+
(priorProject && news.project !== priorProject)
|
|
327
346
|
) {
|
|
328
347
|
return { action: "replace" } as const;
|
|
329
348
|
}
|