diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/analysis.yml similarity index 58% rename from .github/workflows/unit-tests.yml rename to .github/workflows/analysis.yml index a622ffc9..7e7b437a 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/analysis.yml @@ -1,15 +1,10 @@ -name: Unit Tests and Analysis +name: Analysis on: pull_request: - types: - - opened - - reopened - - synchronize - - ready_for_review + types: [opened, reopened, synchronize, ready_for_review] push: - branches: - - main + branches: [main] workflow_dispatch: concurrency: @@ -17,24 +12,10 @@ concurrency: cancel-in-progress: true jobs: - # Note: uncomment code below once a backend is written - tests: - name: Unit Tests + frontend: + name: Frontend Tests if: github.event_name != 'pull_request' || !github.event.pull_request.draft runs-on: ubuntu-22.04 - strategy: - matrix: - # dir: [backend, frontend] - dir: [frontend] - include: - # - dir: backend - # sonar_projectKey: nr-silva-backend - # token: SONAR_TOKEN_BACKEND - # triggers: ('backend/') - - dir: frontend - sonar_projectKey: nr-silva-frontend - token: SONAR_TOKEN_FRONTEND - triggers: ('frontend/') steps: - uses: bcgov-nr/action-test-and-analyse@v1.1.0 with: @@ -47,13 +28,34 @@ jobs: -Dsonar.exclusions=**/coverage/**,**/node_modules/**,**/*spec.ts -Dsonar.organization=bcgov-sonarcloud -Dsonar.project.monorepo.enabled=true - -Dsonar.projectKey=${{ matrix.sonar_projectKey }} + -Dsonar.projectKey=nr-silva-frontend -Dsonar.sources=src -Dsonar.tests.inclusions=**/*spec.ts -Dsonar.javascript.lcov.reportPaths=./coverage/lcov.info - sonar_token: ${{ secrets[matrix.token] }} + sonar_token: ${{ secrets.SONAR_TOKEN_FRONTEND }} # Only use triggers for PRs - triggers: ${{ github.event_name == 'pull_request' && matrix.triggers || '' }} + triggers: ${{ github.event_name == 'pull_request' && '("frontend/")' || '' }} + + backend: + name: Backend Tests + if: github.event_name != 'pull_request' || !github.event.pull_request.draft + runs-on: ubuntu-22.04 + steps: + - uses: bcgov-nr/action-test-and-analyse-java@v0.2.0 + with: + commands: mvn clean package + dir: backend + java-cache: maven + java-distribution: temurin + java-version: 17 + sonar_args: > + -Dsonar.exclusions=**/coverage/**,**/node_modules/**,**/*spec.ts + -Dsonar.organization=bcgov-sonarcloud + -Dsonar.project.monorepo.enabled=true + -Dsonar.projectKey=nr-silva-backend + -Dsonar.sources=src + sonar_token: ${{ secrets.SONAR_TOKEN_BACKEND }} + triggers: ${{ github.event_name == 'pull_request' && '("backend/")' || '' }} # https://github.com/marketplace/actions/aqua-security-trivy trivy: diff --git a/.github/workflows/merge-main.yml b/.github/workflows/merge.yml similarity index 63% rename from .github/workflows/merge-main.yml rename to .github/workflows/merge.yml index 8ff5a6a6..995f9e74 100644 --- a/.github/workflows/merge-main.yml +++ b/.github/workflows/merge.yml @@ -1,10 +1,9 @@ -name: Merge to Main +name: Merge on: workflow_run: workflows: [ "Pull Request Closed" ] - types: - - completed + types: [completed] workflow_dispatch: concurrency: @@ -41,49 +40,62 @@ jobs: runs-on: ubuntu-22.04 permissions: issues: write + strategy: + matrix: + name: [backend, frontend] + include: + - name: backend + - name: frontend + parameters: + -p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }} + -p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }} steps: - uses: bcgov-nr/action-deployer-openshift@v1.4.0 with: - file: frontend/openshift.deploy.yml + file: ${{ matrix.name }}/openshift.deploy.yml oc_namespace: ${{ vars.OC_NAMESPACE }} oc_server: ${{ vars.OC_SERVER }} oc_token: ${{ secrets.OC_TOKEN }} overwrite: true parameters: - -p ZONE=test -p PROMOTE=${{ github.repository }}/frontend:test + -p ZONE=test -p PROMOTE=${{ github.repository }}/${{ matrix.name }}:test -p NAME=${{ github.event.repository.name }} - -p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }} - -p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }} + ${{ matrix.parameters }} penetration_test: true - penetration_test_artifact: frontend - penetration_test_issue: frontend + penetration_test_artifact: ${{ matrix.name }} + penetration_test_issue: ${{ matrix.name }} penetration_test_token: ${{ secrets.GITHUB_SECRET }} deploys-prod: name: PROD Deployments - needs: - - deploys-test + needs: [deploys-test] environment: prod runs-on: ubuntu-22.04 + strategy: + matrix: + name: [backend, frontend] + include: + - name: backend + paratemeters: + -p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }} + -p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }} + -p VITE_REDIRECT_SIGN_OUT="${{ vars.VITE_REDIRECT_SIGN_OUT }}" + - name: frontend steps: - uses: bcgov-nr/action-deployer-openshift@v1.4.0 with: - file: frontend/openshift.deploy.yml + file: ${{ matrix.name }}/openshift.deploy.yml oc_namespace: ${{ vars.OC_NAMESPACE }} oc_server: ${{ vars.OC_SERVER }} oc_token: ${{ secrets.OC_TOKEN }} overwrite: true parameters: - -p ZONE=prod -p PROMOTE=${{ github.repository }}/frontend:test + -p ZONE=prod -p PROMOTE=${{ github.repository }}/${{ matrix.name }}:test -p NAME=${{ github.event.repository.name }} - -p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }} - -p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }} - -p VITE_REDIRECT_SIGN_OUT="${{ vars.VITE_REDIRECT_SIGN_OUT }}" image-promotions: name: Promote images to PROD - needs: - - deploys-prod + needs: [deploys-prod] runs-on: ubuntu-22.04 permissions: packages: write diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index cdc7c178..bfbee040 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -2,8 +2,7 @@ name: Pull Request on: pull_request: - branches: - - main + branches: [main] concurrency: # PR open and close use the same group, allowing only one at a time @@ -32,6 +31,7 @@ jobs: Thanks for the PR! Any successful deployments (not always required) will be available below. + [Backend](https://${{ env.PREFIX }}-backend.${{ env.DOMAIN }}) [Frontend](https://${{ env.PREFIX }}-frontend.${{ env.DOMAIN }}) Once merged, code will be promoted and handed off to following workflow run. @@ -43,34 +43,55 @@ jobs: runs-on: ubuntu-22.04 permissions: packages: write + strategy: + matrix: + name: [backend, frontend] + include: + - name: backend + triggers: ('backend/') + - name: frontend + triggers: ('frontend/') steps: - uses: actions/checkout@v4 - uses: bcgov-nr/action-builder-ghcr@v1.2.1 with: - package: frontend + package: ${{ matrix.name }} tag: ${{ github.event.number }} tag_fallback: test token: ${{ secrets.GITHUB_TOKEN }} - triggers: ('frontend/') + triggers: ${{ matrix.triggers }} deploys: name: Deploys if: "!github.event.pull_request.head.repo.fork" - needs: - - builds + needs: [builds] runs-on: ubuntu-22.04 + strategy: + matrix: + name: [backend, frontend] + include: + - name: backend + file: backend/openshift.deploy.yml + triggers: ('backend/' 'frontend/') + verification_path: /actuator/health + - name: frontend + file: frontend/openshift.deploy.yml + triggers: ('backend/' 'frontend/') + parameters: + -p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }} + -p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }} steps: - uses: bcgov-nr/action-deployer-openshift@v1.4.0 with: - file: frontend/openshift.deploy.yml + file: ${{ matrix.name }}/openshift.deploy.yml oc_namespace: ${{ vars.OC_NAMESPACE }} oc_server: ${{ vars.OC_SERVER }} oc_token: ${{ secrets.OC_TOKEN }} overwrite: true parameters: -p ZONE=${{ github.event.number }} -p NAME=${{ github.event.repository.name }} - -p PROMOTE=${{ github.repository }}/frontend:${{ github.event.number }} - -p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }} - -p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }} + -p PROMOTE=${{ github.repository }}/${{ matrix.name }}:${{ github.event.number }} -p MIN_REPLICAS=1 -p MAX_REPLICAS=2 - triggers: ('frontend/') + ${{ matrix.parameters }} + triggers: ${{ matrix.triggers }} + verification_path: ${{ matrix.verification_path }} diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 00000000..a1b68230 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,6 @@ +.env +Dockerfile +*.yml +*.yaml +*.md +**/target/ diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..41a992fd --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,21 @@ +# Quarkus Images +# https://github.com/quarkusio/quarkus-images + +# "Provides the native-image executable. Used by the Maven and Gradle plugin from Quarkus to build linux64 executables" +FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:jdk-21 AS build + +# Image defaults to /project; copy controlled by .dockerignore +COPY --chown=quarkus:quarkus . ./ +RUN ./mvnw package -Pnative -DskipTests + +# "A base image to run Quarkus native application using UBI Micro" +FROM quay.io/quarkus/quarkus-micro-image:2.0 + +# Port and health check +EXPOSE 8080 +HEALTHCHECK --interval=300s --timeout=3s CMD curl -f http://localhost:8080 + +# Startup +COPY --chown=1001:root --from=build /project/target/results /app +USER 1001 +CMD ["/app", "-Dquarkus.http.host=0.0.0.0"] diff --git a/backend/openshift.deploy.yml b/backend/openshift.deploy.yml new file mode 100644 index 00000000..422a929d --- /dev/null +++ b/backend/openshift.deploy.yml @@ -0,0 +1,168 @@ +apiVersion: template.openshift.io/v1 +kind: Template +parameters: + - name: NAME + description: Module name + value: quickstart-openshift-backends + - name: COMPONENT + description: Component name + value: backend + - name: ZONE + description: Deployment zone, e.g. pr-### or prod + required: true + - name: IMAGE_TAG + description: Image tag to use + value: latest + - name: DOMAIN + value: apps.silver.devops.gov.bc.ca + - name: CPU_REQUEST + value: "25m" + - name: MEMORY_REQUEST + value: "50Mi" + - name: CPU_LIMIT + value: "75m" + - name: MEMORY_LIMIT + value: "150Mi" + - name: MIN_REPLICAS + description: The minimum amount of replicas for the horizontal pod autoscaler. + value: "3" + - name: MAX_REPLICAS + description: The maximum amount of replicas for the horizontal pod autoscaler. + value: "5" + - name: REGISTRY + description: Container registry to import from (internal is image-registry.openshift-image-registry.svc:5000) + value: ghcr.io + - name: ORG_NAME + description: Organization name, e.g. bcgov + value: bcgov + - name: PROMOTE + description: Image (namespace/name:tag) to promote/import + value: bcgov/quickstart-openshift-backends/backend:test +objects: + - apiVersion: v1 + kind: ImageStream + metadata: + labels: + app: "${NAME}-${ZONE}" + name: "${NAME}-${ZONE}-${COMPONENT}" + spec: + lookupPolicy: + local: false + tags: + - name: "${IMAGE_TAG}" + from: + kind: DockerImage + name: "${REGISTRY}/${PROMOTE}" + referencePolicy: + type: Local + - apiVersion: v1 + kind: DeploymentConfig + metadata: + labels: + app: "${NAME}-${ZONE}" + name: "${NAME}-${ZONE}-${COMPONENT}" + spec: + replicas: 1 + triggers: + - type: ConfigChange + - type: ImageChange + imageChangeParams: + automatic: true + containerNames: + - "${NAME}" + from: + kind: ImageStreamTag + name: "${NAME}-${ZONE}-${COMPONENT}:${IMAGE_TAG}" + selector: + deploymentconfig: "${NAME}-${ZONE}-${COMPONENT}" + strategy: + type: Rolling + template: + metadata: + labels: + app: "${NAME}-${ZONE}" + deploymentconfig: "${NAME}-${ZONE}-${COMPONENT}" + spec: + containers: + - image: "${NAME}-${ZONE}-${COMPONENT}:${IMAGE_TAG}" + imagePullPolicy: Always + name: "${NAME}" + ports: + - containerPort: 8080 + protocol: TCP + resources: + requests: + cpu: "${CPU_REQUEST}" + memory: "${MEMORY_REQUEST}" + limits: + cpu: "${CPU_LIMIT}" + memory: "${MEMORY_LIMIT}" + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 2 + timeoutSeconds: 2 + successThreshold: 1 + failureThreshold: 30 + livenessProbe: + successThreshold: 1 + failureThreshold: 3 + httpGet: + path: /actuator/health + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 5 + - apiVersion: v1 + kind: Service + metadata: + labels: + app: "${NAME}-${ZONE}" + name: "${NAME}-${ZONE}-${COMPONENT}" + spec: + ports: + - name: 8080-tcp + protocol: TCP + port: 80 + targetPort: 8080 + selector: + deploymentconfig: "${NAME}-${ZONE}-${COMPONENT}" + - apiVersion: route.openshift.io/v1 + kind: Route + metadata: + labels: + app: "${NAME}-${ZONE}" + name: "${NAME}-${ZONE}-${COMPONENT}" + spec: + host: "${NAME}-${ZONE}-${COMPONENT}.${DOMAIN}" + port: + targetPort: 8080-tcp + to: + kind: Service + name: "${NAME}-${ZONE}-${COMPONENT}" + weight: 100 + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect + - apiVersion: autoscaling/v2 + kind: HorizontalPodAutoscaler + metadata: + name: "${NAME}-${ZONE}-${COMPONENT}" + spec: + scaleTargetRef: + apiVersion: apps.openshift.io/v1 + kind: DeploymentConfig + name: "${NAME}-${ZONE}-${COMPONENT}" + minReplicas: "${{MIN_REPLICAS}}" + maxReplicas: "${{MAX_REPLICAS}}" + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 100 diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index b8acfdd2..5c7c4e1d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -9,3 +9,6 @@ management.endpoint.health.show-details = always # Native Cloud springdoc.enable-native-support = true + +# https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.2 +quarkus.native.additional-build-args=-march=x86-64-v2 diff --git a/docker-compose.yml b/docker-compose.yml index 704be899..416eecfa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,26 @@ version: '3.9' -x-common: &common +x-frontend: &frontend environment: VITE_USER_POOLS_ID: ca-central-1_t2HSZBHur VITE_USER_POOLS_WEB_CLIENT_ID: 3g6n2ha1loi4kp1jhaq359vrvb VITE_ZONE: DEV healthcheck: - test: [ "CMD-SHELL", "curl http://localhost:3000" ] + test: curl http://localhost:3000" interval: 15s timeout: 5s retries: 5 restart: always +x-backend: &backend + ports: ["8080:8080"] + healthcheck: + test: curl -f http://localhost:8080/actuator/health | grep '"status":"UP"' + interval: 5s + timeout: 5s + retries: 5 + restart: always + services: frontend: container_name: frontend @@ -20,7 +29,7 @@ services: ports: ["3000:3000"] volumes: ["./frontend:/app", "/app/node_modules"] working_dir: "/app" - <<: *common + <<: *frontend caddy: container_name: caddy @@ -28,17 +37,18 @@ services: build: ./frontend ports: ["3005:3000"] volumes: ["./frontend/Caddyfile:/etc/caddy/Caddyfile"] - <<: *common + <<: *frontend backend: container_name: backend - ports: ["8080:8080", "5005:5005"] + entrypoint: mvn -ntp spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n" image: maven:3.9.4-amazoncorretto-21 - entrypoint: mvn -ntp spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005" - working_dir: /app volumes: ["./backend:/app"] - healthcheck: - test: curl -f http://localhost:8080/actuator/health | grep '"status":"UP"' - interval: 5s - timeout: 5s - retries: 5 + working_dir: /app + <<: *backend + + backend-native: + container_name: backend-native + profiles: ["native"] + build: ./backend + <<: *backend