@microagi/alchemy-gcp 0.11.2 → 0.11.4
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 +10 -0
- package/README.md +2 -0
- package/lib/ArtifactRegistry/Operations.d.ts +2 -2
- package/lib/ArtifactRegistry/Operations.d.ts.map +1 -1
- package/lib/ArtifactRegistry/Repository.d.ts +1 -1
- package/lib/ArtifactRegistry/RepositoryIamMember.d.ts +40 -0
- package/lib/ArtifactRegistry/RepositoryIamMember.d.ts.map +1 -0
- package/lib/ArtifactRegistry/RepositoryIamMember.js +110 -0
- package/lib/ArtifactRegistry/RepositoryIamMember.js.map +1 -0
- package/lib/ArtifactRegistry/Validation.d.ts.map +1 -1
- package/lib/ArtifactRegistry/index.d.ts +2 -0
- package/lib/ArtifactRegistry/index.d.ts.map +1 -1
- package/lib/ArtifactRegistry/index.js +1 -0
- package/lib/ArtifactRegistry/index.js.map +1 -1
- package/lib/Auth/AuthProvider.d.ts.map +1 -1
- package/lib/Auth/AuthProvider.js.map +1 -1
- package/lib/Auth/Credentials.d.ts +1 -1
- package/lib/Auth/Credentials.d.ts.map +1 -1
- package/lib/CloudResourceManager/Project.d.ts +1 -1
- package/lib/CloudResourceManager/ProjectIamMember.d.ts.map +1 -1
- package/lib/Compute/Address.d.ts +1 -1
- package/lib/Compute/Firewall.d.ts +1 -1
- package/lib/Compute/ForwardingRule.d.ts +1 -1
- package/lib/Compute/GlobalAddress.d.ts +1 -1
- package/lib/Compute/Network.d.ts +1 -1
- package/lib/Compute/Network.d.ts.map +1 -1
- package/lib/Compute/Operations.d.ts +4 -4
- package/lib/Compute/Operations.d.ts.map +1 -1
- package/lib/Compute/ProjectDefaultNetworkTier.d.ts +18 -0
- package/lib/Compute/ProjectDefaultNetworkTier.d.ts.map +1 -0
- package/lib/Compute/ProjectDefaultNetworkTier.js +63 -0
- package/lib/Compute/ProjectDefaultNetworkTier.js.map +1 -0
- package/lib/Compute/Subnetwork.d.ts +1 -1
- package/lib/Compute/SubnetworkIamMember.d.ts.map +1 -1
- package/lib/Compute/index.d.ts +1 -0
- package/lib/Compute/index.d.ts.map +1 -1
- package/lib/Compute/index.js +1 -0
- package/lib/Compute/index.js.map +1 -1
- package/lib/Container/Cluster.d.ts +8 -1
- package/lib/Container/Cluster.d.ts.map +1 -1
- package/lib/Container/Cluster.js +26 -14
- package/lib/Container/Cluster.js.map +1 -1
- package/lib/Container/NodePool.d.ts +1 -1
- package/lib/Container/NodePool.d.ts.map +1 -1
- package/lib/Container/Operations.d.ts +2 -2
- package/lib/Container/Operations.d.ts.map +1 -1
- package/lib/Iam/ServiceAccount.d.ts +1 -1
- package/lib/Iam/ServiceAccountIamMember.d.ts.map +1 -1
- package/lib/Kubernetes/Helm.d.ts +1 -1
- package/lib/Kubernetes/KubernetesManifest.d.ts +1 -1
- package/lib/Kubernetes/Secret.d.ts +1 -1
- package/lib/Kubernetes/client.d.ts.map +1 -1
- package/lib/Kubernetes/connection.d.ts.map +1 -1
- package/lib/ManagedLustre/Instance.d.ts +1 -1
- package/lib/Providers.d.ts +1 -1
- 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.map +1 -1
- package/lib/Run/IamSync.d.ts.map +1 -1
- package/lib/Run/Job.d.ts +1 -1
- package/lib/Run/Operations.d.ts +2 -2
- package/lib/Run/Operations.d.ts.map +1 -1
- package/lib/Run/Service.d.ts +1 -1
- package/lib/Run/Validation.d.ts.map +1 -1
- package/lib/Sqladmin/Instance.d.ts +1 -1
- package/lib/Sqladmin/Operations.d.ts +2 -2
- package/lib/Sqladmin/Operations.d.ts.map +1 -1
- package/lib/Sqladmin/Types.d.ts.map +1 -1
- package/lib/Sqladmin/Validation.d.ts.map +1 -1
- package/lib/Storage/Bucket.d.ts +1 -1
- package/lib/Storage/BucketIamMember.d.ts.map +1 -1
- package/lib/Tags.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ArtifactRegistry/RepositoryIamMember.ts +205 -0
- package/src/ArtifactRegistry/index.ts +8 -0
- package/src/Compute/ProjectDefaultNetworkTier.ts +101 -0
- package/src/Compute/index.ts +1 -0
- package/src/Container/Cluster.ts +41 -16
- package/src/Providers.ts +12 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { ConfigError } from "@distilled.cloud/gcp";
|
|
2
|
+
import * as ar from "@distilled.cloud/gcp/artifactregistry-v1";
|
|
3
|
+
import { Resource } from "alchemy";
|
|
4
|
+
import { isResolved } from "alchemy/Diff";
|
|
5
|
+
import * as Provider from "alchemy/Provider";
|
|
6
|
+
import * as Duration from "effect/Duration";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Schedule from "effect/Schedule";
|
|
9
|
+
import type * as GCP from "../Providers.ts";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A single unconditional `(role, member)` IAM grant on an Artifact Registry
|
|
13
|
+
* repository. The repository is referenced by its fully-qualified resource
|
|
14
|
+
* name and is not created or deleted by this resource.
|
|
15
|
+
*
|
|
16
|
+
* Reconciliation is an additive read-modify-write: foreign bindings,
|
|
17
|
+
* conditional bindings, members, policy version, and etag are preserved.
|
|
18
|
+
* Deletion revokes only this exact member and removes an emptied binding.
|
|
19
|
+
* The whole cycle retries boundedly on stale-etag conflicts so concurrent
|
|
20
|
+
* policy writers compose safely.
|
|
21
|
+
*
|
|
22
|
+
* @example Read access to one Docker repository
|
|
23
|
+
* ```typescript
|
|
24
|
+
* yield* GCP.ArtifactRegistryRepositoryIamMember("EvalsAppsReader", {
|
|
25
|
+
* repository: apps.fullyQualifiedName,
|
|
26
|
+
* role: "roles/artifactregistry.reader",
|
|
27
|
+
* member: "serviceAccount:evals@example-project.iam.gserviceaccount.com",
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export type ArtifactRegistryRepositoryIamMemberProps = {
|
|
32
|
+
/**
|
|
33
|
+
* Fully-qualified repository resource name:
|
|
34
|
+
* `projects/{project}/locations/{location}/repositories/{repository}`.
|
|
35
|
+
* Immutable — changing it replaces the grant.
|
|
36
|
+
*/
|
|
37
|
+
repository: string;
|
|
38
|
+
/** IAM role, e.g. `roles/artifactregistry.reader`. Immutable. */
|
|
39
|
+
role: string;
|
|
40
|
+
/** IAM member string, e.g. `serviceAccount:name@project.iam.gserviceaccount.com`. Immutable. */
|
|
41
|
+
member: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type ArtifactRegistryRepositoryIamMemberAttributes =
|
|
45
|
+
ArtifactRegistryRepositoryIamMemberProps;
|
|
46
|
+
|
|
47
|
+
export type ArtifactRegistryRepositoryIamMember = Resource<
|
|
48
|
+
"GCP.ArtifactRegistryRepositoryIamMember",
|
|
49
|
+
ArtifactRegistryRepositoryIamMemberProps,
|
|
50
|
+
ArtifactRegistryRepositoryIamMemberAttributes,
|
|
51
|
+
never,
|
|
52
|
+
GCP.Providers
|
|
53
|
+
>;
|
|
54
|
+
|
|
55
|
+
export const ArtifactRegistryRepositoryIamMember =
|
|
56
|
+
Resource<ArtifactRegistryRepositoryIamMember>(
|
|
57
|
+
"GCP.ArtifactRegistryRepositoryIamMember",
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export const ArtifactRegistryRepositoryIamMemberProvider = () =>
|
|
61
|
+
Provider.effect(
|
|
62
|
+
ArtifactRegistryRepositoryIamMember,
|
|
63
|
+
Effect.gen(function* () {
|
|
64
|
+
const getIamPolicy = yield* ar.getIamPolicyProjectsLocationsRepositories;
|
|
65
|
+
const setIamPolicy = yield* ar.setIamPolicyProjectsLocationsRepositories;
|
|
66
|
+
|
|
67
|
+
const hasMember = (policy: ar.Policy, role: string, member: string) =>
|
|
68
|
+
(policy.bindings ?? []).some(
|
|
69
|
+
(binding) =>
|
|
70
|
+
binding.role === role &&
|
|
71
|
+
!binding.condition &&
|
|
72
|
+
(binding.members ?? []).includes(member),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const conflictRetry = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
76
|
+
effect.pipe(
|
|
77
|
+
Effect.retry({
|
|
78
|
+
while: (error) => (error as { _tag?: string })?._tag === "Conflict",
|
|
79
|
+
schedule: Schedule.exponential(Duration.millis(250)).pipe(
|
|
80
|
+
Schedule.both(Schedule.recurs(8)),
|
|
81
|
+
),
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const readPolicy = (repository: string) =>
|
|
86
|
+
getIamPolicy({
|
|
87
|
+
resource: repository,
|
|
88
|
+
"options.requestedPolicyVersion": 3,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
stables: ["repository", "role", "member"],
|
|
93
|
+
diff: Effect.fn(function* ({ news, olds = {}, output }) {
|
|
94
|
+
if (!isResolved(news)) return undefined;
|
|
95
|
+
const priorRepository = output?.repository || olds.repository;
|
|
96
|
+
const priorRole = output?.role || olds.role;
|
|
97
|
+
const priorMember = output?.member || olds.member;
|
|
98
|
+
if (
|
|
99
|
+
(priorRepository && priorRepository !== news.repository) ||
|
|
100
|
+
(priorRole && priorRole !== news.role) ||
|
|
101
|
+
(priorMember && priorMember !== news.member)
|
|
102
|
+
) {
|
|
103
|
+
return { action: "replace" } as const;
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}),
|
|
107
|
+
reconcile: Effect.fn(function* ({ news }) {
|
|
108
|
+
yield* conflictRetry(
|
|
109
|
+
Effect.gen(function* () {
|
|
110
|
+
const current = yield* readPolicy(news.repository).pipe(
|
|
111
|
+
Effect.catchTag("NotFound", () =>
|
|
112
|
+
Effect.fail(
|
|
113
|
+
new ConfigError({
|
|
114
|
+
message: `Artifact Registry repository ${news.repository} does not exist — RepositoryIamMember grants on existing repositories only.`,
|
|
115
|
+
}),
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
if (hasMember(current, news.role, news.member)) return;
|
|
120
|
+
|
|
121
|
+
const bindings = (current.bindings ?? []).map((binding) => ({
|
|
122
|
+
...binding,
|
|
123
|
+
members: [...(binding.members ?? [])],
|
|
124
|
+
}));
|
|
125
|
+
const existing = bindings.find(
|
|
126
|
+
(binding) => binding.role === news.role && !binding.condition,
|
|
127
|
+
);
|
|
128
|
+
if (existing) {
|
|
129
|
+
existing.members = [...(existing.members ?? []), news.member];
|
|
130
|
+
} else {
|
|
131
|
+
bindings.push({ role: news.role, members: [news.member] });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
yield* setIamPolicy({
|
|
135
|
+
resource: news.repository,
|
|
136
|
+
body: {
|
|
137
|
+
policy: { ...current, bindings, version: 3 },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
return {
|
|
143
|
+
repository: news.repository,
|
|
144
|
+
role: news.role,
|
|
145
|
+
member: news.member,
|
|
146
|
+
};
|
|
147
|
+
}),
|
|
148
|
+
delete: Effect.fn(function* ({ output }) {
|
|
149
|
+
if (!output.repository || !output.role || !output.member) return;
|
|
150
|
+
yield* conflictRetry(
|
|
151
|
+
Effect.gen(function* () {
|
|
152
|
+
const current = yield* readPolicy(output.repository).pipe(
|
|
153
|
+
Effect.catchTag("NotFound", () =>
|
|
154
|
+
Effect.succeed(undefined as ar.Policy | undefined),
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
if (!current || !hasMember(current, output.role, output.member)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const bindings = (current.bindings ?? [])
|
|
162
|
+
.map((binding) =>
|
|
163
|
+
binding.role === output.role && !binding.condition
|
|
164
|
+
? {
|
|
165
|
+
...binding,
|
|
166
|
+
members: (binding.members ?? []).filter(
|
|
167
|
+
(member) => member !== output.member,
|
|
168
|
+
),
|
|
169
|
+
}
|
|
170
|
+
: {
|
|
171
|
+
...binding,
|
|
172
|
+
members: [...(binding.members ?? [])],
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
.filter((binding) => (binding.members?.length ?? 0) > 0);
|
|
176
|
+
|
|
177
|
+
yield* setIamPolicy({
|
|
178
|
+
resource: output.repository,
|
|
179
|
+
body: {
|
|
180
|
+
policy: { ...current, bindings, version: 3 },
|
|
181
|
+
},
|
|
182
|
+
}).pipe(Effect.catchTag("NotFound", () => Effect.void));
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
}),
|
|
186
|
+
read: Effect.fn(function* ({ output, olds }) {
|
|
187
|
+
const repository = output?.repository ?? olds?.repository;
|
|
188
|
+
const role = output?.role ?? olds?.role;
|
|
189
|
+
const member = output?.member ?? olds?.member;
|
|
190
|
+
if (!repository || !role || !member) return undefined;
|
|
191
|
+
|
|
192
|
+
const current = yield* readPolicy(repository).pipe(
|
|
193
|
+
Effect.catchTag("NotFound", () =>
|
|
194
|
+
Effect.succeed(undefined as ar.Policy | undefined),
|
|
195
|
+
),
|
|
196
|
+
Effect.catchTag("Forbidden", () =>
|
|
197
|
+
Effect.succeed(undefined as ar.Policy | undefined),
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
if (!current || !hasMember(current, role, member)) return undefined;
|
|
201
|
+
return { repository, role, member };
|
|
202
|
+
}),
|
|
203
|
+
};
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
@@ -7,4 +7,12 @@ export type {
|
|
|
7
7
|
ArtifactRegistryRepositoryAttributes,
|
|
8
8
|
ArtifactRegistryRepositoryProps,
|
|
9
9
|
} from "./Repository.ts";
|
|
10
|
+
export {
|
|
11
|
+
ArtifactRegistryRepositoryIamMember,
|
|
12
|
+
ArtifactRegistryRepositoryIamMemberProvider,
|
|
13
|
+
} from "./RepositoryIamMember.ts";
|
|
14
|
+
export type {
|
|
15
|
+
ArtifactRegistryRepositoryIamMemberAttributes,
|
|
16
|
+
ArtifactRegistryRepositoryIamMemberProps,
|
|
17
|
+
} from "./RepositoryIamMember.ts";
|
|
10
18
|
export type { ArtifactRegistryFormat, ArtifactRegistryMode } from "./Types.ts";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as compute from "@distilled.cloud/gcp/compute-v1";
|
|
2
|
+
import { Resource } from "alchemy";
|
|
3
|
+
import { isResolved } from "alchemy/Diff";
|
|
4
|
+
import * as Provider from "alchemy/Provider";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import type * as GCP from "../Providers.ts";
|
|
7
|
+
import { makeAwaitGlobalOperation } from "./Operations.ts";
|
|
8
|
+
|
|
9
|
+
export type ProjectDefaultNetworkTierProps = {
|
|
10
|
+
/** GCP project whose default network tier is managed. Immutable. */
|
|
11
|
+
project: string;
|
|
12
|
+
/** Default tier applied to newly created Compute resources. */
|
|
13
|
+
networkTier: "PREMIUM" | "STANDARD";
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type ProjectDefaultNetworkTierAttributes = {
|
|
17
|
+
project: string;
|
|
18
|
+
networkTier: "PREMIUM" | "STANDARD";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type ProjectDefaultNetworkTier = Resource<
|
|
22
|
+
"GCP.ProjectDefaultNetworkTier",
|
|
23
|
+
ProjectDefaultNetworkTierProps,
|
|
24
|
+
ProjectDefaultNetworkTierAttributes,
|
|
25
|
+
never,
|
|
26
|
+
GCP.Providers
|
|
27
|
+
>;
|
|
28
|
+
export const ProjectDefaultNetworkTier = Resource<ProjectDefaultNetworkTier>(
|
|
29
|
+
"GCP.ProjectDefaultNetworkTier",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const isProjectNetworkTier = (
|
|
33
|
+
value: string | undefined,
|
|
34
|
+
): value is ProjectDefaultNetworkTierAttributes["networkTier"] =>
|
|
35
|
+
value === "PREMIUM" || value === "STANDARD";
|
|
36
|
+
|
|
37
|
+
/** Manage the Compute Engine project-wide default network service tier. */
|
|
38
|
+
export const ProjectDefaultNetworkTierProvider = () =>
|
|
39
|
+
Provider.effect(
|
|
40
|
+
ProjectDefaultNetworkTier,
|
|
41
|
+
Effect.gen(function* () {
|
|
42
|
+
const getProjects = yield* compute.getProjects;
|
|
43
|
+
const setDefaultNetworkTier = yield* compute.setDefaultNetworkTierProjects;
|
|
44
|
+
const getGlobalOperations = yield* compute.getGlobalOperations;
|
|
45
|
+
const awaitOp = makeAwaitGlobalOperation(getGlobalOperations);
|
|
46
|
+
|
|
47
|
+
const observe = (project: string) =>
|
|
48
|
+
getProjects({ project }).pipe(
|
|
49
|
+
Effect.catchTag("NotFound", () =>
|
|
50
|
+
Effect.succeed(undefined as compute.Project | undefined),
|
|
51
|
+
),
|
|
52
|
+
Effect.catchTag("Forbidden", () =>
|
|
53
|
+
Effect.succeed(undefined as compute.Project | undefined),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
stables: ["project"],
|
|
59
|
+
diff: Effect.fn(function* ({ news, olds = {} }) {
|
|
60
|
+
if (!isResolved(news)) return undefined;
|
|
61
|
+
if (olds.project !== undefined && olds.project !== news.project) {
|
|
62
|
+
return { action: "replace" } as const;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// This project-scoped setting can drift independently of Alchemy's
|
|
66
|
+
// persisted props (for example through the Cloud Console or gcloud).
|
|
67
|
+
// Existing resources do not pass through `read`, so consult the live
|
|
68
|
+
// project here and force reconcile whenever reality differs.
|
|
69
|
+
const observed = yield* observe(news.project);
|
|
70
|
+
if (observed?.defaultNetworkTier !== news.networkTier) {
|
|
71
|
+
return { action: "update" } as const;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}),
|
|
75
|
+
reconcile: Effect.fn(function* ({ news, session }) {
|
|
76
|
+
const observed = yield* observe(news.project);
|
|
77
|
+
if (observed?.defaultNetworkTier !== news.networkTier) {
|
|
78
|
+
const op = yield* setDefaultNetworkTier({
|
|
79
|
+
project: news.project,
|
|
80
|
+
body: { networkTier: news.networkTier },
|
|
81
|
+
});
|
|
82
|
+
if (op.name) yield* awaitOp(news.project, op.name, session);
|
|
83
|
+
}
|
|
84
|
+
return { project: news.project, networkTier: news.networkTier };
|
|
85
|
+
}),
|
|
86
|
+
// Project defaults are shared project-level policy. Removing this
|
|
87
|
+
// declaration must not silently revert the project's selected tier.
|
|
88
|
+
delete: Effect.fn(function* () {
|
|
89
|
+
return yield* Effect.void;
|
|
90
|
+
}),
|
|
91
|
+
read: Effect.fn(function* ({ output, olds }) {
|
|
92
|
+
const project = output?.project ?? olds?.project;
|
|
93
|
+
if (!project) return undefined;
|
|
94
|
+
const observed = yield* observe(project);
|
|
95
|
+
const networkTier = observed?.defaultNetworkTier;
|
|
96
|
+
if (!isProjectNetworkTier(networkTier)) return undefined;
|
|
97
|
+
return { project, networkTier };
|
|
98
|
+
}),
|
|
99
|
+
};
|
|
100
|
+
}),
|
|
101
|
+
);
|
package/src/Compute/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./Firewall.ts";
|
|
|
3
3
|
export * from "./ForwardingRule.ts";
|
|
4
4
|
export * from "./GlobalAddress.ts";
|
|
5
5
|
export * from "./Network.ts";
|
|
6
|
+
export * from "./ProjectDefaultNetworkTier.ts";
|
|
6
7
|
export * from "./SharedVpcHost.ts";
|
|
7
8
|
export * from "./SharedVpcServiceProject.ts";
|
|
8
9
|
export * from "./Subnetwork.ts";
|
package/src/Container/Cluster.ts
CHANGED
|
@@ -73,6 +73,8 @@ export type ClusterProps = {
|
|
|
73
73
|
servicesIpv4CidrBlock?: string;
|
|
74
74
|
clusterSecondaryRangeName?: string;
|
|
75
75
|
servicesSecondaryRangeName?: string;
|
|
76
|
+
/** Cluster egress network service tier. Mutable via `update`. */
|
|
77
|
+
networkTierConfig?: cont.NetworkTierConfig;
|
|
76
78
|
};
|
|
77
79
|
/**
|
|
78
80
|
* Cluster-level network settings. `datapathProvider:
|
|
@@ -251,6 +253,32 @@ export type Cluster = Resource<
|
|
|
251
253
|
>;
|
|
252
254
|
export const Cluster = Resource<Cluster>("GCP.Cluster");
|
|
253
255
|
|
|
256
|
+
/** @internal Pure replacement decision, exported for focused unit tests. */
|
|
257
|
+
export const diffClusterProps = (
|
|
258
|
+
olds: Partial<ClusterProps>,
|
|
259
|
+
news: ClusterProps,
|
|
260
|
+
): { action: "replace" } | undefined => {
|
|
261
|
+
if (
|
|
262
|
+
somePropsAreDifferent(olds as ClusterProps, news, [
|
|
263
|
+
"project",
|
|
264
|
+
"location",
|
|
265
|
+
"name",
|
|
266
|
+
"network",
|
|
267
|
+
"subnetwork",
|
|
268
|
+
])
|
|
269
|
+
) {
|
|
270
|
+
return { action: "replace" };
|
|
271
|
+
}
|
|
272
|
+
const { networkTierConfig: _oldTier, ...oldIpAllocationPolicy } =
|
|
273
|
+
olds.ipAllocationPolicy ?? {};
|
|
274
|
+
const { networkTierConfig: _newTier, ...newIpAllocationPolicy } =
|
|
275
|
+
news.ipAllocationPolicy ?? {};
|
|
276
|
+
if (!deepEqual(oldIpAllocationPolicy, newIpAllocationPolicy)) {
|
|
277
|
+
return { action: "replace" };
|
|
278
|
+
}
|
|
279
|
+
return undefined;
|
|
280
|
+
};
|
|
281
|
+
|
|
254
282
|
/**
|
|
255
283
|
* Build the inline `NodePool` entry for the cluster create body.
|
|
256
284
|
*
|
|
@@ -435,7 +463,8 @@ export const ClusterProvider = () =>
|
|
|
435
463
|
});
|
|
436
464
|
|
|
437
465
|
// Catch-all generic update for fields without dedicated setters:
|
|
438
|
-
// currently `releaseChannel` and
|
|
466
|
+
// currently `releaseChannel`, `workloadIdentityConfig`, and the
|
|
467
|
+
// cluster network tier. The
|
|
439
468
|
// API auto-derives the field mask from which `desired*` keys are
|
|
440
469
|
// populated — no `updateMask` parameter needed.
|
|
441
470
|
const syncClusterUpdate = Effect.fn(function* (args: {
|
|
@@ -457,6 +486,16 @@ export const ClusterProvider = () =>
|
|
|
457
486
|
) {
|
|
458
487
|
update.desiredWorkloadIdentityConfig = args.news.workloadIdentityConfig;
|
|
459
488
|
}
|
|
489
|
+
if (
|
|
490
|
+
args.news.ipAllocationPolicy?.networkTierConfig &&
|
|
491
|
+
!deepEqual(
|
|
492
|
+
args.observed.ipAllocationPolicy?.networkTierConfig,
|
|
493
|
+
args.news.ipAllocationPolicy.networkTierConfig,
|
|
494
|
+
)
|
|
495
|
+
) {
|
|
496
|
+
update.desiredNetworkTierConfig =
|
|
497
|
+
args.news.ipAllocationPolicy.networkTierConfig;
|
|
498
|
+
}
|
|
460
499
|
if (Object.keys(update).length === 0) return;
|
|
461
500
|
const op = yield* updateClusters({
|
|
462
501
|
name: args.fqName,
|
|
@@ -476,25 +515,11 @@ export const ClusterProvider = () =>
|
|
|
476
515
|
],
|
|
477
516
|
diff: Effect.fn(function* ({ news, olds = {} }) {
|
|
478
517
|
if (!isResolved(news)) return undefined;
|
|
479
|
-
if (
|
|
480
|
-
somePropsAreDifferent(olds as ClusterProps, news, [
|
|
481
|
-
"project",
|
|
482
|
-
"location",
|
|
483
|
-
"name",
|
|
484
|
-
"network",
|
|
485
|
-
"subnetwork",
|
|
486
|
-
])
|
|
487
|
-
) {
|
|
488
|
-
return { action: "replace" } as const;
|
|
489
|
-
}
|
|
490
|
-
if (!deepEqual(olds.ipAllocationPolicy, news.ipAllocationPolicy)) {
|
|
491
|
-
return { action: "replace" } as const;
|
|
492
|
-
}
|
|
493
518
|
// `initialNodePool` and `networkConfig` are intentionally NOT
|
|
494
519
|
// part of diff — both are consumed only on create. See the
|
|
495
520
|
// ClusterProps JSDoc (networkConfig explains why a same-name
|
|
496
521
|
// replace would be destructive).
|
|
497
|
-
return
|
|
522
|
+
return diffClusterProps(olds, news);
|
|
498
523
|
}),
|
|
499
524
|
reconcile: Effect.fn(function* ({ id, news, session }) {
|
|
500
525
|
const internalLabels = yield* gcpInternalLabels(id);
|
package/src/Providers.ts
CHANGED
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
ArtifactRegistryRepository,
|
|
5
5
|
ArtifactRegistryRepositoryProvider,
|
|
6
6
|
} from "./ArtifactRegistry/Repository.ts";
|
|
7
|
+
import {
|
|
8
|
+
ArtifactRegistryRepositoryIamMember,
|
|
9
|
+
ArtifactRegistryRepositoryIamMemberProvider,
|
|
10
|
+
} from "./ArtifactRegistry/RepositoryIamMember.ts";
|
|
7
11
|
import { GCPAuth } from "./Auth/AuthProvider.ts";
|
|
8
12
|
import { fromAuthProvider } from "./Auth/Credentials.ts";
|
|
9
13
|
import { Project, ProjectProvider } from "./CloudResourceManager/Project.ts";
|
|
@@ -15,6 +19,10 @@ import {
|
|
|
15
19
|
} from "./Compute/ForwardingRule.ts";
|
|
16
20
|
import { GlobalAddress, GlobalAddressProvider } from "./Compute/GlobalAddress.ts";
|
|
17
21
|
import { Network, NetworkProvider } from "./Compute/Network.ts";
|
|
22
|
+
import {
|
|
23
|
+
ProjectDefaultNetworkTier,
|
|
24
|
+
ProjectDefaultNetworkTierProvider,
|
|
25
|
+
} from "./Compute/ProjectDefaultNetworkTier.ts";
|
|
18
26
|
import { SharedVpcHost, SharedVpcHostProvider } from "./Compute/SharedVpcHost.ts";
|
|
19
27
|
import {
|
|
20
28
|
SharedVpcServiceProject,
|
|
@@ -94,6 +102,7 @@ export const providers = () =>
|
|
|
94
102
|
NodePool,
|
|
95
103
|
ApiEnable,
|
|
96
104
|
Network,
|
|
105
|
+
ProjectDefaultNetworkTier,
|
|
97
106
|
Subnetwork,
|
|
98
107
|
Firewall,
|
|
99
108
|
Address,
|
|
@@ -111,6 +120,7 @@ export const providers = () =>
|
|
|
111
120
|
SqlDatabase,
|
|
112
121
|
SqlUser,
|
|
113
122
|
ArtifactRegistryRepository,
|
|
123
|
+
ArtifactRegistryRepositoryIamMember,
|
|
114
124
|
ServiceAccount,
|
|
115
125
|
ServiceAccountKey,
|
|
116
126
|
KubernetesSecret,
|
|
@@ -125,6 +135,7 @@ export const providers = () =>
|
|
|
125
135
|
NodePoolProvider(),
|
|
126
136
|
ApiEnableProvider(),
|
|
127
137
|
NetworkProvider(),
|
|
138
|
+
ProjectDefaultNetworkTierProvider(),
|
|
128
139
|
SubnetworkProvider(),
|
|
129
140
|
FirewallProvider(),
|
|
130
141
|
AddressProvider(),
|
|
@@ -142,6 +153,7 @@ export const providers = () =>
|
|
|
142
153
|
SqlDatabaseProvider(),
|
|
143
154
|
SqlUserProvider(),
|
|
144
155
|
ArtifactRegistryRepositoryProvider(),
|
|
156
|
+
ArtifactRegistryRepositoryIamMemberProvider(),
|
|
145
157
|
ServiceAccountProvider(),
|
|
146
158
|
ServiceAccountKeyProvider(),
|
|
147
159
|
KubernetesSecretProvider(),
|