diff --git a/.github/actions/frontend-deploy/action.yml b/.github/actions/frontend-deploy/action.yml new file mode 100644 index 0000000..9bf1f6c --- /dev/null +++ b/.github/actions/frontend-deploy/action.yml @@ -0,0 +1,16 @@ +name: "Frontend application deployment files" +description: "" + +inputs: + working-directory: + required: false + default: "." + +runs: + using: "composite" + steps: + # Copy files + - name: Copy build/deployment files + shell: bash + run: | + cp ${{ github.action_path }}/files/* ${{ inputs.working-directory }} diff --git a/.github/actions/frontend-deploy/files/Dockerfile-build b/.github/actions/frontend-deploy/files/Dockerfile-build new file mode 100644 index 0000000..d428e78 --- /dev/null +++ b/.github/actions/frontend-deploy/files/Dockerfile-build @@ -0,0 +1,6 @@ +# Use a large Node.js base image to build the application and name it "build" +FROM node:21-alpine + +WORKDIR /app + +COPY . /app diff --git a/.github/actions/frontend-deploy/files/cloudbuild.yaml b/.github/actions/frontend-deploy/files/cloudbuild.yaml new file mode 100644 index 0000000..8e61b65 --- /dev/null +++ b/.github/actions/frontend-deploy/files/cloudbuild.yaml @@ -0,0 +1,144 @@ +steps: + +# build image if not exists in artifact registry +- name: "gcr.io/google.com/cloudsdktool/cloud-sdk" + script: | + #!/usr/bin/env bash + + # Build/Push Image to Artifactory + if [[ -z `gcloud artifacts docker images describe ${_REGION}-docker.pkg.dev/${_DEPLOYMENT_PROJECT}/firebase-repo/${_APP_NAME}:${_SHORT_SHA} --verbosity=none` ]] + then + docker build \ + -f Dockerfile-build \ + -t ${_REGION}-docker.pkg.dev/${_DEPLOYMENT_PROJECT}/firebase-repo/${_APP_NAME}:${_SHORT_SHA} \ + --cache-from ${_REGION}-docker.pkg.dev/${_DEPLOYMENT_PROJECT}/firebase-repo/${_APP_NAME}:latest\ + . + docker push ${_REGION}-docker.pkg.dev/${_DEPLOYMENT_PROJECT}/firebase-repo/${_APP_NAME}:${_SHORT_SHA} + + gcloud artifacts docker tags add \ + ${_REGION}-docker.pkg.dev/${_DEPLOYMENT_PROJECT}/firebase-repo/${_APP_NAME}:${_SHORT_SHA} \ + ${_REGION}-docker.pkg.dev/${_DEPLOYMENT_PROJECT}/firebase-repo/${_APP_NAME}:latest + else + echo "image tag exists" + fi + + # copy content of the build version + docker create --name ${_APP_NAME} northamerica-northeast1-docker.pkg.dev/c4hnrd-tools/firebase-repo/${_APP_NAME}:${_SHORT_SHA} + docker cp ${_APP_NAME}:/app/. /workspace/app + +# Prepare build environments +- name: "northamerica-northeast1-docker.pkg.dev/c4hnrd-tools/cicd-repo/gcp-sre" + secretEnv: ["OP_CONNECT_HOST", "OP_CONNECT_TOKEN"] + dir: "app" + script: | + #!/usr/bin/env bash + + # Set firebase config + APP_HOST_NAME=$(op read -n op://CD/${_DEPLOYMENT_ENVIRONMENT}/${_APP_NAME}/FIREBASE_HOST_NAME) + firebase=$(jq '.hosting.site='\"$APP_HOST_NAME\"'' firebase-${_DEPLOYMENT_ENVIRONMENT}.json) + echo -E "${firebase}" > firebase-${_DEPLOYMENT_ENVIRONMENT}.json + + echo $(op read -n op://CD/${_DEPLOYMENT_ENVIRONMENT}/${_APP_NAME}/DEPLOY_PROJECT_ID) > /workspace/project_id.txt + echo $(op read -n op://CD/${_DEPLOYMENT_ENVIRONMENT}/${_APP_NAME}/BUILD_FOLDER) > /workspace/build_folder.txt + + BUILD_FOLDER=$(cat /workspace/build_folder.txt) + + if [ -z "${BUILD_FOLDER}" ]; then + BUILD_FOLDER=. + fi + + # Prepare .env by vaults + export APP_ENV=${_DEPLOYMENT_ENVIRONMENT} + op inject -i ./devops/vaults.env -o ${BUILD_FOLDER}/.env -f + + # Load firebase Admin SDK from secret manager + if grep -q "GOOGLE_APPLICATION_CREDENTIALS=" "${BUILD_FOLDER}/.env"; then + gcloud secrets versions access ${_DEPLOYMENT_ENVIRONMENT} --secret=FIREBASE_ADMINSDK \ + --project=${_DEPLOYMENT_PROJECT} \ + --format="get(payload.data)" | tr "_-" "/+" | base64 -d > ${BUILD_FOLDER}/project-firebase-adminsdk.json + fi + + # Load e2e settings from secret manager + if grep -q "E2E_SETTINGS=" "${BUILD_FOLDER}/.env"; then + gcloud secrets versions access ${_DEPLOYMENT_ENVIRONMENT} --secret=${_APP_NAME}_E2E_SETTINGS \ + --project=${_DEPLOYMENT_PROJECT} \ + --format="get(payload.data)" | tr "_-" "/+" | base64 -d > ${BUILD_FOLDER}/e2e.json + fi + +# Build application +- name: "northamerica-northeast1-docker.pkg.dev/c4hnrd-tools/firebase-repo/${_APP_NAME}:${_SHORT_SHA}" + dir: "app" + script: | + #!/usr/bin/env sh + + BUILD_FOLDER=$(cat /workspace/build_folder.txt) + pnpm_project=$((ls pnpm_*.yaml >> /dev/null 2>&1 && echo "EXIST") || echo "NOT_EXIST") + + if [ "$pnpm_project" = "EXIST" ]; then + npm install --global pnpm + pnpm install --frozen-lockfile + + # Nuxt's package + if [ -n "${BUILD_FOLDER}" ]; then + echo executing build:$BUILD_FOLDER + pnpm build:$BUILD_FOLDER + + # move build content back to root app folder + mv $BUILD_FOLDER/dist /workspace/app + else + pnpm build + fi + else + npm install + npm build + fi + + ls -la + +# Deploy application to firebase +- name: gcr.io/${_DEPLOYMENT_PROJECT}/firebase + dir: "app" + script: | + #!/usr/bin/env sh + + RUNNING_PROJECT=$(cat /workspace/project_id.txt) + + firebase deploy --project=${RUNNING_PROJECT} --config=firebase-${_DEPLOYMENT_ENVIRONMENT}.json --only hosting + +# E2E testing +- name: "northamerica-northeast1-docker.pkg.dev/c4hnrd-tools/firebase-repo/${_APP_NAME}:${_SHORT_SHA}" + dir: "app" + script: | + #!/usr/bin/env sh + + # pnpm run test:e2e:ui + +# Tag image after deployment done +- name: "gcr.io/google.com/cloudsdktool/cloud-sdk" + script: | + #!/usr/bin/env sh + + # tag image + gcloud artifacts docker tags add \ + ${_REGION}-docker.pkg.dev/${_DEPLOYMENT_PROJECT}/firebase-repo/${_APP_NAME}:${_SHORT_SHA} \ + ${_REGION}-docker.pkg.dev/${_DEPLOYMENT_PROJECT}/firebase-repo/${_APP_NAME}:${_DEPLOYMENT_ENVIRONMENT} + +availableSecrets: + secretManager: + - versionName: projects/331250273634/secrets/OP_CONNECT_HOST/versions/latest + env: "OP_CONNECT_HOST" + - versionName: projects/331250273634/secrets/OP_CONNECT_TOKEN/versions/latest + env: "OP_CONNECT_TOKEN" + +options: + automapSubstitutions: true + substitutionOption: "ALLOW_LOOSE" +substitutions: + _APP_NAME: ${_APP_NAME} + _DEPLOYMENT_ENVIRONMENT: ${_DEPLOYMENT_ENVIRONMENT} #dev/test/sandbox/prod + _DEPLOYMENT_PROJECT: "c4hnrd-tools" + _REGION: "northamerica-northeast1" + +logsBucket: "gs://github-actions-cloudbuild/history" + +timeout: 3600s diff --git a/.github/actions/frontend-deploy/files/firebase-dev.json b/.github/actions/frontend-deploy/files/firebase-dev.json new file mode 100644 index 0000000..509384f --- /dev/null +++ b/.github/actions/frontend-deploy/files/firebase-dev.json @@ -0,0 +1,66 @@ +{ + "hosting": + { + "site": "", + "public": "dist", + "ignore": ["**/devops/**", "**/.*", "**/node_modules/**"], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ], + "headers" : [ + { + "source": "**", + "headers" : [ + { "key" : "Access-Control-Allow-Origin", "value" : "*" }, + { "key" : "X-Frame-Options", "value" : "DENY" }, + { "key" : "X-Content-Type-Options", "value" : "nosniff" }, + { "key" : "X-XSS-Protection", "value" : "1; mode=block" }, + { + "key": "Content-Security-Policy", + "value": "default-src 'self'; frame-src 'self' *.gov.bc.ca *.hotjar.com *.googleapis.com https://*.nr-data.net https://*.newrelic.com https://*.cac1.pure.cloud; script-src 'self' 'unsafe-eval' 'unsafe-inline' *.googletagmanager.com *.gov.bc.ca *.hotjar.com *.googleapis.com https://*.nr-data.net https://*.newrelic.com https://*.cac1.pure.cloud; style-src 'self' 'unsafe-inline' *.cloudflare.com *.googleapis.com *.jsdelivr.net; font-src 'self' *.gov.bc.ca *.hotjar.com *.cloudflare.com *.googleapis.com *.gstatic.com *.jsdelivr.net; img-src 'self' data: *.hotjar.com *.postescanada-canadapost.ca https://*.cac1.pure.cloud; connect-src 'self' blob: *.zenhub.com *.gov.bc.ca *.launchdarkly.com *.run.app *.hotjar.com *.postescanada-canadapost.ca *.sentry.io *.apigee.net wss://*.hotjar.com *.hotjar.io https://*.nr-data.net https://shyrka-prod-cac1.s3.ca-central-1.amazonaws.com https://*.newrelic.com https://*.cac1.pure.cloud wss://*.cac1.pure.cloud *.googleapis.com *.google-analytics.com; manifest-src 'self'; media-src 'self' https://*.cac1.pure.cloud; object-src 'self' https://*.cac1.pure.cloud; child-src 'self' blob: *.gov.bc.ca https://*.cac1.pure.cloud; worker-src blob:;" + }, + { "key": "Cache-Control", "value": "private, no-cache, no-store, must-revalidate"}, + { "key": "Pragma", "value": "no-cache"}, + { "key": "Referrer-Policy", "value": "no-referrer" }, + { "key": "Feature-Policy", "value": "microphone 'self'" }, + { "key": "Strict-Transport-Security", "value": "max-age=31536000;" } + ] + }, + { + "source": "**/*.@(ico|jpg|jpeg|gif|png|svg|eot|otf|ttf|ttc|woff|woff2)", + "headers": [ + { + "key": "Cache-Control", "value": "public,max-age=31536000" + } + ] + }, + { + "source": "**/*.@(css|js)", + "headers": [ + { + "key": "Cache-Control", "value": "public,max-age=31536000" + } + ] + }, + { + "source": "**/*.@(service-worker.js)", + "headers": [ + { + "key": "Cache-Control", "value": "public,no-cache" + } + ] + }, + { + "source": "**/*.@(html|json)", + "headers": [ + { + "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" + } + ] + } + ] + } +} diff --git a/.github/actions/frontend-deploy/files/firebase-prod.json b/.github/actions/frontend-deploy/files/firebase-prod.json new file mode 100644 index 0000000..4308eec --- /dev/null +++ b/.github/actions/frontend-deploy/files/firebase-prod.json @@ -0,0 +1,66 @@ +{ + "hosting": + { + "site": "", + "public": "dist", + "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ], + "headers" : [ + { + "source": "**", + "headers" : [ + { "key" : "Access-Control-Allow-Origin", "value" : "*" }, + { "key" : "X-Frame-Options", "value" : "DENY" }, + { "key" : "X-Content-Type-Options", "value" : "nosniff" }, + { "key" : "X-XSS-Protection", "value" : "1; mode=block" }, + { + "key": "Content-Security-Policy", + "value": "default-src 'self'; frame-src 'self' *.gov.bc.ca *.hotjar.com *.googleapis.com https://*.nr-data.net https://*.newrelic.com https://*.cac1.pure.cloud; script-src 'self' 'unsafe-eval' 'unsafe-inline' *.googletagmanager.com *.gov.bc.ca *.hotjar.com *.googleapis.com https://*.nr-data.net https://*.newrelic.com https://*.cac1.pure.cloud; style-src 'self' 'unsafe-inline' *.cloudflare.com *.googleapis.com *.jsdelivr.net; font-src 'self' *.gov.bc.ca *.hotjar.com *.cloudflare.com *.googleapis.com *.gstatic.com *.jsdelivr.net; img-src 'self' data: *.hotjar.com *.postescanada-canadapost.ca https://*.cac1.pure.cloud; connect-src 'self' blob: *.zenhub.com *.gov.bc.ca *.launchdarkly.com *.hotjar.com *.postescanada-canadapost.ca *.sentry.io *.apigee.net wss://*.hotjar.com *.hotjar.io https://*.nr-data.net https://shyrka-prod-cac1.s3.ca-central-1.amazonaws.com https://*.newrelic.com https://*.cac1.pure.cloud wss://*.cac1.pure.cloud *.googleapis.com *.google-analytics.com; manifest-src 'self'; media-src 'self' https://*.cac1.pure.cloud; object-src 'self' https://*.cac1.pure.cloud; child-src 'self' blob: *.gov.bc.ca https://*.cac1.pure.cloud; worker-src blob:;" + }, + { "key": "Cache-Control", "value": "private, no-cache, no-store, must-revalidate"}, + { "key": "Pragma", "value": "no-cache"}, + { "key": "Referrer-Policy", "value": "no-referrer" }, + { "key": "Feature-Policy", "value": "microphone 'self'" }, + { "key": "Strict-Transport-Security", "value": "max-age=31536000;" } + ] + }, + { + "source": "**/*.@(ico|jpg|jpeg|gif|png|svg|eot|otf|ttf|ttc|woff|woff2)", + "headers": [ + { + "key": "Cache-Control", "value": "public,max-age=31536000" + } + ] + }, + { + "source": "**/*.@(css|js)", + "headers": [ + { + "key": "Cache-Control", "value": "public,max-age=31536000" + } + ] + }, + { + "source": "**/*.@(service-worker.js)", + "headers": [ + { + "key": "Cache-Control", "value": "public,no-cache" + } + ] + }, + { + "source": "**/*.@(html|json)", + "headers": [ + { + "key": "Cache-Control", "value": "private, no-cache, no-store, must-revalidate" + } + ] + } + ] + } +} diff --git a/.github/actions/frontend-deploy/files/firebase-test.json b/.github/actions/frontend-deploy/files/firebase-test.json new file mode 100644 index 0000000..35cfe7c --- /dev/null +++ b/.github/actions/frontend-deploy/files/firebase-test.json @@ -0,0 +1,66 @@ +{ + "hosting": + { + "site": "", + "public": "dist", + "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ], + "headers" : [ + { + "source": "**", + "headers" : [ + { "key" : "Access-Control-Allow-Origin", "value" : "*" }, + { "key" : "X-Frame-Options", "value" : "DENY" }, + { "key" : "X-Content-Type-Options", "value" : "nosniff" }, + { "key" : "X-XSS-Protection", "value" : "1; mode=block" }, + { + "key": "Content-Security-Policy", + "value": "default-src 'self'; frame-src 'self' *.gov.bc.ca *.hotjar.com *.googleapis.com https://*.nr-data.net https://*.newrelic.com https://*.cac1.pure.cloud; script-src 'self' 'unsafe-eval' 'unsafe-inline' *.googletagmanager.com *.gov.bc.ca *.hotjar.com *.googleapis.com https://*.nr-data.net https://*.newrelic.com https://*.cac1.pure.cloud; style-src 'self' 'unsafe-inline' *.cloudflare.com *.googleapis.com *.jsdelivr.net; font-src 'self' *.gov.bc.ca *.hotjar.com *.cloudflare.com *.googleapis.com *.gstatic.com *.jsdelivr.net; img-src 'self' data: *.hotjar.com *.postescanada-canadapost.ca https://*.cac1.pure.cloud; connect-src 'self' blob: *.zenhub.com *.run.app *.gov.bc.ca *.launchdarkly.com *.hotjar.com *.postescanada-canadapost.ca *.sentry.io *.apigee.net wss://*.hotjar.com *.hotjar.io https://*.nr-data.net https://shyrka-prod-cac1.s3.ca-central-1.amazonaws.com https://*.newrelic.com https://*.cac1.pure.cloud wss://*.cac1.pure.cloud *.googleapis.com *.google-analytics.com; manifest-src 'self'; media-src 'self' https://*.cac1.pure.cloud; object-src 'self' https://*.cac1.pure.cloud; child-src 'self' blob: *.gov.bc.ca https://*.cac1.pure.cloud; worker-src blob:;" + }, + { "key": "Cache-Control", "value": "private, no-cache, no-store, must-revalidate"}, + { "key": "Pragma", "value": "no-cache"}, + { "key": "Referrer-Policy", "value": "no-referrer" }, + { "key": "Feature-Policy", "value": "microphone 'self'" }, + { "key": "Strict-Transport-Security", "value": "max-age=31536000;" } + ] + }, + { + "source": "**/*.@(ico|jpg|jpeg|gif|png|svg|eot|otf|ttf|ttc|woff|woff2)", + "headers": [ + { + "key": "Cache-Control", "value": "public,max-age=31536000" + } + ] + }, + { + "source": "**/*.@(css|js)", + "headers": [ + { + "key": "Cache-Control", "value": "public,max-age=31536000" + } + ] + }, + { + "source": "**/*.@(service-worker.js)", + "headers": [ + { + "key": "Cache-Control", "value": "public,no-cache" + } + ] + }, + { + "source": "**/*.@(html|json)", + "headers": [ + { + "key": "Cache-Control", "value": "private, no-cache, no-store, must-revalidate" + } + ] + } + ] + } +} diff --git a/.github/workflows/codecov-ci.yaml b/.github/workflows/codecov-ci.yaml new file mode 100644 index 0000000..dd74ad7 --- /dev/null +++ b/.github/workflows/codecov-ci.yaml @@ -0,0 +1,29 @@ +name: Codecov CI + +on: + workflow_call: + inputs: + app_name: + required: true + type: string + working_directory: + type: string + default: "." + codecov_flag: + type: string + +jobs: + codecov: + strategy: + fail-fast: true + matrix: + os: [ "ubuntu-latest" ] + + runs-on: ${{ matrix.os }} + + defaults: + run: + shell: bash + working-directory: ${{ inputs.working_directory }} + + steps: diff --git a/.github/workflows/frontend-cd.yaml b/.github/workflows/frontend-cd.yaml new file mode 100644 index 0000000..75756c7 --- /dev/null +++ b/.github/workflows/frontend-cd.yaml @@ -0,0 +1,112 @@ +name: Frontend CD + +on: + workflow_call: + inputs: + target: + required: true + type: string + app_name: + required: true + type: string + working_directory: + type: string + default: "." + secrets: + WORKLOAD_IDENTIFY_POOLS_PROVIDER: + required: true + GCP_SERVICE_ACCOUNT: + required: true + +jobs: + setup: + # Only allow run the CD flow in protected branch + if: github.ref_protected == true + + runs-on: ubuntu-20.04 + + defaults: + run: + shell: bash + + # Allow add the tag in the repo. + # Add "id-token" with the intended permissions. + permissions: + contents: "write" + id-token: "none" + + outputs: + TARGETS: ${{ steps.setenv.outputs.TARGETS }} + TARGET: ${{ steps.setenv.outputs.TARGET }} + TARGET_FROM: ${{ steps.setenv.outputs.TARGET_FROM }} + PIPELINE: ${{ steps.setenv.outputs.PIPELINE }} + + steps: + # Checkout code + - name: Checkout out the code + uses: actions/checkout@v4 + + - name: Setup targets + uses: bcgov/bcregistry-sre/.github/actions/setup-deployment-target@main + with: + environment: ${{ inputs.target }} + app_name: ${{ inputs.app_name }} + + - id: setenv + run: | + echo "TARGETS=${{ env.DEPLOY_TARGETS }}" >> "$GITHUB_OUTPUT" + echo "TARGET=${{ env.DEPLOY_TARGET }}" >> "$GITHUB_OUTPUT" + echo "TARGET_FROM=${{ env.DEPLOY_TARGET_FROM }}" >> "$GITHUB_OUTPUT" + echo "PIPELINE=${{ env.DEPLOY_PIPELINE }}" >> "$GITHUB_OUTPUT" + + deploy: + needs: setup + runs-on: ubuntu-20.04 + + environment: + name: "${{ needs.setup.outputs.TARGET }}" + + defaults: + run: + shell: bash + working-directory: ${{ inputs.working_directory }} + + # Allow add the tag in the repo. + # Add "id-token" with the intended permissions. + permissions: + contents: "write" + id-token: "write" + + steps: + # Checkout code + - name: Checkout out the code + uses: actions/checkout@v4 + + # Copy cloud build, Dockerfile, firebase files to the build + - name: Copy deployment files + uses: bcgov/bcregistry-sre/.github/actions/frontend-deploy@main + with: + working-directory: ${{ inputs.working_directory }} + + # GCP authentication + - name: "Authenticate to Google Cloud" + id: "auth" + uses: "google-github-actions/auth@v1" + with: + workload_identity_provider: ${{ secrets.WORKLOAD_IDENTIFY_POOLS_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + # Setup gcloud CLI + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 + + # Trigger Cloud Deploy + - name: Deployment + working-directory: ${{ inputs.working_directory }} + run: |- + SHORT_SHA=$(git rev-parse --short HEAD) + + gcloud builds submit \ + --region=northamerica-northeast1 \ + --substitutions _SHORT_SHA=$SHORT_SHA,_APP_NAME="${{ inputs.app_name }}",_DEPLOY_TARGET="${{ needs.setup.outputs.TARGET }}" \ + --config cloudbuild.yaml diff --git a/.github/workflows/frontend-ci.yaml b/.github/workflows/frontend-ci.yaml new file mode 100644 index 0000000..3c86694 --- /dev/null +++ b/.github/workflows/frontend-ci.yaml @@ -0,0 +1,46 @@ +name: Frontend Application CI + +on: + workflow_call: + inputs: + app_name: + required: true + type: string + working_directory: + type: string + default: "." + codecov_flag: + type: string + +jobs: + linting: + strategy: + fail-fast: true + matrix: + os: [ "ubuntu-latest" ] + + runs-on: ${{ matrix.os }} + + defaults: + run: + shell: bash + working-directory: ${{ inputs.working_directory }} + + steps: + + + testing: + needs: linting + + defaults: + run: + shell: bash + working-directory: ${{ inputs.working_directory }} + + strategy: + fail-fast: true + matrix: + os: [ "ubuntu-latest" ] + + runs-on: ${{ matrix.os }} + steps: diff --git a/gcp/iam/create-developer-roles.sh b/gcp/iam/create-developer-roles.sh new file mode 100755 index 0000000..9c75994 --- /dev/null +++ b/gcp/iam/create-developer-roles.sh @@ -0,0 +1,48 @@ + +#!/bin/bash + +declare -a environments=("dev") +declare -a projects=("a083gt" "mvnjri" "gtksf3" "yfjq17" "c4hnrd" "k973yf" "yfthig" "eogruh") +declare -a services=("developer") + +for ev in "${environments[@]}" +do + for ns in "${projects[@]}" + do + echo "project: $ns-$ev" + PROJECT_ID=$ns-$ev + + if [[ ! -z `gcloud projects describe ${PROJECT_ID} --verbosity=none` ]]; then + gcloud config set project ${PROJECT_ID} + + for se in "${services[@]}" + do + ROLE_NAME="role$se" + SA_NAME="sa-$se" + SA_FULL_NAME="$SA_NAME@${PROJECT_ID}.iam.gserviceaccount.com" + SA_DESCRIPTION="Service Account for running $se services" + SA_ROLE="projects/${PROJECT_ID}/roles/$ROLE_NAME" + + if [[ -z `gcloud iam roles describe $ROLE_NAME --project=${PROJECT_ID} --verbosity=none` ]]; then + gcloud iam roles create $ROLE_NAME --quiet --project=${PROJECT_ID} --file=role-$se-dev.yaml + else + gcloud iam roles update $ROLE_NAME --quiet --project=${PROJECT_ID} --file=role-$se-dev.yaml + fi + + if [[ -z `gcloud iam service-accounts describe $SA_FULL_NAME --project=${PROJECT_ID} --verbosity=none` ]]; then + ## API service account + gcloud iam service-accounts create $SA_NAME \ + --description="$SA_DESCRIPTION" \ + --display-name="$SA_NAME" + fi + + gcloud projects add-iam-policy-binding ${PROJECT_ID} \ + --member="serviceAccount:$SA_FULL_NAME" \ + --role="$SA_ROLE" + + #gcloud iam service-accounts list --filter $SA_NAME + #gcloud iam roles describe $ROLE_NAME --project=${PROJECT_ID} + done + fi + done +done \ No newline at end of file diff --git a/gcp/iam/role-developer-dev.yaml b/gcp/iam/role-developer-dev.yaml index 03e2172..7ca30c5 100644 --- a/gcp/iam/role-developer-dev.yaml +++ b/gcp/iam/role-developer-dev.yaml @@ -94,6 +94,7 @@ includedPermissions: - run.services.update - run.tasks.get - run.tasks.list +- serviceusage.services.use - serviceusage.services.list - storage.managedFolders.get - storage.objects.get