Публикация Go приложения в GitHub

от автора

Пост представляет собой контрольный список (checklist) и его реализацию при публикации Go приложения на Github’е.

TLDR:

  • Makefile как входная точка для выполнения основных действий

  • Линтеры и тесты как инструменты повышающие качество кода

  • Dockerfile как способ распространения приложения

  • Github Actions как возможность автоматической сборки и выкладки приложения при новых изменениях

  • Goreleaser как инструмент для публикации релизов и пакетов

Результатом должно быть опубликованное приложение с настроенными инструментами которые позволяют легко сопровождать приложение в процессе его существования. В качестве реального примера я буду рассматривать утилиту pgcenter.


Disclaimer: Приведенный список не являются абсолютной истинной и является лишь субъективным списком вещей к которым я пришел в процессе публикации приложении. Список может дополняться и изменяться. Все это одно большое ИМХО, если у вас есть альтернативный взгляд или вы уверены/знаете что какие-то вещи можно сделать еще лучше, обязательно дайте знать в комментах.

Будем двигаться от простого к сложному. В случае встречи имен файлов, все они взяты относительно корня проекта где находится код приложения.

Makefile

Makefile хранит в себе служебные сценарии которые приходится часто выполнять при разработке приложения:

  • выполнение линтеров и тестов

  • сборка приложения

  • запуск приложения

  • сборка артефактов типа пакетов, docker образов и т.п.

  • публикация артефактов в сторонние репозитории

  • выполнение операций относительно внешних систем, например SQL миграции если речь идет о корпоративных приложениях В Makefile складываются рутинные операции выполнять которые приходится регулярно. Основная цель Makefile это помочь вам не держать в голове все нужные команды, параметры и аргументы, а собрать и их в одном месте и при необходимости выполнить их и получить результат. Позже Makefile также будет основным сценарием для запуска этих же рутинных процедур в CI/CD. Минимальная версия Makefile может выглядеть так:

PROGRAM_NAME = pgcenter  COMMIT=$(shell git rev-parse --short HEAD) BRANCH=$(shell git rev-parse --abbrev-ref HEAD) TAG=$(shell git describe --tags |cut -d- -f1)  LDFLAGS = -ldflags "-X main.gitTag=${TAG} -X main.gitCommit=${COMMIT} -X main.gitBranch=${BRANCH}"  .PHONY: help clean dep build install uninstall   .DEFAULT_GOAL := help  help: ## Display this help screen. 	@echo "Makefile available targets:" 	@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "  * \033[36m%-15s\033[0m %s\n", $$1, $$2}'  dep: ## Download the dependencies. 	go mod download  build: dep ## Build pgcenter executable. 	mkdir -p ./bin 	CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} go build ${LDFLAGS} -o bin/${PROGRAM_NAME} ./cmd  clean: ## Clean build directory. 	rm -f ./bin/${PROGRAM_NAME} 	rmdir ./bin

Давайте разберем важные моменты в приведенном Makefile.

  • В начале файла указываем служебные переменные которые потребуются дальше. Переменных немного, называние программы и информация из Git которая используется при сборке. В частности коммит, тег и ветка которые через сборщика пробрасываются в код приложения и используются для формирования версии программы. При таком подходе версия всегда берется из Git и не нужно хранить строку с версией в коде приложения, помнить о ней, своевременно обновлять. Отмечу что проброс переменных не автоматическая магия, предполагается что в Git используются теги и также есть соответствующие правки в коде для приема Git переменных. Например 1 и 2.

  • Следующий интересный момент это пункт help, он реализует справку для нашего Makefile — обратите внимание на комментарии начинающиеся с двойной решетки после названий пунктов. Эти комментарии как раз и используются в качестве справки если вызвать make без аргументов или явно make help.

  • Следующие пункты являются базовыми для жизненного цикла Go приложения: загрузка зависимостей, сборка и очистка каталога где хранится собранная программа.

При желании список пунктов можно дополнить, что я и буду делать дальше по ходу текста.

Линтеры и тесты

