@microagi/alchemy-gcp 0.5.0 → 0.6.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 +24 -0
- package/lib/ArtifactRegistry/Repository.d.ts +1 -1
- package/lib/ArtifactRegistry/Repository.d.ts.map +1 -1
- package/lib/ArtifactRegistry/Repository.js +8 -9
- package/lib/ArtifactRegistry/Repository.js.map +1 -1
- package/lib/Auth/Credentials.d.ts +10 -3
- package/lib/Auth/Credentials.d.ts.map +1 -1
- package/lib/Auth/Credentials.js +13 -3
- package/lib/Auth/Credentials.js.map +1 -1
- package/lib/CloudResourceManager/Project.d.ts +1 -1
- package/lib/Compute/Address.d.ts +97 -0
- package/lib/Compute/Address.d.ts.map +1 -0
- package/lib/Compute/Address.js +143 -0
- package/lib/Compute/Address.js.map +1 -0
- package/lib/Compute/Firewall.d.ts +108 -0
- package/lib/Compute/Firewall.d.ts.map +1 -0
- package/lib/Compute/Firewall.js +163 -0
- package/lib/Compute/Firewall.js.map +1 -0
- package/lib/Compute/ForwardingRule.d.ts +106 -0
- package/lib/Compute/ForwardingRule.d.ts.map +1 -0
- package/lib/Compute/ForwardingRule.js +152 -0
- package/lib/Compute/ForwardingRule.js.map +1 -0
- package/lib/Compute/GlobalAddress.d.ts +1 -1
- package/lib/Compute/Network.d.ts +1 -1
- package/lib/Compute/SharedVpcHost.d.ts +1 -1
- package/lib/Compute/SharedVpcServiceProject.d.ts +1 -1
- package/lib/Compute/Subnetwork.d.ts +1 -1
- package/lib/Compute/index.d.ts +3 -0
- package/lib/Compute/index.d.ts.map +1 -1
- package/lib/Compute/index.js +3 -0
- package/lib/Compute/index.js.map +1 -1
- package/lib/Container/Cluster.d.ts +21 -1
- package/lib/Container/Cluster.d.ts.map +1 -1
- package/lib/Container/Cluster.js +5 -2
- package/lib/Container/Cluster.js.map +1 -1
- package/lib/Container/NodePool.d.ts +27 -2
- package/lib/Container/NodePool.d.ts.map +1 -1
- package/lib/Container/NodePool.js +45 -8
- package/lib/Container/NodePool.js.map +1 -1
- package/lib/Kubernetes/Secret.d.ts +89 -0
- package/lib/Kubernetes/Secret.d.ts.map +1 -0
- package/lib/Kubernetes/Secret.js +139 -0
- package/lib/Kubernetes/Secret.js.map +1 -0
- package/lib/Kubernetes/client.d.ts +58 -0
- package/lib/Kubernetes/client.d.ts.map +1 -0
- package/lib/Kubernetes/client.js +131 -0
- package/lib/Kubernetes/client.js.map +1 -0
- package/lib/Kubernetes/index.d.ts +2 -0
- package/lib/Kubernetes/index.d.ts.map +1 -0
- package/lib/Kubernetes/index.js +2 -0
- package/lib/Kubernetes/index.js.map +1 -0
- package/lib/ManagedLustre/Instance.d.ts +1 -1
- package/lib/ManagedLustre/Instance.d.ts.map +1 -1
- package/lib/ManagedLustre/Instance.js +25 -5
- package/lib/ManagedLustre/Instance.js.map +1 -1
- package/lib/Providers.d.ts +1 -1
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Providers.js +9 -1
- package/lib/Providers.js.map +1 -1
- package/lib/Run/Job.d.ts +1 -1
- package/lib/Run/Service.d.ts +1 -1
- package/lib/ServiceNetworking/PsaConnection.d.ts +1 -1
- package/lib/ServiceUsage/ApiEnable.d.ts +1 -1
- package/lib/Sqladmin/Database.d.ts +1 -1
- package/lib/Sqladmin/Instance.d.ts +1 -1
- package/lib/Sqladmin/Instance.d.ts.map +1 -1
- package/lib/Sqladmin/Instance.js +28 -6
- package/lib/Sqladmin/Instance.js.map +1 -1
- package/lib/Sqladmin/User.d.ts +1 -1
- package/lib/Tags.d.ts +4 -4
- 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 +1 -1
- package/src/ArtifactRegistry/Repository.ts +13 -9
- package/src/Auth/Credentials.ts +15 -4
- package/src/Compute/Address.ts +288 -0
- package/src/Compute/Firewall.ts +330 -0
- package/src/Compute/ForwardingRule.ts +301 -0
- package/src/Compute/index.ts +3 -0
- package/src/Container/Cluster.ts +25 -2
- package/src/Container/NodePool.ts +87 -13
- package/src/Kubernetes/Secret.ts +274 -0
- package/src/Kubernetes/client.ts +219 -0
- package/src/Kubernetes/index.ts +1 -0
- package/src/ManagedLustre/Instance.ts +35 -6
- package/src/Providers.ts +18 -0
- package/src/Sqladmin/Instance.ts +36 -7
- package/src/index.ts +1 -0
|
@@ -503,19 +503,23 @@ export const ArtifactRegistryRepositoryProvider = () =>
|
|
|
503
503
|
],
|
|
504
504
|
diff: Effect.fn(function* ({ news, olds = {} }) {
|
|
505
505
|
if (!isResolved(news)) return undefined;
|
|
506
|
+
// String-typed stables are reference-comparable.
|
|
506
507
|
if (
|
|
507
508
|
somePropsAreDifferent(
|
|
508
509
|
olds as ArtifactRegistryRepositoryProps,
|
|
509
510
|
news,
|
|
510
|
-
[
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
511
|
+
["project", "location", "name", "format", "mode", "kmsKeyName"],
|
|
512
|
+
)
|
|
513
|
+
) {
|
|
514
|
+
return { action: "replace" } as const;
|
|
515
|
+
}
|
|
516
|
+
// Object-typed stables must use deep equality — JSON-deserialized
|
|
517
|
+
// state never `===` a freshly-built literal even when content matches.
|
|
518
|
+
const oldsTyped = olds as ArtifactRegistryRepositoryProps;
|
|
519
|
+
if (
|
|
520
|
+
!deepEqual(
|
|
521
|
+
oldsTyped.remoteRepositoryConfig,
|
|
522
|
+
news.remoteRepositoryConfig,
|
|
519
523
|
)
|
|
520
524
|
) {
|
|
521
525
|
return { action: "replace" } as const;
|
package/src/Auth/Credentials.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConfigError, Credentials } from "@distilled.cloud/gcp";
|
|
2
2
|
import { getAuthProvider } from "alchemy/Auth/AuthProvider";
|
|
3
|
-
import { ALCHEMY_PROFILE,
|
|
3
|
+
import { ALCHEMY_PROFILE, AlchemyProfile } from "alchemy/Auth/Profile";
|
|
4
4
|
import * as Config from "effect/Config";
|
|
5
5
|
import * as Effect from "effect/Effect";
|
|
6
6
|
import * as Layer from "effect/Layer";
|
|
@@ -29,11 +29,17 @@ const toConfigError = (e: { message?: string } | unknown) =>
|
|
|
29
29
|
*
|
|
30
30
|
* Prefer {@link fromAuthProvider} when the stack runs through
|
|
31
31
|
* `alchemy login` / `ALCHEMY_PROFILE`.
|
|
32
|
+
*
|
|
33
|
+
* The `Credentials` service is lazy (its value is an `Effect<Config>`
|
|
34
|
+
* re-evaluated per SDK call), so minting happens at request time —
|
|
35
|
+
* `google-auth-library` caches and refreshes underneath. Failures
|
|
36
|
+
* become defects: the service channel carries no typed error, matching
|
|
37
|
+
* distilled's own `CredentialsFromEnv`.
|
|
32
38
|
*/
|
|
33
39
|
export const fromADC = (project?: string) =>
|
|
34
|
-
Layer.
|
|
40
|
+
Layer.succeed(
|
|
35
41
|
Credentials,
|
|
36
|
-
mintToken({ project }).pipe(Effect.mapError(toConfigError)),
|
|
42
|
+
mintToken({ project }).pipe(Effect.mapError(toConfigError), Effect.orDie),
|
|
37
43
|
);
|
|
38
44
|
|
|
39
45
|
/**
|
|
@@ -51,6 +57,7 @@ export const fromAuthProvider = () =>
|
|
|
51
57
|
Layer.effect(
|
|
52
58
|
Credentials,
|
|
53
59
|
Effect.gen(function* () {
|
|
60
|
+
const profile = yield* AlchemyProfile;
|
|
54
61
|
const auth = yield* getAuthProvider<
|
|
55
62
|
GCPAuthConfig,
|
|
56
63
|
GCPResolvedCredentials
|
|
@@ -59,11 +66,15 @@ export const fromAuthProvider = () =>
|
|
|
59
66
|
const ci = yield* Config.boolean("CI").pipe(Config.withDefault(false));
|
|
60
67
|
const ctx = yield* Effect.context<never>();
|
|
61
68
|
|
|
62
|
-
|
|
69
|
+
// The service value is itself an Effect (lazy credentials):
|
|
70
|
+
// profile/provider wiring resolves once at layer build, while
|
|
71
|
+
// loadOrConfigure + token read run per SDK call.
|
|
72
|
+
return profile.loadOrConfigure(auth, profileName, { ci }).pipe(
|
|
63
73
|
Effect.flatMap((cfg) => auth.read(profileName, cfg as GCPAuthConfig)),
|
|
64
74
|
Effect.map(({ accessToken, project }) => ({ accessToken, project })),
|
|
65
75
|
Effect.mapError(toConfigError),
|
|
66
76
|
Effect.provide(ctx),
|
|
77
|
+
Effect.orDie,
|
|
67
78
|
);
|
|
68
79
|
}),
|
|
69
80
|
);
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import * as compute from "@distilled.cloud/gcp/compute-v1";
|
|
2
|
+
import { Resource } from "alchemy";
|
|
3
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
4
|
+
import type { ScopedPlanStatusSession } from "alchemy/Cli/Cli";
|
|
5
|
+
import { deepEqual, isResolved, somePropsAreDifferent } from "alchemy/Diff";
|
|
6
|
+
import { createPhysicalName } from "alchemy/PhysicalName";
|
|
7
|
+
import * as Provider from "alchemy/Provider";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import { gcpInternalLabels, hasAlchemyLabels } from "../Tags.ts";
|
|
10
|
+
import type * as GCP from "../Providers.ts";
|
|
11
|
+
import { makeAwaitRegionOperation } from "./Operations.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A regional compute Address. The intended use here is a **Private
|
|
15
|
+
* Service Connect consumer endpoint IP** — a single internal address
|
|
16
|
+
* reserved in a subnet (`addressType=INTERNAL`, `purpose=GCE_ENDPOINT`)
|
|
17
|
+
* that a `ForwardingRule` then binds to a producer service attachment
|
|
18
|
+
* (e.g. a Cloud SQL instance in another VPC).
|
|
19
|
+
*
|
|
20
|
+
* For global PSA *ranges* (`purpose=VPC_PEERING`) use
|
|
21
|
+
* {@link GlobalAddress} — that is a different API surface
|
|
22
|
+
* (`globalAddresses` vs regional `addresses`).
|
|
23
|
+
*
|
|
24
|
+
* Adoption uses the labels field (Address carries `labels` +
|
|
25
|
+
* `labelFingerprint`).
|
|
26
|
+
*
|
|
27
|
+
* @section Reserving a PSC endpoint IP
|
|
28
|
+
* @example Internal address inside a subnet for a PSC forwarding rule
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const endpointIp = yield* GCP.Address("DbPscAddress", {
|
|
31
|
+
* project: hostProject.projectId,
|
|
32
|
+
* region: "europe-west1",
|
|
33
|
+
* subnetwork: pscSubnet.selfLink,
|
|
34
|
+
* address: "10.0.16.5",
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export type AddressProps = {
|
|
39
|
+
/** GCP project ID hosting the address. Immutable — replace if changed. */
|
|
40
|
+
project: string;
|
|
41
|
+
/** GCP region. Addresses are regional — a different region requires replace. */
|
|
42
|
+
region: string;
|
|
43
|
+
/**
|
|
44
|
+
* Address name. Defaults to `createPhysicalName({ id, lowercase: true,
|
|
45
|
+
* maxLength: 63 })`. Immutable — replace if changed.
|
|
46
|
+
*/
|
|
47
|
+
name?: string;
|
|
48
|
+
/** Description (free-form). Set on create; immutable. */
|
|
49
|
+
description?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Internal vs external. Defaults to `INTERNAL` (the only value that
|
|
52
|
+
* makes sense for a PSC endpoint IP). Immutable — replace.
|
|
53
|
+
*/
|
|
54
|
+
addressType?: "INTERNAL" | "EXTERNAL";
|
|
55
|
+
/**
|
|
56
|
+
* Address purpose. For a PSC endpoint IP use `GCE_ENDPOINT` (the
|
|
57
|
+
* default GCP assigns to plain internal addresses). Immutable — replace.
|
|
58
|
+
*/
|
|
59
|
+
purpose?: "GCE_ENDPOINT" | "SHARED_LOADBALANCER_VIP" | "DNS_RESOLVER";
|
|
60
|
+
/**
|
|
61
|
+
* URL of the subnetwork the address is allocated from. Pass
|
|
62
|
+
* `subnet.selfLink` from a `GCP.Subnetwork` to make alchemy sequence
|
|
63
|
+
* the address after the subnet. Required for INTERNAL addresses.
|
|
64
|
+
* Immutable — replace.
|
|
65
|
+
*/
|
|
66
|
+
subnetwork: string;
|
|
67
|
+
/**
|
|
68
|
+
* Explicit IP to reserve inside the subnet's range. If omitted, GCP
|
|
69
|
+
* picks one. Immutable — replace.
|
|
70
|
+
*/
|
|
71
|
+
address?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Resource labels. Alchemy internal labels (`alchemy_app`,
|
|
74
|
+
* `alchemy_stage`, `alchemy_id`) are merged on top automatically and
|
|
75
|
+
* are reserved. Mutable via `setLabels`.
|
|
76
|
+
*/
|
|
77
|
+
labels?: Record<string, string>;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type AddressAttributes = {
|
|
81
|
+
/** Address name. */
|
|
82
|
+
name: string;
|
|
83
|
+
/** GCP project ID, threaded through from props for delete/read. */
|
|
84
|
+
project: string;
|
|
85
|
+
/** GCP region, threaded through from props for delete/read. */
|
|
86
|
+
region: string;
|
|
87
|
+
/** Server-defined URL. */
|
|
88
|
+
selfLink: string;
|
|
89
|
+
/** Server-assigned numeric id. */
|
|
90
|
+
id: string;
|
|
91
|
+
/** The reserved IP. */
|
|
92
|
+
address: string;
|
|
93
|
+
/** Address type. */
|
|
94
|
+
addressType: string | undefined;
|
|
95
|
+
/** Purpose. */
|
|
96
|
+
purpose: string | undefined;
|
|
97
|
+
/** Subnetwork URL. */
|
|
98
|
+
subnetwork: string | undefined;
|
|
99
|
+
/** Status: `RESERVING`, `RESERVED`, `IN_USE`. */
|
|
100
|
+
status: string | undefined;
|
|
101
|
+
/** Labels currently set, including the alchemy internals. */
|
|
102
|
+
labels: Record<string, string>;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type Address = Resource<
|
|
106
|
+
"GCP.Address",
|
|
107
|
+
AddressProps,
|
|
108
|
+
AddressAttributes,
|
|
109
|
+
never,
|
|
110
|
+
GCP.Providers
|
|
111
|
+
>;
|
|
112
|
+
export const Address = Resource<Address>("GCP.Address");
|
|
113
|
+
|
|
114
|
+
const toAddressAttributes = (
|
|
115
|
+
a: compute.Address,
|
|
116
|
+
parent: { project: string; region: string },
|
|
117
|
+
): AddressAttributes => ({
|
|
118
|
+
name: a.name ?? "",
|
|
119
|
+
project: parent.project,
|
|
120
|
+
region: parent.region,
|
|
121
|
+
selfLink: a.selfLink ?? "",
|
|
122
|
+
id: a.id ?? "",
|
|
123
|
+
address: a.address ?? "",
|
|
124
|
+
addressType: a.addressType,
|
|
125
|
+
purpose: a.purpose,
|
|
126
|
+
subnetwork: a.subnetwork,
|
|
127
|
+
status: a.status,
|
|
128
|
+
labels: { ...(a.labels ?? {}) },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export const AddressProvider = () =>
|
|
132
|
+
Provider.effect(
|
|
133
|
+
Address,
|
|
134
|
+
Effect.gen(function* () {
|
|
135
|
+
const getAddresses = yield* compute.getAddresses;
|
|
136
|
+
const insertAddresses = yield* compute.insertAddresses;
|
|
137
|
+
const deleteAddresses = yield* compute.deleteAddresses;
|
|
138
|
+
const setLabelsAddresses = yield* compute.setLabelsAddresses;
|
|
139
|
+
const getRegionOperations = yield* compute.getRegionOperations;
|
|
140
|
+
const awaitOp = makeAwaitRegionOperation(getRegionOperations);
|
|
141
|
+
|
|
142
|
+
const observe = (project: string, region: string, name: string) =>
|
|
143
|
+
getAddresses({ project, region, address: name }).pipe(
|
|
144
|
+
Effect.catchTag("NotFound", () =>
|
|
145
|
+
Effect.succeed(undefined as compute.Address | undefined),
|
|
146
|
+
),
|
|
147
|
+
Effect.catchTag("Forbidden", () =>
|
|
148
|
+
Effect.succeed(undefined as compute.Address | undefined),
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const syncLabels = Effect.fn(function* (args: {
|
|
153
|
+
project: string;
|
|
154
|
+
region: string;
|
|
155
|
+
observed: compute.Address;
|
|
156
|
+
desired: Record<string, string>;
|
|
157
|
+
session: ScopedPlanStatusSession;
|
|
158
|
+
}) {
|
|
159
|
+
if (deepEqual(args.observed.labels ?? {}, args.desired)) return;
|
|
160
|
+
const op = yield* setLabelsAddresses({
|
|
161
|
+
project: args.project,
|
|
162
|
+
region: args.region,
|
|
163
|
+
resource: args.observed.name ?? "",
|
|
164
|
+
body: {
|
|
165
|
+
labels: args.desired,
|
|
166
|
+
labelFingerprint: args.observed.labelFingerprint,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
if (op.name) {
|
|
170
|
+
yield* awaitOp(args.project, args.region, op.name, args.session);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
stables: ["name", "project", "region", "selfLink", "id", "address"],
|
|
176
|
+
diff: Effect.fn(function* ({ news, olds = {} }) {
|
|
177
|
+
if (!isResolved(news)) return undefined;
|
|
178
|
+
if (
|
|
179
|
+
somePropsAreDifferent(olds as AddressProps, news, [
|
|
180
|
+
"project",
|
|
181
|
+
"region",
|
|
182
|
+
"name",
|
|
183
|
+
"addressType",
|
|
184
|
+
"purpose",
|
|
185
|
+
"subnetwork",
|
|
186
|
+
"address",
|
|
187
|
+
])
|
|
188
|
+
) {
|
|
189
|
+
return { action: "replace" } as const;
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
}),
|
|
193
|
+
reconcile: Effect.fn(function* ({ id, news, session }) {
|
|
194
|
+
const internalLabels = yield* gcpInternalLabels(id);
|
|
195
|
+
const desiredName =
|
|
196
|
+
news.name ??
|
|
197
|
+
(yield* createPhysicalName({ id, maxLength: 63 })).toLowerCase();
|
|
198
|
+
const desiredLabels: Record<string, string> = {
|
|
199
|
+
...(news.labels ?? {}),
|
|
200
|
+
...internalLabels,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
let observed = yield* observe(news.project, news.region, desiredName);
|
|
204
|
+
|
|
205
|
+
if (!observed) {
|
|
206
|
+
const body: compute.Address = {
|
|
207
|
+
name: desiredName,
|
|
208
|
+
...(news.description ? { description: news.description } : {}),
|
|
209
|
+
addressType: news.addressType ?? "INTERNAL",
|
|
210
|
+
...(news.purpose ? { purpose: news.purpose } : {}),
|
|
211
|
+
subnetwork: news.subnetwork,
|
|
212
|
+
...(news.address ? { address: news.address } : {}),
|
|
213
|
+
labels: desiredLabels,
|
|
214
|
+
};
|
|
215
|
+
const op = yield* insertAddresses({
|
|
216
|
+
project: news.project,
|
|
217
|
+
region: news.region,
|
|
218
|
+
body,
|
|
219
|
+
}).pipe(
|
|
220
|
+
Effect.catchTag("Conflict", () =>
|
|
221
|
+
Effect.succeed(undefined as compute.Operation | undefined),
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
if (op?.name) {
|
|
225
|
+
yield* awaitOp(news.project, news.region, op.name, session);
|
|
226
|
+
}
|
|
227
|
+
observed = yield* getAddresses({
|
|
228
|
+
project: news.project,
|
|
229
|
+
region: news.region,
|
|
230
|
+
address: desiredName,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
yield* syncLabels({
|
|
235
|
+
project: news.project,
|
|
236
|
+
region: news.region,
|
|
237
|
+
observed,
|
|
238
|
+
desired: desiredLabels,
|
|
239
|
+
session,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const final = yield* getAddresses({
|
|
243
|
+
project: news.project,
|
|
244
|
+
region: news.region,
|
|
245
|
+
address: desiredName,
|
|
246
|
+
});
|
|
247
|
+
return toAddressAttributes(final, {
|
|
248
|
+
project: news.project,
|
|
249
|
+
region: news.region,
|
|
250
|
+
});
|
|
251
|
+
}),
|
|
252
|
+
delete: Effect.fn(function* ({ output, session }) {
|
|
253
|
+
yield* deleteAddresses({
|
|
254
|
+
project: output.project,
|
|
255
|
+
region: output.region,
|
|
256
|
+
address: output.name,
|
|
257
|
+
}).pipe(
|
|
258
|
+
Effect.flatMap((op) =>
|
|
259
|
+
op.name
|
|
260
|
+
? awaitOp(output.project, output.region, op.name, session)
|
|
261
|
+
: Effect.succeed(op),
|
|
262
|
+
),
|
|
263
|
+
Effect.catchTag("NotFound", () => Effect.void),
|
|
264
|
+
// 400 "is being used by ..." comes back while a forwarding
|
|
265
|
+
// rule still references the address. The rule must be
|
|
266
|
+
// deleted first (alchemy's dep order handles this when the
|
|
267
|
+
// rule consumes `address.selfLink`); surface the error
|
|
268
|
+
// rather than silently swallowing.
|
|
269
|
+
);
|
|
270
|
+
}),
|
|
271
|
+
read: Effect.fn(function* ({ id, output, olds }) {
|
|
272
|
+
const project = output?.project ?? olds?.project;
|
|
273
|
+
const region = output?.region ?? olds?.region;
|
|
274
|
+
if (!project || !region) return undefined;
|
|
275
|
+
const name =
|
|
276
|
+
output?.name ??
|
|
277
|
+
olds?.name ??
|
|
278
|
+
(yield* createPhysicalName({ id, maxLength: 63 })).toLowerCase();
|
|
279
|
+
const observed = yield* observe(project, region, name);
|
|
280
|
+
if (!observed) return undefined;
|
|
281
|
+
const attrs = toAddressAttributes(observed, { project, region });
|
|
282
|
+
return (yield* hasAlchemyLabels(id, observed.labels))
|
|
283
|
+
? attrs
|
|
284
|
+
: Unowned(attrs);
|
|
285
|
+
}),
|
|
286
|
+
};
|
|
287
|
+
}),
|
|
288
|
+
);
|