@abgov/nx-oc 12.6.0 → 12.8.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/generators.json CHANGED
@@ -13,6 +13,11 @@
13
13
  "schema": "./src/generators/apply-infra/schema.json",
14
14
  "description": "Generator that applies OpenShift manifests for pipeline."
15
15
  },
16
+ "setup-secrets": {
17
+ "factory": "./src/generators/setup-secrets/setup-secrets",
18
+ "schema": "./src/generators/setup-secrets/schema.json",
19
+ "description": "Sets GitHub Actions secrets and OpenShift GHCR pull secret for the pipeline."
20
+ },
16
21
  "deployment": {
17
22
  "factory": "./src/generators/deployment/deployment",
18
23
  "schema": "./src/generators/deployment/schema.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-oc",
3
- "version": "12.6.0",
3
+ "version": "12.8.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for OpenShift.",
@@ -1,18 +1,19 @@
1
1
  "use strict";
2
+ var _a, _b, _c, _d, _e, _f;
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  exports.environments = void 0;
4
5
  exports.environments = {
5
6
  dev: {
6
- accessServiceUrl: 'https://access.adsp-dev.gov.ab.ca',
7
- directoryServiceUrl: 'https://directory-service.adsp-dev.gov.ab.ca',
7
+ accessServiceUrl: (_a = process.env.ADSP_E2E_ACCESS_URL) !== null && _a !== void 0 ? _a : 'https://access.adsp-dev.gov.ab.ca',
8
+ directoryServiceUrl: (_b = process.env.ADSP_E2E_DIRECTORY_URL) !== null && _b !== void 0 ? _b : 'https://directory-service.adsp-dev.gov.ab.ca',
8
9
  },
9
10
  test: {
10
- accessServiceUrl: 'https://access-uat.alberta.ca',
11
- directoryServiceUrl: 'https://directory-service.adsp-uat.alberta.ca',
11
+ accessServiceUrl: (_c = process.env.ADSP_E2E_ACCESS_URL) !== null && _c !== void 0 ? _c : 'https://access-uat.alberta.ca',
12
+ directoryServiceUrl: (_d = process.env.ADSP_E2E_DIRECTORY_URL) !== null && _d !== void 0 ? _d : 'https://directory-service.adsp-uat.alberta.ca',
12
13
  },
13
14
  prod: {
14
- accessServiceUrl: 'https://access.alberta.ca',
15
- directoryServiceUrl: 'https://directory-service.adsp.alberta.ca',
15
+ accessServiceUrl: (_e = process.env.ADSP_E2E_ACCESS_URL) !== null && _e !== void 0 ? _e : 'https://access.alberta.ca',
16
+ directoryServiceUrl: (_f = process.env.ADSP_E2E_DIRECTORY_URL) !== null && _f !== void 0 ? _f : 'https://directory-service.adsp.alberta.ca',
16
17
  },
17
18
  };
18
19
  //# sourceMappingURL=environments.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"environments.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/adsp/environments.ts"],"names":[],"mappings":";;;AAMa,QAAA,YAAY,GAAyC;IAChE,GAAG,EAAE;QACH,gBAAgB,EAAE,mCAAmC;QACrD,mBAAmB,EAAE,8CAA8C;KACpE;IACD,IAAI,EAAE;QACJ,gBAAgB,EAAE,+BAA+B;QACjD,mBAAmB,EAAE,+CAA+C;KACrE;IACD,IAAI,EAAE;QACJ,gBAAgB,EAAE,2BAA2B;QAC7C,mBAAmB,EAAE,2CAA2C;KACjE;CACF,CAAC"}
1
+ {"version":3,"file":"environments.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/adsp/environments.ts"],"names":[],"mappings":";;;;AAMa,QAAA,YAAY,GAAyC;IAChE,GAAG,EAAE;QACH,gBAAgB,EAAE,MAAA,OAAO,CAAC,GAAG,CAAC,mBAAmB,mCAAI,mCAAmC;QACxF,mBAAmB,EAAE,MAAA,OAAO,CAAC,GAAG,CAAC,sBAAsB,mCAAI,8CAA8C;KAC1G;IACD,IAAI,EAAE;QACJ,gBAAgB,EAAE,MAAA,OAAO,CAAC,GAAG,CAAC,mBAAmB,mCAAI,+BAA+B;QACpF,mBAAmB,EAAE,MAAA,OAAO,CAAC,GAAG,CAAC,sBAAsB,mCAAI,+CAA+C;KAC3G;IACD,IAAI,EAAE;QACJ,gBAAgB,EAAE,MAAA,OAAO,CAAC,GAAG,CAAC,mBAAmB,mCAAI,2BAA2B;QAChF,mBAAmB,EAAE,MAAA,OAAO,CAAC,GAAG,CAAC,sBAAsB,mCAAI,2CAA2C;KACvG;CACF,CAAC"}
@@ -21,6 +21,8 @@ on:
21
21
  branches:
22
22
  - main
23
23
 
24
+ pull_request:
25
+
24
26
  workflow_dispatch:
25
27
  inputs:
26
28
  SELECTED_BASE:
@@ -37,7 +39,50 @@ concurrency:
37
39
  cancel-in-progress: true
38
40
 
39
41
  jobs:
