Автоматизация release-cycle

Итак, дано:
внутренний SDK компании, от которой будет зависеть несколько команд. Система релизов очень сырая и все делается руками (обновить версию в package.json, добавить тег, смержить ветку, создать релиз на github).
Хотелось бы автоматизировать этот процесс, чтобы не приходилось напоминать команде о необходимости релизов.

В идеале хотелось бы, чтобы в момент нажатия на кнопку merge автоматически считывалась текущая версия, вынимался changelog из пул-реквеста и создавался соответствующий релиз.

Посоветуйте тулзы или расскажите, как это работает у вас.

Контекст. Я делал сравнение разных CI/CD решений для автоматизации наших систем (сборка, деплой java бекендов с ts/angular фронтендов). Смотрел на штуки типа github action, gitlab actions, jenkins, bamboo и иже с ними. В итогде остановился на github actions как самом дешевом и гибком варианте. Думаю что эта платформа отлично подойдет под ваш случай так как вы уже работаете с гитхабом. Если имеешь представление о том как работает bash, linux, command line и docker, то за 2-3 дня можно получить рабочую демку для сборки вашего проекта. Система достаточно простая чтобы разработчик мог сам накрапать решение, и достаточно популярная чтобы нагуглить ответы на свои вопросы.

Ответ. Я бы решал твою задачу через github actions (есть путанье в терминологии в одном месте они называются github workflow, в других github actions. Я продолжу называть их github actions). Ниже опишу элементы из которых будет состоять ваше решение, и в каких условиях оно будет запускаться. Еще ниже покажу каким скриптом собираю и деплою наш проект.

Триггер. Можно делать экшены которые будут запускаться по пушу в ветку, по коммиту, по мержу а так же есть ручные экшены. Ручным экшенам можно задать ввод. Я реализовывал ручной экшен для деплоя на test окружение, ввод для него был название git объекта: коммита, ветки, тега.

Тело экшенов. В теле экшенов у тебя будет микс из уже готовы экшенов (например установка nodejs и npm нужных версий) и bash скриптов. Все это дело будет запускаться в докере на серверах гитхаба. Кстати, платить вы будете по затраченному времени этими серверами. Тех бесплатных минут что идут из коробки будет достаточно чтобы поэкспериментировать и собирать ваш проект. Есть варианты оптимизации этого времени через хостинг воркеров (в которых запускаются шаги github actions) на своем хостинге.

Из готовых экшенов, есть, например работа с гитом: чекаут определенной версии, чекают всего дерева или с глубиной в 1, создание нового коммита, создание релиза. Большой бонус что github actions хорошо интегрированы с самим гитхабом: т.е. merge request, релизы - это все можно найти как реализовать.

В теле экшенов тебе будут доступен богатый набор переменных окружения (от названия ветки до пользовательского инпута для ручных экшенов). Секреты, такие как ssh ключи и пароли хранятся уровне “secrects” самого репозитория и подтягиваются в экшенах через те же переменные окружения. Например, я храню ssh данные для того чтобы экшен ходил на наш сервер и запускал там скрипты бекапа и рестарта.

Из явных нюансов: сделать так чтобы github actions запускались локально можно (GitHub - nektos/act: Run your GitHub Actions locally 🚀), но это требует работы по воссозданию переменных окружения которые используют твои экшены.

Там еще куча нюансов и приколов с системой, давай пообщаемся про то что интересует тебя.

Вот код github actions которым мы пользуемся для деплоя приложения в тестовое окружение.

name: Deploy manually to test
on:
  workflow_dispatch:
    inputs:
      gitEntity:
        description: 'Branch name, commit hash or tag'
        required: true
        default: 'development'
env:
  SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
  SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
  MAIL_APP_NAME: Hours Administration (BE/FE)
  MAIL_APP_URL: https://ppp.metafactory.io/
  MAIL_RECIPIENTS: a@mail.com
