@lanes-sh/link 0.1.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/LICENSE +202 -0
- package/README.md +144 -0
- package/bin/lanes +42 -0
- package/instructions/agents/lanes-link-scout.md +73 -0
- package/instructions/skills/lanes-link/SKILL.md +187 -0
- package/package.json +95 -0
- package/src/audit/chain.ts +278 -0
- package/src/audit/conformance.ts +374 -0
- package/src/audit/fanout.ts +97 -0
- package/src/audit/index.ts +218 -0
- package/src/audit/stdout.ts +60 -0
- package/src/auth/index.ts +220 -0
- package/src/auth/oauth/metadata.ts +75 -0
- package/src/auth/oauth/server.ts +385 -0
- package/src/auth/oauth/store.ts +215 -0
- package/src/auth/oidc.ts +206 -0
- package/src/auth/remote.ts +72 -0
- package/src/cli/argv.ts +94 -0
- package/src/cli/callback-page.ts +256 -0
- package/src/cli/commands/connect/accounts.ts +94 -0
- package/src/cli/commands/connect/authorise.ts +298 -0
- package/src/cli/commands/connect/client.ts +284 -0
- package/src/cli/commands/connect/index.ts +398 -0
- package/src/cli/commands/connect/outcome.ts +119 -0
- package/src/cli/commands/connect/requirements.ts +103 -0
- package/src/cli/commands/connect/scopes-gate.ts +146 -0
- package/src/cli/commands/connect/settle.ts +136 -0
- package/src/cli/commands/connect/setup.ts +276 -0
- package/src/cli/commands/mcp/assets.ts +189 -0
- package/src/cli/commands/mcp/harnesses.ts +143 -0
- package/src/cli/commands/mcp/list.ts +70 -0
- package/src/cli/commands/mcp/register.ts +199 -0
- package/src/cli/commands/mcp/stdio.ts +57 -0
- package/src/cli/commands/mcp.ts +22 -0
- package/src/cli/commands/operate/attach.ts +121 -0
- package/src/cli/commands/operate/audit.ts +119 -0
- package/src/cli/commands/operate/inspect.ts +355 -0
- package/src/cli/commands/operate/outputs.ts +206 -0
- package/src/cli/commands/operate/policy.ts +80 -0
- package/src/cli/commands/operate/serve.ts +55 -0
- package/src/cli/commands/operate/status.ts +133 -0
- package/src/cli/commands/operate/token.ts +70 -0
- package/src/cli/commands/operate.ts +27 -0
- package/src/cli/commands/owner/memory.ts +110 -0
- package/src/cli/commands/owner/shared.ts +125 -0
- package/src/cli/commands/owner/skills.ts +92 -0
- package/src/cli/commands/owner/vault.ts +140 -0
- package/src/cli/commands/owner.ts +46 -0
- package/src/cli/commands/profile/removal.ts +278 -0
- package/src/cli/commands/profile/remove.ts +315 -0
- package/src/cli/commands/profile.ts +156 -0
- package/src/cli/commands/secrets.ts +176 -0
- package/src/cli/commands/setup.ts +150 -0
- package/src/cli/commands/target.ts +310 -0
- package/src/cli/config-edit.ts +397 -0
- package/src/cli/endpoint-url.ts +77 -0
- package/src/cli/identity.ts +109 -0
- package/src/cli/lanes.ts +78 -0
- package/src/cli/main.ts +333 -0
- package/src/cli/oauth-error.ts +13 -0
- package/src/cli/oauth-exchange.ts +146 -0
- package/src/cli/oauth.ts +354 -0
- package/src/cli/output.ts +184 -0
- package/src/cli/prompt.ts +180 -0
- package/src/cli/publish.ts +185 -0
- package/src/cli/runtime/discovery.ts +123 -0
- package/src/cli/runtime/open.ts +342 -0
- package/src/cli/runtime/registry.ts +185 -0
- package/src/cli/runtime/select.ts +124 -0
- package/src/cli/runtime.ts +34 -0
- package/src/cli/scopes.ts +63 -0
- package/src/cli/usage.ts +111 -0
- package/src/cli/version.ts +25 -0
- package/src/connectivity/auth/README.md +36 -0
- package/src/connectivity/auth/api-key/index.ts +43 -0
- package/src/connectivity/auth/authorize.ts +49 -0
- package/src/connectivity/auth/basic/index.ts +68 -0
- package/src/connectivity/auth/bearer/index.ts +13 -0
- package/src/connectivity/auth/credential.ts +19 -0
- package/src/connectivity/auth/header/index.ts +18 -0
- package/src/connectivity/auth/index.ts +35 -0
- package/src/connectivity/auth/none/index.ts +12 -0
- package/src/connectivity/auth/oauth-authcode/broker.ts +261 -0
- package/src/connectivity/auth/oauth-authcode/index.ts +64 -0
- package/src/connectivity/auth/oauth-authcode/provider.ts +279 -0
- package/src/connectivity/auth/oauth-authcode/refresh.ts +118 -0
- package/src/connectivity/auth/resolve.ts +61 -0
- package/src/connectivity/auth/strategy/index.ts +14 -0
- package/src/connectivity/capability.ts +164 -0
- package/src/connectivity/connector.ts +175 -0
- package/src/connectivity/context.ts +77 -0
- package/src/connectivity/index.ts +94 -0
- package/src/connectivity/mail/attachments.ts +368 -0
- package/src/connectivity/mail/compose.ts +73 -0
- package/src/connectivity/mail/index.ts +40 -0
- package/src/connectivity/mail/message.ts +82 -0
- package/src/connectivity/mail/nodemailer.d.ts +83 -0
- package/src/connectivity/mail/staging.ts +143 -0
- package/src/connectivity/mail/url.ts +301 -0
- package/src/connectivity/manifest/auth.ts +135 -0
- package/src/connectivity/manifest/bundles.ts +23 -0
- package/src/connectivity/manifest/connector.ts +168 -0
- package/src/connectivity/manifest/credential-ref.ts +73 -0
- package/src/connectivity/manifest/identity.ts +43 -0
- package/src/connectivity/manifest/index.ts +46 -0
- package/src/connectivity/manifest/primitives.ts +20 -0
- package/src/connectivity/manifest/provider.ts +208 -0
- package/src/connectivity/manifest/requirements.ts +146 -0
- package/src/connectivity/manifest/setup.ts +55 -0
- package/src/connectivity/provider.ts +163 -0
- package/src/connectivity/transports/README.md +33 -0
- package/src/connectivity/transports/composite/index.ts +68 -0
- package/src/connectivity/transports/dav/calendar.ts +217 -0
- package/src/connectivity/transports/dav/capabilities.ts +151 -0
- package/src/connectivity/transports/dav/client.ts +226 -0
- package/src/connectivity/transports/dav/contacts.ts +75 -0
- package/src/connectivity/transports/dav/ical.ts +412 -0
- package/src/connectivity/transports/dav/index.ts +143 -0
- package/src/connectivity/transports/dav/operations.ts +43 -0
- package/src/connectivity/transports/dav/request.ts +161 -0
- package/src/connectivity/transports/dav/xml.ts +123 -0
- package/src/connectivity/transports/factory.ts +181 -0
- package/src/connectivity/transports/fs/capabilities.ts +88 -0
- package/src/connectivity/transports/fs/commands.ts +258 -0
- package/src/connectivity/transports/fs/index.ts +121 -0
- package/src/connectivity/transports/fs/operations.ts +45 -0
- package/src/connectivity/transports/fs/paths.ts +120 -0
- package/src/connectivity/transports/fs/result.ts +12 -0
- package/src/connectivity/transports/http/index.ts +255 -0
- package/src/connectivity/transports/imap/attachment.ts +166 -0
- package/src/connectivity/transports/imap/capabilities.ts +158 -0
- package/src/connectivity/transports/imap/client.ts +398 -0
- package/src/connectivity/transports/imap/commands.ts +385 -0
- package/src/connectivity/transports/imap/index.ts +152 -0
- package/src/connectivity/transports/imap/operations.ts +64 -0
- package/src/connectivity/transports/imap/parse.ts +130 -0
- package/src/connectivity/transports/imap/parser.ts +272 -0
- package/src/connectivity/transports/imap/result.ts +15 -0
- package/src/connectivity/transports/imap/send.ts +92 -0
- package/src/connectivity/transports/imap/socket.ts +111 -0
- package/src/connectivity/transports/imap/utf7.ts +136 -0
- package/src/connectivity/transports/index.ts +20 -0
- package/src/connectivity/transports/local/index.ts +173 -0
- package/src/connectivity/transports/mcp/index.ts +215 -0
- package/src/deployments/README.md +63 -0
- package/src/deployments/adapters/audit-blob.ts +203 -0
- package/src/deployments/adapters/filesystem.ts +184 -0
- package/src/deployments/adapters/gcp-secret-manager.ts +492 -0
- package/src/deployments/adapters/gcs.ts +191 -0
- package/src/deployments/adapters/otlp.ts +128 -0
- package/src/deployments/adapters/s3.ts +195 -0
- package/src/deployments/azure/README.md +21 -0
- package/src/deployments/bootstrap.ts +177 -0
- package/src/deployments/deploy.ts +290 -0
- package/src/deployments/driver.ts +157 -0
- package/src/deployments/drivers.ts +35 -0
- package/src/deployments/gcp/Dockerfile +70 -0
- package/src/deployments/gcp/cloudbuild.yaml +31 -0
- package/src/deployments/gcp/driver.ts +175 -0
- package/src/deployments/gcp/gcloud.ts +178 -0
- package/src/deployments/gcp/provision.ts +290 -0
- package/src/deployments/gcp/survey.ts +319 -0
- package/src/deployments/local/README.md +12 -0
- package/src/deployments/prepare.ts +257 -0
- package/src/deployments/steps.ts +137 -0
- package/src/deployments/target.ts +295 -0
- package/src/deployments/upload.ts +207 -0
- package/src/dispatch/context.ts +195 -0
- package/src/dispatch/dispatch.ts +350 -0
- package/src/dispatch/index.ts +32 -0
- package/src/dispatch/staging.ts +102 -0
- package/src/policy/index.ts +179 -0
- package/src/policy/limits.ts +77 -0
- package/src/profile/authorization.ts +81 -0
- package/src/profile/files.ts +71 -0
- package/src/profile/index.ts +76 -0
- package/src/profile/layout.ts +123 -0
- package/src/profile/load.ts +199 -0
- package/src/profile/primitives.ts +45 -0
- package/src/profile/schema.ts +347 -0
- package/src/profile/secret-detection.ts +162 -0
- package/src/profile/targets.ts +152 -0
- package/src/profile/workspace.ts +262 -0
- package/src/providers/custom/index.ts +21 -0
- package/src/providers/custom/load.ts +115 -0
- package/src/providers/custom/template.ts +156 -0
- package/src/providers/example/provider.ts +207 -0
- package/src/providers/google/calendar/index.ts +66 -0
- package/src/providers/google/calendar/redact.ts +40 -0
- package/src/providers/google/contacts/index.ts +50 -0
- package/src/providers/google/contacts/redact.ts +21 -0
- package/src/providers/google/docs/index.ts +45 -0
- package/src/providers/google/drive/hints.ts +28 -0
- package/src/providers/google/drive/index.ts +34 -0
- package/src/providers/google/drive/redact.ts +39 -0
- package/src/providers/google/drive-mcp/index.ts +21 -0
- package/src/providers/google/gmail/api.ts +42 -0
- package/src/providers/google/gmail/attachment.ts +142 -0
- package/src/providers/google/gmail/hints.ts +55 -0
- package/src/providers/google/gmail/index.ts +112 -0
- package/src/providers/google/gmail/redact.ts +56 -0
- package/src/providers/google/gmail/send.ts +365 -0
- package/src/providers/google/gmail-mcp/index.ts +35 -0
- package/src/providers/google/index.ts +10 -0
- package/src/providers/google/shared/oauth.ts +122 -0
- package/src/providers/google/shared/scopes.ts +99 -0
- package/src/providers/google/shared/setup.ts +80 -0
- package/src/providers/google/sheets/hints.ts +45 -0
- package/src/providers/google/sheets/index.ts +70 -0
- package/src/providers/google/sheets/redact.ts +45 -0
- package/src/providers/google/specs/calendar.v3.json +1829 -0
- package/src/providers/google/specs/docs.v1.json +381 -0
- package/src/providers/google/specs/drive.v3.json +2208 -0
- package/src/providers/google/specs/gmail.v1.json +2578 -0
- package/src/providers/google/specs/people.v1.json +506 -0
- package/src/providers/google/specs/sheets.v4.json +1269 -0
- package/src/providers/google/specs/tasks.v1.json +840 -0
- package/src/providers/google/specs/vendor.ts +661 -0
- package/src/providers/google/tasks/index.ts +53 -0
- package/src/providers/google/tasks/redact.ts +34 -0
- package/src/providers/harness.ts +95 -0
- package/src/providers/icloud/calendar/index.ts +27 -0
- package/src/providers/icloud/contacts/index.ts +17 -0
- package/src/providers/icloud/drive/index.ts +47 -0
- package/src/providers/icloud/index.ts +8 -0
- package/src/providers/icloud/mail/index.ts +37 -0
- package/src/providers/icloud/shared/setup.ts +66 -0
- package/src/providers/index.ts +93 -0
- package/src/providers/linear/index.ts +11 -0
- package/src/providers/linear/scopes.ts +7 -0
- package/src/providers/memory/provider.ts +429 -0
- package/src/providers/notion/index.ts +19 -0
- package/src/providers/owner.ts +49 -0
- package/src/providers/scopes.ts +26 -0
- package/src/providers/setup/plan.ts +141 -0
- package/src/providers/setup/provider.ts +323 -0
- package/src/providers/shared/frontmatter.ts +119 -0
- package/src/providers/skills/provider.ts +283 -0
- package/src/providers/skills/store.ts +252 -0
- package/src/providers/vault/provider.ts +194 -0
- package/src/registry/index.ts +36 -0
- package/src/registry/policy-bridge.ts +32 -0
- package/src/registry/reconcile.ts +313 -0
- package/src/registry/registry.ts +240 -0
- package/src/secrets/document.ts +293 -0
- package/src/secrets/index.ts +154 -0
- package/src/secrets/system.ts +173 -0
- package/src/secrets/vault.ts +336 -0
- package/src/server/attachments.ts +197 -0
- package/src/server/container.ts +96 -0
- package/src/server/edge.ts +53 -0
- package/src/server/endpoint.ts +352 -0
- package/src/server/generations.ts +362 -0
- package/src/server/harness.ts +400 -0
- package/src/server/index.ts +331 -0
- package/src/server/logging.ts +41 -0
- package/src/server/mcp/build.ts +68 -0
- package/src/server/mcp/icon.ts +145 -0
- package/src/server/mcp/index.ts +32 -0
- package/src/server/mcp/instructions.ts +245 -0
- package/src/server/mcp/naming.ts +39 -0
- package/src/server/mcp/prompts.ts +78 -0
- package/src/server/mcp/resources.ts +106 -0
- package/src/server/mcp/routing.ts +117 -0
- package/src/server/mcp/schema.ts +78 -0
- package/src/server/mcp/tools.ts +186 -0
- package/src/server/mcp/visibility.ts +132 -0
- package/src/server/oauth.ts +222 -0
- package/src/server/rebinding.ts +53 -0
- package/src/server/stdio.ts +192 -0
- package/src/stores/blobs/conformance.ts +223 -0
- package/src/stores/blobs/index.ts +125 -0
- package/src/stores/blobs/testing.ts +49 -0
- package/src/stores/state/index.ts +247 -0
- package/src/stores/state/keys.ts +68 -0
- package/src/stores/state/testing.ts +41 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { ConfigError, resolveDeployTarget, type DeployConfig } from '#profile';
|
|
2
|
+
import { announce, fail, heading, ok, print, style, warn } from '#cli/output.ts';
|
|
3
|
+
import { confirm, isInteractive } from '#cli/prompt.ts';
|
|
4
|
+
import { openSecretStoreFor, resolveProfile, type GlobalFlags } from '#cli/runtime.ts';
|
|
5
|
+
import { resolveTarget, vaultEnv } from './bootstrap.ts';
|
|
6
|
+
import { printSteps, runSteps } from './steps.ts';
|
|
7
|
+
import { driverFor } from './drivers.ts';
|
|
8
|
+
import { prepareSecrets, readableRefs, rotatableRefs } from './prepare.ts';
|
|
9
|
+
import { deployedWorkspace, repairSetupSurface, uploadWorkspace } from './upload.ts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `lanes link deploy` — set up what is missing, build the image, roll a revision,
|
|
13
|
+
* print the URL.
|
|
14
|
+
*
|
|
15
|
+
* A thin wrapper, deliberately. `docs/detailed/init.md` is explicit that this must not
|
|
16
|
+
* grow plan-and-apply state files, deploy leases, drift reconciliation, or a
|
|
17
|
+
* rollback manifest: revisions already are the rollback, and that machinery
|
|
18
|
+
* belongs to a multi-target org deployment tool rather than to a single-user
|
|
19
|
+
* instance. What is here is the ordered list of things that have to happen, the
|
|
20
|
+
* questions whose answers it cannot derive, and refusals early enough to be
|
|
21
|
+
* cheap.
|
|
22
|
+
*
|
|
23
|
+
* It knows no vendor. Which platform a target deploys to is a field in its
|
|
24
|
+
* config, and everything vendor-shaped behind `driverFor`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export interface DeployFlags extends GlobalFlags {
|
|
28
|
+
/** Print the commands and the checks without running any of them. */
|
|
29
|
+
readonly dryRun?: boolean | undefined;
|
|
30
|
+
/** Override the declared `access` for this run. */
|
|
31
|
+
readonly access?: string | undefined;
|
|
32
|
+
readonly serviceAccount?: string | undefined;
|
|
33
|
+
readonly tag?: string | undefined;
|
|
34
|
+
/** Skip the confirm before creating cloud resources. */
|
|
35
|
+
readonly yes?: boolean | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Never prompt: take the setup answers from the config and assume the confirm.
|
|
38
|
+
*
|
|
39
|
+
* Implied when stdin is not a terminal, so a scripted deploy needs no flag —
|
|
40
|
+
* this is for a terminal attached to a job nobody is watching.
|
|
41
|
+
*/
|
|
42
|
+
readonly nonInteractive?: boolean | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function deploy(flags: DeployFlags): Promise<void> {
|
|
46
|
+
// The one command allowed to name a target that does not exist yet: creating
|
|
47
|
+
// it is what a first deploy is for.
|
|
48
|
+
const { resolution, config } = await resolveProfile(flags, { allowUndeclaredTarget: true });
|
|
49
|
+
|
|
50
|
+
// And the one command that can work out which target it meant. `resolveProfile`
|
|
51
|
+
// falls back to `instance.default_target`, which is the target commands *run*
|
|
52
|
+
// against — `local`, and never the answer to "deploy what".
|
|
53
|
+
const { target, source } = resolveDeployTarget(config, flags.target);
|
|
54
|
+
announce({ ...resolution, target, targetSource: source });
|
|
55
|
+
|
|
56
|
+
// `check` before anything external, per the gate order: a config that will be
|
|
57
|
+
// rejected on boot should be rejected here, not after a five-minute build.
|
|
58
|
+
print(ok(`${resolution.profilePath} is valid`));
|
|
59
|
+
|
|
60
|
+
const declared = await resolveTarget({
|
|
61
|
+
config,
|
|
62
|
+
profilePath: resolution.profilePath,
|
|
63
|
+
workspaceRoot: resolution.workspaceRoot,
|
|
64
|
+
profile: resolution.profile,
|
|
65
|
+
target,
|
|
66
|
+
flags,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const deployConfig = declared.deploy!;
|
|
70
|
+
const driver = await driverFor(deployConfig.platform);
|
|
71
|
+
|
|
72
|
+
const tag = flags.tag ?? new Date().toISOString().replace(/[^0-9]/g, '').slice(0, 14);
|
|
73
|
+
|
|
74
|
+
// Which credentials the revision will rewrite, so the steps below can grant
|
|
75
|
+
// each one. Scoped by `flags.profile` exactly as the upload and the repair
|
|
76
|
+
// below are, and read from config and manifests before anything opens a
|
|
77
|
+
// store — `--dry-run` must reach the printed step list without touching a
|
|
78
|
+
// credential.
|
|
79
|
+
const rotatable = await rotatableRefs(resolution.workspaceRoot, flags.profile);
|
|
80
|
+
const readable = await readableRefs(resolution.workspaceRoot, flags.profile, declared);
|
|
81
|
+
const provision = await driver.provision({
|
|
82
|
+
deploy: deployConfig,
|
|
83
|
+
declared,
|
|
84
|
+
target,
|
|
85
|
+
rotatable,
|
|
86
|
+
readable,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Where the running instance will read its config. The bucket the target
|
|
90
|
+
// already declares for everything else — the workspace is not baked into the
|
|
91
|
+
// image any more (ADR-023), so this has to be passed at rollout.
|
|
92
|
+
const workspace = deployedWorkspace(declared);
|
|
93
|
+
const secretEnv = vaultEnv(declared);
|
|
94
|
+
|
|
95
|
+
const rollout = driver.plan({
|
|
96
|
+
deploy: deployConfig,
|
|
97
|
+
tag,
|
|
98
|
+
target,
|
|
99
|
+
...(flags.profile !== undefined ? { profile: flags.profile } : {}),
|
|
100
|
+
...(workspace !== undefined ? { workspace } : {}),
|
|
101
|
+
...(secretEnv ? { secretEnv } : {}),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
heading(
|
|
105
|
+
`Deploying ${style.bold(deployConfig.service)} to ${deployConfig.region}` +
|
|
106
|
+
(deployConfig.project ? ` (${deployConfig.project})` : ''),
|
|
107
|
+
);
|
|
108
|
+
print(style.dim(` platform: ${deployConfig.platform}`));
|
|
109
|
+
print(reachability(deployConfig.access));
|
|
110
|
+
|
|
111
|
+
if (flags.dryRun) {
|
|
112
|
+
printSteps(driver, [...provision, ...rollout]);
|
|
113
|
+
print('');
|
|
114
|
+
if (workspace) print(style.dim(` the workspace would be uploaded to ${workspace}`));
|
|
115
|
+
print(style.dim(' --dry-run: nothing was run, and no credential was read or written.'));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const missing = driver.preflight();
|
|
120
|
+
if (missing) throw new ConfigError(missing);
|
|
121
|
+
|
|
122
|
+
// First, and before anything reaches for what they create.
|
|
123
|
+
//
|
|
124
|
+
// These used to run after the credential check and the workspace upload,
|
|
125
|
+
// which meant a genuinely first deploy asked Secret Manager for a token in a
|
|
126
|
+
// project where that API was not enabled yet, and wrote config into a bucket
|
|
127
|
+
// that did not exist — failing, both times, several steps before the step
|
|
128
|
+
// that would have fixed it. The ordering was invisible because the second
|
|
129
|
+
// deploy onwards finds everything present and works.
|
|
130
|
+
if (provision.length > 0) {
|
|
131
|
+
heading(`First-run setup (${provision.length} steps)`);
|
|
132
|
+
printSteps(driver, provision);
|
|
133
|
+
print('');
|
|
134
|
+
print(
|
|
135
|
+
style.dim(
|
|
136
|
+
' Each of these already existing is the expected case on every deploy after\n' +
|
|
137
|
+
' the first, and is not treated as a failure.',
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
// `--non-interactive` assumes it: a confirm nobody can answer is a hang, and
|
|
141
|
+
// the same run already took its setup answers from the config rather than
|
|
142
|
+
// asking. `--yes` remains the way to skip it with a terminal attached.
|
|
143
|
+
const assumed = flags.yes === true || flags.nonInteractive === true || !isInteractive();
|
|
144
|
+
if (!assumed && !(await confirm(' Create these now?'))) {
|
|
145
|
+
throw new ConfigError('Stopped before creating anything. Nothing was changed.');
|
|
146
|
+
}
|
|
147
|
+
await runSteps(driver, provision);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const credentials = await openSecretStoreFor(config, resolution.workspaceRoot, target);
|
|
151
|
+
const prepared = await prepareSecrets({
|
|
152
|
+
config,
|
|
153
|
+
credentials,
|
|
154
|
+
root: resolution.workspaceRoot,
|
|
155
|
+
target,
|
|
156
|
+
readOnly: false,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (prepared.blocking.length > 0) {
|
|
160
|
+
heading('Still missing');
|
|
161
|
+
for (const problem of prepared.blocking) print(fail(problem));
|
|
162
|
+
throw new ConfigError(
|
|
163
|
+
'The deployed instance cannot start without these. Store them with ' +
|
|
164
|
+
`lanes link secrets set <ref> --target ${target}, or copy a local setup with ` +
|
|
165
|
+
`lanes link secrets push --from local --to ${target}.`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Repair the local config before it is copied up, not the copy in the bucket.
|
|
170
|
+
//
|
|
171
|
+
// The bucket copy is the wrong end: config flows one way, local CLI to remote
|
|
172
|
+
// instance (ADR-023), and the deployed revision holds `objectViewer` on
|
|
173
|
+
// `profiles/` so it cannot write there anyway. Fixing the local file keeps
|
|
174
|
+
// the two agreeing, which is the property that makes the one-way flow safe.
|
|
175
|
+
//
|
|
176
|
+
// This is also the only place the gap gets noticed. `doctor` reports a
|
|
177
|
+
// profile with no `setup` connection, but the operator of a deployed endpoint
|
|
178
|
+
// has no reason to run it — and the symptom appears in a chat client, days
|
|
179
|
+
// later, as an agent guessing at a command that does not exist.
|
|
180
|
+
// Behind the same guard as the upload, and reading the same scope.
|
|
181
|
+
//
|
|
182
|
+
// The guard because a target with no bucket uploads nothing: repairing for it
|
|
183
|
+
// would rewrite the operator's config as a side effect of a command that
|
|
184
|
+
// copied their config nowhere. The shared scope because a profile that gets
|
|
185
|
+
// uploaded is a profile that gets served, and repairing a narrower set than
|
|
186
|
+
// the upload sends would leave a served profile without the surface — this
|
|
187
|
+
// bug again, one profile over.
|
|
188
|
+
if (workspace) {
|
|
189
|
+
await repairSetupSurface(resolution.workspaceRoot, flags.profile);
|
|
190
|
+
|
|
191
|
+
// Before the rollout, so the revision that comes up finds a config to read.
|
|
192
|
+
// Uploading after would leave a window where the service is serving and the
|
|
193
|
+
// workspace it was told to read is not there yet.
|
|
194
|
+
await uploadWorkspace(resolution.workspaceRoot, workspace, flags.profile);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
heading('Rolling out');
|
|
198
|
+
await runSteps(driver, rollout);
|
|
199
|
+
|
|
200
|
+
const url = await driver.url(deployConfig);
|
|
201
|
+
if (!url) {
|
|
202
|
+
print(
|
|
203
|
+
warn(
|
|
204
|
+
`deployed, but the platform reported no URL yet — run: lanes link outputs --target ${target}`,
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
heading('Endpoint');
|
|
211
|
+
print(` ${url}/mcp`);
|
|
212
|
+
print(await healthLine(url));
|
|
213
|
+
print('');
|
|
214
|
+
print(style.dim(` Register it with: lanes link outputs --target ${target}`));
|
|
215
|
+
|
|
216
|
+
reportUnauthorised(prepared.warnings, target);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The accounts a browser still has to authorise, and the step after them.
|
|
221
|
+
*
|
|
222
|
+
* Printed last rather than before the build, because this is the only thing
|
|
223
|
+
* left to do and a list eight steps up the scrollback is a list nobody reads.
|
|
224
|
+
*
|
|
225
|
+
* There is no second deploy at the end of it any more, and the reason the old
|
|
226
|
+
* one existed is worth keeping. Connection *credentials* are read live on every
|
|
227
|
+
* call, so a fresh `connect` looked like it should be picked up — but whether a
|
|
228
|
+
* connection was usable at all was decided by a reconcile that ran once per
|
|
229
|
+
* process, so a revision that came up with an account unauthorised went on
|
|
230
|
+
* refusing it, naming the connection rather than the staleness. Reconcile now
|
|
231
|
+
* runs again on every reload, and `connect` asks for one (ADR-029).
|
|
232
|
+
*/
|
|
233
|
+
function reportUnauthorised(warnings: readonly string[], target: string): void {
|
|
234
|
+
if (warnings.length === 0) return;
|
|
235
|
+
|
|
236
|
+
heading('Not authorised yet');
|
|
237
|
+
for (const problem of warnings) print(warn(problem));
|
|
238
|
+
print('');
|
|
239
|
+
print(
|
|
240
|
+
style.dim(
|
|
241
|
+
' A browser consent per account is the one step this cannot take for you:\n' +
|
|
242
|
+
` lanes link connect <provider> --target ${target}\n` +
|
|
243
|
+
' Each is served as soon as it is authorised. There is no second deploy —\n' +
|
|
244
|
+
' deploying is how code gets here, and authorising an account changes none.',
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Who can reach the service once this lands.
|
|
251
|
+
*
|
|
252
|
+
* Printed on every deploy rather than only when it changes, because it is the
|
|
253
|
+
* one property of a deployment that is invisible from the outside until someone
|
|
254
|
+
* either cannot get in or should not have been able to.
|
|
255
|
+
*/
|
|
256
|
+
function reachability(access: DeployConfig['access']): string {
|
|
257
|
+
return access === 'iam'
|
|
258
|
+
? style.dim(
|
|
259
|
+
' access: iam — the platform admits only callers holding its own identity\n' +
|
|
260
|
+
' token. No agent harness can mint one; use --access public with an\n' +
|
|
261
|
+
' authorization block if a remote MCP client has to reach this.',
|
|
262
|
+
)
|
|
263
|
+
: style.dim(
|
|
264
|
+
' access: public — the platform lets requests through and this endpoint\n' +
|
|
265
|
+
' authenticates them. The bearer token is what protects it.',
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Ask the deployed endpoint whether it came up. */
|
|
270
|
+
async function healthLine(url: string): Promise<string> {
|
|
271
|
+
try {
|
|
272
|
+
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(10_000) });
|
|
273
|
+
if (!response.ok) return warn(`the endpoint answered /health with ${response.status}`);
|
|
274
|
+
|
|
275
|
+
// Asked anonymously, so it reports that the revision is up and nothing
|
|
276
|
+
// about what it serves — the profile list is behind the token now.
|
|
277
|
+
// `lanes link outputs` holds one and prints the rest.
|
|
278
|
+
const body = (await response.json()) as { profiles?: string[] };
|
|
279
|
+
return ok(
|
|
280
|
+
body.profiles
|
|
281
|
+
? `healthy — serving ${body.profiles.join(', ')}`
|
|
282
|
+
: 'healthy — run `lanes link outputs` for what it serves',
|
|
283
|
+
);
|
|
284
|
+
} catch {
|
|
285
|
+
// A cold start plus a database connect can outrun a short probe, and
|
|
286
|
+
// `access: iam` makes /health unreachable from here by design. Neither is a
|
|
287
|
+
// failed deploy.
|
|
288
|
+
return warn('could not reach /health from here — with access: iam that is expected');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { DeployConfig, TargetConfig } from '#profile';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a deployment has to be able to do to be rolled out.
|
|
5
|
+
*
|
|
6
|
+
* `lanes link deploy` used to *be* the Cloud Run driver — the CLI imported it by
|
|
7
|
+
* path, so the command's grammar was generic while its behaviour was one
|
|
8
|
+
* vendor's. This interface is the seam that makes the README's existing claim
|
|
9
|
+
* true: adding a host is a folder here and a case in `drivers.ts`, and nothing
|
|
10
|
+
* else in the codebase learns about it.
|
|
11
|
+
*
|
|
12
|
+
* Everything a driver produces is **data**, not effects. `plan` and `provision`
|
|
13
|
+
* return steps rather than running them, which is what lets `--dry-run` show a
|
|
14
|
+
* deploy exactly as it will happen, and what lets the argv be asserted in a test
|
|
15
|
+
* with no cloud project anywhere near it. `run` is the only method that touches
|
|
16
|
+
* the world, and it takes one already-built step.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface CommandResult {
|
|
20
|
+
readonly ok: boolean;
|
|
21
|
+
readonly stdout: string;
|
|
22
|
+
readonly stderr: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DeployStep {
|
|
26
|
+
readonly title: string;
|
|
27
|
+
/**
|
|
28
|
+
* Arguments to the driver's own tool, one element each.
|
|
29
|
+
*
|
|
30
|
+
* Never a shell string. A service name carrying whitespace and a flag has to
|
|
31
|
+
* arrive as one argument the tool rejects, not as an extra option it obeys.
|
|
32
|
+
*/
|
|
33
|
+
readonly argv: readonly string[];
|
|
34
|
+
/** A step whose failure is expected when the thing already exists. */
|
|
35
|
+
readonly tolerateFailure?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PlanInput {
|
|
39
|
+
readonly deploy: DeployConfig;
|
|
40
|
+
readonly tag: string;
|
|
41
|
+
readonly target: string;
|
|
42
|
+
readonly profile?: string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Where the running instance reads its config, as a store URL.
|
|
45
|
+
*
|
|
46
|
+
* Passed at rollout rather than baked into the image, because the bucket
|
|
47
|
+
* belongs to the operator and the image is meant to serve any workspace
|
|
48
|
+
* (ADR-023). Absent for a target that keeps its workspace on a filesystem,
|
|
49
|
+
* which no deployment does.
|
|
50
|
+
*/
|
|
51
|
+
readonly workspace?: string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Environment the revision must read from its own credential store, as
|
|
54
|
+
* variable name → credential reference.
|
|
55
|
+
*
|
|
56
|
+
* A reference rather than a value, and never a value: this argv is printed by
|
|
57
|
+
* `--dry-run`, echoed by the platform, and kept in a revision's description
|
|
58
|
+
* forever. The driver turns a reference into whatever its host calls a secret
|
|
59
|
+
* mount — which for Cloud Run means knowing the id encoding, and is why that
|
|
60
|
+
* translation lives behind this interface rather than in the generic command.
|
|
61
|
+
*/
|
|
62
|
+
readonly secretEnv?: Readonly<Record<string, string>> | undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ProvisionInput {
|
|
66
|
+
readonly deploy: DeployConfig;
|
|
67
|
+
/** The whole target, because what needs creating follows from its adapters. */
|
|
68
|
+
readonly declared: TargetConfig;
|
|
69
|
+
readonly target: string;
|
|
70
|
+
/**
|
|
71
|
+
* Credential references the running revision rewrites, so provision can grant
|
|
72
|
+
* each one and leave the rest of the store read-only.
|
|
73
|
+
*
|
|
74
|
+
* Passed in rather than derived here: it comes from the profile's connections
|
|
75
|
+
* and their manifests, which `deployments` cannot reach on its own — see
|
|
76
|
+
* `rotatableRefs` in `./prepare.ts`.
|
|
77
|
+
*/
|
|
78
|
+
readonly rotatable?: readonly string[];
|
|
79
|
+
/**
|
|
80
|
+
* Credential references the running revision reads, so provision can bind
|
|
81
|
+
* read on each one rather than across the whole project.
|
|
82
|
+
*
|
|
83
|
+
* Same provenance as `rotatable` and for the same reason — see `readableRefs`
|
|
84
|
+
* in `./prepare.ts`. Empty or absent means the project-wide fallback, which is
|
|
85
|
+
* what a target with no profile to walk still needs.
|
|
86
|
+
*/
|
|
87
|
+
readonly readable?: readonly string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SurveyInput {
|
|
91
|
+
/** Whatever the config already says, so a re-run confirms rather than re-asks. */
|
|
92
|
+
readonly current: Partial<DeployConfig>;
|
|
93
|
+
readonly profile: string;
|
|
94
|
+
/**
|
|
95
|
+
* Whether this profile gates requests itself.
|
|
96
|
+
*
|
|
97
|
+
* It decides which `access` to propose: a profile with its own gate wants the
|
|
98
|
+
* platform door open, because platform IAM in front of it admits only callers
|
|
99
|
+
* that can mint the host's identity token — which no agent harness can.
|
|
100
|
+
*/
|
|
101
|
+
readonly gated: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Whether the target exists at all, or only its `deploy` block is missing.
|
|
104
|
+
*
|
|
105
|
+
* A target that is already declared has its adapters chosen; asking again
|
|
106
|
+
* would be offering to overwrite them. A target that does not exist needs all
|
|
107
|
+
* of it, and every answer but the bucket name follows from the platform.
|
|
108
|
+
*/
|
|
109
|
+
readonly adapters: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A surveyed target, plus the profile-level settings its answers imply.
|
|
114
|
+
*
|
|
115
|
+
* `authorization` is separate because it does not live under `targets:` — it is
|
|
116
|
+
* `auth.authorization`, and it applies to every target. It comes back from here
|
|
117
|
+
* anyway because the question that decides it is a deployment question: whether
|
|
118
|
+
* a remote client has to reach this endpoint. Nothing else in the config knows
|
|
119
|
+
* to ask, which is why an endpoint that could not be added to a phone was the
|
|
120
|
+
* default outcome of a first deploy.
|
|
121
|
+
*/
|
|
122
|
+
export interface SurveyResult {
|
|
123
|
+
readonly target: TargetConfig;
|
|
124
|
+
readonly authorization?: { readonly mode: 'self' } | undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface DeployDriver {
|
|
128
|
+
readonly platform: DeployConfig['platform'];
|
|
129
|
+
/** The command line this driver shells out to, for printing a plan. */
|
|
130
|
+
readonly tool: string;
|
|
131
|
+
|
|
132
|
+
/** What is missing before this driver can run anything, or null. */
|
|
133
|
+
preflight(): string | null;
|
|
134
|
+
|
|
135
|
+
/** Ask for what the config does not say yet. Interactive. */
|
|
136
|
+
survey(input: SurveyInput): Promise<SurveyResult>;
|
|
137
|
+
|
|
138
|
+
/** Resources this driver can create for itself, in the order they must happen. */
|
|
139
|
+
provision(input: ProvisionInput): Promise<DeployStep[]>;
|
|
140
|
+
|
|
141
|
+
/** The rollout itself. */
|
|
142
|
+
plan(input: PlanInput): DeployStep[];
|
|
143
|
+
|
|
144
|
+
/** The public URL, or null if the service has none yet. */
|
|
145
|
+
url(deploy: DeployConfig): Promise<string | null>;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Run one step's argv.
|
|
149
|
+
*
|
|
150
|
+
* Streamed by default, because a build takes minutes and silence for the
|
|
151
|
+
* duration is indistinguishable from a hang. `quiet` captures instead, for the
|
|
152
|
+
* short steps whose output is a wall of IAM policy nobody reads and whose
|
|
153
|
+
* failure is usually "already there" — the caller decides what to say about
|
|
154
|
+
* those rather than letting the tool say it.
|
|
155
|
+
*/
|
|
156
|
+
run(argv: readonly string[], options?: { quiet?: boolean }): Promise<CommandResult>;
|
|
157
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ConfigError, type DeployConfig } from '#profile';
|
|
2
|
+
import type { DeployDriver } from './driver.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Turning a declared platform into the driver that rolls it out.
|
|
6
|
+
*
|
|
7
|
+
* **This is the only place the mapping exists**, exactly as `target.ts` is the
|
|
8
|
+
* only place an adapter name becomes an open backend. Before this, the mapping
|
|
9
|
+
* was two static imports in the CLI — `#deployments/gcp/deploy.ts` in `main.ts`
|
|
10
|
+
* and `#deployments/gcp/gcloud.ts` in `outputs.ts` — which meant the CLI knew
|
|
11
|
+
* what Cloud Run was and a second host would have had to be threaded through
|
|
12
|
+
* both.
|
|
13
|
+
*
|
|
14
|
+
* The driver is imported *inside* its branch, for the same reason the cloud
|
|
15
|
+
* adapters are: a `lanes link outputs` against a local target must not load a
|
|
16
|
+
* cloud SDK, and a missing cloud credential must not fail a command that was
|
|
17
|
+
* never going to talk to that cloud.
|
|
18
|
+
*/
|
|
19
|
+
export async function driverFor(platform: DeployConfig['platform']): Promise<DeployDriver> {
|
|
20
|
+
switch (platform) {
|
|
21
|
+
case 'cloudrun': {
|
|
22
|
+
const { cloudRunDriver } = await import('./gcp/driver.ts');
|
|
23
|
+
return cloudRunDriver;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Unreachable while `platform` is a closed enum, and kept anyway: the enum
|
|
28
|
+
// and this switch are edited by different hands at different times, and a
|
|
29
|
+
// platform that parses but does not dispatch should say so rather than
|
|
30
|
+
// return undefined into a call chain.
|
|
31
|
+
throw new ConfigError(
|
|
32
|
+
`No deployment driver for platform "${platform as string}". ` +
|
|
33
|
+
'Declare one of: cloudrun.',
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
#
|
|
3
|
+
# The deployed target's image. Built from the repository root:
|
|
4
|
+
#
|
|
5
|
+
# docker build -f src/deployments/gcp/Dockerfile -t lanes-link .
|
|
6
|
+
#
|
|
7
|
+
# `lanes link deploy` runs the equivalent through Cloud Build. See
|
|
8
|
+
# docs/deployment-cloudrun.md for the whole loop, and for why the config is
|
|
9
|
+
# baked in rather than mounted.
|
|
10
|
+
#
|
|
11
|
+
# There is no build step: Bun runs TypeScript directly, so the image holds
|
|
12
|
+
# source, and `bun install` only fetches the third-party dependencies.
|
|
13
|
+
|
|
14
|
+
FROM oven/bun:1.3.11-slim
|
|
15
|
+
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
|
|
18
|
+
# The manifest first, so a source-only change reuses the install layer. One
|
|
19
|
+
# package now, rather than fifteen manifests copied ahead of the install to
|
|
20
|
+
# satisfy workspace resolution.
|
|
21
|
+
COPY package.json bun.lock bunfig.toml ./
|
|
22
|
+
|
|
23
|
+
RUN bun install --frozen-lockfile
|
|
24
|
+
|
|
25
|
+
COPY src/ src/
|
|
26
|
+
|
|
27
|
+
# The workspace is NOT copied in.
|
|
28
|
+
#
|
|
29
|
+
# It used to be: `COPY lanes-link.yaml` and `COPY profiles/`, on the reasoning
|
|
30
|
+
# that the image *is* the config, so a revision fully described what it served
|
|
31
|
+
# and rollback was a revision switch. The cost was a rebuild per config change,
|
|
32
|
+
# which the comment here called "the tradeoff docs/init.md accepts until it
|
|
33
|
+
# becomes annoying".
|
|
34
|
+
#
|
|
35
|
+
# It became annoying, and it had a second cost that was easier to miss: those
|
|
36
|
+
# paths are gitignored, so the image could not be built from a clean checkout
|
|
37
|
+
# at all. That is what stopped there ever being one image that serves any
|
|
38
|
+
# workspace.
|
|
39
|
+
#
|
|
40
|
+
# Config now lives wherever LANES_LINK_HOME points — a bucket, for a
|
|
41
|
+
# deployment — and `lanes link deploy` uploads it there. See ADR-023 for what
|
|
42
|
+
# that gives up and how the guarantee it kept is kept.
|
|
43
|
+
#
|
|
44
|
+
# What must still NOT arrive here is `data/`: the encrypted credential store
|
|
45
|
+
# and its key live there, and the deployed target reads credentials from Secret
|
|
46
|
+
# Manager instead. The root `.dockerignore` excludes it, and that exclusion is
|
|
47
|
+
# load bearing rather than an optimisation.
|
|
48
|
+
|
|
49
|
+
# Set at deploy time, not here: a bucket URL is the operator's, not the image's.
|
|
50
|
+
# `lanes link deploy` passes it as `LANES_LINK_HOME=gs://<bucket>`. Left unset,
|
|
51
|
+
# `resolveWorkspaceRoot` would walk ancestors for `lanes-link.yaml` and fall
|
|
52
|
+
# back to `~/.lanes-link`, which in a container is a directory nobody wrote —
|
|
53
|
+
# so the endpoint refuses rather than serving an empty workspace.
|
|
54
|
+
# Which adapter set to open. The whole point of the milestone is that this is
|
|
55
|
+
# the only thing that differs from a local run.
|
|
56
|
+
ENV LANES_LINK_TARGET=cloud
|
|
57
|
+
# Cloud Run injects its own PORT; this is the default it also uses, so the
|
|
58
|
+
# image is runnable unchanged with `docker run -p 8080:8080`.
|
|
59
|
+
ENV PORT=8080
|
|
60
|
+
|
|
61
|
+
# No `data/` directory: the container writes nothing to its own filesystem.
|
|
62
|
+
# State, the log, memory, skills and attachments are all objects in the bucket,
|
|
63
|
+
# and credentials come from Secret Manager. The image can run read-only.
|
|
64
|
+
|
|
65
|
+
USER bun
|
|
66
|
+
EXPOSE 8080
|
|
67
|
+
|
|
68
|
+
# Not `lanes link start`: that command is a terminal UI that mints a token when
|
|
69
|
+
# none exists. See the note at the top of src/server/container.ts.
|
|
70
|
+
CMD ["bun", "run", "src/server/container.ts"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Cloud Build config for the deployed target's image.
|
|
2
|
+
#
|
|
3
|
+
# It exists so `gcloud builds submit` can name a Dockerfile that is not at the
|
|
4
|
+
# context root — `--tag` alone always builds `./Dockerfile`, and moving the
|
|
5
|
+
# Dockerfile to the repository root to satisfy that would put deployment
|
|
6
|
+
# machinery in front of everyone who only ever runs locally.
|
|
7
|
+
#
|
|
8
|
+
# Invoked by `lanes link deploy`, which supplies _IMAGE:
|
|
9
|
+
#
|
|
10
|
+
# gcloud builds submit \
|
|
11
|
+
# --config src/deployments/gcp/cloudbuild.yaml \
|
|
12
|
+
# --substitutions _IMAGE=<region>-docker.pkg.dev/<project>/lanes-link/<service>:<tag> .
|
|
13
|
+
#
|
|
14
|
+
# The build context is the repository root, and `.dockerignore` there decides
|
|
15
|
+
# what reaches the builder. `data/` must not — see the note in that file.
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: gcr.io/cloud-builders/docker
|
|
19
|
+
args:
|
|
20
|
+
- build
|
|
21
|
+
- --file
|
|
22
|
+
- src/deployments/gcp/Dockerfile
|
|
23
|
+
- --tag
|
|
24
|
+
- ${_IMAGE}
|
|
25
|
+
- .
|
|
26
|
+
|
|
27
|
+
images:
|
|
28
|
+
- ${_IMAGE}
|
|
29
|
+
|
|
30
|
+
options:
|
|
31
|
+
logging: CLOUD_LOGGING_ONLY
|