42
+ check:
43
+ if: github.event_name == 'pull_request'
44
+ runs-on: ubuntu-24.04
45
+ timeout-minutes: 15
46
+ permissions:
47
+ contents: read
48
+ env:
49
+ AFFECTED_BASE: "origin/main"
50
+ steps:
51
+ - uses: actions/checkout@v5
52
+ with:
53
+ fetch-depth: 0
54
+ - uses: actions/setup-node@v5
55
+ with:
56
+ node-version: "24"
57
+ cache: "npm"
58
+ - run: npm ci
59
+ - name: Get last successful commit
60
+ id: last_successful_commit
61
+ uses: nrwl/nx-set-shas@v5
62
+ - name: Set AFFECTED_BASE to last successful build commit
63
+ if: ${{ steps.last_successful_commit.outputs.base }}
64
+ run: echo "AFFECTED_BASE=${{ steps.last_successful_commit.outputs.base }}" >> $GITHUB_ENV
65
+ - name: Lint
66
+ run: npx nx affected --target=lint --base=${{ env.AFFECTED_BASE }}
67
+ - name: Test
68
+ run: npx nx affected --target=test --base=${{ env.AFFECTED_BASE }}
69
+ - name: Build
70
+ run: npx nx affected --target=build --base=${{ env.AFFECTED_BASE }}
71
+ - name: Audit dependencies
72
+ run: npm audit --audit-level=high
73
+ - name: Install Semgrep
74
+ run: pip install semgrep
75
+ - name: Semgrep SAST
76
+ run: npx nx affected --target=semgrep --base=${{ env.AFFECTED_BASE }}
77
+ - uses: trufflesecurity/trufflehog@main
78
+ with:
79
+ extra_args: --only-verified
80
+ - uses: hadolint/hadolint-action@v3
81
+ with:
82
+ recursive: true
83
+
40
84
  build:
85
+ if: github.event_name != 'pull_request'
41
86
  runs-on: ubuntu-24.04
42
87
  timeout-minutes: 30
43
88
  permissions:
@@ -7,8 +7,9 @@ const path = require("path");
7
7
  const pipeline_envs_1 = require("../../pipeline-envs");
8
8
  const git_utils_1 = require("../../utils/git-utils");
9
9
  const apply_infra_1 = require("../apply-infra/apply-infra");
10
+ const setup_secrets_1 = require("../setup-secrets/setup-secrets");
10
11
  function normalizeOptions(host, options) {
11
- var _a;
12
+ var _a, _b;
12
13
  const ocEnvProjects = ((_a = options.envs) === null || _a === void 0 ? void 0 : _a.split(' ')) || [options.infra];
13
14
  const envsProjectsSet = new Set(ocEnvProjects);
14
15
  if (envsProjectsSet.size !== ocEnvProjects.length) {
@@ -17,7 +18,28 @@ function normalizeOptions(host, options) {
17
18
  else if (ocEnvProjects.length > pipeline_envs_1.pipelineEnvs.length) {
18
19
  throw new Error(`Provided projects must correspond to ${pipeline_envs_1.pipelineEnvs.join(', ')} environments.`);
19
20
  }
20
- return Object.assign(Object.assign({}, options), { ocPipelineName: options.pipeline, ocInfraProject: options.infra, ocEnvProjects: ocEnvProjects, applyPipeline: !!options.apply, pipelineType: options.type === 'jenkins' ? 'jenkins' : 'actions' });
21
+ return Object.assign(Object.assign({}, options), { registry: (_b = options.registry) !== null && _b !== void 0 ? _b : '', ocPipelineName: options.pipeline, ocInfraProject: options.infra, ocEnvProjects: ocEnvProjects, applyPipeline: !!options.apply, pipelineType: options.type === 'jenkins' ? 'jenkins' : 'actions' });
22
+ }
23
+ // Resolves the container registry: CLI flag → derived from git remote → prompted.
24
+ function resolveRegistry(registry) {
25
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
26
+ var _a;
27
+ if (registry)
28
+ return registry;
29
+ const remoteUrl = (_a = (0, git_utils_1.getGitRemoteUrl)()) === null || _a === void 0 ? void 0 : _a.trim();
30
+ const derived = (0, git_utils_1.deriveRegistryFromRemote)(remoteUrl);
31
+ if (derived) {
32
+ console.log(`\n✓ Container registry: ${derived} (derived from git remote)\n`);
33
+ return derived;
34
+ }
35
+ const { prompt } = yield Promise.resolve().then(() => require('enquirer'));
36
+ const answer = yield prompt({
37
+ type: 'input',
38
+ name: 'registry',
39
+ message: 'What container registry should images be published to (e.g., ghcr.io/my-org)?',
40
+ });
41
+ return answer.registry;
42
+ });
21
43
  }
22
44
  function addFiles(host, options) {
23
45
  var _a;
@@ -34,11 +56,22 @@ function addFiles(host, options) {
34
56
  }
35
57
  function default_1(host, options) {
36
58
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
37
- const normalizedOptions = normalizeOptions(host, options);
59
+ var _a;
60
+ const registry = yield resolveRegistry(options.registry);
61
+ const normalizedOptions = normalizeOptions(host, Object.assign(Object.assign({}, options), { registry }));
38
62
  addFiles(host, normalizedOptions);
39
63
  yield (0, devkit_1.formatFiles)(host);
40
64
  if (normalizedOptions.applyPipeline) {
41
65
  yield (0, apply_infra_1.default)(host);
66
+ const remoteUrl = (_a = (0, git_utils_1.getGitRemoteUrl)()) === null || _a === void 0 ? void 0 : _a.trim();
67
+ if (remoteUrl) {
68
+ yield (0, setup_secrets_1.default)(host, { infra: normalizedOptions.ocInfraProject });
69
+ }
70
+ else {
71
+ console.log('\n⚠ No git remote found — skipping GitHub secrets setup.\n' +
72
+ ` Push to GitHub first then run:\n` +
73
+ ` npx nx g @abgov/nx-oc:setup-secrets --infra ${normalizedOptions.ocInfraProject}\n`);
74
+ }
42
75
  }
43
76
  });