jobs:
  deploy-be:
    concurrency:
      group: hours-be
      cancel-in-progress: true
    runs-on: ubuntu-latest
    env:
      SSH_USER: ${{ secrets.SSH_USER_BE }}
      SSH_SERVER: ${{ secrets.SSH_SERVER_BE }}
      JAR_SERVER: hoursadministration.jar
    steps:
      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh
          echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
          sudo chmod 600 ~/.ssh/id_rsa
          echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
      - name: Check out code
        uses: actions/checkout@v2
        with:
          ref: ${{ github.event.inputs.gitEntity }}
      - name: Set up JDK 11
        uses: actions/setup-java@v2
        with:
          java-version: '11'
          distribution: 'adopt'
          cache: maven
      - name: Build with Maven
        run: mvn -Pprod clean package
      - name: Backup server's application.yml
        run: ssh ${SSH_USER}@${SSH_SERVER} "mv ./application/config/application.yml ./application/config/application.yml-$(date +"%Y-%m-%d_%H%M%S")"
      - name: Upload application.yml
        run: scp ./config/application.yml ${SSH_USER}@${SSH_SERVER}:/var/virtual_hosts/hours-backend-test.metafactory.io/application/config/application.yml
        working-directory: ./hours-administration
      - name: Stop server if running
        run: ssh ${SSH_USER}@${SSH_SERVER} "cd application && ./stop.sh"
      - name: Persist previous .jar
        run: ssh ${SSH_USER}@${SSH_SERVER} "cp application/${JAR_SERVER} application/application_backup/${JAR_SERVER}-$(date +\"%Y-%m-%d_%H%M%S\")"
      - name: Backup current DB in the server
        run: ssh ${SSH_USER}@${SSH_SERVER} "cd application && ./backup_database.sh"
      - name: Upload new jar
        run: scp target/hoursadministration-0.0.1-SNAPSHOT.jar ${SSH_USER}@${SSH_SERVER}:/var/virtual_hosts/hours-backend-test.metafactory.io/application/${JAR_SERVER}
        working-directory: ./hours-administration
      - name: Start server
        run: ssh ${SSH_USER}@${SSH_SERVER} '((cd application && ./start.sh) &)'
  deploy-fe:
    concurrency:
      group: hours-fe
      cancel-in-progress: true
    runs-on: ubuntu-latest
    env:
      SSH_USER: ${{ secrets.SSH_USER_FE }}
      SSH_SERVER: ${{ secrets.SSH_SERVER_FE }}
    steps:
      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh
          echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
          sudo chmod 600 ~/.ssh/id_rsa
          echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
      - name: Check out code
        uses: actions/checkout@v2
        with:
          ref: ${{ github.event.inputs.gitEntity }}
      - name: Set up nodejs@14
        uses: actions/setup-node@v2
        with:
          node-version: '14'
          cache: 'npm'
          cache-dependency-path: hours-angular/package-lock.json
      - name: Install npm dependencies
        run: npm install
        working-directory: ./hours-angular
      - name: Build with npm
        run: npm run build
        working-directory: ./hours-angular
      - name: Deploy assets
        run: rsync -avz --delete --exclude '.git' build/resources/main/static/ ${SSH_USER}@${SSH_SERVER}:/var/virtual_hosts/hours-angular-test.metafactory.io/application
        working-directory: ./hours-angular
  notify-success:
    needs:
      - deploy-fe
      - deploy-be
    runs-on: ubuntu-latest
    env:
      SSH_USER:  ${{ secrets.SSH_USER_BE }}
      SSH_SERVER:  ${{ secrets.SSH_SERVER_BE }}
    steps:
      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh
          echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
          sudo chmod 600 ~/.ssh/id_rsa
          echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
      - name: Send email notification about success
        run: ssh ${SSH_USER}@${SSH_SERVER} "echo -e \"${GITHUB_ACTOR} manually deployed \"${{ github.event.inputs.gitEntity }}\" of ${MAIL_APP_NAME} to ${SSH_SERVER} on $(date +'%Y-%m-%d %H:%M:%S').\n\nCheck ${MAIL_APP_URL}\" | mail -s \"${MAIL_APP_NAME} is manually deployed by ${GITHUB_ACTOR}\" -r no-reply@${SSH_SERVER} ${MAIL_RECIPIENTS}"
1 симпатия

Окей, в общем и целом я примерно так и думал. :)
Разве что мой yaml не очень хорош и придется разбираться. Спасибо!

Когда разбирался с yaml мне помогли штуки подобные этой: https://www.json2yaml.com/convert-yaml-to-json. Пишешь ямл, сразу видишь как выглядит результат в json. За несколько итераций мозг привыкает. Плюс я сталкивался с багами из-за неверных отступов. В той ситуации визуализация в json тоже очень помогла.

В общем, в первой итерации решил ограничиться чем-нибудь вроде вот такого:

name: "Create release from a PR"
on:
  push:
    branches:
      - main

jobs:
  build:
    name: Create Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@master
      - name: get-npm-version
        id: package-version
        uses: martinbeentjes/npm-get-version-action@master
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: v${{ steps.package-version.outputs.current-version }}
          release_name: v${{ steps.package-version.outputs.current-version }}
          body: |
            Changes in this Release
            ${{github.event.head_commit.message}}
          draft: false
          prerelease: false

А потом буду потихоньку добавлять функционал

1 симпатия