@abgov/nx-oc 12.1.0 → 12.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-oc",
3
- "version": "12.1.0",
3
+ "version": "12.2.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for OpenShift.",
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/schema",
3
3
  "id": "NxOcApplyInfra",
4
- "title": "",
4
+ "title": "Apply OpenShift Infrastructure",
5
5
  "type": "object",
6
6
  "properties": {},
7
- "required": []
7
+ "required": [],
8
+ "additionalProperties": false
8
9
  }
@@ -33,6 +33,7 @@ describe('Deployment Generator', () => {
33
33
  const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
34
34
  await pipeline(host, {
35
35
  pipeline: 'test',
36
+ registry: 'ghcr.io/test-org',
36
37
  type: 'jenkins',
37
38
  infra: 'test-infra',
38
39
  envs: 'test-dev',
@@ -64,6 +65,7 @@ describe('Deployment Generator', () => {
64
65
  const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
65
66
  await pipeline(host, {
66
67
  pipeline: 'test',
68
+ registry: 'ghcr.io/test-org',
67
69
  type: 'jenkins',
68
70
  infra: 'test-infra',
69
71
  envs: 'test-dev',
@@ -94,6 +96,7 @@ describe('Deployment Generator', () => {
94
96
  const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
95
97
  await pipeline(host, {
96
98
  pipeline: 'test',
99
+ registry: 'ghcr.io/test-org',
97
100
  type: 'jenkins',
98
101
  infra: 'test-infra',
99
102
  envs: 'test-dev',
@@ -121,6 +124,7 @@ describe('Deployment Generator', () => {
121
124
  const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
122
125
  await pipeline(host, {
123
126
  pipeline: 'test',
127
+ registry: 'ghcr.io/test-org',
124
128
  type: 'jenkins',
125
129
  infra: 'test-infra',
126
130
  envs: 'test-dev',
@@ -152,6 +156,7 @@ describe('Deployment Generator', () => {
152
156
  const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
153
157
  await pipeline(host, {
154
158
  pipeline: 'test',
159
+ registry: 'ghcr.io/test-org',
155
160
  type: 'jenkins',
156
161
  infra: 'test-infra',
157
162
  envs: 'test-dev',
@@ -179,6 +184,7 @@ describe('Deployment Generator', () => {
179
184
  const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
180
185
  await pipeline(host, {
181
186
  pipeline: 'test',
187
+ registry: 'ghcr.io/test-org',
182
188
  type: 'jenkins',
183
189
  infra: 'test-infra',
184
190
  envs: 'test-dev',
@@ -11,7 +11,7 @@ parameters:
11
11
  value: <%= ocInfraProject %>
12
12
  required: true
13
13
  - name: PROJECT
14
- description: Project to set up.
14
+ description: Project to deploy into.
15
15
  displayName: Project
16
16
  required: true
17
17
  - name: APP_NAME
@@ -20,7 +20,7 @@ parameters:
20
20
  value: <%= projectName %>
21
21
  required: true
22
22
  - name: DEPLOY_TAG
23
- description: Tag of the ImageStream to deploy.
23
+ description: ImageStream tag to deploy.
24
24
  displayName: Deploy Tag
25
25
  value: latest
26
26
  required: true
@@ -30,42 +30,13 @@ objects:
30
30
  metadata:
31
31
  name: ${APP_NAME}
32
32
  namespace: ${INFRA_PROJECT}
33
- - apiVersion: build.openshift.io/v1
34
- kind: BuildConfig
35
- metadata:
36
- name: ${APP_NAME}
37
- namespace: ${INFRA_PROJECT}
38
- spec:
39
- output:
40
- to:
41
- kind: ImageStreamTag
42
- name: '${APP_NAME}:latest'
43
- resources:
44
- limits:
45
- cpu: 300m
46
- memory: 1Gi
47
- requests:
48
- cpu: 100m
49
- memory: 200Mi
50
- runPolicy: Serial
51
- source:
52
- binary: {}
53
- type: Binary
54
- strategy:
55
- dockerStrategy:
56
- buildArgs:
57
- - name: PROJECT
58
- value: <%= projectName %>
59
- - name: ASSEMBLY
60
- value: <%= projectName %>.dll
61
- dockerfilePath: .openshift/<%= projectName %>/Dockerfile
62
- type: Docker
63
- triggers: []
64
33
  - apiVersion: v1
65
34
  kind: ConfigMap
66
35
  metadata:
67
36
  name: ${APP_NAME}
68
37
  namespace: ${PROJECT}
38
+ labels:
39
+ reference: "true"
69
40
  data:
70
41
  configuration: |-
71
42
  {
@@ -85,27 +56,36 @@ objects:
85
56
  "AllowedHosts": "*",
86
57
  "Urls": "http://${APP_NAME}:5000"
87
58
  }
88
- - apiVersion: apps.openshift.io/v1
89
- kind: DeploymentConfig
59
+ - apiVersion: apps/v1
60
+ kind: Deployment
90
61
  metadata:
91
62
  name: ${APP_NAME}
92
63
  namespace: ${PROJECT}
64
+ annotations:
65
+ image.openshift.io/triggers: |-
66
+ [
67
+ {
68
+ "from": {
69
+ "kind": "ImageStreamTag",
70
+ "name": "${APP_NAME}:${DEPLOY_TAG}",
71
+ "namespace": "${INFRA_PROJECT}"
72
+ },
73
+ "fieldPath": "spec.template.spec.containers[0].image",
74
+ "paused": true
75
+ }
76
+ ]
93
77
  spec:
94
78
  replicas: 1
95
79
  selector:
96
- name: ${APP_NAME}
80
+ matchLabels:
81
+ name: ${APP_NAME}
82
+ revisionHistoryLimit: 3
83
+ progressDeadlineSeconds: 600
97
84
  strategy:
98
- activeDeadlineSeconds: 21600
99
- recreateParams:
100
- timeoutSeconds: 600
101
- resources:
102
- limits:
103
- cpu: 200m
104
- memory: 200Mi
105
- requests:
106
- cpu: 20m
107
- memory: 50Mi
108
- type: Recreate
85
+ type: RollingUpdate
86
+ rollingUpdate:
87
+ maxSurge: 25%
88
+ maxUnavailable: 25%
109
89
  template:
110
90
  metadata:
111
91
  labels:
@@ -113,6 +93,7 @@ objects:
113
93
  spec:
114
94
  containers:
115
95
  - name: ${APP_NAME}
96
+ image: ${APP_NAME}:${DEPLOY_TAG}
116
97
  envFrom:
117
98
  - configMapRef:
118
99
  name: ${APP_NAME}
@@ -131,28 +112,15 @@ objects:
131
112
  requests:
132
113
  cpu: 20m
133
114
  memory: 200Mi
134
- terminationMessagePath: /dev/termination-log
135
115
  volumeMounts:
136
116
  - mountPath: /opt/app-root/app/appsettings.json
137
117
  name: config-volume
138
118
  subPath: configuration
139
- dnsPolicy: ClusterFirst
140
- restartPolicy: Always
141
- schedulerName: default-scheduler
142
119
  volumes:
143
120
  - configMap:
144
121
  defaultMode: 420
145
122
  name: ${APP_NAME}
146
123
  name: config-volume
147
- triggers:
148
- - imageChangeParams:
149
- containerNames:
150
- - ${APP_NAME}
151
- from:
152
- kind: ImageStreamTag
153
- name: '${APP_NAME}:${DEPLOY_TAG}'
154
- namespace: ${INFRA_PROJECT}
155
- type: ImageChange
156
124
  - apiVersion: v1
157
125
  kind: Service
158
126
  metadata:
@@ -11,7 +11,7 @@ parameters:
11
11
  value: <%= ocInfraProject %>
12
12
  required: true
13
13
  - name: PROJECT
14
- description: Project to set up.
14
+ description: Project to deploy into.
15
15
  displayName: Project
16
16
  required: true
17
17
  - name: APP_NAME
@@ -20,7 +20,7 @@ parameters:
20
20
  value: <%= projectName %>
21
21
  required: true
22
22
  - name: DEPLOY_TAG
23
- description: Tag of the ImageStream to deploy.
23
+ description: ImageStream tag to deploy.
24
24
  displayName: Deploy Tag
25
25
  value: latest
26
26
  required: true
@@ -30,42 +30,15 @@ objects:
30
30
  metadata:
31
31
  name: ${APP_NAME}
32
32
  namespace: ${INFRA_PROJECT}
33
- - apiVersion: build.openshift.io/v1
34
- kind: BuildConfig
35
- metadata:
36
- name: ${APP_NAME}
37
- namespace: ${INFRA_PROJECT}
38
- spec:
39
- output:
40
- to:
41
- kind: ImageStreamTag
42
- name: '${APP_NAME}:latest'
43
- resources:
44
- limits:
45
- cpu: 300m
46
- memory: 1Gi
47
- requests:
48
- cpu: 100m
49
- memory: 200Mi
50
- runPolicy: Serial
51
- source:
52
- binary: {}
53
- type: Binary
54
- strategy:
55
- dockerStrategy:
56
- buildArgs:
57
- - name: PROJECT
58
- value: <%= projectName %>
59
- dockerfilePath: .openshift/<%= projectName %>/Dockerfile
60
- type: Docker
61
- triggers: []
62
33
  - apiVersion: v1
63
34
  kind: ConfigMap
64
35
  metadata:
65
36
  name: ${APP_NAME}
66
37
  namespace: ${PROJECT}
38
+ labels:
39
+ reference: "true"
67
40
  data:
68
- configuration: |-
41
+ config.json: |-
69
42
  {
70
43
  "access": {
71
44
  "url": "<%= accessServiceUrl %>",
@@ -73,27 +46,36 @@ objects:
73
46
  "client_id": "urn:ads:<%= tenant %>:<%= projectName %>"
74
47
  }
75
48
  }
76
- - apiVersion: apps.openshift.io/v1
77
- kind: DeploymentConfig
49
+ - apiVersion: apps/v1
50
+ kind: Deployment
78
51
  metadata:
79
52
  name: ${APP_NAME}
80
53
  namespace: ${PROJECT}
54
+ annotations:
55
+ image.openshift.io/triggers: |-
56
+ [
57
+ {
58
+ "from": {
59
+ "kind": "ImageStreamTag",
60
+ "name": "${APP_NAME}:${DEPLOY_TAG}",
61
+ "namespace": "${INFRA_PROJECT}"
62
+ },
63
+ "fieldPath": "spec.template.spec.containers[0].image",
64
+ "paused": true
65
+ }
66
+ ]
81
67
  spec:
82
68
  replicas: 1
83
69
  selector:
84
- name: ${APP_NAME}
70
+ matchLabels:
71
+ name: ${APP_NAME}
72
+ revisionHistoryLimit: 3
73
+ progressDeadlineSeconds: 600
85
74
  strategy:
86
- activeDeadlineSeconds: 21600
87
- recreateParams:
88
- timeoutSeconds: 600
89
- resources:
90
- limits:
91
- cpu: 200m
92
- memory: 200Mi
93
- requests:
94
- cpu: 20m
95
- memory: 50Mi
96
- type: Recreate
75
+ type: RollingUpdate
76
+ rollingUpdate:
77
+ maxSurge: 25%
78
+ maxUnavailable: 25%
97
79
  template:
98
80
  metadata:
99
81
  labels:
@@ -101,9 +83,10 @@ objects:
101
83
  spec:
102
84
  containers:
103
85
  - name: ${APP_NAME}
86
+ image: ${APP_NAME}:${DEPLOY_TAG}
104
87
  imagePullPolicy: IfNotPresent
105
88
  ports:
106
- - containerPort: 3333
89
+ - containerPort: 8080
107
90
  name: http
108
91
  protocol: TCP
109
92
  resources:
@@ -113,30 +96,17 @@ objects:
113
96
  requests:
114
97
  cpu: 20m
115
98
  memory: 50Mi
116
- terminationMessagePath: /dev/termination-log
117
99
  volumeMounts:
118
100
  - mountPath: /opt/app-root/src/config
119
101
  name: config-volume
120
- dnsPolicy: ClusterFirst
121
- restartPolicy: Always
122
- schedulerName: default-scheduler
123
102
  volumes:
124
103
  - configMap:
125
104
  defaultMode: 420
126
105
  items:
127
- - key: configuration
106
+ - key: config.json
128
107
  path: config.json
129
108
  name: ${APP_NAME}
130
109
  name: config-volume
131
- triggers:
132
- - imageChangeParams:
133
- containerNames:
134
- - ${APP_NAME}
135
- from:
136
- kind: ImageStreamTag
137
- name: '${APP_NAME}:${DEPLOY_TAG}'
138
- namespace: ${INFRA_PROJECT}
139
- type: ImageChange
140
110
  - apiVersion: v1
141
111
  kind: Service
142
112
  metadata:
@@ -11,7 +11,7 @@ parameters:
11
11
  value: <%= ocInfraProject %>
12
12
  required: true
13
13
  - name: PROJECT
14
- description: Project to set up.
14
+ description: Project to deploy into.
15
15
  displayName: Project
16
16
  required: true
17
17
  - name: APP_NAME
@@ -20,7 +20,7 @@ parameters:
20
20
  value: <%= projectName %>
21
21
  required: true
22
22
  - name: DEPLOY_TAG
23
- description: Tag of the ImageStream to deploy.
23
+ description: ImageStream tag to deploy.
24
24
  displayName: Deploy Tag
25
25
  value: latest
26
26
  required: true
@@ -30,64 +30,46 @@ objects:
30
30
  metadata:
31
31
  name: ${APP_NAME}
32
32
  namespace: ${INFRA_PROJECT}
33
- - apiVersion: build.openshift.io/v1
34
- kind: BuildConfig
35
- metadata:
36
- name: ${APP_NAME}
37
- namespace: ${INFRA_PROJECT}
38
- spec:
39
- output:
40
- to:
41
- kind: ImageStreamTag
42
- name: '${APP_NAME}:latest'
43
- resources:
44
- limits:
45
- cpu: 300m
46
- memory: 1Gi
47
- requests:
48
- cpu: 100m
49
- memory: 200Mi
50
- runPolicy: Serial
51
- source:
52
- binary: {}
53
- type: Binary
54
- strategy:
55
- dockerStrategy:
56
- buildArgs:
57
- - name: PROJECT
58
- value: <%= projectName %>
59
- dockerfilePath: .openshift/<%= projectName %>/Dockerfile
60
- type: Docker
61
- triggers: []
62
33
  - apiVersion: v1
63
34
  kind: ConfigMap
64
35
  metadata:
65
36
  name: ${APP_NAME}
66
37
  namespace: ${PROJECT}
38
+ labels:
39
+ reference: "true"
67
40
  data:
68
41
  TENANT_REALM: <%= tenantRealm %>
69
42
  ACCESS_SERVICE_URL: <%= accessServiceUrl %>
70
- - apiVersion: apps.openshift.io/v1
71
- kind: DeploymentConfig
43
+ - apiVersion: apps/v1
44
+ kind: Deployment
72
45
  metadata:
73
46
  name: ${APP_NAME}
74
47
  namespace: ${PROJECT}
48
+ annotations:
49
+ image.openshift.io/triggers: |-
50
+ [
51
+ {
52
+ "from": {
53
+ "kind": "ImageStreamTag",
54
+ "name": "${APP_NAME}:${DEPLOY_TAG}",
55
+ "namespace": "${INFRA_PROJECT}"
56
+ },
57
+ "fieldPath": "spec.template.spec.containers[0].image",
58
+ "paused": true
59
+ }
60
+ ]
75
61
  spec:
76
62
  replicas: 1
77
63
  selector:
78
- name: ${APP_NAME}
64
+ matchLabels:
65
+ name: ${APP_NAME}
66
+ revisionHistoryLimit: 3
67
+ progressDeadlineSeconds: 600
79
68
  strategy:
80
- activeDeadlineSeconds: 21600
81
- recreateParams:
82
- timeoutSeconds: 600
83
- resources:
84
- limits:
85
- cpu: 200m
86
- memory: 200Mi
87
- requests:
88
- cpu: 20m
89
- memory: 50Mi
90
- type: Recreate
69
+ type: RollingUpdate
70
+ rollingUpdate:
71
+ maxSurge: 25%
72
+ maxUnavailable: 25%
91
73
  template:
92
74
  metadata:
93
75
  labels:
@@ -95,11 +77,12 @@ objects:
95
77
  spec:
96
78
  containers:
97
79
  - name: ${APP_NAME}
80
+ image: ${APP_NAME}:${DEPLOY_TAG}
98
81
  envFrom:
99
82
  - configMapRef:
100
83
  name: ${APP_NAME}
101
84
  env:
102
- - name: port
85
+ - name: PORT
103
86
  value: '3333'
104
87
  - name: LOG_LEVEL
105
88
  value: info
@@ -115,19 +98,6 @@ objects:
115
98
  requests:
116
99
  cpu: 20m
117
100
  memory: 50Mi
118
- terminationMessagePath: /dev/termination-log
119
- dnsPolicy: ClusterFirst
120
- restartPolicy: Always
121
- schedulerName: default-scheduler
122
- triggers:
123
- - imageChangeParams:
124
- containerNames:
125
- - ${APP_NAME}
126
- from:
127
- kind: ImageStreamTag
128
- name: '${APP_NAME}:${DEPLOY_TAG}'
129
- namespace: ${INFRA_PROJECT}
130
- type: ImageChange
131
101
  - apiVersion: v1
132
102
  kind: Service
133
103
  metadata:
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/schema",
3
3
  "id": "NxOcDeployment",
4
- "title": "",
4
+ "title": "ADSP Application Deployment",
5
5
  "type": "object",
6
6
  "properties": {
7
7
  "project": {
@@ -59,5 +59,6 @@
59
59
  "project",
60
60
  "appType",
61
61
  "env"
62
- ]
62
+ ],
63
+ "additionalProperties": false
63
64
  }
@@ -1,14 +1,19 @@
1
1
  # GitHub Actions pipeline.
2
- # This pipeline runs application build process in GitHub Actions but runs OpenShift BuildConfig for
3
- # creating runtime containers. It does not require a container registry accessible to both OpenShift
4
- # and GitHub.
2
+ # Builds container images with Buildah, publishes to a container registry,
3
+ # imports into OpenShift ImageStreams, and deploys via Kubernetes Deployments.
5
4
  #
6
5
  # Secrets (to be manually added):
7
- # OPENSHIFT_SERVER - URL to the OpenShift server.
8
- # OPENSHIFT_TOKEN - Token for the service account (github-actions) used for OC cli commands.
6
+ # OPENSHIFT_SERVER - URL to the OpenShift server.
7
+ # OPENSHIFT_TOKEN - Token for the service account (github-actions) used for OC cli commands.
9
8
  #
10
- # Envs (auto added but requires manual configuration):
11
- # Dev, Test, Staging, Prod
9
+ # Optional secrets for pulling Red Hat base images:
10
+ # REDHAT_IO_USERNAME - Red Hat registry service account username.
11
+ # REDHAT_IO_PASSWORD - Red Hat registry service account password or token.
12
+ #
13
+ # GitHub Environments (configure in repo Settings > Environments):
14
+ <% ocEnvProjects.forEach(function(ocEnvProject, i){ -%>
15
+ # <%= envs[i] %> - maps to OpenShift project <%= ocEnvProject %>
16
+ <% }); -%>
12
17
  name: Pipeline
13
18
 
14
19
  on:
@@ -22,108 +27,158 @@ on:
22
27
  description: Value for --base= in nx affected commands
23
28
  required: false
24
29
 
30
+ # Deny all permissions by default; each job grants only what it needs.
31
+ permissions: {}
32
+
33
+ # Cancel any in-progress run for the same branch so stale builds don't
34
+ # race against a newer push on the deploy steps.
35
+ concurrency:
36
+ group: ${{ github.workflow }}-${{ github.ref }}
37
+ cancel-in-progress: true
38
+
25
39
  jobs:
26
40
  build:
27
- runs-on: ubuntu-20.04
41
+ runs-on: ubuntu-24.04
42
+ timeout-minutes: 30
43
+ permissions:
44
+ contents: read
45
+ packages: write # Required for pushing images to GHCR with GITHUB_TOKEN
28
46
  env:
29
47
  AFFECTED_BASE: "origin/main"
30
48
  outputs:
31
49
  affected_apps: ${{ steps.set_outputs.outputs.affected_apps }}
32
50
  steps:
33
- # Checkout the source code.
34
- - uses: actions/checkout@v3
51
+ - uses: actions/checkout@v5
35
52
  with:
36
53
  fetch-depth: 0
37
- - uses: actions/setup-node@v3
54
+ - uses: actions/setup-node@v5
38
55
  with:
39
- node-version: "20"
56
+ node-version: "24"
40
57
  cache: "npm"
41
58
  - run: npm ci
42
-
43
- # Determine the last successful build of this (pipeline.yml) workflow and set it as the AFFECTED_BASE.
59
+
44
60
  - name: Get last successful commit
45
61
  id: last_successful_commit
46
- uses: nrwl/nx-set-shas@v3
62
+ uses: nrwl/nx-set-shas@v5
47
63
  - name: Set AFFECTED_BASE to last successful build commit
48
- if: ${{ steps.last_successful_commit.outputs.base }}
64
+ if: ${{ steps.last_successful_commit.outputs.base }}
49
65
  run: echo "AFFECTED_BASE=${{ steps.last_successful_commit.outputs.base }}" >> $GITHUB_ENV
50
-
51
- # Input for base takes precedence over last successful build.
52
- - name: Set AFFECTED_BASE to input for SELECTED_BASE
66
+
67
+ # Workflow dispatch input takes precedence over last successful build.
68
+ - name: Set AFFECTED_BASE to workflow input
53
69
  if: ${{ github.event.inputs.SELECTED_BASE }}
54
70
  run: echo "AFFECTED_BASE=${{ github.event.inputs.SELECTED_BASE }}" >> $GITHUB_ENV
55
71
 
56
- # Project build steps lint, test, and build
57
72
  - name: Lint
58
73
  run: npx nx affected --target=lint --base=${{ env.AFFECTED_BASE }}
59
74
  - name: Test
60
75
  run: npx nx affected --target=test --base=${{ env.AFFECTED_BASE }}
61
76
  - name: Build
62
77
  run: npx nx affected --target=build --base=${{ env.AFFECTED_BASE }} --configuration production
63
- # Set affected apps to env for later steps and jobs.
64
- - name: Set AFFECTED_APPS for OC steps
78
+ - name: Set affected apps
65
79
  run: echo "AFFECTED_APPS=$(npx nx show projects --affected --type=app --base=${{ env.AFFECTED_BASE }} | tr '\n' ' ')" >> $GITHUB_ENV
66
80
 
67
- - run: npm prune --production
68
-
69
- # Run the OC BuildConfigs for create containers.
81
+ - run: npm prune --omit=dev
82
+
83
+ # Log into Red Hat registry if credentials are provided (required for UBI base images).
84
+ - name: Log in to Red Hat registry
85
+ if: ${{ secrets.REDHAT_IO_USERNAME != '' }}
86
+ uses: redhat-actions/podman-login@v1
87
+ with:
88
+ registry: registry.redhat.io
89
+ username: ${{ secrets.REDHAT_IO_USERNAME }}
90
+ password: ${{ secrets.REDHAT_IO_PASSWORD }}
91
+
92
+ # Build container images with Buildah and push to GHCR using GITHUB_TOKEN.
93
+ - name: Log in to container registry
94
+ run: echo "${{ secrets.GITHUB_TOKEN }}" | podman login <%= registry %> -u ${{ github.actor }} --password-stdin
95
+ - name: Build and publish containers
96
+ run: |
97
+ set -eo pipefail
98
+ for app in $AFFECTED_APPS
99
+ do
100
+ buildah build \
101
+ --build-arg PROJECT=$app \
102
+ --file .openshift/$app/Dockerfile \
103
+ --tag <%= registry %>/$app:latest \
104
+ --tag <%= registry %>/$app:${{ github.sha }} \
105
+ .
106
+ buildah push <%= registry %>/$app:latest
107
+ buildah push <%= registry %>/$app:${{ github.sha }}
108
+ done
109
+
110
+ # Import images into OpenShift and tag for the first environment.
70
111
  - name: OC login
71
112
  uses: redhat-actions/oc-login@v1
72
113
  with:
73
114
  openshift_server_url: ${{ secrets.OPENSHIFT_SERVER }}
74
115
  openshift_token: ${{ secrets.OPENSHIFT_TOKEN }}
75
116
  namespace: <%= ocInfraProject %>
76
-
77
- # Disables SSL cert checking. Use this if you don't have the certificate authority data.
78
117
  insecure_skip_tls_verify: true
79
- # This method is more secure, if the certificate from Step 4 is available.
80
- # certificate_authority_data: ${{ secrets.CA_DATA }}
81
- - name: Build containers
118
+ - name: Import images and tag for <%= envs[0] %>
82
119
  run: |
120
+ set -eo pipefail
83
121
  for app in $AFFECTED_APPS
84
- do
85
- oc start-build $app --from-dir . --follow --wait
86
- done
87
- # Set build job outputs for subsequent jobs.
122
+ do
123
+ oc import-image <%= registry %>/$app:latest --reference-policy='local' --confirm
124
+ oc tag $app:latest $app:<%= envs[0].toLowerCase() %> --reference-policy='local'
125
+ done
126
+
88
127
  - id: set_outputs
89
128
  run: echo "affected_apps=$AFFECTED_APPS" >> $GITHUB_OUTPUT
90
129
  <% ocEnvProjects.forEach(function(ocEnvProject, i){ -%>
91
-
130
+
92
131
  deploy<%= envs[i] %>:
93
- runs-on: ubuntu-20.04
94
- needs: [build<%= envs[i - 1] ? `, deploy${envs[i - 1]}` : '' %>]
95
- env:
132
+ runs-on: ubuntu-24.04
133
+ timeout-minutes: 15
134
+ needs: [build<%= i > 0 ? `, deploy${envs[i - 1]}` : '' %>]
135
+ if: needs.build.outputs.affected_apps
136
+ permissions:
137
+ contents: read
138
+ env:
96
139
  AFFECTED_APPS: ${{ needs.build.outputs.affected_apps }}
97
140
  environment:
98
141
  name: <%= envs[i] %>
99
142
  steps:
100
- # Checkout the source code for manifests
101
- - uses: actions/checkout@v3
102
- - name: Oc login
143
+ - uses: actions/checkout@v5
144
+ - name: OC login
103
145
  uses: redhat-actions/oc-login@v1
104
146
  with:
105
147
  openshift_server_url: ${{ secrets.OPENSHIFT_SERVER }}
106
148
  openshift_token: ${{ secrets.OPENSHIFT_TOKEN }}
107
149
  namespace: <%= ocInfraProject %>
108
150
  insecure_skip_tls_verify: true
109
- - name: Tag <%= envs[i] %>
151
+ <% if (i > 0) { -%>
152
+ - name: Promote image to <%= envs[i] %>
110
153
  run: |
154
+ set -eo pipefail
111
155
  for app in $AFFECTED_APPS
112
- do
113
- oc tag $app:<%= envs[i - 1] ? envs[i - 1].toLowerCase() : 'latest' %> $app:<%= envs[i].toLowerCase() %>
114
- done
115
- - run: oc project <%= ocEnvProject %>
116
- - name: Apply Manifests
156
+ do
157
+ oc tag $app:<%= envs[i - 1].toLowerCase() %> $app:<%= envs[i].toLowerCase() %> --reference-policy='local'
158
+ done
159
+ <% } -%>
160
+ - name: Apply manifests
117
161
  run: |
162
+ set -eo pipefail
118
163
  for app in $AFFECTED_APPS
119
164
  do
120
- oc process -f .openshift/$app/$app.yml -p PROJECT=<%= ocEnvProject %> -p DEPLOY_TAG=<%= envs[i].toLowerCase() %> | oc apply -f -
165
+ oc process -f .openshift/$app/$app.yml \
166
+ -p PROJECT=<%= ocEnvProject %> \
167
+ -p DEPLOY_TAG=<%= envs[i].toLowerCase() %> \
168
+ | oc apply -l reference!=true -f -
121
169
  done
122
170
  - name: Deploy <%= envs[i] %>
123
171
  run: |
172
+ set -eo pipefail
124
173
  for app in $AFFECTED_APPS
125
- do
126
- oc rollout latest dc/$app
127
- oc rollout status dc/$app
128
- done
174
+ do
175
+ if oc get deployment/$app -n <%= ocEnvProject %> > /dev/null 2>&1 ; then
176
+ oc set triggers deployment/$app -n <%= ocEnvProject %> --auto
177
+ oc rollout status deployment/$app -n <%= ocEnvProject %>
178
+ oc set triggers deployment/$app -n <%= ocEnvProject %> --manual
179
+ elif oc get dc/$app -n <%= ocEnvProject %> > /dev/null 2>&1 ; then
180
+ oc rollout latest dc/$app -n <%= ocEnvProject %>
181
+ oc rollout status dc/$app -n <%= ocEnvProject %>
182
+ fi
183
+ done
129
184
  <% }); -%>
@@ -1,3 +1,5 @@
1
+ # DEPRECATED: Jenkins pipeline support is deprecated and will be removed in the next major version.
2
+ # Use the GitHub Actions pipeline type (--type actions) instead.
1
3
  apiVersion: v1
2
4
  kind: List
3
5
  items:
@@ -23,6 +23,8 @@ function addFiles(host, options) {
23
23
  var _a;
24
24
  const templateOptions = Object.assign(Object.assign({}, options), { sourceRepositoryUrl: (_a = (0, git_utils_1.getGitRemoteUrl)()) === null || _a === void 0 ? void 0 : _a.trim(), envs: pipeline_envs_1.pipelineEnvs, tmpl: '' });
25
25
  if (options.pipelineType === 'jenkins') {
26
+ console.warn('WARNING: Jenkins pipeline support is deprecated and will be removed in the next major version. ' +
27
+ 'Use --type actions instead.');
26
28
  (0, devkit_1.generateFiles)(host, path.join(__dirname, 'jenkins'), `./.openshift`, templateOptions);
27
29
  }
28
30
  else if (options.pipelineType === 'actions') {
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../../../../packages/nx-oc/src/generators/pipeline/pipeline.ts"],"names":[],"mappings":";;AA4DA,4BASC;;AArED,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,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":";;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"}
@@ -6,6 +6,7 @@ describe('Pipeline Generator', () => {
6
6
  describe('Jenkins', () => {
7
7
  const options: Schema = {
8
8
  pipeline: 'test',
9
+ registry: 'ghcr.io/test-org',
9
10
  type: 'jenkins',
10
11
  infra: 'test-infra',
11
12
  envs: 'test-dev',
@@ -40,6 +41,7 @@ describe('Pipeline Generator', () => {
40
41
  describe('GitHub Actions', () => {
41
42
  const options: Schema = {
42
43
  pipeline: 'test',
44
+ registry: 'ghcr.io/test-org',
43
45
  type: 'actions',
44
46
  infra: 'test-infra',
45
47
  envs: 'test-dev',
@@ -53,6 +55,16 @@ describe('Pipeline Generator', () => {
53
55
  expect(host.exists('.openshift/environments.yml')).toBeTruthy();
54
56
  });
55
57
 
58
+ it('includes registry in pipeline workflow', async () => {
59
+ const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
60
+ await generator(host, options);
61
+ const workflow = host.read('.github/workflows/pipeline.yml').toString();
62
+ expect(workflow).toContain('ghcr.io/test-org');
63
+ expect(workflow).toContain('buildah build');
64
+ expect(workflow).toContain('oc import-image');
65
+ expect(workflow).toContain('oc set triggers');
66
+ });
67
+
56
68
  it('can generate multiple envs', async () => {
57
69
  const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
58
70
  await generator(host, { ...options, envs: 'test-dev test-test' });
@@ -3,6 +3,7 @@ export interface Schema {
3
3
  type: string;
4
4
  infra: string;
5
5
  envs: string;
6
+ registry: string;
6
7
  apply?: boolean;
7
8
  }
8
9
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/schema",
3
3
  "id": "NxOcPipeline",
4
- "title": "",
4
+ "title": "OpenShift CI/CD Pipeline",
5
5
  "type": "object",
6
6
  "properties": {
7
7
  "pipeline": {
@@ -35,6 +35,12 @@
35
35
  "alias": "e",
36
36
  "x-prompt": "What projects should be used for environments (dev test prod)?"
37
37
  },
38
+ "registry": {
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)?"
43
+ },
38
44
  "apply": {
39
45
  "type": "boolean",
40
46
  "description": "Flag indicating if the pipeline should be applied.",
@@ -46,6 +52,8 @@
46
52
  "pipeline",
47
53
  "infra",
48
54
  "type",
49
- "envs"
50
- ]
55
+ "envs",
56
+ "registry"
57
+ ],
58
+ "additionalProperties": false
51
59
  }
@@ -0,0 +1,34 @@
1
+ import { execSync } from 'child_process';
2
+ import { mocked } from 'jest-mock';
3
+ import { getGitRemoteUrl } from './git-utils';
4
+
5
+ jest.mock('child_process');
6
+ const mockedExecSync = mocked(execSync);
7
+
8
+ describe('getGitRemoteUrl', () => {
9
+ beforeEach(() => {
10
+ mockedExecSync.mockReset();
11
+ });
12
+
13
+ it('returns the remote origin url', () => {
14
+ mockedExecSync.mockReturnValue(
15
+ Buffer.from('https://github.com/GovAlta/nx-tools.git\n')
16
+ );
17
+
18
+ const url = getGitRemoteUrl();
19
+ expect(url).toBe('https://github.com/GovAlta/nx-tools.git\n');
20
+ expect(mockedExecSync).toHaveBeenCalledWith(
21
+ 'git config --get remote.origin.url',
22
+ { stdio: 'pipe' }
23
+ );
24
+ });
25
+
26
+ it('returns undefined when git command fails', () => {
27
+ mockedExecSync.mockImplementation(() => {
28
+ throw new Error('not a git repo');
29
+ });
30
+
31
+ const url = getGitRemoteUrl();
32
+ expect(url).toBeUndefined();
33
+ });
34
+ });
@@ -0,0 +1,60 @@
1
+ import { execSync } from 'child_process';
2
+ import { mocked } from 'jest-mock';
3
+ import { runOcCommand } from './oc-utils';
4
+
5
+ jest.mock('child_process');
6
+ const mockedExecSync = mocked(execSync);
7
+
8
+ describe('runOcCommand', () => {
9
+ beforeEach(() => {
10
+ mockedExecSync.mockReset();
11
+ });
12
+
13
+ it('returns success with stdout on successful command', () => {
14
+ const output = Buffer.from('project set to test-dev');
15
+ mockedExecSync.mockReturnValue(output);
16
+
17
+ const result = runOcCommand('project', ['test-dev']);
18
+ expect(result.success).toBe(true);
19
+ expect(result.stdout).toBe(output);
20
+ });
21
+
22
+ it('builds the correct command string without input', () => {
23
+ mockedExecSync.mockReturnValue(Buffer.from(''));
24
+
25
+ runOcCommand('process', ['-f .openshift/app.yml', '-p ENV=dev']);
26
+ expect(mockedExecSync).toHaveBeenCalledWith(
27
+ 'oc process -f .openshift/app.yml -p ENV=dev',
28
+ expect.objectContaining({ stdio: 'pipe' })
29
+ );
30
+ });
31
+
32
+ it('uses cat pipe form when input buffer is provided', () => {
33
+ mockedExecSync.mockReturnValue(Buffer.from(''));
34
+ const input = Buffer.from('{"kind":"List"}');
35
+
36
+ runOcCommand('apply', [], input);
37
+ const [cmd] = mockedExecSync.mock.calls[0];
38
+ expect(cmd).toContain('cat |');
39
+ expect(cmd).toContain('oc apply -f -');
40
+ });
41
+
42
+ it('passes input buffer to execSync', () => {
43
+ mockedExecSync.mockReturnValue(Buffer.from(''));
44
+ const input = Buffer.from('yaml-content');
45
+
46
+ runOcCommand('apply', [], input);
47
+ const [, options] = mockedExecSync.mock.calls[0];
48
+ expect((options as { input: Buffer }).input).toBe(input);
49
+ });
50
+
51
+ it('returns failure when command throws', () => {
52
+ mockedExecSync.mockImplementation(() => {
53
+ throw new Error('oc: command not found');
54
+ });
55
+
56
+ const result = runOcCommand('start-build', ['my-pipeline']);
57
+ expect(result.success).toBe(false);
58
+ expect(result.stdout).toBeUndefined();
59
+ });
60
+ });