diff --git a/.controlplane/controlplane.yml b/.controlplane/controlplane.yml index 49a0ee45..af198fba 100644 --- a/.controlplane/controlplane.yml +++ b/.controlplane/controlplane.yml @@ -14,7 +14,7 @@ aliases: # Production apps will use a different org than staging for security. # Change this value to your org name # or set ENV CPLN_ORG to your org name as that will override whatever is used here for all cpflow commands - # cpln_org: shakacode-open-source-examples + cpln_org: shakacode-open-source-examples-staging # Example apps use only location. CPLN offers the ability to use multiple locations. default_location: aws-us-east-2 @@ -34,6 +34,9 @@ aliases: # Configure the workload name used when maintenance mode is on (defaults to "maintenance"). maintenance_workload: maintenance + # Configure the script to run when releasing an app., either with deploy-image or promote-app-from-upstream + release_script: release_script.sh + apps: react-webpack-rails-tutorial: # Simulate Production Version @@ -47,8 +50,6 @@ apps: upstream: react-webpack-rails-tutorial-staging - release_script: release_script.sh - react-webpack-rails-tutorial-staging: <<: *common # QA Apps are like Heroku review apps, but the use `prefix` so you can run a commmand like @@ -56,5 +57,17 @@ apps: # `cpflow setup gvc postgres redis rails -a qa-react-webpack-rails-tutorial-pr-1234` qa-react-webpack-rails-tutorial: <<: *common - # Prefix is used to identify these "qa" apps. - prefix: true + # Order matters! + setup_app_templates: + # GVC template contains the identity + - app + + # Resources + - postgres + - redis + + # Workloads, like Dynos types on Heroku + - daily-task + - rails + # match_if_app_name_starts_with is used to identify these "qa" apps. + match_if_app_name_starts_with: true diff --git a/.controlplane/readme.md b/.controlplane/readme.md index 5588cfa2..cfab19a8 100644 --- a/.controlplane/readme.md +++ b/.controlplane/readme.md @@ -74,7 +74,7 @@ export APP_NAME=react-webpack-rails-tutorial # Provision all infrastructure on Control Plane. # app react-webpack-rails-tutorial will be created per definition in .controlplane/controlplane.yml -cpflow apply-template gvc postgres redis rails daily-task -a $APP_NAME +cpflow setup-app -a $APP_NAME # Build and push docker image to Control Plane repository # Note, may take many minutes. Be patient. diff --git a/.controlplane/release_script.sh b/.controlplane/release_script.sh index 2d9e1c59..fe2ab785 100755 --- a/.controlplane/release_script.sh +++ b/.controlplane/release_script.sh @@ -1,8 +1,22 @@ #!/bin/bash -e -echo 'Running release_script.sh per controlplane.yml' -echo 'Run DB migrations' -./bin/rails db:prepare +log() { + echo "[`date +%Y-%m-%d:%H:%M:%S`]: $1" +} -echo 'Completed release_script.sh per controlplane.yml' +error_exit() { + log "$1" 1>&2 + exit 1 +} + +log 'Running release_script.sh per controlplane.yml' + +if [ -x ./bin/rails ]; then + log 'Run DB migrations' + ./bin/rails db:prepare || error_exit "Failed to run DB migrations" +else + error_exit "./bin/rails does not exist or is not executable" +fi + +log 'Completed release_script.sh per controlplane.yml' diff --git a/.controlplane/templates/gvc.yml b/.controlplane/templates/app.yml similarity index 64% rename from .controlplane/templates/gvc.yml rename to .controlplane/templates/app.yml index 93028630..4ef5b8d0 100644 --- a/.controlplane/templates/gvc.yml +++ b/.controlplane/templates/app.yml @@ -1,15 +1,15 @@ # Template setup of the GVC, roughly corresponding to a Heroku app kind: gvc -name: APP_GVC +name: { { APP_NAME } } spec: # For using templates for test apps, put ENV values here, stored in git repo. # Production apps will have values configured manually after app creation. env: - name: DATABASE_URL - # Password does not matter because host postgres.APP_GVC.cpln.local can only be accessed + # Password does not matter because host postgres.{{APP_NAME}}.cpln.local can only be accessed # locally within CPLN GVC, and postgres running on a CPLN workload is something only for a # test app that lacks persistence. - value: 'postgres://the_user:the_password@postgres.APP_GVC.cpln.local:5432/APP_GVC' + value: 'postgres://the_user:the_password@postgres.{{APP_NAME}}.cpln.local:5432/{{APP_NAME}}' - name: RAILS_ENV value: production - name: NODE_ENV @@ -18,8 +18,13 @@ spec: value: 'true' - name: REDIS_URL # No password for GVC local Redis. See comment above for postgres. - value: 'redis://redis.APP_GVC.cpln.local:6379' + value: 'redis://redis.{{APP_NAME}}.cpln.local:6379' # Part of standard configuration staticPlacement: locationLinks: - - /org/APP_ORG/location/APP_LOCATION + - { { APP_LOCATION_LINK } } + +--- +# Identity is needed to access secrets +kind: identity +name: { { APP_IDENTITY } } diff --git a/.controlplane/templates/daily-task.yml b/.controlplane/templates/daily-task.yml index 9fdd8fa4..f6b8dd9f 100644 --- a/.controlplane/templates/daily-task.yml +++ b/.controlplane/templates/daily-task.yml @@ -20,7 +20,7 @@ spec: - rake - daily inheritEnv: true - image: "/org/APP_ORG/image/APP_IMAGE" + image: {{APP_IMAGE_LINK}} defaultOptions: autoscaling: minScale: 1 @@ -30,4 +30,5 @@ spec: external: outboundAllowCIDR: - 0.0.0.0/0 - identityLink: /org/APP_ORG/gvc/APP_GVC/identity/postgres-poc-identity + # Identity is used for binding workload to secrets + identityLink: {{APP_IDENTITY_LINK}} diff --git a/.controlplane/templates/org.yml b/.controlplane/templates/org.yml new file mode 100644 index 00000000..6616376d --- /dev/null +++ b/.controlplane/templates/org.yml @@ -0,0 +1,23 @@ +# Org level secrets are used to store sensitive information that is +# shared across multiple apps in the same organization. This is +# useful for storing things like API keys, database credentials, and +# other sensitive information that is shared across multiple apps +# in the same organization. + +# This is how you apply this once (not during CI) +# cpl apply-template secrets -a qa-react-webpack-rails-tutorial --org shakacode-open-source-examples-staging + +kind: secret +name: {{APP_SECRETS}} +type: dictionary +data: + SOME_ENV: "123456" + +--- + +# Policy is needed to allow identities to access secrets +kind: policy +name: {{APP_SECRETS_POLICY}} +targetKind: secret +targetLinks: + - //secret/{{APP_SECRETS}} diff --git a/.controlplane/templates/postgres.yml b/.controlplane/templates/postgres.yml index 7291e37d..77e3497b 100644 --- a/.controlplane/templates/postgres.yml +++ b/.controlplane/templates/postgres.yml @@ -106,7 +106,7 @@ bindings: # - use # - view principalLinks: - - //gvc/APP_GVC/identity/postgres-poc-identity + - //gvc/{{APP_NAME}}/identity/postgres-poc-identity targetKind: secret targetLinks: - //secret/postgres-poc-credentials @@ -139,9 +139,6 @@ spec: args: - "-c" - "cat /usr/local/bin/cpln-entrypoint.sh >> ./cpln-entrypoint.sh && chmod u+x ./cpln-entrypoint.sh && ./cpln-entrypoint.sh postgres" - #command: "cpln-entrypoint.sh" - #args: - # - "postgres" ports: - number: 5432 protocol: tcp diff --git a/.controlplane/templates/rails.yml b/.controlplane/templates/rails.yml index 728b5a12..9641165b 100644 --- a/.controlplane/templates/rails.yml +++ b/.controlplane/templates/rails.yml @@ -14,7 +14,7 @@ spec: value: debug # Inherit other ENV values from GVC inheritEnv: true - image: '/org/APP_ORG/image/APP_IMAGE' + image: {{APP_IMAGE_LINK}} # 512 corresponds to a standard 1x dyno type memory: 512Mi ports: @@ -34,3 +34,5 @@ spec: # Could configure outbound for more security outboundAllowCIDR: - 0.0.0.0/0 + # Identity is used for binding workload to secrets + identityLink: {{APP_IDENTITY_LINK}} diff --git a/.github/actions/deploy-to-control-plane/action.yml b/.github/actions/deploy-to-control-plane/action.yml index 9d4e5090..7105204c 100644 --- a/.github/actions/deploy-to-control-plane/action.yml +++ b/.github/actions/deploy-to-control-plane/action.yml @@ -4,7 +4,6 @@ name: Deploy-To-Control-Plane description: 'Deploys both to staging and to review apps' inputs: - # The name of the app to deploy app_name: description: 'The name of the app to deploy' required: true @@ -20,7 +19,7 @@ runs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.2' # Specify your Ruby version here + ruby-version: '3.3.3' # Specify your Ruby version here - name: Install Control Plane CLI shell: bash @@ -28,17 +27,17 @@ runs: sudo npm install -g @controlplane/cli@3.1.0 cpln --version gem install cpflow -v 4.0.0 + cpflow --version - name: Set Short SHA id: vars shell: bash run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" - - name: cpflow profile + - name: cpln profile shell: bash run: | cpln profile update default - # cpln profile update default --token ${CPLN_TOKEN} # Caching step - uses: actions/cache@v2 @@ -49,22 +48,24 @@ runs: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile', '**/package.json', '**/yarn.lock') }} ${{ runner.os }}-docker- + - name: cpflow setup-app + shell: bash + run: | + if ! cpflow exists -a ${{ inputs.app_name }} ; then + cpflow setup-app -a ${{ inputs.app_name }} + fi + # Provision all infrastructure on Control Plane. + # app react-webpack-rails-tutorial will be created per definition in .controlplane/controlplane.yml - name: cpflow build-image shell: bash run: | cpln image docker-login + # Use BUILDKIT_PROGRESS=plain to get more verbose logging of the build + # BUILDKIT_PROGRESS=plain cpflow build-image -a ${{ inputs.app_name }} --commit ${{steps.vars.outputs.sha_short}} --org ${{inputs.org}} cpflow build-image -a ${{ inputs.app_name }} --commit ${{steps.vars.outputs.sha_short}} --org ${{inputs.org}} - # --cache /tmp/.docker-cache - - - name: Run release script - shell: bash - run: | - # Run database migrations (or other release tasks) with the latest image, - # while the app is still running on the previous image. - # This is analogous to the release phase. - cpflow run './.controlplane/release_script.sh' -a ${{ inputs.app_name }} --image latest - + # --cache /tmp/.docker-cache - name: Deploy to Control Plane shell: bash run: | - cpflow deploy-image -a ${{ inputs.app_name }} --org ${{inputs.org}} + echo "Deploying to Control Plane" + cpflow deploy-image -a ${{ inputs.app_name }} --run-release-phase --org ${{inputs.org}} --verbose diff --git a/.github/workflows/add-comment-on-pr-creation.yml b/.github/workflows/add-comment-on-pr-creation.yml new file mode 100644 index 00000000..06a30089 --- /dev/null +++ b/.github/workflows/add-comment-on-pr-creation.yml @@ -0,0 +1,20 @@ +name: Add helper Comment on PR creation + +on: + pull_request: + types: [opened] + +jobs: + comment-on-pr: + runs-on: ubuntu-latest + steps: + - name: Add GitHub Comment for review app instructions + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "Hi 👋 To deploy a review app, please comment `/deploy-review-app`" + }) diff --git a/.github/workflows/deploy-to-control-plane-review.yml b/.github/workflows/deploy-to-control-plane-review.yml index 99ca9b90..db8841a9 100644 --- a/.github/workflows/deploy-to-control-plane-review.yml +++ b/.github/workflows/deploy-to-control-plane-review.yml @@ -6,6 +6,13 @@ name: Deploy Review App to Control Plane on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: + + # Uncomment these lines to trigger the workflow on pull request events + # pull_request: + # branches: + # - master + + # deploy on comment "/deploy-review-app" issue_comment: types: [created, edited] @@ -13,17 +20,66 @@ on: env: CPLN_ORG: ${{secrets.CPLN_ORG_STAGING}} CPLN_TOKEN: ${{secrets.CPLN_TOKEN_STAGING}} + # Uncomment this line to use the PR number from the pull requests trigger event (that trigger is commented) + # PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} + PR_NUMBER: ${{ github.event.issue.number }} jobs: - deploy-to-control-plane-staging: + deploy-to-control-plane-review: if: ${{ github.event_name != 'issue_comment' || (github.event.comment.body == '/deploy-review-app' && github.event.issue.pull_request) }} runs-on: ubuntu-latest steps: - - name: Check out the repo - uses: actions/checkout@v2 + - name: Get PR HEAD Ref + if: ${{ github.event_name == 'issue_comment' }} + id: getRef + run: echo "PR_REF=$(gh pr view \"$PR_NUMBER\" --repo \"${{ github.repository }}\" --json headRefName | jq -r '.headRefName')" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout source code from Github + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ steps.getRef.outputs.PR_REF || github.ref }} + + - name: Add GitHub Comment + if: ${{ github.event_name == 'issue_comment' }} + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "We started working on your review-app deployment. You can track progress in the "Actions" Tab [here](https://github.com/shakacode/react-webpack-rails-tutorial/actions/workflows/deploy-to-control-plane-review.yml) on Github." + }) + - name: Get PR number + if: ${{ github.event_name != 'issue_comment' }} + run: | + echo "GITHUB_REPOSITORY: \"$GITHUB_REPOSITORY\"" + if [ -z "$PR_NUMBER" ]; then + echo "PR_NUMBER is not in the trigger event. Fetching PR number from open PRs." + REF="${{ github.ref }}" + REF=${REF#refs/heads/} # Remove 'refs/heads/' prefix + echo "REF: \"$REF\"" + API_RESPONSE=$(curl --location --request GET "https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls?state=open" \ + --header 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}') + PR_NUMBER=$(echo "$API_RESPONSE" | jq '.[] | select(.head.ref=="'$REF'") | .number') + fi + echo "PR_NUMBER: $PR_NUMBER" + if [ -z "$PR_NUMBER" ]; then + echo "PR_NUMBER is not set. Aborting." + exit 1 + fi + echo "PR_NUMBER=\"$PR_NUMBER\"" >> $GITHUB_ENV + - name: Get App Name + run: | + echo "PR_NUMBER: ${{ env.PR_NUMBER }}" + echo "APP_NAME=qa-react-webpack-rails-tutorial-pr-${{ env.PR_NUMBER }}" >> "$GITHUB_ENV" + echo "App Name: ${{ env.APP_NAME }}" - uses: ./.github/actions/deploy-to-control-plane with: - app_name: qa-react-webpack-rails-tutorial-pr-${{ github.event.pull_request.number }} - org: ${{ secrets.CPLN_ORG_STAGING }} + app_name: ${{ env.APP_NAME }} + org: ${{ env.CPLN_ORG }}