Skip to content

Instantly share code, notes, and snippets.

@copiousfreetime
Last active October 9, 2022 21:49
Show Gist options
  • Select an option

  • Save copiousfreetime/4e41bd115b63bc928aa8e5f8daf26d1b to your computer and use it in GitHub Desktop.

Select an option

Save copiousfreetime/4e41bd115b63bc928aa8e5f8daf26d1b to your computer and use it in GitHub Desktop.

Revisions

  1. copiousfreetime revised this gist Apr 28, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -44,10 +44,10 @@ upon `THING_KEY_REVIEW` or `THING_KEY_TEST` as appropriate.
    1. Deal with the environment variables - possible approach above.
    2. Create a `continuous_integration` job as a GitHub action and use the values
    that you did have in your `app.json` file for `environments.test.scripts.test-setup`
    and `environments.test.scripts.test` and make them individual stesp in the
    and `environments.test.scripts.test` and make them individual steps in the
    workflow.
    3. Turn on this workflow as appropriate for your GitHub events. In this workflow
    in the conintuous integration job is happening on every push to `main` and on
    in the `continuous_integration` job is happening on every push to `main` and on
    every push to a branch that is part of a pull request.
    4. Once this is in place, if you want to make the CI be required for branch
    merging, go to the Branch Protection rules of your repo and put this job in
  2. copiousfreetime created this gist Apr 28, 2022.
    79 changes: 79 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,79 @@
    # How to deal with missing Heroku CI/CD and Review Apps

    ## The reason for this

    - <https://status.heroku.com/incidents/2413>

    In particular the update from 2022-04-26 23:54:00 UTC

    > For the protection of our customers, we will not be reconnecting to GitHub
    > until we are certain that we can do so safely, **which may take some time
    > We recommend that customers use alternate methods rather than waiting for us
    > to restore this integration.** [emphasis mine].
    ## Generate HEROKU_API_KEY

    You'll need to take a heroku use for your team/group/organization and make sure
    they have all the permissions needed. Then go to that [account
    page](https://dashboard.heroku.com/account), scrolldown to the bottom under the
    **API Key** section. Take that API Key and put it in GitHub secrets for your
    repository as `HEROKU_API_KEY`.

    This does mean that ALL of the items that happen in heroku as part of these
    workflows will appear to happen as the user that is associated with that api key.

    ## Setup GitHub Secrets/Environment variables

    In addition to the `HEROKU_API_KEY` above, you'll need to replicate any and all
    "secret" items that were in your Heroku config vars for your Test and Review
    environments to either GitHub secrets, or hardcode them into the workflows
    below.

    This example uses a combination of both. If you did have the same environment
    variable in both Heroku Test and Heroku Review, but with different values,
    you'll have to take that into account when you do the GitHub secrets. I'd
    probably recomented doing something like `THING_KEY_TEST` and then
    `THING_KEY_REVIEW` as GitHub secrets.

    And then in your workflows in the appropriate sections for CI or deploy use the
    `env:` section to setup the appropriate environment varable of `THING_KEY` baed
    upon `THING_KEY_REVIEW` or `THING_KEY_TEST` as appropriate.

    ## General Migration steps for CI

    1. Deal with the environment variables - possible approach above.
    2. Create a `continuous_integration` job as a GitHub action and use the values
    that you did have in your `app.json` file for `environments.test.scripts.test-setup`
    and `environments.test.scripts.test` and make them individual stesp in the
    workflow.
    3. Turn on this workflow as appropriate for your GitHub events. In this workflow
    in the conintuous integration job is happening on every push to `main` and on
    every push to a branch that is part of a pull request.
    4. Once this is in place, if you want to make the CI be required for branch
    merging, go to the Branch Protection rules of your repo and put this job in
    as a status check requirement. It should show up in the dropdown list if the
    job has run at least once in the past week.

    ## General Migration steps for "Review" apps

    1. Deal with the environment variables - possible approach above.
    2. Create a `review_deploy` job as a GitHub action that manges a heroku
    application in your pipeline in the `development` stage.
    3. Update the `Set Member Permissions` step as appropriate for your situation.
    The old permissions that we done for the review apps where "ephemeral" and
    had specific rules around them in heroku along with the team member status in
    the pipeline. This does not apply for what we are dong now so we need to be
    explicity about the permissions of heroku users.
    4. Wire up GitHub Deployment status around the review app so the PR links are
    good
    5. Create a `review_destroy` job as a GitHub action that destorys the
    appropriate heroku app on pull request close.

    ## General Migration steps for Autodeployment to other stages of your pipeline

    1. Deal with the environment variables - possible approacch above.
    2. Create a `<stage>_deploy` job as a GitHub action that does does a heroku
    deploy based upon the appropriate branch from your repo. The example in this
    workflow deploys from the `main` branch of the repo to the `staging` stage of
    a heroku pipeline. And it also only does this if the `continuous_integration`
    job is successful.
    274 changes: 274 additions & 0 deletions replicate-heroku-ci-cd.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,274 @@
    name: "CI/CD"

    on:
    push:
    branches:
    - 'main'

    pull_request:
    types: [opened, synchronize, reopened, closed]

    env:
    HEROKU_TEAM: "my-heroku-team"
    HEROKU_STAGING_APP: "my-staging-pipeline-app"
    STAGING_APP_URL: "https://url-to-staging-pipeline-app.invalid/"
    HEROKU_REVIEW_APP_ADDONS: "--addons=heroku-postgresql:hobby-dev"
    HEROKU_PIPELINE_NAME: "my-heroku-pipeline-name"

    # All the other environment variables your test environment needs
    # XYZ_TOKEN: ${{ secrets.XYZ_TOKEN }}}
    # SOMETHING_URL: ${{ secrets.SOMETHING_URL }}

    jobs:

    # Continus Integration job that runs all the tests - similar to what was
    # done with the Heroku CI Test. If you had an in-dyno database on your CI Test
    # runs, then this should also work for you here with the postgres service.
    continuous_integration:
    if: github.event.action != 'closed'
    env:
    DATABASE_URL: postgresql://postgres:[email protected]/dbname_test
    NODE_ENV: test
    NOCK_DISABLE_NETWORK: on

    runs-on: ubuntu-latest
    services:
    postgres:
    image: postgres
    env:
    POSTGRES_PASSWORD: postgres
    POSTGRES_USER: postgres
    ports:
    - 5432:5432
    # Set health checks to wait until postgres has started
    options: >-
    --health-cmd pg_isready
    --health-interval 10s
    --health-timeout 5s
    --health-retries 5
    steps:
    - name: "Check out repository code"
    uses: actions/checkout@v3

    - name: Setup Node
    uses: actions/setup-node@v3
    with:
    node-version-file: .nvmrc
    cache: 'npm'

    - name: Update to latest npm
    run: npm install npm@latest -g

    - name: Install Node packages
    run: npm install

    - name: Setup the Test environment
    run: ./ci/test-setup # This was what you did have in app.json for test environment for `test-setup` script

    - name: Run tests
    run: ./ci/test # This was hwat you had in app.json for test environment `test` script

    # Auto deploy to the staging stage of the application pipelin when a push
    # happens on the main branch in the repo. This replicates auto deployment to a
    # heroku pipeline stage from a known branch. If you used a different branch,
    # change it here and in the `on` section at the top of the workflow. Feel free
    # to add other `_deploy` items if you had other branches going to other stages
    #
    # This also enforces that the test above run before the deploy happens
    staging_deploy:
    if: github.ref_name == 'main'
    name: Deploy to Heroku Staging
    needs: [continuous_integration]
    runs-on: ubuntu-latest
    steps:
    - name: "Check out repository code"
    uses: actions/checkout@v3

    - name: Get git output names
    id: git
    shell: bash
    run: |
    if [[ "${{ github.ref }}" != "refs/tags/"* ]]; then
    if [[ ${{ github.event_name }} == 'pull_request' ]]; then
    echo "::set-output name=current_branch::$HEAD_REF"
    else
    echo "::set-output name=current_branch::$REF_BRANCH"
    fi
    else
    REF=$(printf "%q" "${{ github.ref }}")
    REF_BRANCH=${REF/refs\/tags\/${{ inputs.strip_tag_prefix }}/}
    echo "::set-output name=current_branch::$(eval printf "%s" "$REF_BRANCH")"
    fi
    # Wire up github deployments so that these can fire so if other
    # integrations depended on the github deployment notifications, they can
    # also still happen.
    - uses: chrnorm/deployment-action@releases/v1
    name: Create GitHub deployment
    id: deployment
    with:
    initial_status: "in_progress"
    token: "${{ github.token }}"
    target_url: ${{ env.STAGING_APP_URL }}
    environment: staging
    ref: ${{ steps.git.outputs.current_branch }}

    - uses: akhileshns/[email protected]
    name: Heroku Deploy
    with:
    heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
    heroku_app_name: ${{ env.HEROKU_STAGING_APP }}
    heroku_email: ${{ secrets.HEROKU_EMAIL }}

    - name: Update deployment status (success)
    if: ${{ success() }}
    uses: chrnorm/deployment-status@releases/v1
    with:
    token: "${{ github.token }}"
    target_url: ${{ env.STAGING_APP_URL }}
    state: "success"
    deployment_id: ${{ steps.deployment.outputs.deployment_id }}
    ref: ${{ steps.git.outputs.current_branch }}

    - name: Update deployment status (failure)
    if: ${{ failure() }}
    uses: chrnorm/deployment-status@releases/v1
    with:
    token: "${{ github.token }}"
    target_url: ${{ env.STAGING_APP_URL }}
    state: "failure"
    deployment_id: ${{ steps.deployment.outputs.deployment_id }}
    ref: ${{ steps.git.outputs.current_branch }}

    # Create a review app on pull request open or reopen, and push to the review
    # app on PR syncronize a.k.a 'push'
    review_deploy:
    name: "Deploy to Heroku Review"
    if: github.event_name == 'pull_request' && github.event.action != 'closed'
    runs-on: ubuntu-latest
    env:
    HEROKU_APP_NAME: my-app-slug-review-pr-${{ github.event.number }}
    steps:
    - name: "Check out repository code"
    uses: actions/checkout@v3
    with:
    fetch-depth: 0
    ref: ${{ github.head_ref }}

    - name: Get git output names
    id: git
    shell: bash
    run: |
    if [[ "${{ github.ref }}" != "refs/tags/"* ]]; then
    if [[ ${{ github.event_name }} == 'pull_request' ]]; then
    echo "::set-output name=current_branch::$HEAD_REF"
    else
    echo "::set-output name=current_branch::$REF_BRANCH"
    fi
    else
    REF=$(printf "%q" "${{ github.ref }}")
    REF_BRANCH=${REF/refs\/tags\/${{ inputs.strip_tag_prefix }}/}
    echo "::set-output name=current_branch::$(eval printf "%s" "$REF_BRANCH")"
    fi
    # Wire up the github deployment so the links are valid for the PR under
    # `view deployment`
    - name: Create GitHub deployment
    uses: chrnorm/deployment-action@releases/v1
    id: deployment
    with:
    initial_status: "in_progress"
    token: "${{ github.token }}"
    target_url: https://${{ env.HEROKU_APP_NAME}}.herokuapp.com/
    environment: ${{ env.HEROKU_APP_NAME }}
    ref: ${{ github.head_ref }}

    - name: Login to Heroku
    uses: akhileshns/[email protected]
    with:
    heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
    heroku_email: ${{ secrets.HEROKU_EMAIL }}
    heroku_app_name: ${{ env.HEROKU_APP_NAME }}
    justlogin: true

    - name: Create Heroku app
    if: github.event.action == 'opened' || github.event.action == 'reopened'
    run: heroku apps:create ${{ env.HEROKU_APP_NAME }} --team=${{ env.HEROKU_TEAM }} ${{ env.HEROKU_REVIEW_APP_ADDONS }}

    - name: Add Heroku app to pipeline
    if: github.event.action == 'opened' || github.event.action == 'reopened'
    run: heroku pipelines:add ${{ env.HEROKU_PIPELINE_NAME }} --app=${{ env.HEROKU_APP_NAME }} --stage=development

    # This is where you set the environment variables that were part of the
    # original Heroku Config Vars for the Review App. Update this as
    # appropriate
    - name: Set the environment variables
    if: github.event.action == 'opened' || github.event.action == 'reopened'
    run: |
    env | grep AUTH0 > .env
    env | grep API_AUDIENCE >> .env
    env | grep HEALTHCLOUD >> .env
    env | grep SALESFORCE >> .env
    cat .env | tr '\n' ' ' | xargs heroku config:set --app=${{ env.HEROKU_APP_NAME }}
    rm -f .env
    - name: Add Heroku remote
    if: github.event.action != 'closed'
    run: heroku git:remote --app=${{ env.HEROKU_APP_NAME }}

    # Depending on how your heroku organization is setup, the step may not
    # be necessary. Previously in heroku review apps, the members of the
    # pipeline team would be automatically given ephemeral permissions on the
    # review app. That doesn't happen so this needs to be replicated.
    #
    # This will cause a lot of email notifications.
    - name: Set Member Permissions
    if: github.event.action != 'closed'
    shell: bash --noprofile --norc {0}
    run: |
    users=$(heroku members --team ${{ env.HEROKU_TEAM }} | grep "something appropriate" | awk '{ print $1 }')
    for user in ${users}
    do
    heroku access:add ${user} --app=${{ env.HEROKU_APP_NAME }} --permissions deploy,operate,manage
    heroku access:update ${user} --app=${{ env.HEROKU_APP_NAME }} --permissions deploy,operate,manage
    done
    exit 0
    - name: Push to Heroku
    if: github.event.action != 'closed'
    run: git push heroku ${{ github.head_ref }}:main --force

    # Update the github deployment with success and link to the environment
    - name: Update deployment status
    if: github.event.action != 'closed'
    uses: chrnorm/deployment-status@releases/v1
    with:
    token: "${{ github.token }}"
    target_url: https://${{ env.HEROKU_APP_NAME}}.herokuapp.com/
    environment_url: https://${{ env.HEROKU_APP_NAME}}.herokuapp.com/
    state: "success"
    deployment_id: ${{ steps.deployment.outputs.deployment_id }}

    # Once the PR is closed, destroy the app in heroku.
    #
    review_destroy:
    if: github.event_name == 'pull_request' && github.event.action == 'closed'
    name: "Cleanup and Remove Heroku Review"
    runs-on: ubuntu-latest
    env:
    HEROKU_APP_NAME: my-app-slug-review-pr-${{ github.event.number }}

    steps:
    - name: Login to Heroku
    uses: akhileshns/[email protected]
    with:
    heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
    heroku_email: ${{ secrets.HEROKU_EMAIL }}
    heroku_app_name: ${{ env.HEROKU_APP_NAME }}
    justlogin: true

    - name: Destroy Heroku app
    run: heroku apps:destroy --app=${{ env.HEROKU_APP_NAME }} --confirm=${{ env.HEROKU_APP_NAME }}