44
77
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../../../../packages/nx-oc/src/generators/pipeline/pipeline.ts"],"names":[],"mappings":";;AAgEA,4BASC;;AAzED,uCAA8D;AAC9D,6BAA6B;AAC7B,uDAA2D;AAC3D,qDAAwD;AACxD,4DAA6D;AAG7D,SAAS,gBAAgB,CAAC,IAAU,EAAE,OAAe;;IACnD,MAAM,aAAa,GAAG,CAAA,MAAA,OAAO,CAAC,IAAI,0CAAE,KAAK,CAAC,GAAG,CAAC,KAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAElE,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC;IAC/C,IAAI,eAAe,CAAC,IAAI,KAAK,aAAa,CAAC,MAAM,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;SAAM,IAAI,aAAa,CAAC,MAAM,GAAG,4BAAI,CAAC,MAAM,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,wCAAwC,4BAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CACxE,CAAC;IACJ,CAAC;IAED,uCACK,OAAO,KACV,cAAc,EAAE,OAAO,CAAC,QAAQ,EAChC,cAAc,EAAE,OAAO,CAAC,KAAK,EAC7B,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAC9B,YAAY,EAAE,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,IAChE;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,IAAU,EAAE,OAAyB;;IACrD,MAAM,eAAe,mCAChB,OAAO,KACV,mBAAmB,EAAE,MAAA,IAAA,2BAAe,GAAE,0CAAE,IAAI,EAAE,EAC9C,IAAI,EAAJ,4BAAI,EACJ,IAAI,EAAE,EAAE,GACT,CAAC;IAEF,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACvC,OAAO,CAAC,IAAI,CACV,iGAAiG;YAC/F,6BAA6B,CAChC,CAAC;QACF,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC/B,cAAc,EACd,eAAe,CAChB,CAAC;IACJ,CAAC;SAAM,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QAC9C,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,EACzC,cAAc,EACd,eAAe,CAChB,CAAC;QACF,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,EACzC,qBAAqB,EACrB,eAAe,CAChB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE1D,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QAClC,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;QAExB,IAAI,iBAAiB,CAAC,aAAa,EAAE,CAAC;YACpC,MAAM,IAAA,qBAAmB,EAAC,IAAI,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;CAAA"}
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../../../../packages/nx-oc/src/generators/pipeline/pipeline.ts"],"names":[],"mappings":"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"}
@@ -3,11 +3,12 @@ export interface Schema {
3
3
  type: string;
4
4
  infra: string;
5
5
  envs: string;
6
- registry: string;
6
+ registry?: string;
7
7
  apply?: boolean;
8
8
  }
9
9
 
10
10
  export interface NormalizedSchema extends Schema {
11
+ registry: string;
11
12
  ocPipelineName: string;
12
13
  ocInfraProject: string;
13
14
  ocEnvProjects: string[];
@@ -37,9 +37,8 @@
37
37
  },
38
38
  "registry": {
39
39
  "type": "string",
40
- "description": "Container registry to publish images to (e.g., ghcr.io/my-org).",
41
- "alias": "r",
42
- "x-prompt": "What container registry should images be published to (e.g., ghcr.io/my-org)?"
40
+ "description": "Container registry to publish images to (e.g., ghcr.io/my-org). Derived from the git remote when not provided.",
41
+ "alias": "r"
43
42
  },
44
43
  "apply": {
45
44
  "type": "boolean",
@@ -52,8 +51,7 @@
52
51
  "pipeline",
53
52
  "infra",
54
53
  "type",
55
- "envs",
56
- "registry"
54
+ "envs"
57
55
  ],
58
56
  "additionalProperties": false
59
57
  }
