@lanes-sh/link 0.6.4 → 0.6.5
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/package.json
CHANGED
|
@@ -92,6 +92,20 @@ export interface ProvisionInput {
|
|
|
92
92
|
* what a target with no profile to walk still needs.
|
|
93
93
|
*/
|
|
94
94
|
readonly readable?: readonly string[];
|
|
95
|
+
/**
|
|
96
|
+
* The profiles this revision serves, so the bucket conditions can name each
|
|
97
|
+
* one's provider manifests.
|
|
98
|
+
*
|
|
99
|
+
* Needed because Cloud Storage IAM conditions cannot express "any profile
|
|
100
|
+
* segment": their CEL is a restricted subset with no `matches`, so the only
|
|
101
|
+
* way to carve out `data/<profile>/providers.d/` is to enumerate the profiles
|
|
102
|
+
* and write one `startsWith` each. `deploy.ts` already resolved the list.
|
|
103
|
+
*
|
|
104
|
+
* Absent leaves the carve-out off entirely rather than guessing, which is the
|
|
105
|
+
* safe direction: the revision keeps write on its own data and the manifests
|
|
106
|
+
* inside it, exactly as it did before the carve-out existed.
|
|
107
|
+
*/
|
|
108
|
+
readonly profiles?: readonly string[];
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
export interface SurveyInput {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { VAULT_DOCUMENT_REF, type SecretRef } from '#secrets';
|
|
2
2
|
import type { DeployStep, ProvisionInput } from '../driver.ts';
|
|
3
3
|
import { encodeRef } from '../adapters/gcp-secret-manager.ts';
|
|
4
|
+
import { layout } from '#profile';
|
|
4
5
|
import { requireProject } from './gcloud.ts';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -296,22 +297,34 @@ export function provisionSteps(input: ProvisionInput): Promise<DeployStep[]> {
|
|
|
296
297
|
`resource.name == "projects/_/buckets/${bucket}/objects/${path}"`;
|
|
297
298
|
|
|
298
299
|
// A provider manifest is configuration that happens to live inside the
|
|
299
|
-
// profile's directory (ADR-030), so `data/` alone no longer separates
|
|
300
|
-
//
|
|
301
|
-
// profile segment rather than matched loosely: `contains("/providers.d/")`
|
|
302
|
-
// would also catch a blob whose own key happened to spell it.
|
|
300
|
+
// profile's directory (ADR-030), so `data/` alone no longer separates what
|
|
301
|
+
// the revision owns from what declares what it is.
|
|
303
302
|
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
303
|
+
// **One `startsWith` per profile, because Cloud Storage IAM conditions
|
|
304
|
+
// cannot express anything else.** Their CEL is a restricted subset —
|
|
305
|
+
// `resource.type`, `resource.name` with `startsWith`/`endsWith`/`==`, and
|
|
306
|
+
// the date functions — and it has no `matches`. This was a regex, and it
|
|
307
|
+
// was refused twice over: first because it spelled the dot `\.`, which is
|
|
308
|
+
// not a CEL escape, so the string literal would not parse; then, with that
|
|
309
|
+
// fixed, because `matches` is `undeclared` in this dialect.
|
|
310
|
+
//
|
|
311
|
+
// Both bindings carry `tolerateFailure`, so each attempt printed a warning
|
|
312
|
+
// and left whatever conditions the bucket already had. On a real
|
|
313
|
+
// deployment that was `expression=true` on the read binding — every object
|
|
314
|
+
// in the bucket, the exact opposite of the narrowing the step title claims,
|
|
315
|
+
// and the state ADR-007 says must not exist.
|
|
316
|
+
//
|
|
317
|
+
// Enumerating the served profiles is expressible in the subset that does
|
|
318
|
+
// exist, and it makes `grants.test.ts` honest as a side effect: that file
|
|
319
|
+
// evaluates these as JavaScript, where `startsWith` means what it means
|
|
320
|
+
// here and `matches` quietly did not.
|
|
321
|
+
const manifestPrefixes = (input.profiles ?? []).map((profile) =>
|
|
322
|
+
objectsUnder(`${layout.providers(profile)}/`),
|
|
323
|
+
);
|
|
324
|
+
// No profiles leaves the carve-out off rather than guessing at one: the
|
|
325
|
+
// revision keeps write on its own data, as it did before this existed.
|
|
313
326
|
const providerManifests =
|
|
314
|
-
`
|
|
327
|
+
manifestPrefixes.length > 0 ? `(${manifestPrefixes.join(' || ')})` : null;
|
|
315
328
|
|
|
316
329
|
steps.push({
|
|
317
330
|
title: 'let the revision write its own data, but not the manifests in it',
|
|
@@ -327,7 +340,7 @@ export function provisionSteps(input: ProvisionInput): Promise<DeployStep[]> {
|
|
|
327
340
|
'--role',
|
|
328
341
|
'roles/storage.objectAdmin',
|
|
329
342
|
'--condition',
|
|
330
|
-
`title=owns-its-data,expression=${objectsUnder('data/')} && !${providerManifests}`,
|
|
343
|
+
`title=owns-its-data,expression=${objectsUnder('data/')}${providerManifests ? ` && !${providerManifests}` : ''}`,
|
|
331
344
|
],
|
|
332
345
|
tolerateFailure: true,
|
|
333
346
|
});
|
|
@@ -348,7 +361,7 @@ export function provisionSteps(input: ProvisionInput): Promise<DeployStep[]> {
|
|
|
348
361
|
// The config the revision reads is the workspace file, the profiles
|
|
349
362
|
// beside it, and each profile's own manifests, so name exactly those.
|
|
350
363
|
'--condition',
|
|
351
|
-
`title=reads-its-config,expression=${objectsUnder('profiles/')} || ${objectIs('lanes-link.yaml')} || ${providerManifests}`,
|
|
364
|
+
`title=reads-its-config,expression=${objectsUnder('profiles/')} || ${objectIs('lanes-link.yaml')}${providerManifests ? ` || ${providerManifests}` : ''}`,
|
|
352
365
|
],
|
|
353
366
|
tolerateFailure: true,
|
|
354
367
|
});
|