Следующий шаг добавление линтеров. Основная задача линтеров проверять код на предмет «странных» конструкций которые не соответствуют принятым стилям программирования или даже хуже, могут быть неоптимальными и приводить к каким-либо ошибкам. Наличие линтеров позволяют поддерживать хорошее качество кода, ориентироваться на правильный стиль написания кода принятый в языке. Использование линтеров необязательно, но крайне желательно. В go есть масса разных линтеров, я пришел пока к использованию golangci-lint и gosec. Первый включает в себя большой набор разных линтеров (включены при этом не все), второй является линтером с уклоном в соблюдение правил информационной безопасности.

Выполнение линтеров также регулярная задача, поэтому также добавляем их в Makefile

lint: dep ## Lint the source files 	golangci-lint run --timeout 5m -E golint 	gosec -quiet ./...

В приведенном случае для выполнения golangci-lint выставлен таймаут, через флаг -E включаются дополнительные линтеры. Выполнение gosec особо ничем не примечательно, просто рекурсивный обход каталогов.

Очевидно что golangci-lint и gosec должны быть установлены в системе. Их установка проста, описывать тут не буду.

Также код сопровождается тестами, добавим и их выполнение тоже.

test: dep ## Run tests 	go test -race -p 1 -timeout 300s -coverprofile=.test_coverage.txt ./... && \     	go tool cover -func=.test_coverage.txt | tail -n1 | awk '{print "Total test coverage: " $$3}' 	@rm .test_coverage.txt

Команда запускает тесты, формирует файл с покрытием кода этими тестами и печатает результат покрытия.

Dockerfile

Следующим этапом является Dockerfile, который позволит собирать Docker образы нашего приложения.

# stage 1: build FROM golang:1.15 as build LABEL stage=intermediate WORKDIR /app COPY . . RUN make build  # stage 2: scratch FROM scratch as scratch COPY --from=build /app/bin/pgcenter /bin/pgcenter CMD ["pgcenter"]

Сборка образа состоит из двух этапов, сборка приложения и его копирование в пустой образ. Таким двухэтапным образом можно упаковывать приложения которые не требуют внешних зависимостей типа библиотек, сертификатов, других программ или чего-то аналогичного.

Помимо сборки Docker образа потребуется место откуда другие пользователи смогут его забрать, например этим местом может быть Docker Hub. Для размещения там образа потребуется аккаунт и реквизиты (логин/пароль).

Сборка и публикация образа также регулярная операция, поэтому добавляем команды в Makefile.

docker-build: ## Build docker image 	docker build -t lesovsky/pgcenter:${TAG} . 	docker image prune --force --filter label=stage=intermediate  docker-push: ## Push docker image to registry 	docker push lesovsky/pgcenter:${TAG}

Обратите внимание, что используется переменная TAG которая определяется в начале Makefile.

Github Actions

Теперь когда у нас есть способ для сборки приложения (Makefile) и публикации (Dockerfile), нам нужен механизм который поможет автоматически выполнять сборку и публикацию обновлений приложения. Здесь нам поможет Github Actions.

Однако в этом месте мы начинаем соприкасаться с организацией процесса, как новые изменения становятся частью существующего кода. Тема довольно обширная, об этом написано много постов, выработаны целые подходы со своими правилами. Поэтому те кто в теме уже и так все знают, а кто не в теме пусть отправляется на поиски чтива Git Flow, Github Flow, Gitlab Flow и их сравнение друг с другом.

В нашем случае все просто. Все изменения будут вливаться в master ветку. От ветки master мы создадим release ветку которая и будет источником релизов. Когда мы захотим сделать релиз, просто создадим новый тег и подтянем все изменения из master в release.

Теперь когда у нас есть понимание того как вносить изменения и делать релизы можно перейти к настройке workflow в Github Actions. Кто знает нормальный однословный русский перевод слову wokflow дайте знать в комментариях.

Будет два workflow:

  • Default (.github/workflows/default.yml) — это flow по-умолчанию, выполняет тесты при поступлении новых изменений.

  • Release (.github/workflows/release.yml) — это релизный flow делает тесты, сборку Docker образов и пакетов для пакетных менеджеров.