@@ -0,0 +1,3 @@
1
+ export interface Schema {
2
+ infra: string;
3
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "id": "NxOcSetupSecrets",
4
+ "title": "Setup Pipeline Secrets",
5
+ "type": "object",
6
+ "properties": {
7
+ "infra": {
8
+ "type": "string",
9
+ "description": "Name of the OpenShift infra project containing the github-actions service account.",
10
+ "alias": "i",
11
+ "x-prompt": "What is the OpenShift infra project?"
12
+ }
13
+ },
14
+ "required": ["infra"],
15
+ "additionalProperties": false
16
+ }
@@ -0,0 +1,3 @@
1
+ import { Tree } from '@nx/devkit';
2
+ import { Schema } from './schema';
3
+ export default function (host: Tree, options: Schema): Promise<void>;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = default_1;
4
+ const tslib_1 = require("tslib");
5
+ const oc_utils_1 = require("../../utils/oc-utils");
6
+ const gh_utils_1 = require("../../utils/gh-utils");
7
+ const git_utils_1 = require("../../utils/git-utils");
8
+ function default_1(host, options) {
9
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
10
+ var _a, _b;
11
+ try {
12
+ (0, oc_utils_1.ensureOcLogin)();
13
+ }
14
+ catch (e) {
15
+ console.log(e instanceof Error ? e.message : String(e));
16
+ return;
17
+ }
18
+ const remoteUrl = (_a = (0, git_utils_1.getGitRemoteUrl)()) === null || _a === void 0 ? void 0 : _a.trim();
19
+ const repo = (0, git_utils_1.getGitHubRepo)(remoteUrl);
20
+ if (!repo) {
21
+ console.log('\n⚠ No GitHub remote found — skipping GitHub Actions secrets setup.\n' +
22
+ ' Push to GitHub first then re-run this generator.');
23
+ return;
24
+ }
25
+ try {
26
+ (0, gh_utils_1.checkGhCli)();
27
+ }
28
+ catch (e) {
29
+ console.log(e instanceof Error ? e.message : String(e));
30
+ return;
31
+ }
32
+ console.log('\nSetting up pipeline secrets...');
33
+ const serverUrl = (0, oc_utils_1.getOcServerUrl)();
34
+ if (serverUrl) {
35
+ const ok = (0, gh_utils_1.setGhSecret)('OPENSHIFT_SERVER', serverUrl, repo);
36
+ console.log(ok ? '✓ OPENSHIFT_SERVER set' : '✗ Failed to set OPENSHIFT_SERVER');
37
+ }
38
+ else {
39
+ console.log('⚠ Could not determine OpenShift server URL — set OPENSHIFT_SERVER manually.');
40
+ }
41
+ const saToken = (0, oc_utils_1.getSaToken)('github-actions', options.infra);
42
+ if (saToken) {
43
+ const ok = (0, gh_utils_1.setGhSecret)('OPENSHIFT_TOKEN', saToken, repo);
44
+ console.log(ok ? '✓ OPENSHIFT_TOKEN set' : '✗ Failed to set OPENSHIFT_TOKEN');
45
+ }
46
+ else {
47
+ console.log('⚠ Could not retrieve github-actions token — set OPENSHIFT_TOKEN manually.');
48
+ }
49
+ const org = (_b = (0, git_utils_1.deriveRegistryFromRemote)(remoteUrl)) === null || _b === void 0 ? void 0 : _b.replace('ghcr.io/', '');
50
+ if (!org) {
51
+ console.log('⚠ Could not derive GitHub org from remote — create ghcr-pull-secret manually.');
52
+ return;
53
+ }
54
+ const { prompt } = yield Promise.resolve().then(() => require('enquirer'));
55
+ let pat;
56
+ try {
57
+ const answer = yield prompt({
58
+ type: 'password',
59
+ name: 'pat',
60
+ message: 'Enter a GitHub PAT with read:packages scope (for OpenShift image import):',
61
+ });
62
+ pat = answer.pat;
63
+ }
64
+ catch (_c) {
65
+ return;
66
+ }
67
+ if (!pat)
68
+ return;
69
+ const secretCreated = (0, oc_utils_1.createDockerRegistrySecret)('ghcr-pull-secret', 'ghcr.io', org, pat, options.infra);
70
+ if (secretCreated) {
71
+ console.log(`✓ ghcr-pull-secret created in ${options.infra}`);
72
+ const linked = (0, oc_utils_1.linkSecretToServiceAccount)('ghcr-pull-secret', 'github-actions', options.infra);
73
+ console.log(linked ? '✓ ghcr-pull-secret linked to github-actions service account' : '✗ Failed to link secret');
74
+ }
75
+ else {
76
+ console.log(`✗ Failed to create ghcr-pull-secret in ${options.infra}`);
77
+ }
78
+ });
79
+ }
80
+ //# sourceMappingURL=setup-secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-secrets.js","sourceRoot":"","sources":["../../../../../../packages/nx-oc/src/generators/setup-secrets/setup-secrets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
2
+ import * as ocUtils from '../../utils/oc-utils';
3
+ import * as ghUtils from '../../utils/gh-utils';
4
+ import * as gitUtils from '../../utils/git-utils';
5
+ import { Schema } from './schema';
6
+ import generator from './setup-secrets';
7
+
8
+ jest.mock('../../utils/oc-utils');
9
+ jest.mock('../../utils/gh-utils');
10
+ jest.mock('../../utils/git-utils');
11
+ jest.mock('enquirer', () => ({
12
+ prompt: jest.fn().mockResolvedValue({ pat: 'test-pat' }),
13
+ }));
14
+
15
+ const ocMock = ocUtils as jest.Mocked<typeof ocUtils>;
16
+ const ghMock = ghUtils as jest.Mocked<typeof ghUtils>;
17
+ const gitMock = gitUtils as jest.Mocked<typeof gitUtils>;
18
+
19
+ describe('Setup Secrets Generator', () => {
20
+ const options: Schema = { infra: 'my-infra' };
21
+
22
+ beforeEach(() => {
23
+ jest.clearAllMocks();
24
+ ocMock.ensureOcLogin.mockImplementation(() => undefined);
25
+ ocMock.getOcServerUrl.mockReturnValue('https://api.example.com:6443');
26
+ ocMock.getSaToken.mockReturnValue('test-sa-token');
27
+ ocMock.createDockerRegistrySecret.mockReturnValue(true);
28
+ ocMock.linkSecretToServiceAccount.mockReturnValue(true);
29
+ ghMock.checkGhCli.mockImplementation(() => undefined);
30
+ ghMock.setGhSecret.mockReturnValue(true);
31
+ gitMock.getGitRemoteUrl.mockReturnValue('https://github.com/GovAlta/nx-tools.git');
32
+ gitMock.getGitHubRepo.mockReturnValue('GovAlta/nx-tools');
33
+ gitMock.deriveRegistryFromRemote.mockReturnValue('ghcr.io/GovAlta');
34
+ });
35
+
36
+ it('sets GitHub secrets and creates GHCR pull secret', async () => {
37
+ const host = createTreeWithEmptyWorkspace();
38
+ await generator(host, options);
39
+
40
+ expect(ghMock.setGhSecret).toHaveBeenCalledWith('OPENSHIFT_SERVER', 'https://api.example.com:6443', 'GovAlta/nx-tools');
41
+ expect(ghMock.setGhSecret).toHaveBeenCalledWith('OPENSHIFT_TOKEN', 'test-sa-token', 'GovAlta/nx-tools');
42
+ expect(ocMock.createDockerRegistrySecret).toHaveBeenCalledWith(
43
+ 'ghcr-pull-secret', 'ghcr.io', 'GovAlta', 'test-pat', 'my-infra'
44
+ );
45
+ expect(ocMock.linkSecretToServiceAccount).toHaveBeenCalledWith(
46
+ 'ghcr-pull-secret', 'github-actions', 'my-infra'
47
+ );
48
+ });
49
+
50
+ it('skips secrets when no GitHub remote is found', async () => {
51
+ gitMock.getGitRemoteUrl.mockReturnValue(undefined);
52
+ gitMock.getGitHubRepo.mockReturnValue(undefined);
53
+
54
+ const host = createTreeWithEmptyWorkspace();
55
+ await generator(host, options);
56
+
57
+ expect(ghMock.setGhSecret).not.toHaveBeenCalled();
58
+ expect(ocMock.createDockerRegistrySecret).not.toHaveBeenCalled();
59
+ });
60
+
61
+ it('skips secrets when gh CLI is not available', async () => {
62
+ ghMock.checkGhCli.mockImplementation(() => { throw new Error('gh: not found'); });
63
+
64
+ const host = createTreeWithEmptyWorkspace();
65
+ await generator(host, options);
66
+
67
+ expect(ghMock.setGhSecret).not.toHaveBeenCalled();
68
+ });
69
+
70
+ it('skips GHCR secret when SA token cannot be retrieved', async () => {
71
+ ocMock.getSaToken.mockReturnValue(undefined);
72
+
73
+ const host = createTreeWithEmptyWorkspace();
74
+ await generator(host, options);
75
+
76
+ expect(ocMock.createDockerRegistrySecret).toHaveBeenCalled();
77
+ });
78
+ });
@@ -0,0 +1,2 @@
1
+ export declare function checkGhCli(): void;
2
+ export declare function setGhSecret(name: string, value: string, repo: string): boolean;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkGhCli = checkGhCli;
4
+ exports.setGhSecret = setGhSecret;
5
+ const child_process_1 = require("child_process");
6
+ function checkGhCli() {
7
+ try {
8
+ (0, child_process_1.execFileSync)('gh', ['auth', 'status'], { stdio: 'pipe' });
9
+ }
10
+ catch (_a) {
11
+ throw new Error('gh CLI is not installed or not authenticated. Run `gh auth login` then re-try.');
12
+ }
13
+ }
14
+ function setGhSecret(name, value, repo) {
15
+ try {
16
+ (0, child_process_1.execFileSync)('gh', ['secret', 'set', name, '--repo', repo], {
17
+ input: Buffer.from(value),
18
+ stdio: ['pipe', 'pipe', 'pipe'],
19
+ });
20
+ return true;
21
+ }
22
+ catch (_a) {
23
+ return false;
24
+ }
25
+ }
26
+ //# sourceMappingURL=gh-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gh-utils.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/utils/gh-utils.ts"],"names":[],"mappings":";;AAEA,gCAQC;AAED,kCAUC;AAtBD,iDAA6C;AAE7C,SAAgB,UAAU;IACxB,IAAI,CAAC;QACH,IAAA,4BAAY,EAAC,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,WAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,gFAAgF,CACjF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAgB,WAAW,CAAC,IAAY,EAAE,KAAa,EAAE,IAAY;IACnE,IAAI,CAAC;QACH,IAAA,4BAAY,EAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE;YAC1D,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;YACzB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { mocked } from 'jest-mock';
3
+ import { checkGhCli, setGhSecret } from './gh-utils';
4
+
5
+ jest.mock('child_process');
6
+ const mockedExecFileSync = mocked(execFileSync);
7
+
8
+ describe('checkGhCli', () => {
9
+ beforeEach(() => mockedExecFileSync.mockReset());
10
+
11
+ it('does not throw when gh is authenticated', () => {
12
+ mockedExecFileSync.mockReturnValue(Buffer.from('Logged in to github.com'));
13
+ expect(() => checkGhCli()).not.toThrow();
14
+ });
15
+
16
+ it('throws a clear error when gh CLI is unavailable or unauthenticated', () => {
17
+ mockedExecFileSync.mockImplementation(() => { throw new Error('gh: command not found'); });
18
+ expect(() => checkGhCli()).toThrow('gh CLI is not installed or not authenticated');
19
+ });
20
+ });
21
+
22
+ describe('setGhSecret', () => {
23
+ beforeEach(() => mockedExecFileSync.mockReset());
24
+
25
+ it('returns true on success', () => {
26
+ mockedExecFileSync.mockReturnValue(Buffer.from(''));
27
+ expect(setGhSecret('OPENSHIFT_TOKEN', 'tok', 'GovAlta/nx-tools')).toBe(true);
28
+ expect(mockedExecFileSync).toHaveBeenCalledWith(
29
+ 'gh',
30
+ ['secret', 'set', 'OPENSHIFT_TOKEN', '--repo', 'GovAlta/nx-tools'],
31
+ expect.objectContaining({ input: Buffer.from('tok') })
32
+ );
33
+ });
34
+
35
+ it('returns false when gh command fails', () => {
36
+ mockedExecFileSync.mockImplementation(() => { throw new Error('gh: not found'); });
37
+ expect(setGhSecret('OPENSHIFT_TOKEN', 'tok', 'GovAlta/nx-tools')).toBe(false);
38
+ });
39
+ });
@@ -1 +1,3 @@
1
1
  export declare function getGitRemoteUrl(): string | undefined;
