diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8c0544ac..a5282bda 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,8 +17,7 @@ "christian-kohler.path-intellisense", "ms-dotnettools.csdevkit", "ms-dotnettools.csharp", - "ms-dotnettools.vscode-dotnet-runtime", - "foxundermoon.shell-format" + "ms-dotnettools.vscode-dotnet-runtime" ], "settings": { "editor.codeActionsOnSave": { diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..7c8c1219 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,45 @@ + +# Pull Request for JIRA Ticket: ----**put ticket number here**---- + +## Issue ticket number and link +Include the JIRA ticket # and link here + +## Description + +Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration + +- [ ] Test A +- [ ] Test B + +**Test Configuration**: +If applicable + +## Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules + + +## Documentation References + +Put any doc references here \ No newline at end of file diff --git a/.github/workflows/actions/docker-build-push-artifactory/action.yaml b/.github/workflows/actions/docker-build-push-artifactory/action.yaml new file mode 100644 index 00000000..2f4ef82d --- /dev/null +++ b/.github/workflows/actions/docker-build-push-artifactory/action.yaml @@ -0,0 +1,80 @@ +name: Build docker and push to artifactory repo +description: perform a build and tag and push to a desired repo. Artifactory secrets should be set. + +inputs: + docker_context_directory: + type: string + description: The directory to work in + default: ./ + image_name: + type: string + description: The name of the image to build + required: true + image_tag: + type: string + description: The docker image tag + required: true + artifactory_repo: + type: string + description: The Artifactory repository to push the image to + required: true + artifactory_image_path: + type: string + description: The path in the Artifactory repository to push the image to + required: true + build_dockerfile: + type: string + description: The path to the Dockerfile to build + docker_target: + type: string + description: The build stage target to build in the Dockerfile. Optional will build final stage by default. + docker_build_args: + type: string + description: The build arguments to pass to the Dockerfile. Pipe separated list of key=value pairs. +runs: + using: composite + + steps: + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Artifactory + uses: docker/login-action@v3 + with: + registry: ${{ inputs.artifactory_repo }} + username: ${{ secrets.ARTIFACTORY_USERNAME }} + password: ${{ secrets.ARTIFACTORY_PASSWORD }} + + - name: Setup Image Metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ inputs.artifactory_repo }}/${{ inputs.artifactory_image_path }}/${{ inputs.image_name }} + tags: | + type=raw,value=${{ inputs.artifactory_repo }}/${{ inputs.artifactory_image_path }}/${{ inputs.image_name }}:${{ inputs.image_tag }} + + - name: Build and Push Image to artifactory.io + uses: docker/build-push-action@v5 + with: + push: true + context: ${{ inputs.docker_context_directory }} + file: ${{ inputs.build_dockerfile || 'Dockerfile' }} + build-args: ${{ inputs.docker_build_args }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + # Docker build target refers to the build stage in the docker files + # requiring a value here will make the shareable action difficult to use. + # Ommitting the target will build the final stage by default. + # https://docs.docker.com/get-started/docker-concepts/building-images/multi-stage-builds/#:~:text=In%20your%20multi,stage%20by%20default. + target: ${{ inputs.docker_target }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max diff --git a/.github/workflows/actions/update-argo-repo/action.yaml b/.github/workflows/actions/update-argo-repo/action.yaml new file mode 100644 index 00000000..da08dbd6 --- /dev/null +++ b/.github/workflows/actions/update-argo-repo/action.yaml @@ -0,0 +1,62 @@ +name: Update Argo CD gitops repo +description: Update the gitops tenant repository with the latest image tag + +inputs: + licence_plate: + type: string + description: The OpenShift licence plate + required: true + gitops_branch: + type: string + description: The branch to update standard branches for tenant-gitops (develop, test, prod) + required: true + image_tag: + type: string + description: The image tag to update + required: true + helm_property: + type: string + description: The property in the Helm values file to update + required: true + helm_paths: + type: string + description: space separated list of paths to the Helm values files to update + required: true + +runs: + using: composite + + steps: + - name: Checkout ArgoCD Repo + id: gitops + uses: actions/checkout@v4 + with: + repository: bcgov-c/tenant-gitops-${{ inputs.licence_plate }} + ref: ${{ inputs.gitops_branch }} + token: ${{ secrets.GIT_OPS_SSH_KEY }} # `GH_PAT` is a secret that contains your PAT + path: gitops + + - name: Update Helm Values and Commit + id: helm + if: steps.gitops.outcome == 'success' + run: | + # Navigate to the directory containing your Helm values file for the environment develop -> DEV, test -> test and + cd gitops/charts + + # Update the Helm values file with the new image tag and version + DATETIME=$(date +'%Y-%m-%d %H:%M:%S') # Get current date and time + + # Split incoming helm_paths by space into an array and loop through each path update the image tag in each file + IFS=' ' read -r -a paths <<< "${{ inputs.helm_paths }}" + for path in "${paths[@]}"; do + sed -i "s/${{ inputs.helm_property }}: .*/${{ inputs.helm_property }}: ${{ inputs.image_tag }} # Image Updated on $DATETIME/" $path + # Stage the changed path immediately for upcoming commit + git add $path + done + + # Commit and push the changes + git config --global user.email "actions@github.com" + git config --global user.name "GitHub Actions" + + git commit -m "Update API image tag" + git push origin ${{ inputs.gitops_branch }} diff --git a/.github/workflows/cd-api.yaml b/.github/workflows/cd-api.yaml new file mode 100644 index 00000000..c0676dc6 --- /dev/null +++ b/.github/workflows/cd-api.yaml @@ -0,0 +1,54 @@ +name: API + +on: + push: + branches: [master] + paths: + - "api/**" + - "docker/api/**" + - ".github/workflows/actions/**" + - ".github/workflows/cd-api.yaml" + workflow_dispatch: +env: + IMAGE_NAME: api + IMAGE_TAG_PREFIX: dev + WORKING_DIRECTORY: ./ + GITOPS_BRANCH: develop + GITOPS_LICENCE_PLATE: b3c707 + ARTIFACTORY_REPO: artifacts.developer.gov.bc.ca + ARTIFACTORY_IMAGE_PATH: sbc3-images + BUILD_DOCKERFILE: api/Dockerfile.release +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Get short SHA + id: short_sha + run: | + echo "::set-output name=SHORT_SHA::$(git rev-parse --short HEAD)" + echo "Short SHA: $SHORT_SHA" + + - name: Build and Push API images to Artifactory + id: build_push + uses: ./.github/workflows/actions/docker-build-push-artifactory + with: + image_name: ${{ env.IMAGE_NAME }} + image_tag: ${{ env.IMAGE_TAG_PREFIX }}-${{ steps.short_sha.outputs.SHORT_SHA }} + artifactory_repo: ${{ env.ARTIFACTORY_REPO }} + artifactory_image_path: ${{ env.ARTIFACTORY_IMAGE_PATH }} + build_dockerfile: ${{ env.BUILD_DOCKERFILE }} + + - name: Update ArgoCD Repo + id: update_argo_repo + if: steps.build_push.outcome == 'success' + uses: ./.github/workflows/actions/update-argo-repo + with: + licence_plate: ${{ env.GITOPS_LICENCE_PLATE }} + gitops_branch: ${{ env.GITOPS_BRANCH }} + image_tag: ${{ env.IMAGE_TAG_PREFIX }}-${{ steps.short_sha.outputs.SHORT_SHA }} + helm_property: /apitag + helm_paths: "api/values.yaml ../develop/values.yaml" diff --git a/.github/workflows/cd-backup.yaml b/.github/workflows/cd-backup.yaml new file mode 100644 index 00000000..8ab5aef8 --- /dev/null +++ b/.github/workflows/cd-backup.yaml @@ -0,0 +1,30 @@ +name: Backup + +on: + workflow_dispatch: +env: + IMAGE_NAME: backup + WORKING_DIRECTORY: ./ + ARTIFACTORY_REPO: artifacts.developer.gov.bc.ca + ARTIFACTORY_IMAGE_PATH: sbc3-images + BUILD_DOCKERFILE: api/Dockerfile.release +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout Backup Container Repo + id: gitops + uses: actions/checkout@v4 + with: + repository: BCDevOps/backup-container.git + ref: 2.9.0 + + - name: Docker Build Backup Image and push to Artifactory + uses: ./.github/workflows/actions/docker-build-push-artifactory + with: + docker_context_directory: docker + image_name: ${{ env.IMAGE_NAME }} + image_tag: latest + artifactory_repo: ${{ env.ARTIFACTORY_REPO }} + artifactory_image_path: ${{ env.ARTIFACTORY_IMAGE_PATH }} diff --git a/.github/workflows/cd-schema-spy.yaml b/.github/workflows/cd-schema-spy.yaml new file mode 100644 index 00000000..fbc7b021 --- /dev/null +++ b/.github/workflows/cd-schema-spy.yaml @@ -0,0 +1,29 @@ +name: Schema-Spy + +on: + workflow_dispatch: +env: + IMAGE_NAME: schema-spy + WORKING_DIRECTORY: ./ + ARTIFACTORY_REPO: artifacts.developer.gov.bc.ca + ARTIFACTORY_IMAGE_PATH: sbc3-images + BUILD_DOCKERFILE: api/Dockerfile.release +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout Schema Spy Repo + id: gitops + uses: actions/checkout@v4 + with: + repository: bcgov/SchemaSpy.git + ref: master + + - name: Docker Build Schema Spy Image and push to Artifactory + uses: ./.github/workflows/actions/docker-build-push-artifactory + with: + image_name: ${{ env.IMAGE_NAME }} + image_tag: latest + artifactory_repo: ${{ env.ARTIFACTORY_REPO }} + artifactory_image_path: ${{ env.ARTIFACTORY_IMAGE_PATH }} diff --git a/.github/workflows/cd-web.yaml b/.github/workflows/cd-web.yaml new file mode 100644 index 00000000..166a3484 --- /dev/null +++ b/.github/workflows/cd-web.yaml @@ -0,0 +1,58 @@ +name: Web + +on: + push: + branches: [master] + paths: + - "web/**" + - "docker/web/**" + - ".github/workflows/actions/**" + - ".github/workflows/cd-web.yaml" + workflow_dispatch: +env: + IMAGE_NAME: web + IMAGE_TAG_PREFIX: dev + WORKING_DIRECTORY: ./ + GITOPS_BRANCH: develop + GITOPS_LICENCE_PLATE: b3c707 + ARTIFACTORY_REPO: artifacts.developer.gov.bc.ca + ARTIFACTORY_IMAGE_PATH: sbc3-images + +jobs: + builds: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Get short SHA + id: short_sha + run: | + echo "::set-output name=SHORT_SHA::$(git rev-parse --short HEAD)" + echo "Short SHA: $SHORT_SHA" + + - name: Build and Push API images to Artifactory + id: build_push + uses: ./.github/workflows/actions/docker-build-push-artifactory + with: + image_name: ${{ env.IMAGE_NAME }} + image_tag: ${{ env.IMAGE_TAG_PREFIX }}-${{ steps.short_sha.outputs.SHORT_SHA }} + artifactory_repo: ${{ env.ARTIFACTORY_REPO }} + artifactory_image_path: ${{ env.ARTIFACTORY_IMAGE_PATH }} + build_dockerfile: web/Dockerfile.release + build_docker_args: | + NGINX_RUNTIME_SRC='./docker/nginx-runtime' + VUE_ON_NGINX_SRC='./docker/vue-on-nginx' + WEB_SRC='./docker/web' + + - name: Update ArgoCD Repo + id: update_argo_repo + if: steps.build_push.outcome == 'success' + uses: ./.github/workflows/actions/update-argo-repo + with: + licence_plate: ${{ env.GITOPS_LICENCE_PLATE }} + gitops_branch: ${{ env.GITOPS_BRANCH }} + image_tag: ${{ env.IMAGE_TAG_PREFIX }}-${{ steps.short_sha.outputs.SHORT_SHA }} + helm_property: /webtag + helm_paths: web/values.yaml ../develop/values.yaml diff --git a/.github/workflows/main.yml b/.github/workflows/ci-api-dotnet.yaml similarity index 96% rename from .github/workflows/main.yml rename to .github/workflows/ci-api-dotnet.yaml index 36a57138..9451b229 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/ci-api-dotnet.yaml @@ -1,4 +1,4 @@ -name: API (.NET Core) +name: CI - API (.NET Core) on: push: diff --git a/.github/workflows/app-vue.yml b/.github/workflows/ci-app-vue.yaml similarity index 96% rename from .github/workflows/app-vue.yml rename to .github/workflows/ci-app-vue.yaml index 7bc8e7fd..e64527f7 100644 --- a/.github/workflows/app-vue.yml +++ b/.github/workflows/ci-app-vue.yaml @@ -1,4 +1,4 @@ -name: APP (Vue) +name: CI - APP (Vue) on: push: diff --git a/docker/api/Dockerfile.dev b/docker/api/Dockerfile.dev new file mode 100644 index 00000000..79268468 --- /dev/null +++ b/docker/api/Dockerfile.dev @@ -0,0 +1,9 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 + +ENV ASPNETCORE_ENVIRONMENT='Production' +ENV ASPNETCORE_URLS='http://+:5000' +ENV CORS_DOMAIN='http://localhost:8080' +ENV DOTNET_STARTUP_PROJECT='./api/api.csproj' +ENV DOTNET_USE_POLLING_FILE_WATCHER=1 + +RUN curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l /vsdbg diff --git a/docker/api/Dockerfile.release b/docker/api/Dockerfile.release new file mode 100644 index 00000000..cd2069d0 --- /dev/null +++ b/docker/api/Dockerfile.release @@ -0,0 +1,35 @@ +# Dockerfile used by Github Action +ARG dotnet_version=8.0 +FROM mcr.microsoft.com/dotnet/aspnet:${dotnet_version} AS base +WORKDIR /app +EXPOSE 8080 +ENV ASPNETCORE_URLS=http://*:8080 +ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true +ENV DOTNET_gcServer=1 +ARG VERSION +ENV VERSION=$VERSION + +FROM mcr.microsoft.com/dotnet/sdk:${dotnet_version} AS build + +WORKDIR /src + +COPY ["api/api.csproj", "api/"] +COPY ["db/db.csproj", "db/"] +COPY ["jc-interface-client/jc-interface-client.csproj", "jc-interface-client/"] +COPY ["pcss-client/pcss-client.csproj", "pcss-client/"] +RUN dotnet restore api/api.csproj +RUN dotnet restore db/db.csproj +RUN dotnet restore jc-interface-client/jc-interface-client.csproj +RUN dotnet restore pcss-client/pcss-client.csproj +COPY . . +RUN dotnet build "api/api.csproj" -c Release +# build +FROM build AS publish +RUN dotnet publish "api/api.csproj" -c Release -o /app/publish --runtime linux-musl-x64 --no-self-contained + +FROM base AS final + +# copy app +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "api.dll"] diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 12488e4b..0b50fb52 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -1,8 +1,7 @@ version: "3.4" services: - web: - image: scv-web + image: "${COMPOSE_PROJECT_NAME}-web" environment: - API_URL=${API_URL} - USE_SELF_SIGNED_SSL=${USE_SELF_SIGNED_SSL} @@ -16,8 +15,8 @@ services: - api api: - image: scv-api - environment: + image: "${COMPOSE_PROJECT_NAME}-api" + environment: - ASPNETCORE_URLS=${ASPNETCORE_URLS} - FileServicesClient__Username=${FileServicesClientUsername} - FileServicesClient__Password=${FileServicesClientPassword} @@ -53,14 +52,20 @@ services: ports: - 5000:5000 volumes: - - ./seed:/opt/app-root/data + - ${LOCAL_WORKSPACE_FOLDER-..}/api/:/opt/app-root/src/api + - ${LOCAL_WORKSPACE_FOLDER-..}/db/:/opt/app-root/src/db + - ${LOCAL_WORKSPACE_FOLDER-..}/jc-interface-client/:/opt/app-root/src/jc-interface-client + - ${LOCAL_WORKSPACE_FOLDER-..}/pcss-client/:/opt/app-root/src/pcss-client + - api-dev-bin:/opt/app-root/src/api/bin + - api-dev-obj:/opt/app-root/src/api/obj + - ${LOCAL_WORKSPACE_FOLDER-.}/seed:/opt/app-root/data depends_on: - db command: > /bin/bash -c " echo Waiting for the database service to start up ...; sleep 10; - /usr/libexec/s2i/run;" + dotnet watch run --project /opt/app-root/src/api/api.csproj --urls='http://+:5000';" db: image: centos/postgresql-12-centos8 @@ -72,4 +77,8 @@ services: ports: - 5432:5432 volumes: - - ./tmp:/tmp2 \ No newline at end of file + - ${LOCAL_WORKSPACE_FOLDER-.}/tmp:/tmp2 + +volumes: + api-dev-bin: + api-dev-obj: diff --git a/docker/manage b/docker/manage index dc57cf40..9b2dce73 100755 --- a/docker/manage +++ b/docker/manage @@ -57,8 +57,8 @@ exit 1 # Functions: # ----------------------------------------------------------------------------------------------------------------- build-all() { - build-web - build-api + build-web + build-api } build-api() { @@ -66,17 +66,12 @@ build-api() { # api # echo -e "\n\n====================================================================================================" - echo -e "Building api image using s2i ..." + echo -e "Building api image using docker ..." echo -e "----------------------------------------------------------------------------------------------------" - ${S2I_EXE} build \ - --copy \ - -e "ASPNETCORE_ENVIRONMENT=Production" \ - -e "ASPNETCORE_URLS=http://+:5000" \ - -e "CORS_DOMAIN=http://localhost:8080" \ - -e "DOTNET_STARTUP_PROJECT=./api/api.csproj" \ - '..' \ - 'registry.access.redhat.com/ubi8/dotnet-80:8.0' \ - 'scv-api' + docker build \ + -t "${COMPOSE_PROJECT_NAME}-api" \ + -f './api/Dockerfile.dev' '..' + echo -e "====================================================================================================" } @@ -84,38 +79,18 @@ build-web() { # # web # - # The web-runtime image is used for the final runtime image. - # The scv-app image is used to build the artifacts for the vue distribution. - # The vue-on-nginx image is copy of the nginx-runtime image complete with a copy of the build artifacts. - # - echo -e "\n\n====================================================================================================" - echo -e "Building the scv-web-runtime (nginx-runtime) image using Docker ..." - echo -e "----------------------------------------------------------------------------------------------------" - docker build \ - -t 'scv-web-runtime' \ - -f './nginx-runtime/Dockerfile' './nginx-runtime/' - echo -e "====================================================================================================" - - # Apparently vue-cli-tools wants WEB_BASE_HREF -> vue.config.js -> publicPath at compile time. - # I tried using __webpack_public_path__, but the CSS file path and JS file path weren't correctly updated. - # Also note we don't load in environment variables from the arguments here. echo -e "\n\n====================================================================================================" - echo -e "Building the scv-web-artifacts image using s2i ..." - echo -e "----------------------------------------------------------------------------------------------------" - ${S2I_EXE} build \ - --copy \ - '../web' \ - 'centos/nodejs-12-centos7' \ - 'scv-web-artifacts' - echo -e "====================================================================================================" - - echo -e "\n\n====================================================================================================" - echo -e "Building the scv-web image using Docker ..." + echo -e "Building the ${COMPOSE_PROJECT_NAME}-web image using Docker ..." echo -e "----------------------------------------------------------------------------------------------------" + # --build-arg NODE_VERSION=18 \ # To be reinstated when the project advances to Vue 2.7 or 3 docker build \ - -t 'scv-web' \ + -t "${COMPOSE_PROJECT_NAME}-web" \ --build-arg IMAGE_PREFIX=${COMPOSE_PROJECT_NAME}- \ - -f './vue-on-nginx/Dockerfile' './vue-on-nginx/' + --build-arg NGINX_RUNTIME_SRC='../docker/nginx-runtime' \ + --build-arg VUE_ON_NGINX_SRC='../docker/vue-on-nginx' \ + --build-arg WEB_SRC='./web' \ + -f './web/Dockerfile.release' '..' + echo -e "====================================================================================================" } @@ -142,7 +117,7 @@ build-web-dev() { echo -e "====================================================================================================" } -configureEnvironment () { +configureEnvironment() { if [ -f .env ]; then echo -e "\nLoading environment variables from .env ...\n" @@ -178,7 +153,7 @@ getStartupParams() { *=*) # Skip it ;; - -*) + -*) ARGS+=" $arg";; *) CONTAINERS+=" $arg";; @@ -239,7 +214,7 @@ case "$COMMAND" in stop) docker-compose stop ;; - rm|down) + rm | down) configureEnvironment deleteVolumes ;; @@ -264,4 +239,4 @@ case "$COMMAND" in usage esac -popd >/dev/null \ No newline at end of file +popd >/dev/null diff --git a/docker/web/Dockerfile.release b/docker/web/Dockerfile.release new file mode 100644 index 00000000..eee6edcc --- /dev/null +++ b/docker/web/Dockerfile.release @@ -0,0 +1,110 @@ +# Define ARG at the top level +ARG NODE_VERSION=10 +ARG WEB_BASE_HREF=/ +ARG NGINX_RUNTIME_SRC=./nginx-runtime +ARG VUE_ON_NGINX_SRC=./vue-on-nginx +ARG WEB_SRC=./web +ARG NPM_INSTALL_ARGS="" + +################################################################################### +# 1. Build web-runtime +################################################################################### +# Use the offical nginx (based on debian) +FROM nginx:stable AS runtime +ARG NGINX_RUNTIME_SRC +ARG VUE_ON_NGINX_SRC + +ENV STI_SCRIPTS_PATH=/usr/libexec/s2i + +# Required for HTTP Basic feature +RUN apt-get update -y && \ + apt-get install -y openssl ca-certificates && \ + rm -rf /var/lib/apt/lists/* + +# Copy our OpenShift s2i scripts over to default location +COPY ${VUE_ON_NGINX_SRC}/s2i/bin/ /usr/libexec/s2i/ + +# Expose this variable to OpenShift +LABEL io.openshift.s2i.scripts-url=image:///usr/libexec/s2i + +# Copy config from source to container +COPY ${NGINX_RUNTIME_SRC}/nginx.conf.template /tmp/ + +# Copy run script +COPY ${NGINX_RUNTIME_SRC}/s2i/bin/run /usr/libexec/s2i/run + +# ================================================================================= +# Fix up permissions +# ref: https://torstenwalter.de/openshift/nginx/2017/08/04/nginx-on-openshift.html +# - S2I scripts must be executable +# - Make sure nginx can read and write it's working directories. +# - The container dynamically configures nginx on startup +# - The application artifacts live in /tmp +# --------------------------------------------------------------------------------- +RUN chmod -R g+rwx $STI_SCRIPTS_PATH +RUN chmod g+rw /var/cache/nginx \ + /var/run \ + /var/log/nginx \ + /etc/nginx/nginx.conf \ + /tmp + +# ================================================================================= + +# Work-around for issues with S2I builds on Windows +WORKDIR /tmp + +# Nginx runs on port 8080 by default +EXPOSE 8080 + +# Switch to usermode +USER 104 + +################################################################################### +# 2. Build web-artifacts +################################################################################### +# Update to later nodejs version when application is upgrade to Vue 2.7 or later. The centos version has python2 installed which is required for the current application version. +# FROM registry.access.redhat.com/ubi9/nodejs-${NODE_VERSION} AS artifacts +FROM registry.hub.docker.com/centos/nodejs-12-centos7 AS artifacts +ARG NODE_VERSION +ARG WEB_SRC +ARG NPM_INSTALL_ARGS + +WORKDIR /opt/app-root/src + +# Copy package.json and package-lock.json +COPY ${WEB_SRC}/package*.json . + +# Install packages +RUN npm install ${NPM_INSTALL_ARGS} + +# Copy the source code +COPY ${WEB_SRC} . + +RUN npm run build + +################################################################################### +# 3. Build web image +################################################################################### +FROM runtime +ARG VUE_ON_NGINX_SRC + +# Copy the build artifacts from the 'builder' image +# to the distribution folder on the runtime image. +COPY --from=artifacts /opt/app-root/src/dist/. /tmp/app/dist/ + +# # Ensure S2I script copied over is runnable. +COPY ${VUE_ON_NGINX_SRC}/s2i/bin/fix-base-url /usr/libexec/s2i/fix-base-url + +# Fix permissions. +USER root +RUN chmod 674 /usr/libexec/s2i/fix-base-url +RUN chmod -R 674 /tmp/app/dist/ + +# From nginx-runtime. +USER 104 + +# Since the runtime image is itself an s2i image we need to +# short circuit the s2i lifecycle. +# The runtime image "loses" its s2i runtime voodoo when it +# is used in a dockerStrategy, which is why the explicit `CMD` is necessary +CMD /usr/libexec/s2i/fix-base-url diff --git a/web/.devcontainer/Dockerfile b/web/.devcontainer/Dockerfile deleted file mode 100644 index 6c965bfa..00000000 --- a/web/.devcontainer/Dockerfile +++ /dev/null @@ -1,58 +0,0 @@ -# Use the offical nginx (based on debian) -FROM nginx:stable - -ENV STI_SCRIPTS_PATH=/usr/libexec/s2i -ENV USE_SELF_SIGNED_SSL='usss' -ENV WEB_BASE_HREF='/scjscv/' -ENV API_URL='http://host.docker.internal:5000/api/' -ENV RealIpFrom='172.17.0.1' -ENV NODE_ENV='development' -ENV SHELL /bin/bash - -# Required for HTTP Basic feature -RUN apt-get update -y && \ - apt-get install -y openssl ca-certificates procps gpgconf net-tools && \ - rm -rf /var/lib/apt/lists/* - -RUN mkdir /var/www && mkdir /var/www/.vscode-server && mkdir /var/www/.gnupg && mkdir /var/www/.devcontainer && mkdir /workspaces - -# Copy our OpenShift s2i scripts over to default location -COPY ./fix-base-url /usr/libexec/s2i/ -COPY ./run /usr/libexec/s2i/ - -# Expose this variable to OpenShift -LABEL io.openshift.s2i.scripts-url=image:///usr/libexec/s2i - -# Copy config from source to container -COPY nginx.conf.template /tmp/ - -# ================================================================================= -# Fix up permissions -# ref: https://torstenwalter.de/openshift/nginx/2017/08/04/nginx-on-openshift.html -# - S2I scripts must be executable -# - Make sure nginx can read and write it's working directories. -# - The container dynamically configures nginx on startup -# - The application artifacts live in /tmp -# --------------------------------------------------------------------------------- -RUN chmod -R g+rwx $STI_SCRIPTS_PATH -RUN chmod og+rw /var/cache/nginx \ - /var/run \ - /etc/nginx/nginx.conf \ - /tmp -RUN chmod og+rw /var/www/.vscode-server \ - /var/www \ - /var/www/.gnupg \ - /var/cache/nginx \ - /var/www/.devcontainer \ - /workspaces - -# ================================================================================= - -# Work-around for issues with S2I builds on Windows -WORKDIR /tmp - -# Nginx runs on port 8080 by default -EXPOSE 8080 - -# Switch to usermode -USER www-data diff --git a/web/.devcontainer/devcontainer.json b/web/.devcontainer/devcontainer.json deleted file mode 100644 index ca870fe3..00000000 --- a/web/.devcontainer/devcontainer.json +++ /dev/null @@ -1,58 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.224.2/containers/dotnet -{ - "name": "SCV Web", - "build": { - "dockerfile": "Dockerfile", - "args": { - // Update 'VARIANT' to pick a .NET Core version: 3.1, 5.0, 6.0, 7.0 - // Append -bullseye or -focal to pin to an OS version. - // "VARIANT": "7.0", - // Options - // "NODE_VERSION": "lts/*" - } - }, - // Set *default* container specific settings.json values on container create. - //"settings": {}, - // Add the IDs of extensions you want installed when the container is created. - // "extensions": [ - // "eamodio.gitlens", - // "adrianwilczynski.add-reference", - // "editorconfig.editorconfig", - // "pflannery.vscode-versionlens" - // ], - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [ - 8080 - ], - // [Optional] To reuse of your local HTTPS dev cert: - // - // 1. Export it locally using this command: - // * Windows PowerShell: - // dotnet dev-certs https --trust; dotnet dev-certs https -ep "$env:USERPROFILE/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" - // * macOS/Linux terminal: - // dotnet dev-certs https --trust; dotnet dev-certs https -ep "${HOME}/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" - // - // 2. Uncomment these 'remoteEnv' lines: - // "remoteEnv": { - // "ASPNETCORE_Kestrel__Certificates__Default__Password": "SecurePwdGoesHere", - // "ASPNETCORE_Kestrel__Certificates__Default__Path": "/home/vscode/.aspnet/https/aspnetapp.pfx", - // }, - // - // 3. Do one of the following depending on your scenario: - // * When using GitHub Codespaces and/or Remote - Containers: - // 1. Start the container - // 2. Drag ~/.aspnet/https/aspnetapp.pfx into the root of the file explorer - // 3. Open a terminal in VS Code and run "mkdir -p /home/vscode/.aspnet/https && mv aspnetapp.pfx /home/vscode/.aspnet/https" - // - // * If only using Remote - Containers with a local container, uncomment this line instead: - // "mounts": [ "source=${env:HOME}${env:USERPROFILE}/.aspnet/https,target=/home/vscode/.aspnet/https,type=bind" ], - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "dotnet restore", - // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "www-data", - "features": { - "git": "latest" - }, - "postStartCommand": "/usr/libexec/s2i/fix-base-url" -} \ No newline at end of file diff --git a/web/.devcontainer/fix-base-url b/web/.devcontainer/fix-base-url deleted file mode 100644 index 523fadaa..00000000 --- a/web/.devcontainer/fix-base-url +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# Work around for Vue having a lack of a configurable publicPath that isn't relative (relative breaks history routing). -# Unfortunately there doesn't seem to be an easier way of doing this without rebuilding. -# Since we have a single web image, doesn't make sense to build the web-artifacts twice. -# Perhaps webpack 5 will have some sort of fix for this. -echo "---> Replacing public path /S2I_INJECT_PUBLIC_PATH/ -> $WEB_BASE_HREF in Vue artifacts..." -FILES="/workspaces/supreme-court-viewer/web/dist/index.html -/workspaces/supreme-court-viewer/web/dist/js/*.*" -for f in $FILES -do - tmp=$(sed "s|/S2I_INJECT_PUBLIC_PATH/|$WEB_BASE_HREF|g" "$f"); - printf "%s" "$tmp" > "$f"; -done - -/usr/libexec/s2i/run \ No newline at end of file diff --git a/web/.devcontainer/nginx.conf.template b/web/.devcontainer/nginx.conf.template deleted file mode 100644 index cca1c98d..00000000 --- a/web/.devcontainer/nginx.conf.template +++ /dev/null @@ -1,144 +0,0 @@ -worker_processes 1; - -error_log /var/log/nginx/error.log; -pid /var/run/nginx.pid; - -events { - worker_connections 4096; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - server_tokens off; - - # ip filtering - %IpFilterRules% - - # logging rules - geo $loggable { - default 1; - %RealIpFrom% 0; - } - - # Use a w3c standard log format - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for" ' - 'rt=$request_time urt=$upstream_response_time $pipe'; - - access_log /var/log/nginx/access.log main if=$loggable; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; - - #real_ip module - set_real_ip_from %RealIpFrom%; - %AdditionalRealIpFromRules% - real_ip_recursive on; - real_ip_header X-Forwarded-For; - - #throttle zones - limit_req_zone $binary_remote_addr zone=bra1:10m rate=2r/s; - limit_req_zone $binary_remote_addr zone=bra3:10m rate=6r/s; - limit_req_zone $binary_remote_addr zone=bra5:10m rate=10r/s; - limit_req_zone $binary_remote_addr zone=bra25:10m rate=50r/s; - limit_req_zone $binary_remote_addr zone=bra100:10m rate=200r/s; - - #default throttle; not inherited if set in nested level - limit_req zone=bra5 burst=100; - - # HTTP Basic rules - auth_basic_user_file /tmp/.htpasswd; - - # Allows headers with underscores to be passed through. EX. SMGOV_USERGUID - %IgnoreInvalidHeaders% - - # ====================================================== - # Set variables for API proxy - # ------------------------------------------------------ - # Ensure the original scheme is forwarded correctly - map $http_x_forwarded_proto $proxy_scheme { - default $scheme; - https "https"; - } - # Ensure the original port is forwarded correctly - map $http_x_forwarded_port $proxy_port { - default $http_x_forwarded_port; - '' $server_port; - } - # Ensure the original host is forwarded correctly: - # - When the application is hosted on OpenShift and sitting behind - # a second proxy layer such as a jag of justice proxy - # X-Forwarded-Host gets overwritten with the Hostname defined by the route - # at the OpenShift layer. X-Forwarded-Server contains the original Hostname - # that needs to be passed along to the various application components. - map $http_x_forwarded_server $proxy_host { - default $http_x_forwarded_server; - '' $host; - } - # ====================================================== - - server { - %LISTEN_CONFIG_SECTION% - - # Allow large headers. - large_client_header_buffers 4 32k; - # add in most common security headers - add_header Content-Security-Policy "default-src * data: blob: filesystem: 'unsafe-inline' 'unsafe-eval'"; - add_header Strict-Transport-Security "max-age=86400; includeSubDomains"; - add_header X-Content-Type-Options "nosniff"; - add_header X-XSS-Protection 1; - add_header X-Frame-Options DENY; - - %REMOVE_BASE_HREF% - - %API_CONFIG_SECTION% - - # serve our app here - location / { - root /workspaces/supreme-court-viewer/web/dist; - index index.html index.htm; - try_files $uri $uri/ /index.html =404; - gzip on; - gzip_min_length 1000; - gzip_types *; - - # Deploy-time configurable - %HTTP_BASIC% - } - - # redirect server error pages to the static page /50x.html - error_page 500 502 503 504 /50x.html; - location = 50x.html { - root /usr/share/nginx/html; - } - - # For status of ngnix service, OpenShift is configured to call this - location /nginx_status { - # Enable Nginx stats - stub_status on; - - # Only allow access from localhost - allow all; - - # Other request should be denied - # deny all; - - # No need to log this request, its just noise - access_log off; - } - - # serve the fathom analytics tracking code, if available - location =/fathom.js { - root /tmp; - gzip on; - gzip_min_length 1000; - gzip_types *; - } - } -} diff --git a/web/.devcontainer/run b/web/.devcontainer/run deleted file mode 100644 index 38a4b510..00000000 --- a/web/.devcontainer/run +++ /dev/null @@ -1,220 +0,0 @@ -#!/bin/bash - -getListenConfigSection () { -# ================================================================================ -# Generate the listen configuration section. -# -------------------------------------------------------------------------------- -if [ ! -z "${USE_SELF_SIGNED_SSL}" ]; then - read -r -d '' _LISTEN_SECTION <<- EOF - listen 8080 ssl; - server_name localhost; - ssl_certificate /var/run/nginx-selfsigned.crt; - ssl_certificate_key /var/run/nginx-selfsigned.key; - EOF -else - read -r -d '' _LISTEN_SECTION <<- EOF - listen 8080; - server_name localhost; - EOF -fi -echo "${_LISTEN_SECTION}" -} - -getApiProxyConfigSection (){ -# ================================================================================ -# Generate the /api proxy configuration section. -# -# Assumes there is a default for API_URL -# -------------------------------------------------------------------------------- -# API_URL: -# - The URL for the API endpoint. -# ================================================================================ -apiUrl=${1%/} -proxyUrl=${apiUrl%/api} - -read -r -d '' _FORWARDED_HEADERS << EOF - - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host \$proxy_host; - proxy_set_header X-Forwarded-Server \$proxy_host; - proxy_set_header X-Forwarded-Port \$proxy_port; - proxy_set_header X-Forwarded-Proto \$proxy_scheme; - proxy_set_header X-Base-Href ${WEB_BASE_HREF}; - proxy_pass_request_headers on; - proxy_pass_header Authorization; - proxy_pass_header Accept; - -EOF - -read -r -d '' _CONFIG_SECTION << EOF - location /api { - limit_except OPTIONS { - ${HTTP_BASIC} - } - ${_FORWARDED_HEADERS} - proxy_pass ${apiUrl}; - - proxy_buffer_size 128k; - proxy_buffers 4 256k; - proxy_busy_buffers_size 256k; - proxy_cookie_path /api/auth ${WEB_BASE_HREF}api/auth; - proxy_read_timeout 600s; - } -EOF - -if [[ ! -z "${DEBUG}" ]]; then - echo "${_CONFIG_SECTION}${_DEBUG_SECTION}" -else - echo "${_CONFIG_SECTION}" -fi -} - -getApiUrl (){ - # ================================================================================ - # Extract the API URL from the container's environment variables based on - # OpenShift service conventions. - # -------------------------------------------------------------------------------- - # API_URL: - # - The default URL for the API endpoint. - # - Used in the case API_SERVICE_NAME or one of the related service resource - # variables is not defined. - # - # API_SERVICE_NAME: - # - The name of the service endpoint for the API. - # - For example; django - # - # API_PATH: - # - The root path for the API. - # - For example /api/ - # -------------------------------------------------------------------------------- - # Examples: - # - # 1) - # API_URL=https://dev-demo-api.orgbook.gov.bc.ca/api/ - # API_SERVICE_NAME=django - # DJANGO_SERVICE_HOST=172.50.105.217 - # DJANGO_SERVICE_PORT=8080 - # API_PATH=/api/ - # - # Results in API_URL=http://172.50.105.217:8080/api/ - # - # 2) - # API_URL=https://dev-demo-api.orgbook.gov.bc.ca/api/ - # API_SERVICE_NAME=django - # DJANGO_SERVICE_HOST=172.50.105.217 - # API_PATH=/api/ - # - # Results in API_URL=http://172.50.105.217/api/ - # - # 3) - # If either API_SERVICE_NAME or *_SERVICE_HOST are not defined... - # - # API_URL=https://dev-demo-api.orgbook.gov.bc.ca/api/ - # - # Results in API_URL=https://dev-demo-api.orgbook.gov.bc.ca/api/ - # ================================================================================ - if [ ! -z "${API_SERVICE_NAME}" ]; then - _SERVICE_NAME="$(tr '[:lower:]' '[:upper:]' <<< ${API_SERVICE_NAME//-/_})" - _SERVICE_HOST_NAME=${_SERVICE_NAME}_SERVICE_HOST - _SERVICE_PORT_NAME=${_SERVICE_NAME}_SERVICE_PORT - - if [ ! -z "${!_SERVICE_HOST_NAME}" ]; then - if [ ! -z "${!_SERVICE_PORT_NAME}" ]; then - API_URL="http://${!_SERVICE_HOST_NAME}:${!_SERVICE_PORT_NAME}${API_PATH}" - else - API_URL="http://${!_SERVICE_HOST_NAME}${API_PATH}" - fi - fi - fi - - echo ${API_URL} -} - -getRemoveBaseHref() { - # If we are nested under a subpath, remove the subpath before processing URLs - # Base href must have a leading and trailing slash - BASE="$1" - if [ ! -z "${BASE}" ] && [ "${BASE}" != "/" ]; then - echo "rewrite ^${BASE}(.*)\$ /\$1 last;" - fi -} - -if [ -n "$HTTP_BASIC_USERNAME" ] && [ -n "$HTTP_BASIC_PASSWORD" ]; then - echo "---> Generating .htpasswd file" - `echo "$HTTP_BASIC_USERNAME:$(openssl passwd -crypt $HTTP_BASIC_PASSWORD)" > /tmp/.htpasswd` - if [ -z "${HTTP_BASIC+test}" ] || [ "on" == "${HTTP_BASIC}" ] || [ "1" == "${HTTP_BASIC}" ]; then - HTTP_BASIC="auth_basic 'restricted';" - elif [ "0" == "${HTTP_BASIC}" ] || [ "off" == "${HTTP_BASIC}" ]; then - HTTP_BASIC="" - fi -fi - -if [ "on" == "${IncludeSiteminderHeaders}" ] || [ "1" == "${IncludeSiteminderHeaders}" ] || [ "true" == "${IncludeSiteminderHeaders}" ]; then - export IgnoreInvalidHeaders="ignore_invalid_headers off;" -else - export IgnoreInvalidHeaders="ignore_invalid_headers on;" -fi - -export RealIpFrom="${RealIpFrom:-172.51.0.0/16}" -export WEB_BASE_HREF="${WEB_BASE_HREF:-/}" -export REMOVE_BASE_HREF=$(getRemoveBaseHref ${WEB_BASE_HREF}) -export API_URL=$(getApiUrl) -if [ ! -z "${API_URL}" ]; then - # ================================================================================ - # If an API_URL is defined, generate an /api proxy configuration section and - # escape it so sed can digest it and process it properly. - # Otherwise leave things undefined so the placeholder in the template gets - # replaced with a blank string. - # ================================================================================ - API_CONFIG_SECTION=$(getApiProxyConfigSection "${API_URL}") - API_CONFIG_SECTION="$(echo "${API_CONFIG_SECTION}" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/\$/\\$/g')" -fi -LISTEN_CONFIG_SECTION=$(getListenConfigSection) -LISTEN_CONFIG_SECTION="$(echo "${LISTEN_CONFIG_SECTION}" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/\$/\\$/g')" - -makeReplacementPattern() { - # Generate a replacement pattern for sed in order to replace our variables in - # the nginx configuration template - REPLACE="" - for VAR in $@; do - REPLACE+="s~%${VAR}%~${!VAR}~g; " - done - echo "${REPLACE}" -} - -echo "---> Replacing Configuration ..." -echo "Setting:" -echo "RealIpFrom = ${RealIpFrom}" -echo "IpFilterRules = ${IpFilterRules}" -echo "AdditionalRealIpFromRules = ${AdditionalRealIpFromRules}" -echo "IgnoreInvalidHeaders = ${IgnoreInvalidHeaders}" -echo "API_URL = ${API_URL}" -echo "HTTP_BASIC = ${HTTP_BASIC}" -echo "WEB_BASE_HREF = ${WEB_BASE_HREF}" - -REPLACE_VARS=" - RealIpFrom IpFilterRules AdditionalRealIpFromRules IgnoreInvalidHeaders - HTTP_BASIC WEB_BASE_HREF REMOVE_BASE_HREF API_CONFIG_SECTION LISTEN_CONFIG_SECTION -" - -sed "$(makeReplacementPattern $REPLACE_VARS)" /tmp/nginx.conf.template > /etc/nginx/nginx.conf - -if [ ! -z "${USE_SELF_SIGNED_SSL}" ]; then -echo "---> Using self-signed SSH keys ..." - if [ ! -r /var/run/nginx-selfsigned.crt ]; then - echo "---> Generating self-signed SSH keys ..." - openssl req -newkey rsa:4096 \ - -x509 \ - -sha256 \ - -days 365 \ - -nodes \ - -out /var/run/nginx-selfsigned.crt \ - -keyout /var/run/nginx-selfsigned.key \ - -subj "/C=CA/ST=BC/L=BC/O=BCGOV/OU=Testing" - fi -fi - -echo "---> Starting nginx ..." -/usr/sbin/nginx -g "daemon off;"