Этот workflow запускается при наступлении push и pull request событий в ветке master. Здесь всего одна задача (job), запуск линтеров и тестов. Github Actions имеет хорошие возможности для кастомизации и позволяют описывать очень сложные сценарии. В нашем случае таким примером кастомизации является запуск и выполнение в специально подготовленном контейнере где есть все необходимое для осуществления тестов.

Второй workflow.

--- name: Release  on:   push:     branches: [ release ]  jobs:   test:     runs-on: ubuntu-latest     container: lesovsky/pgcenter-testing:v0.0.1      steps:       - name: Checkout code         uses: actions/checkout@v2       - name: Prepare test environment         run: prepare-test-environment.sh       - name: Run lint         run: make lint       - name: Run test         run: make test    build:     runs-on: ubuntu-latest     needs: test     steps:       - name: Checkout code         uses: actions/checkout@v2         with:           fetch-depth: 0       - name: Build image         run: make docker-build       - name: Log in to Docker Hub         run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}       - name: Push image to Docker Hub         run: make docker-push    goreleaser:     runs-on: ubuntu-latest     needs: [ test, build ]     steps:       - uses: actions/checkout@v2         with:           fetch-depth: 0       - uses: actions/setup-go@v2         with:           go-version: 1.15       - uses: goreleaser/goreleaser-action@v2         with:           version: latest           args: release --rm-dist         env:           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Релизный workflow чуть больше, и запускается он при push событиях в release ветке. Этот workflow также включает в себя выполнение линтеров и тестов. Но также тут есть еще две задачи — build и goreleaser.

В задаче build выполняется сборка и публикация Docker образа. Обратите внимание, что используются секреты, которые предварительно нужно указать в разделе Secrets, в настройках Github репозитория.

В задаче goreleaser выполняется публикация релиза в разделе Releases репозитория. Настройки goreleaser определим позже. Здесь также используется секрет GITHUB_TOKEN его указывать нигде не нужно, он создается автоматически для нужд workflow.

Goreleaser

Последний шаг это публикация релиза и дополнительная сборка пакетов. Кроме Docker образов существуют способы распространения с помощью пакетных менеджеров. Наиболее распространенные это deb и rpm пакеты. Есть и другие варианты, но для меня они экзотичны
и их я рассматривать не буду. Для всего этого нам потребуется goreleaser который и сделает всю работу по сборке. Настройки определяются в .goreleaser.yml

before:   hooks:   - make dep  builds:   - binary: pgcenter     main: ./cmd     goarch:       - amd64     goos:       - linux     env:       - CGO_ENABLED=0     ldflags:       - -a -installsuffix cgo       - -X main.gitTag={{.Tag}} -X main.gitCommit={{.Commit}} -X main.gitBranch={{.Branch}}  archives:   - builds: [pgcenter]  changelog:   sort: asc  nfpms:   - vendor: pgcenter     homepage: https://github.com/lesovsky/pgcenter     maintainer: Alexey Lesovsky     description: Command-line admin tool for observing and troubleshooting Postgres.     license: BSD-3     formats: [ deb, rpm ]

Важным моментом является то что goreleaser не имеет интеграции с Makefile и сборку нужно описывать отдельно в формате goreleaser правил. Поэтому важно описать сборку точно так же как это осуществляется в Makefile, т.е. указать все те же флаги, переменные окружения и т.п. В секции nfpms описываем метаданные пакета и указываем необходимые форматы пакетов.

Собственно на этом все. Можно фиксировать изменения, пушить в репозиторий, перейти в Actions и наблюдать за тем как выполняются workflow. При успешном выполнении, можем создать новый тег, и влить изменения в релизную ветвь и также понаблюдать за прогрессом в Actions.

Спасибо за внимание, если у кого есть дополнения, замечания, вопросы, пожелания — Go в комменты.

Ссылки

ссылка на оригинал статьи https://habr.com/ru/post/542410/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *