From 972cabd1900da5bc6a4272447f53e1e7975df4eb Mon Sep 17 00:00:00 2001 From: Darren Reid Date: Wed, 27 Nov 2024 13:40:27 +1100 Subject: [PATCH] fix-kamal-release --- .github/workflows/release.yml | 229 ++++++++++++---------------------- 1 file changed, 82 insertions(+), 147 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0fdcb44..8c02ef6 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,171 +3,106 @@ permissions: packages: write contents: write on: - # Triggered on new GitHub Release - release: - types: [published] - # Triggered on every successful Build action workflow_run: - workflows: ["Build"] - branches: [main,master] + workflows: ["Build Container"] types: - completed - # Manual trigger for rollback to specific release or redeploy latest + branches: + - main + - master workflow_dispatch: - inputs: - version: - default: latest - description: Tag you want to release. - required: true + +env: + DOCKER_BUILDKIT: 1 + KAMAL_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + KAMAL_REGISTRY_USERNAME: ${{ github.actor }} jobs: - push_to_registry: - runs-on: ubuntu-22.04 - if: ${{ github.event.workflow_run.conclusion != 'failure' }} + release: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - # Checkout latest or specific tag - - name: checkout - if: ${{ github.event.inputs.version == '' || github.event.inputs.version == 'latest' }} - uses: actions/checkout@v3 - - name: checkout tag - if: ${{ github.event.inputs.version != '' && github.event.inputs.version != 'latest' }} + - name: Checkout code uses: actions/checkout@v3 - with: - ref: refs/tags/${{ github.event.inputs.version }} - - # Assign environment variables used in subsequent steps - - name: Env variable assignment - run: echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - # TAG_NAME defaults to 'latest' if not a release or manual deployment - - name: Assign version + + - name: Set up environment variables run: | - echo "TAG_NAME=latest" >> $GITHUB_ENV - if [ "${{ github.event.release.tag_name }}" != "" ]; then - echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV - fi; - if [ "${{ github.event.inputs.version }}" != "" ]; then - echo "TAG_NAME=${{ github.event.inputs.version }}" >> $GITHUB_ENV - fi; - if [ ! -z "${{ secrets.APPSETTINGS_PATCH }}" ]; then - echo "HAS_APPSETTINGS_PATCH=true" >> $GITHUB_ENV + echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + echo "repository_name=$(echo ${{ github.repository }} | cut -d '/' -f 2)" >> $GITHUB_ENV + echo "repository_name_lower=$(echo ${{ github.repository }} | cut -d '/' -f 2 | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + echo "org_name=$(echo ${{ github.repository }} | cut -d '/' -f 1)" >> $GITHUB_ENV + if find . -maxdepth 2 -type f -name "Configure.Db.Migrations.cs" | grep -q .; then + echo "HAS_MIGRATIONS=true" >> $GITHUB_ENV else - echo "HAS_APPSETTINGS_PATCH=false" >> $GITHUB_ENV - fi; - + echo "HAS_MIGRATIONS=false" >> $GITHUB_ENV + fi + if [ -n "${{ secrets.KAMAL_DEPLOY_IP }}" ]; then + echo "HAS_DEPLOY_ACTION=true" >> $GITHUB_ENV + else + echo "HAS_DEPLOY_ACTION=false" >> $GITHUB_ENV + fi + + # This step is for the deployment of the templates only, safe to delete + - name: Modify deploy.yml + if: env.HAS_DEPLOY_ACTION == 'true' + run: | + sed -i "s/service: my-app/service: ${{ env.repository_name_lower }}/g" config/deploy.yml + sed -i "s#image: my-user/myapp#image: ${{ env.image_repository_name }}#g" config/deploy.yml + sed -i "s/- 192.168.0.1/- ${{ secrets.KAMAL_DEPLOY_IP }}/g" config/deploy.yml + sed -i "s/host: my-app.example.com/host: ${{ secrets.KAMAL_DEPLOY_HOST }}/g" config/deploy.yml + sed -i "s/MyApp/${{ env.repository_name }}/g" config/deploy.yml + - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - - name: Setup dotnet - uses: actions/setup-dotnet@v3 + username: ${{ env.KAMAL_REGISTRY_USERNAME }} + password: ${{ env.KAMAL_REGISTRY_PASSWORD }} + + - name: Set up SSH key + uses: webfactory/ssh-agent@v0.9.0 with: - dotnet-version: '8.0' - - - name: Install x tool - if: env.HAS_APPSETTINGS_PATCH == 'true' - run: dotnet tool install -g x - - - name: Apply Production AppSettings - if: env.HAS_APPSETTINGS_PATCH == 'true' - working-directory: ./MyApp - run: | - cat <> appsettings.json.patch - ${{ secrets.APPSETTINGS_PATCH }} - EOF - x patch appsettings.json.patch - - # Build and push new docker image, skip for manual redeploy other than 'latest' - - name: Build and push Docker image - run: | - dotnet publish --os linux --arch x64 -c Release -p:ContainerRepository=${{ env.image_repository_name }} -p:ContainerRegistry=ghcr.io -p:ContainerImageTags=${{ env.TAG_NAME }} -p:ContainerPort=80 + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - deploy_via_ssh: - needs: push_to_registry - runs-on: ubuntu-22.04 - if: ${{ github.event.workflow_run.conclusion != 'failure' }} - steps: - # Checkout latest or specific tag - - name: checkout - if: ${{ github.event.inputs.version == '' || github.event.inputs.version == 'latest' }} - uses: actions/checkout@v3 - - name: checkout tag - if: ${{ github.event.inputs.version != '' && github.event.inputs.version != 'latest' }} - uses: actions/checkout@v3 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 with: - ref: refs/tags/${{ github.event.inputs.version }} + ruby-version: 3.3.0 + bundler-cache: true - - name: repository name fix and env + - name: Install Kamal + run: gem install kamal -v 2.3.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: image=moby/buildkit:master + + - name: Kamal bootstrap + run: kamal server bootstrap + + - name: Check if first run and execute kamal app boot if necessary run: | - echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - echo "TAG_NAME=latest" >> $GITHUB_ENV - if [ "${{ github.event.release.tag_name }}" != "" ]; then - echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV - fi; - if [ "${{ github.event.inputs.version }}" != "" ]; then - echo "TAG_NAME=${{ github.event.inputs.version }}" >> $GITHUB_ENV - fi; + FIRST_RUN_FILE=".${{ env.repository_name }}" + if ! kamal server exec --no-interactive -q "test -f $FIRST_RUN_FILE"; then + kamal server exec --no-interactive -q "touch $FIRST_RUN_FILE" || true + kamal deploy -q -P --version latest || true + else + echo "Not first run, skipping kamal app boot" + fi - - name: Create .env file + - name: Ensure file permissions run: | - echo "Generating .env file" - - echo "# Autogenerated .env file" > .deploy/.env - echo "HOST_DOMAIN=${{ secrets.DEPLOY_HOST }}" >> .deploy/.env - echo "LETSENCRYPT_EMAIL=${{ secrets.LETSENCRYPT_EMAIL }}" >> .deploy/.env - echo "APP_NAME=${{ github.event.repository.name }}" >> .deploy/.env - echo "IMAGE_REPO=${{ env.image_repository_name }}" >> .deploy/.env - echo "RELEASE_VERSION=${{ env.TAG_NAME }}" >> .deploy/.env - - # Copy only the docker-compose.yml to remote server home folder - - name: copy files to target server via scp - uses: appleboy/scp-action@v0.1.3 - with: - host: ${{ secrets.DEPLOY_HOST }} - username: ${{ secrets.DEPLOY_USERNAME }} - port: 22 - key: ${{ secrets.DEPLOY_KEY }} - strip_components: 2 - source: "./.deploy/docker-compose.yml,./.deploy/.env" - target: "~/.deploy/${{ github.event.repository.name }}/" - - - name: Run remote db migrations - uses: appleboy/ssh-action@v0.1.5 - env: - APPTOKEN: ${{ secrets.GITHUB_TOKEN }} - USERNAME: ${{ secrets.DEPLOY_USERNAME }} - with: - host: ${{ secrets.DEPLOY_HOST }} - username: ${{ secrets.DEPLOY_USERNAME }} - key: ${{ secrets.DEPLOY_KEY }} - port: 22 - envs: APPTOKEN,USERNAME - script: | - set -e - echo $APPTOKEN | docker login ghcr.io -u $USERNAME --password-stdin - cd ~/.deploy/${{ github.event.repository.name }} - docker compose pull - export APP_ID=$(docker compose run --entrypoint "id -u" --rm app) - docker compose run --entrypoint "chown $APP_ID:$APP_ID /app/App_Data" --user root --rm app - docker compose up app-migration --exit-code-from app-migration + kamal server exec --no-interactive "mkdir -p /opt/docker/${{ env.repository_name }}/App_Data && chown -R 1654:1654 /opt/docker/${{ env.repository_name }}" - # Deploy Docker image with your application using `docker compose up` remotely - - name: remote docker-compose up via ssh - uses: appleboy/ssh-action@v0.1.5 - env: - APPTOKEN: ${{ secrets.GITHUB_TOKEN }} - USERNAME: ${{ secrets.DEPLOY_USERNAME }} - with: - host: ${{ secrets.DEPLOY_HOST }} - username: ${{ secrets.DEPLOY_USERNAME }} - key: ${{ secrets.DEPLOY_KEY }} - port: 22 - envs: APPTOKEN,USERNAME - script: | - echo $APPTOKEN | docker login ghcr.io -u $USERNAME --password-stdin - cd ~/.deploy/${{ github.event.repository.name }} - docker compose pull - docker compose up app -d + - name: Migration + if: env.HAS_MIGRATIONS == 'true' + run: | + kamal server exec --no-interactive 'echo "${{ env.KAMAL_REGISTRY_PASSWORD }}" | docker login ghcr.io -u ${{ env.KAMAL_REGISTRY_USERNAME }} --password-stdin' + kamal server exec --no-interactive "docker pull ghcr.io/${{ env.image_repository_name }}:latest || true" + kamal app exec --no-reuse --no-interactive --version=latest "--AppTasks=migrate" + + - name: Deploy with Kamal + run: | + kamal lock release -v + kamal deploy -P --version latest \ No newline at end of file