@abgov/nx-oc 12.4.0 → 12.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -9
- package/package.json +1 -1
- package/src/adsp/adsp-utils.js +28 -0
- package/src/adsp/adsp-utils.js.map +1 -1
- package/src/adsp/token-cache.d.ts +9 -0
- package/src/adsp/token-cache.js +49 -0
- package/src/adsp/token-cache.js.map +1 -0
- package/src/adsp/token-cache.spec.ts +117 -0
- package/src/generators/deployment/deployment.js +2 -1
- package/src/generators/deployment/deployment.js.map +1 -1
- package/src/generators/deployment/deployment.spec.ts +58 -0
- package/src/generators/deployment/node-files/__projectName__.yml__tmpl__ +30 -0
- package/src/generators/deployment/schema.d.ts +2 -0
- package/src/generators/deployment/schema.json +5 -0
package/README.md
CHANGED
|
@@ -1,13 +1,148 @@
|
|
|
1
|
-
#
|
|
2
|
-
This is the Government of Alberta - Nx plugin for OpenShift.
|
|
1
|
+
# @abgov/nx-oc
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
Nx plugin for generating and applying OpenShift manifests for Government of Alberta applications.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
The plugin provides generators for CI/CD pipeline setup and per-application deployment configuration, and an executor for running `oc apply` against an OpenShift cluster.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
2. Install plugin: `npm i -D @abgov/nx-oc`
|
|
10
|
-
3. Login to OpenShift: `oc login {url} --token={token}`
|
|
11
|
-
4. Generate basic infrastructure yml and apply it: `npx nx g @abgov/nx-oc:pipeline`
|
|
12
|
-
5. Set up OpenShift yml on apps using `npx nx g @abgov/nx-oc:deployment`
|
|
7
|
+
## Prerequisites
|
|
13
8
|
|
|
9
|
+
- [OpenShift CLI (`oc`)](https://docs.openshift.com/container-platform/latest/cli_reference/openshift_cli/getting-started-cli.html) installed and on `PATH`
|
|
10
|
+
- An active OpenShift login: `oc login <url> --token=<token>`
|
|
11
|
+
- Separate OpenShift projects provisioned for build infrastructure and each runtime environment (dev, test, prod)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i -D @abgov/nx-oc
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Create workspace and install plugin
|
|
23
|
+
npx create-nx-workspace my-workspace
|
|
24
|
+
npm i -D @abgov/nx-oc
|
|
25
|
+
|
|
26
|
+
# 2. Login to OpenShift
|
|
27
|
+
oc login <url> --token=<token>
|
|
28
|
+
|
|
29
|
+
# 3. Generate pipeline infrastructure manifests
|
|
30
|
+
npx nx g @abgov/nx-oc:pipeline my-pipeline \
|
|
31
|
+
--infra my-infra-project \
|
|
32
|
+
--envs "my-dev my-test my-prod" \
|
|
33
|
+
--registry ghcr.io/my-org
|
|
34
|
+
|
|
35
|
+
# 4. Apply the generated infrastructure manifests to the cluster
|
|
36
|
+
npx nx g @abgov/nx-oc:apply-infra
|
|
37
|
+
|
|
38
|
+
# 5. Add deployment manifests to each application
|
|
39
|
+
npx nx g @abgov/nx-oc:deployment my-app --appType node --env dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Generators
|
|
43
|
+
|
|
44
|
+
### `pipeline`
|
|
45
|
+
|
|
46
|
+
Generates OpenShift manifests for a CI/CD pipeline, including shared build infrastructure resources used across all environments.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx nx g @abgov/nx-oc:pipeline my-pipeline \
|
|
50
|
+
--infra my-infra-project \
|
|
51
|
+
--envs "my-dev my-test my-prod" \
|
|
52
|
+
--registry ghcr.io/my-org \
|
|
53
|
+
--type actions
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Option | Alias | Required | Description |
|
|
57
|
+
|--------|-------|----------|-------------|
|
|
58
|
+
| `pipeline` | — | Yes | Name of the OpenShift pipeline |
|
|
59
|
+
| `infra` | `-i` | Yes | OpenShift project name used for build infrastructure |
|
|
60
|
+
| `envs` | `-e` | Yes | Space-separated names of the OpenShift environment projects (e.g., `"my-dev my-test my-prod"`) |
|
|
61
|
+
| `registry` | `-r` | Yes | Container registry to publish images to (e.g., `ghcr.io/my-org`) |
|
|
62
|
+
| `type` | `-t` | No | Pipeline type: `actions` (default) or `jenkins` |
|
|
63
|
+
| `apply` | `-a` | No | Apply the generated manifests to OpenShift immediately after generation |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### `apply-infra`
|
|
68
|
+
|
|
69
|
+
Applies the OpenShift infrastructure manifests that were generated by `pipeline`. Takes no options — reads the generated pipeline configuration from the workspace.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx nx g @abgov/nx-oc:apply-infra
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Use `apply-infra` as a follow-up to `pipeline` when you want to defer cluster provisioning, or as a re-apply step after editing the generated manifests.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### `deployment`
|
|
80
|
+
|
|
81
|
+
Adds OpenShift deployment manifests (Deployment, Service, Route, etc.) to an existing Nx project for a specific environment.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx nx g @abgov/nx-oc:deployment my-app --appType node --env dev
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Option | Alias | Required | Description |
|
|
88
|
+
|--------|-------|----------|-------------|
|
|
89
|
+
| `project` | — | Yes | Name of the existing Nx project to add deployment manifests to |
|
|
90
|
+
| `appType` | `-t` | Yes | Application type: `frontend`, `dotnet`, or `node` |
|
|
91
|
+
| `env` | `-e` | Yes | ADSP environment: `dev`, `test`, or `prod` |
|
|
92
|
+
| `accessToken` | `-at` | No | Access token for non-interactive retrieval of ADSP configuration |
|
|
93
|
+
|
|
94
|
+
Run the generator once per environment per application. For a typical three-environment setup, run it three times with `--env dev`, `--env test`, and `--env prod`.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Executor: `apply`
|
|
99
|
+
|
|
100
|
+
Runs `oc apply` to deploy an application's OpenShift manifests. Configure it as a target in the project's `project.json`:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"targets": {
|
|
105
|
+
"deploy": {
|
|
106
|
+
"executor": "@abgov/nx-oc:apply",
|
|
107
|
+
"options": {
|
|
108
|
+
"ocProject": "my-dev-project"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Then run:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx nx run my-app:deploy
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Options
|
|
122
|
+
|
|
123
|
+
| Option | Required | Description |
|
|
124
|
+
|--------|----------|-------------|
|
|
125
|
+
| `ocProject` | Yes | OpenShift project(s) to apply manifests to |
|
|
126
|
+
|
|
127
|
+
`ocProject` accepts three forms:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
// Single project
|
|
131
|
+
{ "ocProject": "my-dev" }
|
|
132
|
+
|
|
133
|
+
// Multiple projects
|
|
134
|
+
{ "ocProject": ["my-dev", "my-test"] }
|
|
135
|
+
|
|
136
|
+
// Tagged deployments
|
|
137
|
+
{ "ocProject": [{ "project": "my-dev", "tag": "v1.2.3" }] }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Typical workflow
|
|
143
|
+
|
|
144
|
+
1. Run `pipeline` once per workspace to generate shared build infrastructure manifests.
|
|
145
|
+
2. Run `apply-infra` (or pass `--apply` to `pipeline`) to provision the resources on the cluster.
|
|
146
|
+
3. Run `deployment` for each application and environment combination.
|
|
147
|
+
4. Add an `apply` executor target to each application's `project.json`.
|
|
148
|
+
5. In your GitHub Actions or Jenkins pipeline, call `npx nx run my-app:deploy` to apply manifests during CI/CD.
|
package/package.json
CHANGED
package/src/adsp/adsp-utils.js
CHANGED
|
@@ -14,12 +14,14 @@ const express = require("express");
|
|
|
14
14
|
const open = require("open");
|
|
15
15
|
const simple_oauth2_1 = require("simple-oauth2");
|
|
16
16
|
const environments_1 = require("./environments");
|
|
17
|
+
const token_cache_1 = require("./token-cache");
|
|
17
18
|
function hasDependency(host, dependency) {
|
|
18
19
|
const { dependencies, devDependencies } = (0, devkit_1.readJson)(host, 'package.json');
|
|
19
20
|
return !!(dependencies === null || dependencies === void 0 ? void 0 : dependencies[dependency]) || !!(devDependencies === null || devDependencies === void 0 ? void 0 : devDependencies[dependency]);
|
|
20
21
|
}
|
|
21
22
|
function realmLogin(accessServiceUrl, realm) {
|
|
22
23
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
var _a, _b;
|
|
23
25
|
const client = new simple_oauth2_1.AuthorizationCode({
|
|
24
26
|
client: {
|
|
25
27
|
id: 'nx-adsp-cli',
|
|
@@ -31,6 +33,28 @@ function realmLogin(accessServiceUrl, realm) {
|
|
|
31
33
|
authorizePath: `/auth/realms/${realm}/protocol/openid-connect/auth`,
|
|
32
34
|
},
|
|
33
35
|
});
|
|
36
|
+
// Core realm is only used to fetch the tenant list — not worth caching.
|
|
37
|
+
if (realm !== 'core') {
|
|
38
|
+
const cached = (0, token_cache_1.getCachedToken)(accessServiceUrl, realm);
|
|
39
|
+
if (cached) {
|
|
40
|
+
if (!(0, token_cache_1.isExpired)(cached)) {
|
|
41
|
+
return cached.accessToken;
|
|
42
|
+
}
|
|
43
|
+
if (cached.refreshToken) {
|
|
44
|
+
try {
|
|
45
|
+
const { token } = yield client
|
|
46
|
+
.createToken({ access_token: cached.accessToken, refresh_token: cached.refreshToken })
|
|
47
|
+
.refresh();
|
|
48
|
+
const expiresIn = (_a = token['expires_in']) !== null && _a !== void 0 ? _a : 300;
|
|
49
|
+
(0, token_cache_1.setCachedToken)(accessServiceUrl, realm, token['access_token'], token['refresh_token'], expiresIn);
|
|
50
|
+
return token['access_token'];
|
|
51
|
+
}
|
|
52
|
+
catch (_c) {
|
|
53
|
+
// Refresh failed — fall through to browser login.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
34
58
|
const redirect_uri = 'http://localhost:3000/callback';
|
|
35
59
|
const authorizationUri = client.authorizeURL({
|
|
36
60
|
redirect_uri,
|
|
@@ -59,6 +83,10 @@ function realmLogin(accessServiceUrl, realm) {
|
|
|
59
83
|
tokenPromise,
|
|
60
84
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Timed out waiting for login.')), 120000)),
|
|
61
85
|
]).finally(() => server.close());
|
|
86
|
+
if (realm !== 'core') {
|
|
87
|
+
const expiresIn = (_b = token['expires_in']) !== null && _b !== void 0 ? _b : 300;
|
|
88
|
+
(0, token_cache_1.setCachedToken)(accessServiceUrl, realm, token['access_token'], token['refresh_token'], expiresIn);
|
|
89
|
+
}
|
|
62
90
|
return token['access_token'];
|
|
63
91
|
});
|
|
64
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adsp-utils.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/adsp/adsp-utils.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"adsp-utils.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/adsp/adsp-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface CacheEntry {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
refreshToken?: string;
|
|
4
|
+
expiresAt: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getCacheFilePath(): string;
|
|
7
|
+
export declare function isExpired(entry: CacheEntry): boolean;
|
|
8
|
+
export declare function getCachedToken(accessServiceUrl: string, realm: string): CacheEntry | null;
|
|
9
|
+
export declare function setCachedToken(accessServiceUrl: string, realm: string, accessToken: string, refreshToken: string | undefined, expiresIn: number): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCacheFilePath = getCacheFilePath;
|
|
4
|
+
exports.isExpired = isExpired;
|
|
5
|
+
exports.getCachedToken = getCachedToken;
|
|
6
|
+
exports.setCachedToken = setCachedToken;
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const os = require("os");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const EXPIRY_BUFFER_MS = 60000;
|
|
11
|
+
function getCacheFilePath() {
|
|
12
|
+
return path.join(os.homedir(), '.nx-adsp', 'token-cache.json');
|
|
13
|
+
}
|
|
14
|
+
function readCache() {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(fs.readFileSync(getCacheFilePath(), 'utf-8'));
|
|
17
|
+
}
|
|
18
|
+
catch (_a) {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function writeCache(cache) {
|
|
23
|
+
const filePath = getCacheFilePath();
|
|
24
|
+
const dir = path.dirname(filePath);
|
|
25
|
+
if (!fs.existsSync(dir)) {
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
27
|
+
}
|
|
28
|
+
fs.writeFileSync(filePath, JSON.stringify(cache, null, 2), { mode: 0o600, encoding: 'utf-8' });
|
|
29
|
+
}
|
|
30
|
+
function cacheKey(accessServiceUrl, realm) {
|
|
31
|
+
return `${accessServiceUrl}::${realm}`;
|
|
32
|
+
}
|
|
33
|
+
function isExpired(entry) {
|
|
34
|
+
return new Date(entry.expiresAt).getTime() - EXPIRY_BUFFER_MS < Date.now();
|
|
35
|
+
}
|
|
36
|
+
function getCachedToken(accessServiceUrl, realm) {
|
|
37
|
+
var _a;
|
|
38
|
+
return (_a = readCache()[cacheKey(accessServiceUrl, realm)]) !== null && _a !== void 0 ? _a : null;
|
|
39
|
+
}
|
|
40
|
+
function setCachedToken(accessServiceUrl, realm, accessToken, refreshToken, expiresIn) {
|
|
41
|
+
const cache = readCache();
|
|
42
|
+
cache[cacheKey(accessServiceUrl, realm)] = {
|
|
43
|
+
accessToken,
|
|
44
|
+
refreshToken,
|
|
45
|
+
expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString(),
|
|
46
|
+
};
|
|
47
|
+
writeCache(cache);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=token-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-cache.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/adsp/token-cache.ts"],"names":[],"mappings":";;AAcA,4CAEC;AAuBD,8BAEC;AAED,wCAEC;AAED,wCAcC;AA7DD,yBAAyB;AACzB,yBAAyB;AACzB,6BAA6B;AAU7B,MAAM,gBAAgB,GAAG,KAAM,CAAC;AAEhC,SAAgB,gBAAgB;IAC9B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,SAAS;IAChB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,gBAAgB,EAAE,EAAE,OAAO,CAAC,CAAe,CAAC;IAChF,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAiB;IACnC,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;AACjG,CAAC;AAED,SAAS,QAAQ,CAAC,gBAAwB,EAAE,KAAa;IACvD,OAAO,GAAG,gBAAgB,KAAK,KAAK,EAAE,CAAC;AACzC,CAAC;AAED,SAAgB,SAAS,CAAC,KAAiB;IACzC,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAC7E,CAAC;AAED,SAAgB,cAAc,CAAC,gBAAwB,EAAE,KAAa;;IACpE,OAAO,MAAA,SAAS,EAAE,CAAC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,mCAAI,IAAI,CAAC;AAChE,CAAC;AAED,SAAgB,cAAc,CAC5B,gBAAwB,EACxB,KAAa,EACb,WAAmB,EACnB,YAAgC,EAChC,SAAiB;IAEjB,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,KAAK,CAAC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,GAAG;QACzC,WAAW;QACX,YAAY;QACZ,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;KACjE,CAAC;IACF,UAAU,CAAC,KAAK,CAAC,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { getCacheFilePath, getCachedToken, isExpired, setCachedToken, CacheEntry } from './token-cache';
|
|
5
|
+
|
|
6
|
+
jest.mock('fs');
|
|
7
|
+
jest.mock('os');
|
|
8
|
+
|
|
9
|
+
const mockedFs = jest.mocked(fs);
|
|
10
|
+
const mockedOs = jest.mocked(os);
|
|
11
|
+
|
|
12
|
+
const FAKE_HOME = '/home/testuser';
|
|
13
|
+
const CACHE_PATH = path.join(FAKE_HOME, '.nx-adsp', 'token-cache.json');
|
|
14
|
+
|
|
15
|
+
const ACCESS_URL = 'https://access.example.com/auth';
|
|
16
|
+
const REALM = 'my-tenant';
|
|
17
|
+
|
|
18
|
+
function futureIso(secondsFromNow: number): string {
|
|
19
|
+
return new Date(Date.now() + secondsFromNow * 1000).toISOString();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function pastIso(secondsAgo: number): string {
|
|
23
|
+
return new Date(Date.now() - secondsAgo * 1000).toISOString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('getCacheFilePath', () => {
|
|
27
|
+
it('returns path under home directory', () => {
|
|
28
|
+
mockedOs.homedir.mockReturnValue(FAKE_HOME);
|
|
29
|
+
expect(getCacheFilePath()).toBe(CACHE_PATH);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('isExpired', () => {
|
|
34
|
+
it('returns false when token expires in the future (beyond buffer)', () => {
|
|
35
|
+
const entry: CacheEntry = { accessToken: 'tok', expiresAt: futureIso(120) };
|
|
36
|
+
expect(isExpired(entry)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('returns true when token is past its expiry', () => {
|
|
40
|
+
const entry: CacheEntry = { accessToken: 'tok', expiresAt: pastIso(10) };
|
|
41
|
+
expect(isExpired(entry)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns true when token expires within the 60-second buffer', () => {
|
|
45
|
+
const entry: CacheEntry = { accessToken: 'tok', expiresAt: futureIso(30) };
|
|
46
|
+
expect(isExpired(entry)).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('getCachedToken', () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
mockedOs.homedir.mockReturnValue(FAKE_HOME);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('returns null when cache file does not exist', () => {
|
|
56
|
+
mockedFs.readFileSync.mockImplementation(() => { throw new Error('ENOENT'); });
|
|
57
|
+
expect(getCachedToken(ACCESS_URL, REALM)).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns null when realm is not in cache', () => {
|
|
61
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ 'other::realm': { accessToken: 'x', expiresAt: futureIso(3600) } }));
|
|
62
|
+
expect(getCachedToken(ACCESS_URL, REALM)).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns the cached entry when present', () => {
|
|
66
|
+
const entry: CacheEntry = { accessToken: 'abc', refreshToken: 'ref', expiresAt: futureIso(3600) };
|
|
67
|
+
const key = `${ACCESS_URL}::${REALM}`;
|
|
68
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ [key]: entry }));
|
|
69
|
+
|
|
70
|
+
expect(getCachedToken(ACCESS_URL, REALM)).toEqual(entry);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('setCachedToken', () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
mockedOs.homedir.mockReturnValue(FAKE_HOME);
|
|
77
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
78
|
+
mockedFs.readFileSync.mockImplementation(() => { throw new Error('ENOENT'); });
|
|
79
|
+
(mockedFs.writeFileSync as jest.Mock).mockReset();
|
|
80
|
+
(mockedFs.mkdirSync as jest.Mock).mockReset();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('writes a new entry with correct fields', () => {
|
|
84
|
+
setCachedToken(ACCESS_URL, REALM, 'access-tok', 'refresh-tok', 3600);
|
|
85
|
+
|
|
86
|
+
expect(mockedFs.writeFileSync).toHaveBeenCalledWith(
|
|
87
|
+
CACHE_PATH,
|
|
88
|
+
expect.stringContaining('"accessToken": "access-tok"'),
|
|
89
|
+
expect.objectContaining({ mode: 0o600 })
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const written = JSON.parse((mockedFs.writeFileSync as jest.Mock).mock.calls[0][1] as string);
|
|
93
|
+
const entry = written[`${ACCESS_URL}::${REALM}`];
|
|
94
|
+
expect(entry.accessToken).toBe('access-tok');
|
|
95
|
+
expect(entry.refreshToken).toBe('refresh-tok');
|
|
96
|
+
expect(new Date(entry.expiresAt).getTime()).toBeGreaterThan(Date.now() + 3590 * 1000);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('writes without refreshToken when not provided', () => {
|
|
100
|
+
setCachedToken(ACCESS_URL, REALM, 'access-tok', undefined, 3600);
|
|
101
|
+
|
|
102
|
+
const written = JSON.parse((mockedFs.writeFileSync as jest.Mock).mock.calls[0][1] as string);
|
|
103
|
+
const entry = written[`${ACCESS_URL}::${REALM}`];
|
|
104
|
+
expect(entry.refreshToken).toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('creates the cache directory if it does not exist', () => {
|
|
108
|
+
mockedFs.existsSync.mockReturnValue(false);
|
|
109
|
+
|
|
110
|
+
setCachedToken(ACCESS_URL, REALM, 'tok', undefined, 300);
|
|
111
|
+
|
|
112
|
+
expect(mockedFs.mkdirSync).toHaveBeenCalledWith(
|
|
113
|
+
path.dirname(CACHE_PATH),
|
|
114
|
+
expect.objectContaining({ recursive: true, mode: 0o700 })
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -50,7 +50,8 @@ function normalizeOptions(host, options) {
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
function addFiles(host, options) {
|
|
53
|
-
|
|
53
|
+
var _a;
|
|
54
|
+
const templateOptions = Object.assign(Object.assign(Object.assign({}, options), options.adsp), { sourceRepositoryUrl: (0, git_utils_1.getGitRemoteUrl)(), database: (_a = options.database) !== null && _a !== void 0 ? _a : 'none', tmpl: '' });
|
|
54
55
|
(0, devkit_1.generateFiles)(host, path.join(__dirname, `${options.appType}-files`), `./.openshift/${options.projectName}`, templateOptions);
|
|
55
56
|
}
|
|
56
57
|
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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"}
|
|
@@ -180,6 +180,64 @@ describe('Deployment Generator', () => {
|
|
|
180
180
|
expect(dockerfile).toContain('dotnet');
|
|
181
181
|
});
|
|
182
182
|
|
|
183
|
+
it('includes DATABASE_URL secretKeyRef and init container for postgres node deployment', async () => {
|
|
184
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
185
|
+
await pipeline(host, {
|
|
186
|
+
pipeline: 'test',
|
|
187
|
+
registry: 'ghcr.io/test-org',
|
|
188
|
+
type: 'jenkins',
|
|
189
|
+
infra: 'test-infra',
|
|
190
|
+
envs: 'test-dev',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
addProjectConfiguration(host, 'test', {
|
|
194
|
+
root: 'apps/test',
|
|
195
|
+
projectType: 'application',
|
|
196
|
+
targets: {
|
|
197
|
+
build: {
|
|
198
|
+
executor: '@nx/webpack:webpack',
|
|
199
|
+
options: { compiler: 'tsc', target: 'node' },
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await generator(host, { ...options, appType: 'node', database: 'postgres' });
|
|
205
|
+
|
|
206
|
+
const manifest = host.read('.openshift/test/test.yml').toString();
|
|
207
|
+
expect(manifest).toContain('DATABASE_URL');
|
|
208
|
+
expect(manifest).toContain('initContainers');
|
|
209
|
+
expect(manifest).toContain('prisma');
|
|
210
|
+
expect(manifest).toContain('migrate');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('includes MONGODB_URI secretKeyRef without init container for mongo node deployment', async () => {
|
|
214
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
215
|
+
await pipeline(host, {
|
|
216
|
+
pipeline: 'test',
|
|
217
|
+
registry: 'ghcr.io/test-org',
|
|
218
|
+
type: 'jenkins',
|
|
219
|
+
infra: 'test-infra',
|
|
220
|
+
envs: 'test-dev',
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
addProjectConfiguration(host, 'test', {
|
|
224
|
+
root: 'apps/test',
|
|
225
|
+
projectType: 'application',
|
|
226
|
+
targets: {
|
|
227
|
+
build: {
|
|
228
|
+
executor: '@nx/webpack:webpack',
|
|
229
|
+
options: { compiler: 'tsc', target: 'node' },
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await generator(host, { ...options, appType: 'node', database: 'mongo' });
|
|
235
|
+
|
|
236
|
+
const manifest = host.read('.openshift/test/test.yml').toString();
|
|
237
|
+
expect(manifest).toContain('MONGODB_URI');
|
|
238
|
+
expect(manifest).not.toContain('initContainers');
|
|
239
|
+
});
|
|
240
|
+
|
|
183
241
|
it('can skip unknown project type', async () => {
|
|
184
242
|
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
185
243
|
await pipeline(host, {
|
|
@@ -75,6 +75,21 @@ objects:
|
|
|
75
75
|
labels:
|
|
76
76
|
name: ${APP_NAME}
|
|
77
77
|
spec:
|
|
78
|
+
<% if (database === 'postgres') { %>
|
|
79
|
+
initContainers:
|
|
80
|
+
- name: ${APP_NAME}-migrate
|
|
81
|
+
image: image-registry.openshift-image-registry.svc:5000/${INFRA_PROJECT}/${APP_NAME}:${DEPLOY_TAG}
|
|
82
|
+
command: ["npx", "prisma", "migrate", "deploy"]
|
|
83
|
+
envFrom:
|
|
84
|
+
- configMapRef:
|
|
85
|
+
name: ${APP_NAME}
|
|
86
|
+
env:
|
|
87
|
+
- name: DATABASE_URL
|
|
88
|
+
valueFrom:
|
|
89
|
+
secretKeyRef:
|
|
90
|
+
name: ${APP_NAME}-database
|
|
91
|
+
key: DATABASE_URL
|
|
92
|
+
<% } %>
|
|
78
93
|
containers:
|
|
79
94
|
- name: ${APP_NAME}
|
|
80
95
|
image: ${APP_NAME}:${DEPLOY_TAG}
|
|
@@ -86,6 +101,21 @@ objects:
|
|
|
86
101
|
value: '3333'
|
|
87
102
|
- name: LOG_LEVEL
|
|
88
103
|
value: info
|
|
104
|
+
<% if (database === 'postgres') { %>
|
|
105
|
+
# Set via: oc create secret generic ${APP_NAME}-database --from-literal=DATABASE_URL=<connection-string>
|
|
106
|
+
- name: DATABASE_URL
|
|
107
|
+
valueFrom:
|
|
108
|
+
secretKeyRef:
|
|
109
|
+
name: ${APP_NAME}-database
|
|
110
|
+
key: DATABASE_URL
|
|
111
|
+
<% } else if (database === 'mongo') { %>
|
|
112
|
+
# Set via: oc create secret generic ${APP_NAME}-database --from-literal=MONGODB_URI=<connection-string>
|
|
113
|
+
- name: MONGODB_URI
|
|
114
|
+
valueFrom:
|
|
115
|
+
secretKeyRef:
|
|
116
|
+
name: ${APP_NAME}-database
|
|
117
|
+
key: MONGODB_URI
|
|
118
|
+
<% } %>
|
|
89
119
|
imagePullPolicy: IfNotPresent
|
|
90
120
|
ports:
|
|
91
121
|
- containerPort: 3333
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AdspConfiguration } from '../../adsp';
|
|
2
2
|
|
|
3
3
|
export type ApplicationType = 'node' | 'dotnet' | 'frontend';
|
|
4
|
+
export type DatabaseType = 'none' | 'postgres' | 'mongo';
|
|
4
5
|
|
|
5
6
|
export interface Schema {
|
|
6
7
|
project: string;
|
|
@@ -8,6 +9,7 @@ export interface Schema {
|
|
|
8
9
|
env: EnvironmentName;
|
|
9
10
|
adsp?: AdspConfiguration;
|
|
10
11
|
accessToken?: string;
|
|
12
|
+
database?: DatabaseType;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export interface NormalizedSchema extends Schema {
|
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
"type": "string",
|
|
54
54
|
"description": "Access token for retrieving configuration from ADSP APIs.",
|
|
55
55
|
"alias": "at"
|
|
56
|
+
},
|
|
57
|
+
"database": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "Database type used by the service. When set, the deployment manifest references the appropriate Secret.",
|
|
60
|
+
"enum": ["none", "postgres", "mongo"]
|
|
56
61
|
}
|
|
57
62
|
},
|
|
58
63
|
"required": [
|