diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 2f001c0..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,184 +0,0 @@ -#!groovy - -// -------------------- -// Declarative Pipeline -// -------------------- -pipeline { - agent any - - environment { - // Enable pipeline verbose debug output if greater than 0 - DEBUG_OUTPUT = 'false' - - // Get projects/namespaces from config maps - DEV_PROJECT = new File('/var/run/configs/ns/project.dev').getText('UTF-8').trim() - TEST_PROJECT = new File('/var/run/configs/ns/project.test').getText('UTF-8').trim() - PROD_PROJECT = new File('/var/run/configs/ns/project.prod').getText('UTF-8').trim() - TOOLS_PROJECT = new File('/var/run/configs/ns/project.tools').getText('UTF-8').trim() - - // Get application config from config maps - REPO_OWNER = new File('/var/run/configs/jobs/repo.owner').getText('UTF-8').trim() - REPO_NAME = new File('/var/run/configs/jobs/repo.name').getText('UTF-8').trim() - APP_NAME = new File('/var/run/configs/jobs/app.name').getText('UTF-8').trim() - APP_DOMAIN = new File('/var/run/configs/jobs/app.domain').getText('UTF-8').trim() - - // JOB_NAME should be the pull request/branch identifier (i.e. 'pr-5') - JOB_NAME = JOB_BASE_NAME.toLowerCase() - - // SOURCE_REPO_* references git repository resources - SOURCE_REPO_RAW = "https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${JOB_NAME}" - SOURCE_REPO_REF = "${JOB_NAME}" - SOURCE_REPO_URL = "https://github.com/${REPO_OWNER}/${REPO_NAME}.git" - - // ENV_HOST is the full domain without the path (ie. 'appname-dev.pathfinder.gov.bc.ca') - DEV_HOST = "${APP_NAME}-dev.${APP_DOMAIN}" - TEST_HOST = "${APP_NAME}-test.${APP_DOMAIN}" - PROD_HOST = "${APP_NAME}.${APP_DOMAIN}" - // PATH_ROOT will be appended to ENV_HOST - PATH_ROOT = "/${JOB_NAME.equalsIgnoreCase('master') ? 'app' : JOB_NAME}" - } - - options { - parallelsAlwaysFailFast() - } - - stages { - stage('Initialize') { - agent any - steps { - // Cancel any running builds in progress - timeout(time: 10, unit: 'MINUTES') { - echo "Cancelling previous ${APP_NAME}-${JOB_NAME} builds in progress..." - abortAllPreviousBuildInProgress(currentBuild) - } - - script { - if(DEBUG_OUTPUT.equalsIgnoreCase('true')) { - // Force OpenShift Plugin directives to be verbose - openshift.logLevel(1) - - // Print all environment variables - echo 'DEBUG - All pipeline environment variables:' - echo sh(returnStdout: true, script: 'env') - } - - loadCommonPipeline() - } - } - } - - stage('Build') { - agent any - steps { - script { - loadCommonPipeline() - commonPipeline.runStageBuild() - } - } - post { - success { - echo 'Cleanup App BuildConfigs...' - script { - openshift.withCluster() { - openshift.withProject(TOOLS_PROJECT) { - if(DEBUG_OUTPUT.equalsIgnoreCase('true')) { - echo "DEBUG - Using project: ${openshift.project()}" - } else { - def bcApp = openshift.selector('bc', "${REPO_NAME}-app-${JOB_NAME}") - - if(bcApp.exists()) { - echo "Removing BuildConfig ${REPO_NAME}-app-${JOB_NAME}..." - bcApp.delete() - } - } - } - } - } - } - } - } - - stage('Deploy - Dev') { - agent any - steps { - script { - loadCommonPipeline() - commonPipeline.runStageDeploy('Dev', DEV_PROJECT, DEV_HOST, PATH_ROOT) - } - } - post { - success { - script { - commonPipeline.createDeploymentStatus(DEV_PROJECT, 'SUCCESS', JOB_NAME, DEV_HOST, PATH_ROOT) - commonPipeline.notifyStageStatus('Deploy - Dev', 'SUCCESS') - } - } - unsuccessful { - script { - commonPipeline.createDeploymentStatus(DEV_PROJECT, 'FAILURE', JOB_NAME, DEV_HOST, PATH_ROOT) - commonPipeline.notifyStageStatus('Deploy - Dev', 'FAILURE') - } - } - } - } - - stage('Deploy - Test') { - agent any - steps { - script { - loadCommonPipeline() - commonPipeline.runStageDeploy('Test', TEST_PROJECT, TEST_HOST, PATH_ROOT) - } - } - post { - success { - script { - commonPipeline.createDeploymentStatus(TEST_PROJECT, 'SUCCESS', JOB_NAME, TEST_HOST, PATH_ROOT) - commonPipeline.notifyStageStatus('Deploy - Test', 'SUCCESS') - } - } - unsuccessful { - script { - commonPipeline.createDeploymentStatus(TEST_PROJECT, 'FAILURE', JOB_NAME, TEST_HOST, PATH_ROOT) - commonPipeline.notifyStageStatus('Deploy - Test', 'FAILURE') - } - } - } - } - - stage('Deploy - Prod') { - agent any - steps { - script { - loadCommonPipeline() - commonPipeline.runStageDeploy('Prod', PROD_PROJECT, PROD_HOST, PATH_ROOT) - } - } - post { - success { - script { - commonPipeline.createDeploymentStatus(PROD_PROJECT, 'SUCCESS', JOB_NAME, PROD_HOST, PATH_ROOT) - commonPipeline.notifyStageStatus('Deploy - Prod', 'SUCCESS') - } - } - unsuccessful { - script { - commonPipeline.createDeploymentStatus(PROD_PROJECT, 'FAILURE', JOB_NAME, PROD_HOST, PATH_ROOT) - commonPipeline.notifyStageStatus('Deploy - Prod', 'FAILURE') - } - } - } - } - } -} - -// -------------------- -// Supporting Functions -// -------------------- - -// Load Common Code as Global Variable -def loadCommonPipeline() { - if (!binding.hasVariable('commonPipeline')) { - commonPipeline = load "${WORKSPACE}/openshift/commonPipeline.groovy" - } -} diff --git a/Jenkinsfile.cicd b/Jenkinsfile.cicd deleted file mode 100644 index 2cf6828..0000000 --- a/Jenkinsfile.cicd +++ /dev/null @@ -1,148 +0,0 @@ -#!groovy - -// -------------------- -// Declarative Pipeline -// -------------------- -pipeline { - agent any - - environment { - // Enable pipeline verbose debug output if greater than 0 - DEBUG_OUTPUT = 'false' - - // Get projects/namespaces from config maps - DEV_PROJECT = new File('/var/run/configs/ns/project.dev').getText('UTF-8').trim() - TEST_PROJECT = new File('/var/run/configs/ns/project.test').getText('UTF-8').trim() - PROD_PROJECT = new File('/var/run/configs/ns/project.prod').getText('UTF-8').trim() - TOOLS_PROJECT = new File('/var/run/configs/ns/project.tools').getText('UTF-8').trim() - - // Get application config from config maps - REPO_OWNER = new File('/var/run/configs/jobs/repo.owner').getText('UTF-8').trim() - REPO_NAME = new File('/var/run/configs/jobs/repo.name').getText('UTF-8').trim() - APP_NAME = new File('/var/run/configs/jobs/app.name').getText('UTF-8').trim() - APP_DOMAIN = new File('/var/run/configs/jobs/app.domain').getText('UTF-8').trim() - - // JOB_NAME should be the pull request/branch identifier (i.e. 'pr-5') - JOB_NAME = JOB_BASE_NAME.toLowerCase() - - // SOURCE_REPO_* references git repository resources - SOURCE_REPO_RAW = "https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/master" - SOURCE_REPO_REF = "pull/${CHANGE_ID}/head" - SOURCE_REPO_URL = "https://github.com/${REPO_OWNER}/${REPO_NAME}.git" - - // ENV_HOST is the full domain without the path (ie. 'appname-dev.pathfinder.gov.bc.ca') - DEV_HOST = "${APP_NAME}-dev.${APP_DOMAIN}" - TEST_HOST = "${APP_NAME}-test.${APP_DOMAIN}" - PROD_HOST = "${APP_NAME}.${APP_DOMAIN}" - // PATH_ROOT will be appended to ENV_HOST - PATH_ROOT = "/${JOB_NAME.equalsIgnoreCase('master') ? 'app' : JOB_NAME}" - } - - options { - parallelsAlwaysFailFast() - } - - stages { - stage('Initialize') { - agent any - steps { - // Cancel any running builds in progress - timeout(time: 10, unit: 'MINUTES') { - echo "Cancelling previous ${APP_NAME}-${JOB_NAME} builds in progress..." - abortAllPreviousBuildInProgress(currentBuild) - } - - script { - if(DEBUG_OUTPUT.equalsIgnoreCase('true')) { - // Force OpenShift Plugin directives to be verbose - openshift.logLevel(1) - - // Print all environment variables - echo 'DEBUG - All pipeline environment variables:' - echo sh(returnStdout: true, script: 'env') - } - - // Load Common Code as Global Variable - commonPipeline = load "${WORKSPACE}/openshift/commonPipeline.groovy" - } - } - } - - stage('Build') { - agent any - steps { - script { - loadCommonPipeline() - commonPipeline.runStageBuild() - } - } - post { - success { - echo 'Cleanup App BuildConfigs...' - script { - openshift.withCluster() { - openshift.withProject(TOOLS_PROJECT) { - if(DEBUG_OUTPUT.equalsIgnoreCase('true')) { - echo "DEBUG - Using project: ${openshift.project()}" - } else { - def bcApp = openshift.selector('bc', "${REPO_NAME}-app-${JOB_NAME}") - - if(bcApp.exists()) { - echo "Removing BuildConfig ${REPO_NAME}-app-${JOB_NAME}..." - bcApp.delete() - } - } - } - } - } - } - } - } - - stage('Deploy - Dev') { - agent any - steps { - script { - loadCommonPipeline() - commonPipeline.runStageDeploy('Dev', DEV_PROJECT, DEV_HOST, PATH_ROOT) - } - } - post { - success { - script { - commonPipeline.createDeploymentStatus(DEV_PROJECT, 'SUCCESS', JOB_NAME, DEV_HOST, PATH_ROOT) - commonPipeline.notifyStageStatus('Deploy - Dev', 'SUCCESS') - } - } - unsuccessful { - script { - commonPipeline.createDeploymentStatus(DEV_PROJECT, 'FAILURE', JOB_NAME, DEV_HOST, PATH_ROOT) - commonPipeline.notifyStageStatus('Deploy - Dev', 'FAILURE') - } - } - } - } - } - post { - changed { - script { - // Comment on Pull Request if build changes to successful - if(currentBuild.currentResult.equalsIgnoreCase('SUCCESS')) { - echo "Posting 'PR Deployed at: https://${DEV_HOST}${PATH_ROOT}' to Pull Request..." - commonPipeline.commentOnPR("PR Deployed at: https://${DEV_HOST}${PATH_ROOT}") - } - } - } - } -} - -// -------------------- -// Supporting Functions -// -------------------- - -// Load Common Code as Global Variable -def loadCommonPipeline() { - if (!binding.hasVariable('commonPipeline')) { - commonPipeline = load "${WORKSPACE}/openshift/commonPipeline.groovy" - } -} diff --git a/README.md b/README.md index 06eb3cb..1be5b62 100644 --- a/README.md +++ b/README.md @@ -9,27 +9,24 @@ A showcase application for the Common Hosted Email Service (CHES). ## Directory Structure - .github/ - PR and Issue templates + .github/ - PR and Issue templates, GH Actions configuration app/ - Application Root ├── frontend/ - Frontend Root │ ├── src/ - Vue.js frontend web application │ └── tests/ - Vue.js frontend web application tests ├── src/ - Node.js backend web application └── tests/ - Node.js backend web application tests - openshift/ - OpenShift-deployment and shared pipeline files + charts/ - Helm charts for CI/CD pipeline CODE-OF-CONDUCT.md - Code of Conduct COMPLIANCE.yaml - BCGov PIA/STRA compliance status CONTRIBUTING.md - Contributing Guidelines - Jenkinsfile - Top-level Pipeline - Jenkinsfile.cicd - Pull-Request Pipeline + Dockerfile - for building app in deployments LICENSE - License ## Documentation * [Application Readme](app/README.md) * [Frontend Readme](app/frontend/README.md) -* [Openshift Readme](openshift/README.md) -* [Devops Tools Setup](https://github.com/bcgov/nr-showcase-devops-tools) ## Quick Start Dev Guide diff --git a/openshift/README.md b/openshift/README.md deleted file mode 100644 index 2d0c575..0000000 --- a/openshift/README.md +++ /dev/null @@ -1,204 +0,0 @@ -# Application on Openshift - -This application is deployed on Openshift. This readme will outline how to setup and configure an Openshift project to get the application to a deployable state. This document assumes a working knowledge of Kubernetes/Openshift container orchestration concepts (i.e. buildconfigs, deployconfigs, imagestreams, secrets, configmaps, routes, networksecuritypolicies, etc) - -Our builds and deployments are orchestrated with Jenkins. Refer to [Jenkinsfile](../Jenkinsfile) and [Jenkinsfile.cicd](../Jenkinsfile.cicd) to see how the Openshift templates are used for building and deploying in our CI/CD pipeline. - -## Table of Contents - -- [Environment Setup - ConfigMaps and Secrets](#environment-setup---configmaps-and-secrets) -- [Build Config & Deployment](#build-config--deployment) -- [Templates](#templates) -- [Pull Request Cleanup](#pull-request-cleanup) -- [Appendix - Supporting Deployments](#appendix---supporting-deployments) - -## Environment Setup - ConfigMaps and Secrets - -There are some requirements in the target Openshift namespace/project which are **outside** of the CI/CD pipeline process. This application requires that a few Secrets as well as Config Maps are already present in the environment before it is able to function as intended. Otherwise the Jenkins pipeline will fail the deployment by design. - -In order to prepare an environment, you will need to ensure that all of the following configmaps and secrets are populated. This is achieved by executing the following commands as a project administrator of the targeted environment. Note that this must be repeated on *each* of the target deployment namespace/projects (i.e. `dev`, `test` and `prod`) as that they are independent of each other. Deployments will fail otherwise. Refer to [custom-environment-variables](../app/config/custom-environment-variables.json) for the direct mapping of environment variables to the app. - -### Config Maps - -*Note:* Replace anything in angle brackets with the appropriate value! - -*Note 2:* The Keycloak Public Key can be found in the Keycloak Admin Panel under Realm Settings > Keys. Look for the Public key button (normally under RS256 row), and click to see the key. The key should begin with a pattern of `MIIBIjANB...`. - -```sh -export NAMESPACE= -export APP_NAME= -export PUBLIC_KEY= - -oc create -n $NAMESPACE configmap $APP_NAME-frontend-config \ - --from-literal=FRONTEND_APIPATH=api/v1 \ - --from-literal=FRONTEND_BASEPATH=/app \ - --from-literal=FRONTEND_KC_CLIENTID=chess-frontend \ - --from-literal=FRONTEND_KC_REALM=98r0z7rz \ - --from-literal=FRONTEND_KC_SERVERURL=https://dev.oidc.gov.bc.ca/auth -``` - -```sh -oc create -n $NAMESPACE configmap $APP_NAME-sc-ches-config \ - --from-literal=SC_CHES_ENDPOINT=https://ches-dev.api.gov.bc.ca/api \ - --from-literal=SC_CS_TOKEN_ENDPOINT=https://dev.loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token -``` - -```sh -oc create -n $NAMESPACE configmap $APP_NAME-server-config \ - --from-literal=SERVER_APIPATH=/api/v1 \ - --from-literal=SERVER_BASEPATH=/app \ - --from-literal=SERVER_BODYLIMIT=30mb \ - --from-literal=SERVER_KC_PUBLICKEY=$PUBLIC_KEY \ - --from-literal=SERVER_KC_REALM=standard \ - --from-literal=SERVER_KC_SERVERURL=https://dev.loginproxy.gov.bc.ca/auth \ - --from-literal=SERVER_LOGLEVEL=info \ - --from-literal=SERVER_MORGANFORMAT=combined \ - --from-literal=SERVER_PORT=8080 -``` - -### Secrets - -Replace anything in angle brackets with the appropriate value! - -```sh -export NAMESPACE= -export APP_NAME= - -oc create -n $NAMESPACE secret generic $APP_NAME-keycloak-secret \ - --type=kubernetes.io/basic-auth \ - --from-literal=username= \ - --from-literal=password= -``` - -```sh -oc create -n $NAMESPACE secret generic $APP_NAME-sc-ches-secret \ - --type=kubernetes.io/basic-auth \ - --from-literal=username= \ - --from-literal=password= -``` - -## Build Config & Deployment - -This application is currently designed as a single application pod deployments. It will host a static frontend containing all of the Vue.js resources and assets, and a Node.js backend which serves the API that the frontend requires. We are currently leveraging Openshift Routes with path based filtering in order to forward incoming traffic to the right deployment service. - -### Frontend - -The frontend temporarily installs dependencies needed to generate the static assets that will appear in the `/app/frontend/dist` folder. These contents will be picked up by the application and hosted appropriately. - -### Application - -The backend is a standard [Node](https://nodejs.org)/[Express](https://expressjs.com) server. It handles the JWT based authentication via OIDC authentication flow, and exposes the API to authorized users. This deployment container is built up on top of an Alpine Node image. The resulting container after build is what is deployed. - -## Templates - -The Jenkins pipeline heavily leverages Openshift Templates in order to ensure that all of the environment variables, settings, and contexts are pushed to Openshift correctly. Files ending with `.bc.yaml` specify the build configurations, while files ending with `.dc.yaml` specify the components required for deployment. - -### Build Configurations - -Build configurations will emit and handle the chained builds or standard builds as necessary. They take in the following parameters: - -| Name | Required | Description | -| --- | --- | --- | -| REPO_NAME | yes | Application repository name | -| JOB_NAME | yes | Job identifier (i.e. 'pr-5' OR 'master') | -| SOURCE_REPO_REF | yes | Git Pull Request Reference (i.e. 'pull/CHANGE_ID/head') | -| SOURCE_REPO_URL | yes | Git Repository URL | - -The template can be manually invoked and deployed via Openshift CLI. For example: - -```sh -export NAMESPACE= - -oc process -n $NAMESPACE -f openshift/app.bc.yaml -p REPO_NAME=common-hosted-email-service-showcase - -p JOB_NAME=master -p SOURCE_REPO_URL=https://github.com/bcgov/common-hosted-email-service-showcase.git -p SOURCE_REPO_REF=master -o yaml | oc apply -n $NAMESPACE -f - -``` - -Note that these build configurations do not have any triggers defined. They will be invoked by the Jenkins pipeline, started manually in the console, or by an equivalent oc command for example: - -```sh -oc start-build -n $NAMESPACE --follow -``` - -Finally, we generally tag the resultant image so that the deployment config will know which exact image to use. This is also handled by the Jenkins pipeline. The equivalent oc command for example is: - -```sh -oc tag -n $NAMESPACE :latest :master -``` - -*Note: Remember to swap out the bracketed values with the appropriate values!* - -### Deployment Configurations - -Deployment configurations will emit and handle the deployment lifecycles of running containers based off of the previously built images. They generally contain a deploymentconfig, a service, and a route. Before our application is deployed, Patroni (a Highly Available Postgres Cluster implementation) needs to be deployed. Refer to any `patroni*` templates and their [official documentation](https://patroni.readthedocs.io/en/latest/) for more details. - -Our application template take in the following parameters: - -| Name | Required | Description | -| --- | --- | --- | -| REPO_NAME | yes | Application repository name | -| JOB_NAME | yes | Job identifier (i.e. 'pr-5' OR 'master') | -| NAMESPACE | yes | which namespace/"environment" are we deploying to? dev, test, prod? | -| APP_NAME | yes | short name for the application | -| ROUTE_HOST | yes | base domain for the publicly accessible URL | -| ROUTE_PATH | yes | base path for the publicly accessible URL | - -The Jenkins pipeline will handle deployment invocation automatically. However should you need to run it manually, you can do so with the following for example: - -```sh -export NAMESPACE= -export APP_NAME= - -oc process -n $NAMESPACE -f openshift/app.dc.yaml -p REPO_NAME=common-hosted-email-service-showcase -p JOB_NAME=master -p NAMESPACE=$NAMESPACE -p APP_NAME=$APP_NAME -p ROUTE_HOST=$APP_NAME-dev.apps.silver.devops.gov.bc.ca -p ROUTE_PATH=master -o yaml | oc apply -n $NAMESPACE -f - -``` - -Due to the triggers that are set in the deploymentconfig, the deployment will begin automatically. However, you can deploy manually by use the following command for example: - -```sh -oc rollout -n $NAMESPACE latest dc/-master -``` - -*Note: Remember to swap out the bracketed values with the appropriate values!* - -## Pull Request Cleanup - -As of this time, we do not automatically clean up resources generated by a Pull Request once it has been accepted and merged in. This is still a manual process. Our PR deployments are all named in the format "pr-###", where the ### is the number of the specific PR. In order to clear all resources for a specific PR, run the following two commands to delete all relevant resources from the Openshift project (replacing `PRNUMBER` with the appropriate number): - -```sh -export NAMESPACE= -export APP_NAME= - -oc delete all,secret,nsp -n $NAMESPACE --selector app=$APP_NAME-pr- -oc delete all,svc,cm,sa,role,secret -n $NAMESPACE --selector cluster-name=pr- -``` - -The first command will clear out all related executable resources for the application, while the second command will clear out the remaining Patroni cluster resources associated with that PR. - -## Appendix - Supporting Deployments - -There will be instances where this application will need supporting modifications or deployment such as databases and business analytics tools. Below is a list of initial reference points for other Openshift templates that could be leveraged and bolted onto the existing Jenkins pipeline if applicable. - -### Metabase - -- [Overview & Templates](https://github.com/bcgov/nr-get-token/wiki/Metabase) - -### MongoDB - -Refer to the `mongodb.dc.yaml` and `mongodb.secret.yaml` files found below for a simple persistent MongoDB deployment: - -- [Templates](https://github.com/jujaga/common-hosted-form-service/tree/feature/octemplates/openshift) - -### Patroni (HA Postgres) - -Refer to the `patroni.dc.yaml` and `patroni.secret.yaml` files found below for a Highly Available Patroni cluster statefulset: - -- [Templates](https://github.com/bcgov/nr-get-token/tree/master/openshift) - -#### Database Backup - -- [Documentation & Templates](https://github.com/bcgov/nr-get-token/wiki/Database-Backup) - -### Redis - -Refer to the `redis.dc.yaml` and `redis.secret.yaml` files found below for a simple persistent Redis deployment: - -- [Templates](https://github.com/bcgov/common-hosted-email-service/tree/master/openshift) diff --git a/openshift/app.bc.yaml b/openshift/app.bc.yaml deleted file mode 100644 index c2019ca..0000000 --- a/openshift/app.bc.yaml +++ /dev/null @@ -1,84 +0,0 @@ ---- -apiVersion: v1 -kind: Template -labels: - build: "${REPO_NAME}-app" - template: "${REPO_NAME}-app-bc-template" -metadata: - name: "${REPO_NAME}-app-bc" -objects: - - apiVersion: v1 - kind: ImageStream - metadata: - name: "${REPO_NAME}-app" - spec: - lookupPolicy: - local: false - - apiVersion: v1 - kind: BuildConfig - metadata: - name: "${REPO_NAME}-app-${JOB_NAME}" - spec: - completionDeadlineSeconds: 600 - failedBuildsHistoryLimit: 3 - nodeSelector: - output: - to: - kind: ImageStreamTag - name: "${REPO_NAME}-app:latest" - postCommit: {} - resources: - requests: - cpu: 2000m - memory: 1Gi - limits: - cpu: 4000m - memory: 6Gi - runPolicy: SerialLatestOnly - source: - contextDir: app - git: - ref: "${SOURCE_REPO_REF}" - uri: "${SOURCE_REPO_URL}" - type: Git - dockerfile: |- - FROM BuildConfig - ENV NO_UPDATE_NOTIFIER=true - WORKDIR /opt/app-root/src - COPY . . - RUN npm run all:ci \ - && npm run all:build \ - && npm run frontend:purge - EXPOSE 8000 - CMD ["npm", "run", "start"] - strategy: - dockerStrategy: - env: - - name: FRONTEND_BASEPATH - value: "${ROUTE_PATH}" - from: - kind: DockerImage - name: docker.io/node:16.15.0-alpine - type: Docker - successfulBuildsHistoryLimit: 3 -parameters: - - name: REPO_NAME - description: Application repository name - displayName: Repository Name - required: true - - name: ROUTE_PATH - description: Configure the route path (ex. /pr-5 or /app), also used for FRONTEND_BASEPATH - displayName: Route path - required: true - - name: JOB_NAME - description: Job identifier (i.e. 'pr-5' OR 'master') - displayName: Job Branch Name - required: true - - name: SOURCE_REPO_REF - description: Git Pull Request Reference (i.e. 'pull/CHANGE_ID/head') - displayName: Source Repository Reference - required: true - - name: SOURCE_REPO_URL - description: Git Repository URL - displayName: Source Repository URL - required: true diff --git a/openshift/app.dc.yaml b/openshift/app.dc.yaml deleted file mode 100644 index 4eea294..0000000 --- a/openshift/app.dc.yaml +++ /dev/null @@ -1,193 +0,0 @@ ---- -apiVersion: v1 -kind: Template -labels: - app.kubernetes.io/component: app - app.kubernetes.io/instance: "${APP_NAME}-${JOB_NAME}" - app.kubernetes.io/managed-by: jenkins - app.kubernetes.io/name: nodejs - app.kubernetes.io/part-of: "${APP_NAME}-${JOB_NAME}" - app: "${APP_NAME}-${JOB_NAME}" - template: "${REPO_NAME}-app-dc-template" -metadata: - name: "${REPO_NAME}-app-dc" -objects: - - apiVersion: v1 - kind: DeploymentConfig - metadata: - name: "${APP_NAME}-app-${JOB_NAME}" - spec: - replicas: 2 - revisionHistoryLimit: 10 - selector: - app: "${APP_NAME}-${JOB_NAME}" - deploymentconfig: "${APP_NAME}-app-${JOB_NAME}" - role: app - strategy: - type: Rolling - resources: {} - rollingParams: - timeoutSeconds: 600 - template: - metadata: - labels: - app: "${APP_NAME}-${JOB_NAME}" - deploymentconfig: "${APP_NAME}-app-${JOB_NAME}" - role: app - spec: - containers: - - name: app - image: "${IMAGE_REGISTRY}/${NAMESPACE}/${REPO_NAME}-app:${JOB_NAME}" - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: "${ROUTE_PATH}/api" - port: 8080 - scheme: HTTP - initialDelaySeconds: 10 - timeoutSeconds: 1 - failureThreshold: 3 - ports: - - containerPort: 8080 - protocol: TCP - readinessProbe: - httpGet: - path: "${ROUTE_PATH}/api" - port: 8080 - scheme: HTTP - initialDelaySeconds: 10 - timeoutSeconds: 1 - failureThreshold: 1 - resources: - requests: - cpu: "${CPU_REQUEST}" - memory: "${MEMORY_REQUEST}" - limits: - cpu: "${CPU_LIMIT}" - memory: "${MEMORY_LIMIT}" - env: - - name: NODE_ENV - value: production - - name: FRONTEND_BASEPATH - value: "${ROUTE_PATH}" - - name: SERVER_BASEPATH - value: "${ROUTE_PATH}" - - name: SERVER_KC_CLIENTID - valueFrom: - secretKeyRef: - key: username - name: "${APP_NAME}-keycloak-secret" - - name: SERVER_KC_CLIENTSECRET - valueFrom: - secretKeyRef: - key: password - name: "${APP_NAME}-keycloak-secret" - - name: SC_CHES_USERNAME - valueFrom: - secretKeyRef: - key: username - name: "${APP_NAME}-sc-ches-secret" - - name: SC_CHES_PASSWORD - valueFrom: - secretKeyRef: - key: password - name: "${APP_NAME}-sc-ches-secret" - envFrom: - - configMapRef: - name: "${APP_NAME}-frontend-config" - - configMapRef: - name: "${APP_NAME}-sc-ches-config" - - configMapRef: - name: "${APP_NAME}-server-config" - restartPolicy: Always - terminationGracePeriodSeconds: 30 - test: false - triggers: - - type: ConfigChange - - imageChangeParams: - automatic: true - containerNames: - - app - from: - kind: ImageStreamTag - name: "${REPO_NAME}-app:${JOB_NAME}" - namespace: "${NAMESPACE}" - type: ImageChange - - apiVersion: v1 - kind: Service - metadata: - name: "${APP_NAME}-app-${JOB_NAME}" - spec: - ports: - - name: 8080-tcp - port: 8080 - protocol: TCP - targetPort: 8080 - selector: - app: "${APP_NAME}-${JOB_NAME}" - deploymentconfig: "${APP_NAME}-app-${JOB_NAME}" - role: app - sessionAffinity: None - - apiVersion: v1 - kind: Route - metadata: - name: "${APP_NAME}-app-${JOB_NAME}" - spec: - host: "${ROUTE_HOST}" - path: "${ROUTE_PATH}" - port: - targetPort: 8080-tcp - tls: - insecureEdgeTerminationPolicy: Redirect - termination: edge - to: - kind: Service - name: "${APP_NAME}-app-${JOB_NAME}" - weight: 100 - wildcardPolicy: None -parameters: - - name: APP_NAME - description: Application name - displayName: Application name - required: true - - name: ROUTE_HOST - description: The host the route will use to expose service outside cluster - displayName: Route host - required: true - - name: ROUTE_PATH - description: Configure the route path (ex. /pr-5 or /app), also used for FRONTEND_BASEPATH - displayName: Route path - required: true - - name: JOB_NAME - description: Job identifier (i.e. 'pr-5' OR 'master') - displayName: Job Branch Name - required: true - - name: IMAGE_REGISTRY - description: The base OpenShift docker registry - displayName: Docker Image Registry - required: true - value: image-registry.openshift-image-registry.svc:5000 - - name: NAMESPACE - description: Target namespace reference (i.e. 'wfezkf-dev') - displayName: Target Namespace - required: true - - name: REPO_NAME - description: Application repository name - displayName: Repository Name - required: true - - name: CPU_LIMIT - description: Limit Peak CPU per pod (in millicores ex. 1000m) - displayName: CPU Limit - value: 250m - - name: CPU_REQUEST - description: Requested CPU per pod (in millicores ex. 500m) - displayName: CPU Request - value: 50m - - name: MEMORY_LIMIT - description: Limit Peak Memory per pod (in gigabytes Gi or megabytes Mi ex. 2Gi) - displayName: Memory Limit - value: 1Gi - - name: MEMORY_REQUEST - description: Requested Memory per pod (in gigabytes Gi or megabytes Mi ex. 500Mi) - displayName: Memory Request - value: 256Mi diff --git a/openshift/commonPipeline.groovy b/openshift/commonPipeline.groovy deleted file mode 100644 index 63e8483..0000000 --- a/openshift/commonPipeline.groovy +++ /dev/null @@ -1,150 +0,0 @@ -#!groovy -import bcgov.GitHubHelper - -// --------------- -// Pipeline Stages -// --------------- - -// Build Images -def runStageBuild() { - openshift.withCluster() { - openshift.withProject(TOOLS_PROJECT) { - if(DEBUG_OUTPUT.equalsIgnoreCase('true')) { - echo "DEBUG - Using project: ${openshift.project()}" - } - - try { - notifyStageStatus('Build App', 'PENDING') - - echo "Processing BuildConfig ${REPO_NAME}-app-${JOB_NAME}..." - def bcApp = openshift.process('-f', - 'openshift/app.bc.yaml', - "REPO_NAME=${REPO_NAME}", - "ROUTE_PATH=${PATH_ROOT}", - "JOB_NAME=${JOB_NAME}", - "SOURCE_REPO_URL=${SOURCE_REPO_URL}", - "SOURCE_REPO_REF=${SOURCE_REPO_REF}" - ) - - echo "Building ImageStream..." - openshift.apply(bcApp).narrow('bc').startBuild('-w').logs('-f') - - echo "Tagging Image ${REPO_NAME}-app:latest..." - openshift.tag("${REPO_NAME}-app:latest", - "${REPO_NAME}-app:${JOB_NAME}" - ) - - echo 'App build successful' - notifyStageStatus('Build App', 'SUCCESS') - } catch (e) { - echo 'App build failed' - notifyStageStatus('Build App', 'FAILURE') - throw e - } - } - } -} - -// Deploy Application and Dependencies -def runStageDeploy(String stageEnv, String projectEnv, String hostEnv, String pathEnv) { - if (!stageEnv.equalsIgnoreCase('Dev')) { - input("Deploy to ${projectEnv}?") - } - - openshift.withCluster() { - openshift.withProject(projectEnv) { - if(DEBUG_OUTPUT.equalsIgnoreCase('true')) { - echo "DEBUG - Using project: ${openshift.project()}" - } - - notifyStageStatus("Deploy - ${stageEnv}", 'PENDING') - createDeploymentStatus(projectEnv, 'PENDING', JOB_NAME, hostEnv, pathEnv) - - echo "Checking for ConfigMaps and Secrets in project ${openshift.project()}..." - if(!(openshift.selector('cm', "${APP_NAME}-frontend-config").exists() && - openshift.selector('cm', "${APP_NAME}-sc-ches-config").exists() && - openshift.selector('cm', "${APP_NAME}-server-config").exists() && - openshift.selector('secret', "${APP_NAME}-keycloak-secret").exists() && - openshift.selector('secret', "${APP_NAME}-sc-ches-secret").exists())) { - echo 'Some ConfigMaps and/or Secrets are missing. Please consult the openshift readme for details.' - throw new Exception('Missing ConfigMaps and/or Secrets') - } - - // Wait for deployments to roll out - timeout(time: 10, unit: 'MINUTES') { - // Apply App Server - echo "Tagging Image ${REPO_NAME}-app:${JOB_NAME}..." - openshift.tag("${TOOLS_PROJECT}/${REPO_NAME}-app:${JOB_NAME}", "${REPO_NAME}-app:${JOB_NAME}") - - echo "Processing DeploymentConfig ${REPO_NAME}-app-${JOB_NAME}..." - def dcAppTemplate = openshift.process('-f', - 'openshift/app.dc.yaml', - "REPO_NAME=${REPO_NAME}", - "JOB_NAME=${JOB_NAME}", - "NAMESPACE=${projectEnv}", - "APP_NAME=${APP_NAME}", - "ROUTE_HOST=${hostEnv}", - "ROUTE_PATH=${pathEnv}" - ) - - echo "Applying ${REPO_NAME}-app-${JOB_NAME} Deployment..." - def dcApp = openshift.apply(dcAppTemplate).narrow('dc') - dcApp.rollout().status('--watch=true') - } - } - } -} - -// -------------------- -// Supporting Functions -// -------------------- - -// Notify stage status and pass to Jenkins-GitHub library -def notifyStageStatus(String name, String status) { - def sha1 = GIT_COMMIT - if(JOB_BASE_NAME.startsWith('PR-')) { - sha1 = GitHubHelper.getPullRequestLastCommitId(this) - } - - GitHubHelper.createCommitStatus( - this, sha1, status, BUILD_URL, '', "Stage: ${name}" - ) -} - -// Create deployment status and pass to Jenkins-GitHub library -def createDeploymentStatus(String environment, String status, String jobName, String hostEnv, String pathEnv) { - // library createDeploymentStatus is busted - skipping for now - // def task = (JOB_BASE_NAME.startsWith('PR-')) ? "deploy:pull:${CHANGE_ID}" : "deploy:${jobName}" - // def ghDeploymentId = new GitHubHelper().createDeployment( - // this, - // SOURCE_REPO_REF, - // [ - // 'environment': environment, - // 'task': task - // ] - // ) - - // new GitHubHelper().createDeploymentStatus( - // this, - // ghDeploymentId, - // status, - // ['targetUrl': "https://${hostEnv}${pathEnv}"] - // ) - - if (status.equalsIgnoreCase('SUCCESS')) { - echo "${environment} deployment successful at https://${hostEnv}${pathEnv}" - } else if (status.equalsIgnoreCase('PENDING')) { - echo "${environment} deployment pending..." - } else if (status.equalsIgnoreCase('FAILURE')) { - echo "${environment} deployment failed" - } -} - -// Creates a comment and pass to Jenkins-GitHub library -def commentOnPR(String comment) { - if(JOB_BASE_NAME.startsWith('PR-')) { - GitHubHelper.commentOnPullRequest(this, comment) - } -} - -return this