@abgov/nx-oc 13.0.0-beta.1 → 13.0.0-beta.3
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/executors.json +5 -0
- package/migrations.json +10 -0
- package/package.json +4 -1
- package/src/adsp/adsp-utils.d.ts +10 -0
- package/src/adsp/adsp-utils.js +48 -0
- package/src/adsp/adsp-utils.js.map +1 -1
- package/src/build-assets.spec.ts +62 -0
- package/src/executors/sandbox/sandbox.d.ts +5 -0
- package/src/executors/sandbox/sandbox.js +183 -0
- package/src/executors/sandbox/sandbox.js.map +1 -0
- package/src/executors/sandbox/sandbox.spec.ts +186 -0
- package/src/executors/sandbox/schema.d.ts +12 -0
- package/src/executors/sandbox/schema.json +50 -0
- package/src/generators/deployment/deployment.js +11 -25
- package/src/generators/deployment/deployment.js.map +1 -1
- package/src/generators/deployment/deployment.spec.ts +5 -2
- package/src/generators/deployment/dotnet-files/Dockerfile__tmpl__ +16 -0
- package/src/generators/deployment/frontend-files/Dockerfile__tmpl__ +10 -0
- package/src/generators/deployment/node-files/Dockerfile__tmpl__ +10 -0
- package/src/generators/deployment/node-files/__projectName__.yml__tmpl__ +12 -1
- package/src/generators/deployment/schema.d.ts +1 -0
- package/src/generators/sandbox/database-files/sandbox-postgres.yml__tmpl__ +3 -1
- package/src/generators/sandbox/files/SANDBOX.md__tmpl__ +91 -0
- package/src/generators/sandbox/sandbox.js +109 -56
- package/src/generators/sandbox/sandbox.js.map +1 -1
- package/src/generators/sandbox/sandbox.spec.ts +110 -13
- package/src/generators/sandbox/schema.d.ts +10 -0
- package/src/generators/sandbox/schema.json +13 -0
- package/src/migrations/convert-sandbox-target/convert-sandbox-target.d.ts +2 -0
- package/src/migrations/convert-sandbox-target/convert-sandbox-target.js +54 -0
- package/src/migrations/convert-sandbox-target/convert-sandbox-target.js.map +1 -0
- package/src/migrations/convert-sandbox-target/convert-sandbox-target.spec.ts +111 -0
- package/src/utils/app-type.d.ts +18 -0
- package/src/utils/app-type.js +43 -0
- package/src/utils/app-type.js.map +1 -0
- package/src/utils/git-utils.js +4 -2
- package/src/utils/git-utils.js.map +1 -1
- package/src/utils/oc-utils.d.ts +1 -0
- package/src/utils/oc-utils.js +21 -0
- package/src/utils/oc-utils.js.map +1 -1
- package/src/generators/deployment/dotnet-files/Dockerfile.template +0 -10
- package/src/generators/deployment/frontend-files/Dockerfile.template +0 -8
- package/src/generators/deployment/node-files/Dockerfile.template +0 -8
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 2,
|
|
3
|
+
"outputCapture": "direct-nodejs",
|
|
4
|
+
"$schema": "http://json-schema.org/schema",
|
|
5
|
+
"title": "Sandbox deploy executor",
|
|
6
|
+
"description": "Builds the project image locally with podman, pushes it to the container registry, imports it into the sandbox namespace, and rolls out the deployment.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"sandboxProject": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "The OpenShift namespace to deploy the sandbox into."
|
|
12
|
+
},
|
|
13
|
+
"registry": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Container registry the sandbox image is published to (e.g. ghcr.io/my-org)."
|
|
16
|
+
},
|
|
17
|
+
"database": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"enum": ["none", "postgres", "mongo"],
|
|
20
|
+
"default": "none",
|
|
21
|
+
"description": "Provision a shared sandbox database of this type before deploying."
|
|
22
|
+
},
|
|
23
|
+
"appType": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"enum": ["node", "frontend", "dotnet"],
|
|
26
|
+
"description": "Application type. Detected from the project configuration when omitted."
|
|
27
|
+
},
|
|
28
|
+
"imageTag": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"default": "sandbox",
|
|
31
|
+
"description": "Image tag pushed and imported into the namespace imagestream."
|
|
32
|
+
},
|
|
33
|
+
"skipBuild": {
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"default": false,
|
|
36
|
+
"description": "Skip the nx build + podman build steps and reuse the already-built image (resume a partial deploy)."
|
|
37
|
+
},
|
|
38
|
+
"skipPush": {
|
|
39
|
+
"type": "boolean",
|
|
40
|
+
"default": false,
|
|
41
|
+
"description": "Skip the registry login + push and reuse the already-pushed image (resume from the import step)."
|
|
42
|
+
},
|
|
43
|
+
"importRetries": {
|
|
44
|
+
"type": "number",
|
|
45
|
+
"default": 5,
|
|
46
|
+
"description": "How many times to retry oc import-image (absorbs the 409 from oc tag's async reconcile)."
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"required": ["sandboxProject", "registry"]
|
|
50
|
+
}
|
|
@@ -7,51 +7,37 @@ const path = require("path");
|
|
|
7
7
|
const yaml = require("yaml");
|
|
8
8
|
const pipeline_envs_1 = require("../../pipeline-envs");
|
|
9
9
|
const git_utils_1 = require("../../utils/git-utils");
|
|
10
|
+
const app_type_1 = require("../../utils/app-type");
|
|
10
11
|
const adsp_1 = require("../../adsp");
|
|
11
12
|
const infraManifestFile = '.openshift/environments.yml';
|
|
12
13
|
function normalizeOptions(host, options) {
|
|
13
14
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
14
|
-
var _a, _b, _c, _d;
|
|
15
|
+
var _a, _b, _c, _d, _e;
|
|
15
16
|
const projectName = (0, devkit_1.names)(options.project).fileName;
|
|
16
17
|
const result = host.read(infraManifestFile).toString();
|
|
17
18
|
const { items } = yaml.parse(result);
|
|
18
19
|
const ocInfraProject = ((_b = (_a = items[0]) === null || _a === void 0 ? void 0 : _a.metadata) === null || _b === void 0 ? void 0 : _b.namespace) || '';
|
|
19
20
|
const SA_PREFIX = 'system:serviceaccounts:';
|
|
20
21
|
const ocEnvProjects = (_d = (_c = items[0]) === null || _c === void 0 ? void 0 : _c.subjects) === null || _d === void 0 ? void 0 : _d.filter((s) => s.kind === 'Group' && s.name.startsWith(SA_PREFIX)).map((s) => s.name.replace(SA_PREFIX, ''));
|
|
21
|
-
// TODO: Find a better way to determine this.
|
|
22
22
|
const config = (0, devkit_1.readProjectConfiguration)(host, projectName);
|
|
23
|
-
|
|
24
|
-
if (!appType) {
|
|
25
|
-
switch (config.targets.build.executor) {
|
|
26
|
-
case '@nx/web:webpack':
|
|
27
|
-
case '@angular-devkit/build-angular:browser':
|
|
28
|
-
appType = 'frontend';
|
|
29
|
-
break;
|
|
30
|
-
case '@nx/node:build':
|
|
31
|
-
appType = 'node';
|
|
32
|
-
break;
|
|
33
|
-
case '@nx-dotnet/core:build':
|
|
34
|
-
appType = 'dotnet';
|
|
35
|
-
break;
|
|
36
|
-
case '@nx/webpack:webpack': {
|
|
37
|
-
// More recent version of NX switched to use a generic webpack executor for builds.
|
|
38
|
-
appType =
|
|
39
|
-
config.targets.build.options.target === 'node' ? 'node' : 'frontend';
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
23
|
+
const appType = (_e = options.appType) !== null && _e !== void 0 ? _e : (0, app_type_1.detectApplicationType)(config);
|
|
44
24
|
const adsp = yield (0, adsp_1.getAdspConfiguration)(host, options);
|
|
45
25
|
return Object.assign(Object.assign({}, options), { appType,
|
|
46
26
|
adsp,
|
|
47
27
|
projectName,
|
|
48
28
|
ocInfraProject,
|
|
49
|
-
ocEnvProjects });
|
|
29
|
+
ocEnvProjects, buildOutputPath: (0, app_type_1.getBuildOutputPath)(config) });
|
|
50
30
|
});
|
|
51
31
|
}
|
|
52
32
|
function addFiles(host, options) {
|
|
53
33
|
var _a, _b;
|
|
54
|
-
const templateOptions = Object.assign(Object.assign(Object.assign({}, options), options.adsp), {
|
|
34
|
+
const templateOptions = Object.assign(Object.assign(Object.assign({}, options), options.adsp), {
|
|
35
|
+
// Clean https URL for the image's source label (never the raw remote, which
|
|
36
|
+
// may be an SSH URL or carry a trailing newline).
|
|
37
|
+
sourceRepositoryUrl: (() => {
|
|
38
|
+
const repo = (0, git_utils_1.getGitHubRepo)((0, git_utils_1.getGitRemoteUrl)());
|
|
39
|
+
return repo ? `https://github.com/${repo}` : '';
|
|
40
|
+
})(), database: (_a = options.database) !== null && _a !== void 0 ? _a : 'none', sandbox: (_b = options.sandbox) !== null && _b !== void 0 ? _b : false, tmpl: '' });
|
|
55
41
|
(0, devkit_1.generateFiles)(host, path.join(__dirname, `${options.appType}-files`), `./.openshift/${options.projectName}`, templateOptions);
|
|
56
42
|
}
|
|
57
43
|
function default_1(host, options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployment.js","sourceRoot":"","sources":["../../../../../../packages/nx-oc/src/generators/deployment/deployment.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"deployment.js","sourceRoot":"","sources":["../../../../../../packages/nx-oc/src/generators/deployment/deployment.ts"],"names":[],"mappings":"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"}
|
|
@@ -158,6 +158,10 @@ describe('Deployment Generator', () => {
|
|
|
158
158
|
const manifest = host.read('.openshift/test/test.yml').toString();
|
|
159
159
|
expect(manifest).toContain('readinessProbe');
|
|
160
160
|
expect(manifest).toContain('livenessProbe');
|
|
161
|
+
// The service authenticates with ADSP using a confidential client secret,
|
|
162
|
+
// injected from the ${APP_NAME}-secrets Secret.
|
|
163
|
+
expect(manifest).toContain('CLIENT_SECRET');
|
|
164
|
+
expect(manifest).toContain('${APP_NAME}-secrets');
|
|
161
165
|
});
|
|
162
166
|
|
|
163
167
|
it('can generate deployment for dotnet', async () => {
|
|
@@ -218,8 +222,7 @@ describe('Deployment Generator', () => {
|
|
|
218
222
|
const manifest = host.read('.openshift/test/test.yml').toString();
|
|
219
223
|
expect(manifest).toContain('DATABASE_URL');
|
|
220
224
|
expect(manifest).toContain('initContainers');
|
|
221
|
-
expect(manifest).toContain('
|
|
222
|
-
expect(manifest).toContain('migrate');
|
|
225
|
+
expect(manifest).toContain('migrate.js');
|
|
223
226
|
});
|
|
224
227
|
|
|
225
228
|
it('includes MONGODB_URI secretKeyRef without init container for mongo node deployment', async () => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
FROM registry.access.redhat.com/ubi8/dotnet-80 AS build
|
|
2
|
+
ARG PROJECT
|
|
3
|
+
WORKDIR /src
|
|
4
|
+
COPY . .
|
|
5
|
+
RUN dotnet publish ${PROJECT} -c Release -o /app/publish
|
|
6
|
+
|
|
7
|
+
FROM registry.access.redhat.com/ubi8/dotnet-80-runtime
|
|
8
|
+
<% if (sourceRepositoryUrl) { %>
|
|
9
|
+
# Provenance: associate the image with its source repo (shown in GHCR).
|
|
10
|
+
LABEL org.opencontainers.image.source=<%= sourceRepositoryUrl %>
|
|
11
|
+
<% } %>
|
|
12
|
+
WORKDIR /app
|
|
13
|
+
COPY --from=build /app/publish .
|
|
14
|
+
ENV ASPNETCORE_URLS=http://+:5000
|
|
15
|
+
EXPOSE 5000
|
|
16
|
+
CMD ["dotnet", "<%= projectName %>.dll"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
FROM registry.access.redhat.com/ubi9/nginx-120
|
|
2
|
+
<% if (sourceRepositoryUrl) { %>
|
|
3
|
+
# Provenance: associate the image with its source repo (shown in GHCR).
|
|
4
|
+
LABEL org.opencontainers.image.source=<%= sourceRepositoryUrl %>
|
|
5
|
+
<% } %>
|
|
6
|
+
# Build output path mirrors the workspace layout (from the project's build target).
|
|
7
|
+
COPY ./<%= buildOutputPath %>/nginx.conf "${NGINX_CONF_PATH}"
|
|
8
|
+
COPY ./<%= buildOutputPath %> .
|
|
9
|
+
|
|
10
|
+
CMD nginx -g "daemon off;"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
FROM registry.access.redhat.com/ubi9/nodejs-24
|
|
2
|
+
<% if (sourceRepositoryUrl) { %>
|
|
3
|
+
# Provenance: associate the image with its source repo (shown in GHCR).
|
|
4
|
+
LABEL org.opencontainers.image.source=<%= sourceRepositoryUrl %>
|
|
5
|
+
<% } %>
|
|
6
|
+
# Build output path mirrors the workspace layout (from the project's build target).
|
|
7
|
+
COPY ./<%= buildOutputPath %> .
|
|
8
|
+
COPY ./node_modules ./node_modules
|
|
9
|
+
|
|
10
|
+
CMD node main.js
|
|
@@ -91,7 +91,7 @@ objects:
|
|
|
91
91
|
<% } else { %>
|
|
92
92
|
image: image-registry.openshift-image-registry.svc:5000/${INFRA_PROJECT}/${APP_NAME}:${DEPLOY_TAG}
|
|
93
93
|
<% } %>
|
|
94
|
-
command: ["
|
|
94
|
+
command: ["node", "migrate.js"]
|
|
95
95
|
envFrom:
|
|
96
96
|
- configMapRef:
|
|
97
97
|
name: ${APP_NAME}
|
|
@@ -127,6 +127,17 @@ objects:
|
|
|
127
127
|
value: '3333'
|
|
128
128
|
- name: LOG_LEVEL
|
|
129
129
|
value: info
|
|
130
|
+
# ADSP confidential-client secret the service uses to authenticate
|
|
131
|
+
# with the access service. Sandbox: created by `nx run <app>:sandbox`
|
|
132
|
+
# from the service's .env.
|
|
133
|
+
<% if (!sandbox) { %>
|
|
134
|
+
# Other envs: oc create secret generic ${APP_NAME}-secrets --from-literal=CLIENT_SECRET=<secret>
|
|
135
|
+
<% } %>
|
|
136
|
+
- name: CLIENT_SECRET
|
|
137
|
+
valueFrom:
|
|
138
|
+
secretKeyRef:
|
|
139
|
+
name: ${APP_NAME}-secrets
|
|
140
|
+
key: CLIENT_SECRET
|
|
130
141
|
<% if (database === 'postgres') { %>
|
|
131
142
|
<% if (sandbox) { %>
|
|
132
143
|
- name: POSTGRES_PASSWORD
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
# deleted on re-apply, so data persists across deployments.
|
|
4
4
|
#
|
|
5
5
|
# Multiple apps in the same namespace share this instance. Each app uses its
|
|
6
|
-
# own database (<appName>_sandbox)
|
|
6
|
+
# own database (<appName>_sandbox): the `nx run <app>:sandbox` target creates it
|
|
7
|
+
# (idempotently) after Postgres is ready, and the app's migrate init container
|
|
8
|
+
# applies the schema on deploy.
|
|
7
9
|
#
|
|
8
10
|
# Credentials are stored in the sandbox-postgres-creds Secret, created once
|
|
9
11
|
# per namespace by `nx run <app>:sandbox` on first use. Subsequent apps read
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Sandbox deploy — <%= projectName %>
|
|
2
|
+
|
|
3
|
+
Deploy runbook for coding agents. `nx run <%= projectName %>:sandbox` builds the
|
|
4
|
+
image **locally with podman**, pushes it to GHCR, imports it into the
|
|
5
|
+
`<%= sandboxProject %>` namespace, and rolls out — no git push or CI wait. The
|
|
6
|
+
orchestration lives in the `@abgov/nx-oc:sandbox` executor (versioned in the
|
|
7
|
+
plugin), so the steps below are what it runs for you.
|
|
8
|
+
|
|
9
|
+
## What the target does, in order
|
|
10
|
+
|
|
11
|
+
1. **Preflight** — `oc` login, `gh` auth, and `podman` machine are checked up
|
|
12
|
+
front; a failure here stops before the build with an actionable message.
|
|
13
|
+
2. Upsert the pull secret + <% if (appType === 'node') { %>the `CLIENT_SECRET` secret + <% } %>any paired Services<% if (database && database !== 'none') { %> + the shared `<%= database %>` database<% } %>.
|
|
14
|
+
3. `nx build <%= projectName %> --configuration production`
|
|
15
|
+
4. `podman build` → `podman login` (gh token) → `podman push` to
|
|
16
|
+
`<%= registry %>/<%= imageName %>:sandbox`.
|
|
17
|
+
5. `oc tag … --reference-policy=local` then `oc import-image` (retried to absorb
|
|
18
|
+
the tag-reconcile 409).
|
|
19
|
+
6. `oc process | oc apply` the manifest, then `oc rollout restart` + `status`.
|
|
20
|
+
|
|
21
|
+
## Options (resume a partial deploy without rebuilding)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
nx run <%= projectName %>:sandbox --skipBuild # reuse the built image; re-push + import + roll out
|
|
25
|
+
nx run <%= projectName %>:sandbox --skipBuild --skipPush # reuse the pushed image; re-import + roll out only
|
|
26
|
+
nx run <%= projectName %>:sandbox --imageTag=<tag> # push/import under a different tag
|
|
27
|
+
nx run <%= projectName %>:sandbox --importRetries=8 # more import retries on a slow cluster
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Preflight failures — cause → fix
|
|
31
|
+
|
|
32
|
+
- **`'podman' is required …`** — podman not installed. `brew install podman` (macOS).
|
|
33
|
+
- **`podman is installed but not responding`** — machine stopped. `podman machine start`.
|
|
34
|
+
- **`'gh' is required …`** — GitHub CLI not installed. See https://cli.github.com.
|
|
35
|
+
- **`gh is installed but not authenticated`** — run `gh auth login`.
|
|
36
|
+
- **oc login prompts / fails** — `oc login` to your cluster, targeting the
|
|
37
|
+
`<%= sandboxProject %>` namespace (`oc project <%= sandboxProject %>`).
|
|
38
|
+
|
|
39
|
+
## If the deploy fails mid-way (work around by hand)
|
|
40
|
+
|
|
41
|
+
The executor stops at the first failing step. After fixing the cause, the
|
|
42
|
+
fastest path is usually `--skipBuild` (or `--skipBuild --skipPush`) to resume.
|
|
43
|
+
If you need to drive it manually, these are the remaining steps — the image ref
|
|
44
|
+
is `<%= registry %>/<%= imageName %>:sandbox`:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
NS=<%= sandboxProject %>
|
|
48
|
+
REF=<%= registry %>/<%= imageName %>:sandbox
|
|
49
|
+
# (1) push, if not already pushed
|
|
50
|
+
gh auth token | podman login <%= registryHost %> -u "$(gh api user -q .login)" --password-stdin
|
|
51
|
+
podman push "$REF"
|
|
52
|
+
# (2) point the imagestream at it and import (retry absorbs the tag race)
|
|
53
|
+
oc tag "$REF" <%= projectName %>:sandbox --reference-policy=local -n "$NS"
|
|
54
|
+
until oc import-image <%= projectName %>:sandbox --confirm -n "$NS"; do sleep 3; done
|
|
55
|
+
# (3) apply the manifest and roll out
|
|
56
|
+
oc process -f .openshift/<%= projectName %>/<%= projectName %>.yml -p PROJECT="$NS" | oc apply -f -
|
|
57
|
+
oc rollout restart deployment/<%= projectName %> -n "$NS"
|
|
58
|
+
oc rollout status deployment/<%= projectName %> -n "$NS" --timeout=180s
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Verify
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
oc get pods -n <%= sandboxProject %> -l name=<%= projectName %>
|
|
65
|
+
<% if (appType === 'node') { %>curl "https://$(oc get route <%= projectName %> -n <%= sandboxProject %> -o jsonpath='{.spec.host}')/health"<% } else { %>curl -I "https://$(oc get route <%= projectName %> -n <%= sandboxProject %> -o jsonpath='{.spec.host}')/"<% } %>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Known issues & workarounds
|
|
69
|
+
|
|
70
|
+
- **`podman push` / import fails with an auth error** — the *active* `gh`
|
|
71
|
+
account lacks `write:packages` (or access to the org). Preflight only confirms
|
|
72
|
+
*an* account is logged in, not its scope. Check `gh auth status`; switch with
|
|
73
|
+
`gh auth switch -u <account>`, then re-run with `--skipBuild`.
|
|
74
|
+
- **Pod `FailedCreate` "exceeded quota"** — the namespace CPU quota is full.
|
|
75
|
+
Free room: `oc get deploy -n <%= sandboxProject %>` then delete stale apps
|
|
76
|
+
(`oc delete all,configmap,is -l app=<old-app> -n <%= sandboxProject %>`), and re-run.
|
|
77
|
+
- **Pod `CrashLoopBackOff`** — `oc logs -n <%= sandboxProject %> <pod> -c <%= projectName %>`.<% if (appType === 'node' && database && database !== 'none') { %>
|
|
78
|
+
For the DB migration, also check the init container:
|
|
79
|
+
`oc logs -n <%= sandboxProject %> <pod> -c <%= projectName %>-migrate`.<% } %>
|
|
80
|
+
<% if (appType === 'frontend') { %>- **Sign-in returns `Invalid redirect_uri`** — the sandbox Route wasn't
|
|
81
|
+
registered on the Keycloak public client (the generator does this when it can
|
|
82
|
+
resolve the cluster ingress domain). Add `https://<%= projectName %>-<%= sandboxProject %>.<ingress-domain>/*`
|
|
83
|
+
to the client's valid redirect URIs.
|
|
84
|
+
<% } %>- **Import 409 "object has been modified"** — the `oc tag` reconcile race; the
|
|
85
|
+
executor already retries. If you hit it manually, just re-run the import.
|
|
86
|
+
|
|
87
|
+
## Teardown
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
nx run <%= projectName %>:sandbox-teardown # remove sandbox resources + delete the GHCR image
|
|
91
|
+
```
|
|
@@ -6,41 +6,86 @@ const devkit_1 = require("@nx/devkit");
|
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const adsp_1 = require("../../adsp");
|
|
8
8
|
const git_utils_1 = require("../../utils/git-utils");
|
|
9
|
+
const oc_utils_1 = require("../../utils/oc-utils");
|
|
10
|
+
const app_type_1 = require("../../utils/app-type");
|
|
11
|
+
const SANDBOX_GENERATOR = '@abgov/nx-oc:sandbox';
|
|
12
|
+
// Resolves the sandbox container registry once per workspace and persists it to
|
|
13
|
+
// nx.json so subsequent sandbox generations reuse it without re-prompting:
|
|
14
|
+
// --registry flag → nx.json → derived from git remote (ghcr.io/<org>) → prompt.
|
|
15
|
+
function resolveRegistry(host, registry, remoteUrl) {
|
|
16
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
var _a, _b, _c;
|
|
18
|
+
if (registry)
|
|
19
|
+
return persistRegistry(host, registry);
|
|
20
|
+
const stored = (_c = (_b = (_a = (0, devkit_1.readNxJson)(host)) === null || _a === void 0 ? void 0 : _a.generators) === null || _b === void 0 ? void 0 : _b[SANDBOX_GENERATOR]) === null || _c === void 0 ? void 0 : _c.registry;
|
|
21
|
+
if (stored)
|
|
22
|
+
return stored;
|
|
23
|
+
const derived = (0, git_utils_1.deriveRegistryFromRemote)(remoteUrl);
|
|
24
|
+
if (derived) {
|
|
25
|
+
console.log(`\n✓ Sandbox registry: ${derived.toLowerCase()} (derived from git remote)\n`);
|
|
26
|
+
return persistRegistry(host, derived);
|
|
27
|
+
}
|
|
28
|
+
const { prompt } = yield Promise.resolve().then(() => require('enquirer'));
|
|
29
|
+
const { registry: answered } = yield prompt({
|
|
30
|
+
type: 'input',
|
|
31
|
+
name: 'registry',
|
|
32
|
+
message: 'What container registry should sandbox images be published to (e.g., ghcr.io/my-org)?',
|
|
33
|
+
});
|
|
34
|
+
return persistRegistry(host, answered);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Container registries (GHCR) require lowercase paths, so normalize on store.
|
|
38
|
+
function persistRegistry(host, registry) {
|
|
39
|
+
var _a, _b, _c;
|
|
40
|
+
const value = registry.toLowerCase();
|
|
41
|
+
const nxJson = (_a = (0, devkit_1.readNxJson)(host)) !== null && _a !== void 0 ? _a : {};
|
|
42
|
+
const generators = ((_b = nxJson.generators) !== null && _b !== void 0 ? _b : {});
|
|
43
|
+
generators[SANDBOX_GENERATOR] = Object.assign(Object.assign({}, ((_c = generators[SANDBOX_GENERATOR]) !== null && _c !== void 0 ? _c : {})), { registry: value });
|
|
44
|
+
nxJson.generators = generators;
|
|
45
|
+
(0, devkit_1.updateNxJson)(host, nxJson);
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
9
48
|
function normalizeOptions(host, options) {
|
|
10
49
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
11
|
-
var _a;
|
|
50
|
+
var _a, _b;
|
|
12
51
|
const projectName = (0, devkit_1.names)(options.project).fileName;
|
|
13
52
|
const config = (0, devkit_1.readProjectConfiguration)(host, projectName);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
appType = 'node';
|
|
23
|
-
break;
|
|
24
|
-
case '@nx-dotnet/core:build':
|
|
25
|
-
appType = 'dotnet';
|
|
26
|
-
break;
|
|
27
|
-
case '@nx/webpack:webpack':
|
|
28
|
-
appType =
|
|
29
|
-
config.targets.build.options.target === 'node' ? 'node' : 'frontend';
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const adsp = yield (0, adsp_1.getAdspConfiguration)(host, Object.assign(Object.assign({}, options), { env: (_a = options.env) !== null && _a !== void 0 ? _a : 'dev' }));
|
|
53
|
+
const appType = (_a = options.appType) !== null && _a !== void 0 ? _a : (0, app_type_1.detectApplicationType)(config);
|
|
54
|
+
const adsp = yield (0, adsp_1.getAdspConfiguration)(host, Object.assign(Object.assign({}, options), { env: (_b = options.env) !== null && _b !== void 0 ? _b : 'dev' }));
|
|
55
|
+
const remoteUrl = (0, git_utils_1.getGitRemoteUrl)();
|
|
56
|
+
const registry = (yield resolveRegistry(host, options.registry, remoteUrl)).toLowerCase();
|
|
57
|
+
// Prefix the image with the (per-user) sandbox namespace so images from
|
|
58
|
+
// different experimenters never collide on GHCR's org-global package names.
|
|
59
|
+
const imageName = `${options.sandboxProject}-${projectName}`.toLowerCase();
|
|
60
|
+
const repoSlug = (0, git_utils_1.getGitHubRepo)(remoteUrl);
|
|
34
61
|
return Object.assign(Object.assign({}, options), { appType,
|
|
35
62
|
adsp,
|
|
36
|
-
projectName });
|
|
63
|
+
projectName, buildOutputPath: (0, app_type_1.getBuildOutputPath)(config), registry, registryHost: registry.split('/')[0], registryOrg: registry.split('/').slice(1).join('/'), imageName, imageRef: `${registry}/${imageName}:sandbox`, sourceRepositoryUrl: repoSlug ? `https://github.com/${repoSlug}` : undefined });
|
|
37
64
|
});
|
|
38
65
|
}
|
|
39
66
|
function addManifestFiles(host, options) {
|
|
40
|
-
var _a;
|
|
41
|
-
const templateOptions = Object.assign(Object.assign(Object.assign({}, options), options.adsp), {
|
|
67
|
+
var _a, _b;
|
|
68
|
+
const templateOptions = Object.assign(Object.assign(Object.assign({}, options), options.adsp), {
|
|
69
|
+
// Clean https URL for the image's source label (provenance / UI connect).
|
|
70
|
+
sourceRepositoryUrl: (_a = options.sourceRepositoryUrl) !== null && _a !== void 0 ? _a : '', database: (_b = options.database) !== null && _b !== void 0 ? _b : 'none', sandbox: true, ocInfraProject: options.sandboxProject, tmpl: '' });
|
|
42
71
|
(0, devkit_1.generateFiles)(host, path.join(__dirname, `../deployment/${options.appType}-files`), `./.openshift/${options.projectName}`, templateOptions);
|
|
43
72
|
}
|
|
73
|
+
// Emit the per-app deploy + troubleshooting runbook next to the manifests, so a
|
|
74
|
+
// coding agent has one canonical, app-aware source (rather than the guidance
|
|
75
|
+
// being duplicated across every app's AGENTS.md).
|
|
76
|
+
function addSandboxDoc(host, options) {
|
|
77
|
+
var _a;
|
|
78
|
+
(0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), `./.openshift/${options.projectName}`, {
|
|
79
|
+
projectName: options.projectName,
|
|
80
|
+
appType: options.appType,
|
|
81
|
+
database: (_a = options.database) !== null && _a !== void 0 ? _a : 'none',
|
|
82
|
+
sandboxProject: options.sandboxProject,
|
|
83
|
+
registry: options.registry,
|
|
84
|
+
registryHost: options.registryHost,
|
|
85
|
+
imageName: options.imageName,
|
|
86
|
+
tmpl: '',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
44
89
|
function addDatabaseFiles(host, options) {
|
|
45
90
|
if (!options.database || options.database === 'none')
|
|
46
91
|
return;
|
|
@@ -48,48 +93,54 @@ function addDatabaseFiles(host, options) {
|
|
|
48
93
|
}
|
|
49
94
|
function addSandboxTarget(host, options) {
|
|
50
95
|
const config = (0, devkit_1.readProjectConfiguration)(host, options.project);
|
|
51
|
-
const { projectName, sandboxProject, database } = options;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
`--from-literal=POSTGRESQL_ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=') ` +
|
|
57
|
-
`-n ${sandboxProject}`);
|
|
58
|
-
commands.push(`oc apply -f .openshift/sandbox/sandbox-postgres.yml -n ${sandboxProject}`);
|
|
59
|
-
}
|
|
60
|
-
else if (database === 'mongo') {
|
|
61
|
-
commands.push(`oc get secret sandbox-mongodb-creds -n ${sandboxProject} 2>/dev/null || ` +
|
|
62
|
-
`oc create secret generic sandbox-mongodb-creds ` +
|
|
63
|
-
`--from-literal=MONGODB_ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=') ` +
|
|
64
|
-
`-n ${sandboxProject}`);
|
|
65
|
-
commands.push(`oc apply -f .openshift/sandbox/sandbox-mongodb.yml -n ${sandboxProject}`);
|
|
66
|
-
}
|
|
67
|
-
commands.push(`oc process -f .openshift/${projectName}/${projectName}.yml -p PROJECT=${sandboxProject} | oc apply -f -`);
|
|
68
|
-
commands.push([
|
|
69
|
-
`REGISTRY=$(oc registry info)`,
|
|
70
|
-
`CONTAINER_CLI=$(command -v podman || command -v docker)`,
|
|
71
|
-
`$CONTAINER_CLI login -u $(oc whoami) -p $(oc whoami -t) $REGISTRY`,
|
|
72
|
-
`$CONTAINER_CLI build -t $REGISTRY/${sandboxProject}/${projectName}:sandbox -f .openshift/${projectName}/Dockerfile .`,
|
|
73
|
-
`$CONTAINER_CLI push $REGISTRY/${sandboxProject}/${projectName}:sandbox`,
|
|
74
|
-
].join(' && '));
|
|
75
|
-
commands.push(`oc rollout restart deployment/${projectName} -n ${sandboxProject}`);
|
|
76
|
-
commands.push(`oc rollout status deployment/${projectName} -n ${sandboxProject} --timeout=120s`);
|
|
96
|
+
const { projectName, sandboxProject, database, appType, registry, registryOrg, imageName, } = options;
|
|
97
|
+
// The deploy orchestration (preflight, build → podman → push → import with
|
|
98
|
+
// retry → rollout, database/paired-service provisioning) lives in the
|
|
99
|
+
// @abgov/nx-oc:sandbox executor, versioned in the plugin — so bug fixes reach
|
|
100
|
+
// every project on `npm update` instead of being baked into project.json.
|
|
77
101
|
config.targets = Object.assign(Object.assign({}, config.targets), { sandbox: {
|
|
78
|
-
executor: 'nx:
|
|
79
|
-
options: {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
},
|
|
102
|
+
executor: '@abgov/nx-oc:sandbox',
|
|
103
|
+
options: Object.assign({ sandboxProject,
|
|
104
|
+
registry,
|
|
105
|
+
appType }, (database && database !== 'none' ? { database } : {})),
|
|
83
106
|
}, 'sandbox-teardown': {
|
|
84
107
|
executor: 'nx:run-commands',
|
|
85
108
|
options: {
|
|
86
109
|
commands: [
|
|
87
|
-
`oc delete all,configmap -l app=${projectName} -n ${sandboxProject} --ignore-not-found`,
|
|
110
|
+
`oc delete all,configmap,is -l app=${projectName} -n ${sandboxProject} --ignore-not-found`,
|
|
111
|
+
`oc delete imagestream ${projectName} -n ${sandboxProject} --ignore-not-found`,
|
|
112
|
+
// Remove the sandbox package (needs delete:packages). Best-effort:
|
|
113
|
+
// sandbox packages are the unlinked ones and can also be pruned org-wide
|
|
114
|
+
// via `select(.repository == null)`.
|
|
115
|
+
`gh api --method DELETE /orgs/${registryOrg}/packages/container/${imageName} 2>/dev/null || true`,
|
|
88
116
|
],
|
|
89
117
|
},
|
|
90
118
|
} });
|
|
91
119
|
(0, devkit_1.updateProjectConfiguration)(host, options.project, config);
|
|
92
120
|
}
|
|
121
|
+
// Register the sandbox deployment's Route with the frontend's public client so
|
|
122
|
+
// browser sign-in works against the deployed URL — not just localhost. The
|
|
123
|
+
// Route host is deterministic by convention (<app>-<namespace>.<ingressDomain>),
|
|
124
|
+
// so this happens once at generate time using the token already obtained for
|
|
125
|
+
// ADSP config — no per-deploy login.
|
|
126
|
+
function registerSandboxRedirectUri(options) {
|
|
127
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
if (options.appType !== 'frontend')
|
|
129
|
+
return;
|
|
130
|
+
const { adsp, projectName, sandboxProject } = options;
|
|
131
|
+
if (!(adsp === null || adsp === void 0 ? void 0 : adsp.accessToken))
|
|
132
|
+
return;
|
|
133
|
+
const ingressDomain = (0, oc_utils_1.getClusterIngressDomain)();
|
|
134
|
+
if (!ingressDomain) {
|
|
135
|
+
console.log('[nx-oc] Could not determine the cluster ingress domain; skipping redirect URI registration. ' +
|
|
136
|
+
'Add the deployment Route to the client manually if browser sign-in fails.');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const routeUrl = `https://${projectName}-${sandboxProject}.${ingressDomain}`;
|
|
140
|
+
const clientId = `urn:ads:${adsp.tenant}:${projectName}`;
|
|
141
|
+
yield (0, adsp_1.addClientRedirectUris)(adsp.accessServiceUrl, adsp.tenantRealm, clientId, [`${routeUrl}/*`], adsp.accessToken);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
93
144
|
function default_1(host, options) {
|
|
94
145
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
95
146
|
const normalizedOptions = yield normalizeOptions(host, options);
|
|
@@ -99,7 +150,9 @@ function default_1(host, options) {
|
|
|
99
150
|
}
|
|
100
151
|
addManifestFiles(host, normalizedOptions);
|
|
101
152
|
addDatabaseFiles(host, normalizedOptions);
|
|
153
|
+
addSandboxDoc(host, normalizedOptions);
|
|
102
154
|
addSandboxTarget(host, normalizedOptions);
|
|
155
|
+
yield registerSandboxRedirectUri(normalizedOptions);
|
|
103
156
|
yield (0, devkit_1.formatFiles)(host);
|
|
104
157
|
});
|
|
105
158
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../../../../../packages/nx-oc/src/generators/sandbox/sandbox.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../../../../../packages/nx-oc/src/generators/sandbox/sandbox.ts"],"names":[],"mappings":"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"}
|