2
+ export declare function deriveRegistryFromRemote(remoteUrl?: string): string | undefined;
3
+ export declare function getGitHubRepo(remoteUrl?: string): string | undefined;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getGitRemoteUrl = getGitRemoteUrl;
4
+ exports.deriveRegistryFromRemote = deriveRegistryFromRemote;
5
+ exports.getGitHubRepo = getGitHubRepo;
4
6
  const child_process_1 = require("child_process");
5
7
  function getGitRemoteUrl() {
6
8
  try {
@@ -11,4 +13,27 @@ function getGitRemoteUrl() {
11
13
  console.log(`Failed to execute git`, e);
12
14
  }
13
15
  }
16
+ // Parses a GitHub remote URL and returns the ghcr.io registry for the org.
17
+ // Handles both HTTPS (https://github.com/ORG/REPO.git) and
18
+ // SSH (git@github.com:ORG/REPO.git) formats.
19
+ function deriveRegistryFromRemote(remoteUrl) {
20
+ var _a;
21
+ if (!remoteUrl)
22
+ return undefined;
23
+ const url = remoteUrl.trim();
24
+ const httpsMatch = url.match(/https:\/\/github\.com\/([^/]+)\//);
25
+ const sshMatch = url.match(/git@github\.com:([^/]+)\//);
26
+ const org = (_a = httpsMatch === null || httpsMatch === void 0 ? void 0 : httpsMatch[1]) !== null && _a !== void 0 ? _a : sshMatch === null || sshMatch === void 0 ? void 0 : sshMatch[1];
27
+ return org ? `ghcr.io/${org}` : undefined;
28
+ }
29
+ // Returns the "owner/repo" slug for use with `gh secret set --repo`.
30
+ function getGitHubRepo(remoteUrl) {
31
+ var _a;
32
+ if (!remoteUrl)
33
+ return undefined;
34
+ const url = remoteUrl.trim();
35
+ const httpsMatch = url.match(/https:\/\/github\.com\/(.+?)(?:\.git)?\s*$/);
36
+ const sshMatch = url.match(/git@github\.com:(.+?)(?:\.git)?\s*$/);
37
+ return (_a = httpsMatch === null || httpsMatch === void 0 ? void 0 : httpsMatch[1]) !== null && _a !== void 0 ? _a : sshMatch === null || sshMatch === void 0 ? void 0 : sshMatch[1];
38
+ }
14
39
  //# sourceMappingURL=git-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-utils.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/utils/git-utils.ts"],"names":[],"mappings":";;AAEA,0CAWC;AAbD,iDAAwC;AAExC,SAAgB,eAAe;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAA,wBAAQ,EACrB,oCAAoC,EACpC,EAAE,KAAK,EAAE,MAAM,EAAE,CAClB,CAAC,QAAQ,EAAE,CAAA;QAEZ,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAA;IACzC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"git-utils.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/utils/git-utils.ts"],"names":[],"mappings":";;AAEA,0CAWC;AAKD,4DAOC;AAGD,sCAMC;AAlCD,iDAAwC;AAExC,SAAgB,eAAe;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAA,wBAAQ,EACrB,oCAAoC,EACpC,EAAE,KAAK,EAAE,MAAM,EAAE,CAClB,CAAC,QAAQ,EAAE,CAAA;QAEZ,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAA;IACzC,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,2DAA2D;AAC3D,6CAA6C;AAC7C,SAAgB,wBAAwB,CAAC,SAAkB;;IACzD,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACjC,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAK,GAAG,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAG,CAAC,CAAC,mCAAI,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAG,CAAC,CAAC,CAAC;IAC7C,OAAO,GAAG,CAAC,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5C,CAAC;AAED,qEAAqE;AACrE,SAAgB,aAAa,CAAC,SAAkB;;IAC9C,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACjC,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAK,GAAG,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACpE,OAAO,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAG,CAAC,CAAC,mCAAI,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAG,CAAC,CAAC,CAAC;AAC1C,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { execSync } from 'child_process';
2
2
  import { mocked } from 'jest-mock';
3
- import { getGitRemoteUrl } from './git-utils';
3
+ import { getGitRemoteUrl, deriveRegistryFromRemote, getGitHubRepo } from './git-utils';
4
4
 
5
5
  jest.mock('child_process');
6
6
  const mockedExecSync = mocked(execSync);
@@ -32,3 +32,53 @@ describe('getGitRemoteUrl', () => {
32
32
  expect(url).toBeUndefined();
33
33
  });
34
34
  });
35
+
36
+ describe('deriveRegistryFromRemote', () => {
37
+ it('derives registry from HTTPS remote', () => {
38
+ expect(deriveRegistryFromRemote('https://github.com/GovAlta/nx-tools.git'))
39
+ .toBe('ghcr.io/GovAlta');
40
+ });
41
+
42
+ it('derives registry from SSH remote', () => {
43
+ expect(deriveRegistryFromRemote('git@github.com:GovAlta/nx-tools.git'))
44
+ .toBe('ghcr.io/GovAlta');
45
+ });
46
+
47
+ it('returns undefined for non-GitHub remote', () => {
48
+ expect(deriveRegistryFromRemote('https://gitlab.com/org/repo.git')).toBeUndefined();
49
+ });
50
+
51
+ it('returns undefined when remoteUrl is undefined', () => {
52
+ expect(deriveRegistryFromRemote(undefined)).toBeUndefined();
53
+ });
54
+
55
+ it('handles trailing newline from git output', () => {
56
+ expect(deriveRegistryFromRemote('https://github.com/GovAlta/nx-tools.git\n'))
57
+ .toBe('ghcr.io/GovAlta');
58
+ });
59
+ });
60
+
61
+ describe('getGitHubRepo', () => {
62
+ it('returns owner/repo from HTTPS remote', () => {
63
+ expect(getGitHubRepo('https://github.com/GovAlta/nx-tools.git'))
64
+ .toBe('GovAlta/nx-tools');
65
+ });
66
+
67
+ it('returns owner/repo from SSH remote', () => {
68
+ expect(getGitHubRepo('git@github.com:GovAlta/nx-tools.git'))
69
+ .toBe('GovAlta/nx-tools');
70
+ });
71
+
72
+ it('handles trailing newline from git output', () => {
73
+ expect(getGitHubRepo('https://github.com/GovAlta/nx-tools.git\n'))
74
+ .toBe('GovAlta/nx-tools');
75
+ });
76
+
77
+ it('returns undefined for non-GitHub remote', () => {
78
+ expect(getGitHubRepo('https://gitlab.com/org/repo.git')).toBeUndefined();
79
+ });
80
+
81
+ it('returns undefined when remoteUrl is undefined', () => {
82
+ expect(getGitHubRepo(undefined)).toBeUndefined();
83
+ });
84
+ });
@@ -1,3 +1,7 @@
1
+ export declare function getOcServerUrl(): string | undefined;
2
+ export declare function getSaToken(saName: string, namespace: string): string | undefined;
3
+ export declare function createDockerRegistrySecret(name: string, server: string, username: string, password: string, namespace: string): boolean;
4
+ export declare function linkSecretToServiceAccount(secretName: string, saName: string, namespace: string): boolean;
1
5
  export declare function runOcCommand(command: 'project' | 'start-build' | 'process' | 'apply', params: string[], input?: Buffer): {
2
6
  success: boolean;
3
7
  stdout?: Buffer;
@@ -1,8 +1,59 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOcServerUrl = getOcServerUrl;
4
+ exports.getSaToken = getSaToken;
5
+ exports.createDockerRegistrySecret = createDockerRegistrySecret;
6
+ exports.linkSecretToServiceAccount = linkSecretToServiceAccount;
3
7
  exports.runOcCommand = runOcCommand;
4
8
  exports.ensureOcLogin = ensureOcLogin;
5
9
  const child_process_1 = require("child_process");
10
+ function getOcServerUrl() {
11
+ try {
12
+ return (0, child_process_1.execFileSync)('oc', ['config', 'view', '--minify', '-o', 'jsonpath={.clusters[0].cluster.server}'], { stdio: 'pipe' }).toString().trim() || undefined;
13
+ }
14
+ catch (_a) {
15
+ return undefined;
16
+ }
17
+ }
18
+ // Creates a bounded SA token valid for one year (OCP 4.11+ TokenRequest API).
19
+ // Falls back to the legacy `oc sa get-token` on older clusters.
20
+ function getSaToken(saName, namespace) {
21
+ try {
22
+ return (0, child_process_1.execFileSync)('oc', ['create', 'token', saName, '-n', namespace, '--duration=8760h'], { stdio: 'pipe' }).toString().trim() || undefined;
23
+ }
24
+ catch (_a) {
25
+ try {
26
+ return (0, child_process_1.execFileSync)('oc', ['sa', 'get-token', saName, '-n', namespace], { stdio: 'pipe' }).toString().trim() || undefined;
27
+ }
28
+ catch (_b) {
29
+ return undefined;
30
+ }
31
+ }
32
+ }
33
+ function createDockerRegistrySecret(name, server, username, password, namespace) {
34
+ try {
35
+ (0, child_process_1.execFileSync)('oc', [
36
+ 'create', 'secret', 'docker-registry', name,
37
+ `--docker-server=${server}`,
38
+ `--docker-username=${username}`,
39
+ `--docker-password=${password}`,
40
+ '-n', namespace,
41
+ ], { stdio: 'pipe' });
42
+ return true;
43
+ }
44
+ catch (_a) {
45
+ return false;
46
+ }
47
+ }
48
+ function linkSecretToServiceAccount(secretName, saName, namespace) {
49
+ try {
50
+ (0, child_process_1.execFileSync)('oc', ['secrets', 'link', saName, secretName, '--for=pull', '-n', namespace], { stdio: 'pipe' });
51
+ return true;
52
+ }
53
+ catch (_a) {
54
+ return false;
55
+ }
56
+ }
6
57
  function runOcCommand(command, params, input) {
7
58
  const args = input
8
59
  ? [command, '-f', '-', ...params]
@@ -1 +1 @@
1
- {"version":3,"file":"oc-utils.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/utils/oc-utils.ts"],"names":[],"mappings":";;AAEA,oCAiBC;AAED,sCAmCC;AAxDD,iDAAwD;AAExD,SAAgB,YAAY,CAC1B,OAAwD,EACxD,MAAgB,EAChB,KAAc;IAEd,MAAM,IAAI,GAAG,KAAK;QAChB,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;QACjC,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC;IAEzB,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAA,4BAAY,EAAC,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAClE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACnC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,iCAAiC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAClE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,SAAgB,aAAa;IAC3B,IAAI,CAAC;QACH,IAAA,4BAAY,EAAC,IAAI,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACjE,CAAC;IAAC,WAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,wIAAwI,CACzI,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,IAAA,4BAAY,EAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAClD,OAAO;IACT,CAAC;IAAC,WAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;IAED,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,CAAC;QACH,MAAM;YACJ,IAAA,4BAAY,EAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,wCAAwC,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;iBAClH,QAAQ,EAAE;iBACV,IAAI,EAAE,IAAI,IAAI,CAAC;IACtB,CAAC;IAAC,WAAM,CAAC;QACP,kDAAkD;IACpD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;IACzE,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,IAAA,yBAAS,EAAC,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAEhE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,6FAA6F,CAC9F,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"oc-utils.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/utils/oc-utils.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,13 @@
1
1
  import { execFileSync, spawnSync } from 'child_process';
2
2
  import { mocked } from 'jest-mock';
3
- import { ensureOcLogin, runOcCommand } from './oc-utils';
3
+ import {
4
+ ensureOcLogin,
5
+ runOcCommand,
6
+ getOcServerUrl,
7
+ getSaToken,
8
+ createDockerRegistrySecret,
9
+ linkSecretToServiceAccount,
10
+ } from './oc-utils';
4
11
 
5
12
  jest.mock('child_process');
6
13
  const mockedExecFileSync = mocked(execFileSync);
@@ -126,3 +133,81 @@ describe('ensureOcLogin', () => {
126
133
  expect(() => ensureOcLogin()).toThrow('OpenShift login failed or was cancelled');
127
134
  });
128
135
  });
136
+
137
+ describe('getOcServerUrl', () => {
138
+ beforeEach(() => mockedExecFileSync.mockReset());
139
+
140
+ it('returns the server URL from kubeconfig', () => {
141
+ mockedExecFileSync.mockReturnValue(Buffer.from('https://api.example.com:6443'));
142
+ expect(getOcServerUrl()).toBe('https://api.example.com:6443');
143
+ });
144
+
145
+ it('returns undefined when oc command fails', () => {
146
+ mockedExecFileSync.mockImplementation(() => { throw new Error('no config'); });
147
+ expect(getOcServerUrl()).toBeUndefined();
148
+ });
149
+ });
150
+
151
+ describe('getSaToken', () => {
152
+ beforeEach(() => mockedExecFileSync.mockReset());
153
+
154
+ it('returns token from oc create token', () => {
155
+ mockedExecFileSync.mockReturnValue(Buffer.from('eyJhbGciOi...'));
156
+ expect(getSaToken('github-actions', 'my-infra')).toBe('eyJhbGciOi...');
157
+ expect(mockedExecFileSync).toHaveBeenCalledWith(
158
+ 'oc', ['create', 'token', 'github-actions', '-n', 'my-infra', '--duration=8760h'],
159
+ expect.objectContaining({ stdio: 'pipe' })
160
+ );
161
+ });
162
+
163
+ it('falls back to oc sa get-token when create token fails', () => {
164
+ mockedExecFileSync.mockImplementationOnce(() => { throw new Error('unsupported'); }); // oc create token
165
+ mockedExecFileSync.mockReturnValueOnce(Buffer.from('legacy-token')); // oc sa get-token
166
+ expect(getSaToken('github-actions', 'my-infra')).toBe('legacy-token');
167
+ });
168
+
169
+ it('returns undefined when both commands fail', () => {
170
+ mockedExecFileSync.mockImplementation(() => { throw new Error('not found'); });
171
+ expect(getSaToken('github-actions', 'my-infra')).toBeUndefined();
172
+ });
173
+ });
174
+
175
+ describe('createDockerRegistrySecret', () => {
176
+ beforeEach(() => mockedExecFileSync.mockReset());
177
+
178
+ it('returns true on success', () => {
179
+ mockedExecFileSync.mockReturnValue(Buffer.from('secret/ghcr-pull-secret created'));
180
+ expect(createDockerRegistrySecret('ghcr-pull-secret', 'ghcr.io', 'org', 'pat', 'my-infra')).toBe(true);
181
+ expect(mockedExecFileSync).toHaveBeenCalledWith(
182
+ 'oc',
183
+ ['create', 'secret', 'docker-registry', 'ghcr-pull-secret',
184
+ '--docker-server=ghcr.io', '--docker-username=org', '--docker-password=pat',
185
+ '-n', 'my-infra'],
186
+ expect.objectContaining({ stdio: 'pipe' })
187
+ );
188
+ });
189
+
190
+ it('returns false when oc command fails', () => {
191
+ mockedExecFileSync.mockImplementation(() => { throw new Error('already exists'); });
192
+ expect(createDockerRegistrySecret('ghcr-pull-secret', 'ghcr.io', 'org', 'pat', 'my-infra')).toBe(false);
193
+ });
194
+ });
195
+
196
+ describe('linkSecretToServiceAccount', () => {
197
+ beforeEach(() => mockedExecFileSync.mockReset());
198
+
199
+ it('returns true on success', () => {
200
+ mockedExecFileSync.mockReturnValue(Buffer.from(''));
201
+ expect(linkSecretToServiceAccount('ghcr-pull-secret', 'github-actions', 'my-infra')).toBe(true);
202
+ expect(mockedExecFileSync).toHaveBeenCalledWith(
203
+ 'oc',
204
+ ['secrets', 'link', 'github-actions', 'ghcr-pull-secret', '--for=pull', '-n', 'my-infra'],
205
+ expect.objectContaining({ stdio: 'pipe' })
206
+ );
207
+ });
208
+
209
+ it('returns false when oc command fails', () => {
210
+ mockedExecFileSync.mockImplementation(() => { throw new Error('not found'); });
211
+ expect(linkSecretToServiceAccount('ghcr-pull-secret', 'github-actions', 'my-infra')).toBe(false);
212
+ });
213
+ });