Я делал кучу работы с Действия GitHub Недавно от Развертывание функций Azure к Ремонт моего блога Но каждая из этих рабочих процессов была довольно прямой, просто построить и развернуть все с одним рабочим процессом.
С моим последним проектом Fsharp. Cosmosdb Я хотел использовать действия GitHub, но рабочий процесс, который я хочу, это немного сложнее. Для других проектов OSS, таких как dotnet-delice Рабочий процесс работает так: я нажимаю владелец Он будет компилировать приложение, создать пакеты Nuget, а затем ждать, чтобы я одобрил релиз, прежде чем нажать Nuget, создавая релиз Github и пометить правильное Commit. Это дает мне уровень контроля от случайных толкаков к Мастер И я с этим справляюсь через Лазурный трубопровод Что поддерживает простое одобрение потока, нажав кнопку «Утвердить».
Но на данный момент действия GitHub не имеют функциональности, чтобы делать одобрения, поэтому я создал меня собственным! Если вы просто хотите увидеть последние части вот построить рабочий процесс и Отпустите рабочий процесс , но вы захотите прочитать, чтобы понять, как они работают. 😊
Определение нашего рабочего процесса
Идея этого рабочего процесса — это то, что я думаю, что я думаю, что довольно распространена в проектах с открытым исходным кодом, я хочу иметь сборку и упаковку в качестве одного рабочего процесса, а затем эти активы были доступны для людей, которые будут потреблять и тестировать, затем на основе обратной связи (выпуск Это хорошо или нет) Это будет «продвигается» к официальному репозитории пакета, создается релиз GitHub, коммиты отмечены, все такое дело.
Сборка будет довольно прямо вперед, я использую Подделка Чтобы сценарировать рабочий процесс сборки, и я использую изменение изменений в следующем Держите изменяемый файл определить выпуск и его детали. Работа выглядит так:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Setup Dotnet ${{ env.DOTNET_VERSION }}
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore dotnet tools
run: dotnet tool restore
- name: Generate packages
run: dotnet fake run ./build.fsx --target Release
Теперь у меня есть свои артефакты, я хочу настроить некоторые метаданные, которые будут доступны, такие как номер версии из ChangeLog. Для этого я создал специальную поддельную задачу:
let getChangelog() =
let changelog = "CHANGELOG.md" |> Changelog.load
changelog.LatestEntry
Target.create "SetVersionForCI" (fun _ ->
let changelog = getChangelog()
printfn "::set-env name=package_version::%s" changelog.NuGetVersion)
Обратите внимание, как это делает printfn. :: set-env ? Вот как вы создаете свои собственные переменные среды, и она удобно работает из любого места, который пишет на stdout Отказ
С этим прочитаем мы можем добавить его в рабочий процесс:
- name: Set Version
run: dotnet fake run ./build.fsx --target SetVersionForCI
- name: Create version file
run: echo ${{ env.package_version }} >> ${{ env.OUTPUT_PATH }}/version.txt
- name: Publish release packages
uses: actions/upload-artifact@v1
Одобрения через проблемы GitHub
Когда я думал о том, как делать одобрения, я думал: «Что в Github вы бы использовали для обсуждения и одобрения?» И есть очевидный ответ, проблемы! Моя мысль заключается в том, что если я смогу автоматически автоматизировать создание проблемы и метить ее соответствующим образом, я могу использовать Действия GitHub Trigger Выпуск помечен мониторировать для определенной этикетки, чтобы выключить их. В моем случае я собираюсь иметь ярлык Одобрено освобождение И как только эта метка применяется, я хочу запустить рабочий процесс, чтобы освободить пакеты.
Создание проблем с действиями GitHub
Если вы посмотрите на Действия Marketplace Есть много действий по созданию проблемы, но у меня будет несколько странных требований, поэтому я решил построить свои собственные (также, я не построил свои собственные действия, чтобы это был еще один хороший шанс учиться). Это (а другие, которые мы будем строить) являются частью моего Git Reppo, а не на рынке, поэтому они будут жить в ..Github/Действия Папка, наряду с рабочими процессами, и они будут написаны в Teadercript.
Сначала я бы порекомендовал, чтобы вы прочитали Как создать действие Если вы не сделали один раньше, как он будет говорить через руководство по установке и файлам, которые вам понадобятся.
Поскольку мы будем работать с проблемами GitHub, нам понадобится токен доступа, который удобно доступен в виде секретной переменной секреты. Github_token. И я собираюсь пройти еще два аргумента, идентификатор текущего действия ( github.run_id ) и версия выпуска ( env.package_version ).
Начнем с создания наших пустых действий:
import * as core from "@actions/core";
import * as github from "@actions/github";
import * as fs from "fs";
async function run() {}
run();
И теперь мы можем начать заполнять Беги Функция:
async function run() {
const token = core.getInput("token");
const octokit = new github.GitHub(token);
const context = github.context;
}
Это дает нам доступ к API GitHub через октокит . Теперь я хочу, чтобы изменяемость, как я хочу бросить, что в тело вопроса, которую мы создаем (поэтому, одобряя, я могу отработать, что находится в выпуске):
async function run() {
const token = core.getInput("token");
const octokit = new github.GitHub(token);
const context = github.context;
const changelog = fs.readFileSync("./.nupkg/changelog.md", {
encoding: "UTF8",
});
}
Примечание. Этот файл создается одним из моих поддельных задач и содержит только текущая версия ChangeLog, не полная история, как корень CHANGELOG.md содержит.
Теперь, чтобы создать проблему:
async function run() {
const token = core.getInput("token");
const octokit = new github.GitHub(token);
const context = github.context;
const changelog = fs.readFileSync("./.nupkg/changelog.md", {
encoding: "UTF8",
});
const newIssue = await octokit.issues.create({
...context.repo,
labels: [`awaiting-review`, "release-candidate"],
title: `Release ${core.getInput("package-version")} ready for review`,
body: `# :rocket: Release ${core.getInput(
"package-version"
)} ready for review
## Changelog
--------
${changelog}
`,
});
}
Потому что у нас есть context.repo Чтобы дать нам информацию о текущем репо GitHub, я просто распространяю ( ... context.repo ) что на вход otcokit.issues.create. а затем дать ему еще несколько предметов информации, этикетки Ожидание - Обзор и релиз-кандидат , название и тело, которое содержит изменяемый файл. Эти этикетки полезны для меня создавать фильтры в вопросах GitHub, и я могу искать их в будущем рабочем процессе.
И теперь мы закончили, пришло время подключить его к нашему рабочему процессу сборки:
- name: Prepare create release issue action
uses: actions/setup-node@v1
with:
node-version: "12.x"
- name: Building Action
run: npm i && npm run build
working-directory: ./.github/actions/create-issue
- name: Create Release Issue
uses: ./.github/actions/create-issue
with:
token: ${{ secrets.GITHUB_TOKEN }}
action-id: ${{ github.run_id }}
package-version: ${{ env.package_version }}
Так как я решил сделать эти типографы Я должен добавить 2 дополнительных шага в рабочий процесс, один на настройку Node.js и один для компиляции действия, но важный материал находится в 3-м действии. Как это местное действие использовать Указывает на каталог, в котором оно живет, который является абсолютным путем от корня Git Reppo (Так что вам не нужно использовать .gitub/Действия , но я люблю держать их всех вместе).
И там мы идем, рабочий процесс создает нашу проблему (да, это закрыто, потому что я уже одобрил его):
Утверждение релизов
Это оказалось немного ударом, чем я надеялся, поэтому я надеюсь, что это поможет вам избежать некоторых проблем, которые я ударил с этим шагом. Во-первых, мы используем Выпуск помечен Мероприятие в Действиях GitHub, которые запускают каждый раз, когда вы обследуете проблему, поэтому, если вы используете проблемы, в значительной степени вашу историю действий, скорее всего, станет довольно шумным. Это означает, что вам нужно будет думать о том, как пройти только, когда правильно Добавлена метка, чтобы сделать то, что я создал действие, чтобы проверить, имеет ли проблема определенной этикетки:
import * as core from "@actions/core";
import * as github from "@actions/github";
async function run() {
const token = core.getInput("token");
const octokit = new github.GitHub(token);
const context = github.context;
if (!context.payload.issue) {
throw new Error("This should not happen");
}
const issue = await octokit.issues.get({
...context.repo,
issue_number: context.payload.issue.number,
});
core.setOutput(
"exists",
issue.data.labels
.some((label) => label.name === core.getInput("label"))
.toString()
);
}
run();
Действие достаточно прямо вперед, он будет схватить проблему, которая вызвала рабочий процесс из контекста действия и посмотреть, если этикетка передается в действие, присутствовало, и установить параметр выходного сигнала, указывающий его присутствие. Мы будем использовать действие, как так:
- name: Check issue was release issue
uses: ./.github/actions/check-issue
id: check-issue
with:
token: ${{ secrets.GITHUB_TOKEN }}
label: release-candidate
Не забудьте установить пакеты и сначала построить действие, я только что пропустил это для краткости здесь.
Проблема в том, что теперь теперь каждое действие после этого нам нужно проверить вывод, чтобы решить, хотите ли мы запустить его, то есть мы добавляем Если: shads.Check-yumpe.outputs.exists каждому действию, которое раздражает. Если кто-то знает, как улучшить, что я все уши!
Получение релиз артефактов
Поскольку фаза сборки создал артефакты, и мы могли бы запустить ряд рабочих процессов, с тех пор нам нужно получить правильно артефакты. В прошлом я использовал Загрузка-артефакт и Скачать-артефакт Чтобы обработать это (и в сборке Workflow, который я использовал Загрузка-артефакт ) Но вот проблема, загрузка ожидает скачать от текущего рабочего процесса , Но я не в рабочем процессе, что артефакт был создан, я в совершенно новом, так как я узнаю, какие артефакты, чтобы получить?
Для этого мы собираемся обновить Создать-выпуск Действие, которое мы создали ранее, чтобы включить идентификатор действия в нем где-то. Изначально я думал сделать это как этикетку, поэтому у вас будет ярлык, как Действие: , но на оживленном хранилище вероятно, что это будет быстро раздражать, так как каждая этикетка — это однометражное использование, и они не удаляются автоматически. Поэтому вместо этого давайте создадим комментарий к проблеме с идентификатором действия. Сразу после того, как мы создали проблему, мы добавим это:
await octokit.issues.createComment({
...context.repo,
issue_number: newIssue.data.number,
body: `Action: ${core.getInput("action-id")}`,
});
С комментарием добавлено, мы создадим другое действие, чтобы извлечь его, я назвал это Get-Action-ID :
import * as core from "@actions/core";
import * as github from "@actions/github";
async function run() {
const token = core.getInput("token");
const octokit = new github.GitHub(token);
const context = github.context;
if (!context.payload.issue) {
throw new Error("This should not happen");
}
const comments = await octokit.issues.listComments({
...context.repo,
issue_number: context.payload.issue.number,
});
const actionComment = comments.data.find(
(comment) => comment.body.indexOf("Action: ") >= 0
);
if (!actionComment) {
throw new Error("No comment found that has the right pattern");
}
core.setOutput("id", actionComment.body.replace("Action: ", "").trim());
}
run();
Опять же, все происходит в контексте проблемы, поэтому мы знаем, где посмотреть комментарии, которые мы делаем с OctoKit.issues.ListComments И тогда от этого мы посмотрим комментарий, который соответствует шаблону, который мы ожидаем, начать с Действие: . Если это найдено, мы сможем вытащить идентификатор из него и выдвинуть его как выходную переменную!
- name: Get the ID of the Action
uses: ./.github/actions/get-action-id
if: steps.check-issue.outputs.exists == 'true'
id: get-action-id
with:
token: ${{ secrets.GITHUB_TOKEN }}
С идентификатором действия в руке мы теперь можем скачать действия, и для этого я решил ленивый и просто написать встроенный скрипт Bash:
- name: Download packages
if: steps.check-issue.outputs.exists == 'true'
run: |
echo ${{ steps.get-action-id.outputs.id }}
mkdir ${{ env.OUTPUT_PATH }}
cd ${{ env.OUTPUT_PATH }}
curl https://api.github.com/repos/aaronpowell/FSharp.CosmosDb/actions/runs/${{ steps.get-action-id.outputs.id }}/artifacts --output artifacts.json
downloadUrl=$(cat artifacts.json | jq -c '.artifacts[] | select(.name == "packages") | .archive_download_url' | tr -d '"')
echo $downloadUrl
curl $downloadUrl --output packages.zip --user octocat:${{ secrets.GITHUB_TOKEN }} --verbose --location
unzip packages.zip
ls
Ой, это сложно, давайте сломаемся. Я начинаю с небольшого количества диагностики информации, поэтому я могу посмотреть, какой идентификатор действия есть, а затем создать местоположение, в которое я хочу выбросить файлы. Далее нам нужно получить информацию о артефактах для выпуска:
curl https://api.github.com/repos/aaronpowell/FSharp.CosmosDb/actions/runs/${{ steps.get-action-id.outputs.id }}/artifacts --output artifacts.json
Мы схватываем вывод предыдущего шага и вызов на API GitHub и вернувшимся JSON так:
{
"total_count": 1,
"artifacts": [
{
"id": 2861674,
"node_id": "MDg6QXJ0aWZhY3QyODYxNjc0",
"name": "packages",
"size_in_bytes": 39715,
"url": "https://api.github.com/repos/aaronpowell/FSharp.CosmosDb/actions/artifacts/2861674",
"archive_download_url": "https://api.github.com/repos/aaronpowell/FSharp.CosmosDb/actions/artifacts/2861674/zip",
"expired": false,
"created_at": "2020-03-13T03:37:13Z",
"updated_at": "2020-03-13T03:37:14Z"
}
]
}
Я хочу archive_download_url от артефакта по имени Пакеты и делать то, что я снова сложно и использовал JQ Чтобы найти это:
downloadUrl=$(cat artifacts.json | jq -c '.artifacts[] | select(.name == "packages") | .archive_download_url' | tr -d '"')
Так как это будет » вокруг него я использую тр чтобы разделить их.
Наконец, мы загружаем ZIP-пакет из этого местоположения, используя CURL, но вам нужно аутентифицировать этот запрос, поэтому мы передаем - Прозеро восьмикат: $ {{secrets.github_token}} чтобы скручиваться, а также --локация Чтобы сказать ему, чтобы следить за перенаправлением 302. И с загруженным пакетом мы можем расстегнуть его И я просто бегаю Ls сделать еще больше лесозаготовки.
Публикация на Nuget
С загрузкой пакетов мы можем начать толкать их в различные каналы, давайте начнем с Nuget. Я не чувствовал необходимости использовать третьего действия для этого, так как вам нужно только запустить Dotnet Nuget Push (Примечание. Вам понадобится токен доступа Nuget, поэтому поп-один в ваших секретах), но то, что мне нужно было узнать, было то, что был номер версией, чтобы поставить в путь к файлу при публикации.
К счастью, я создал файл, который я толкнул в список артефактов под названием version.txt который содержит номер версии из CHANGELOG.md . Давайте превратимся в переменную среды:
- name: Get release version
if: steps.check-issue.outputs.exists == 'true'
working-directory: ${{ env.OUTPUT_PATH }}
run: |
version=$(cat version.txt)
echo "::set-env name=package_version::$version"
Хороший Ол ‘ кот в помощь. Тогда мы можем настроить среду Dotnet и нажмите Nuget:
- name: Setup Dotnet ${{ env.DOTNET_VERSION }}
uses: actions/setup-dotnet@v1
if: steps.check-issue.outputs.exists == 'true'
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Push NuGet Package
if: steps.check-issue.outputs.exists == 'true'
working-directory: ${{ env.OUTPUT_PATH }}
run: |
dotnet nuget push FSharp.CosmosDb.${{ env.package_version }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source ${{ env.NUGET_SOURCE }}
dotnet nuget push FSharp.CosmosDb.Analyzer.${{ env.package_version }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source ${{ env.NUGET_SOURCE }}
И с этим у нас есть пакеты на ногу.
Режущий релиз
Последнее, что нужно сделать, это создать выпуск GitHub, который будет означать, что мы должны знать, что SHA создается сборка. Первоначально я думал сделать это, добавил его к комментариям вопроса (который я все еще делаю) но тогда я понял, что знаю идентификатор оригинального рабочего процесса Так что я могу просто потянуть метаданные оттуда:
- name: Get Action sha
if: steps.check-issue.outputs.exists == 'true'
run: |
echo ${{ steps.get-action-id.outputs.id }}
cd ${{ env.OUTPUT_PATH }}
curl https://api.github.com/repos/aaronpowell/FSharp.CosmosDb/actions/runs/${{ steps.get-action-id.outputs.id }} --output run.json
action_sha=$(cat run.json | jq -c '.head_sha' | tr -d '"')
echo "::set-env name=action_sha::$action_sha"
Снова есть немного JQ Разборки вывода, чтобы найти его, но теперь у нас есть полный SHA в переменной окружающей среды, поэтому мы можем создать выпуск, который я определил пользовательское действие для (в основном для того, чтобы соответствовать пути Я Хотите, чтобы это структурировано, но вы можете использовать один с рынка, если вы предпочитаете).
На этот раз давайте сначала посмотрим на использование действия:
- name: Cut GitHub Release
uses: ./.github/actions/github-release
if: steps.check-issue.outputs.exists == 'true'
with:
token: ${{ secrets.GITHUB_TOKEN }}
sha: ${{ env.action_sha }}
version: ${{ env.package_version }}
path: ${{ env.OUTPUT_PATH }}
Результат работы он увидит, как этот релиз:
Это действие мы создадим релиз для правого SHA, а затем загрузите его файлы (я не передаю в файлы, я их жестко кодирую), так что давайте посмотрим на Беги Функция:
async function run() {
const token = core.getInput("token");
const sha = core.getInput("sha");
const version = core.getInput("version");
const artifactPath = core.getInput("path");
const releaseNotes = readFileSync(join(artifactPath, "changelog.md"), {
encoding: "UTF8",
});
const octokit = new github.GitHub(token);
const context = github.context;
const release = await octokit.repos.createRelease({
...context.repo,
tag_name: version,
target_commitish: sha,
name: `Release ${version}`,
body: releaseNotes,
});
await upload(
octokit,
context,
release.data.upload_url,
join(artifactPath, `FSharp.CosmosDb.${version}.nupkg`)
);
await upload(
octokit,
context,
release.data.upload_url,
join(artifactPath, `FSharp.CosmosDb.Analyzer.${version}.nupkg`)
);
}
otcokit.repos.createrease Наш первый главный шаг мы используем текущий контекст для установки информации о репозитории, а затем установить Tag_Name к версии, определенной в нашем изменении изменений и target_commitish вправо SHA, которое создаст для нас Git для нас (приятно!) И тогда мы устанавливаем название и, наконец, тело, я просто впрыскивая изменяемый файл (который заставит меня написать достойный изменение!).
Когда речь идет о прикреплении файлов к выпуску, это то, что вам нужно сделать для каждого файла после создания выпуска, так как создание выпуска дает вам upload_url Для того, где файлы должны быть опубликованы. Так как я делаю это несколько раз, я вытащил функцию, чтобы справиться с ним под названием загрузить :
async function upload(
octokit: github.GitHub,
context: Context,
url: string,
path: string
) {
let { name, mime, size, file } = fileInfo(path);
console.log(`Uploading ${name}...`);
await octokit.repos.uploadReleaseAsset({
...context.repo,
name,
file,
url,
headers: {
"content-length": size,
"content-type": mime,
},
});
}
Это использует otcokit.repos.uploadreleaseset Функция для отправки файла, и нам нужно предоставить ему размер ( длина содержимого ) и тип MIME ( тип контента ), который я прохожу с функцией, которая захватывает информацию о файлах . FileInfo :
function mimeOrDefault(path: string) {
return getType(path) || "application/octet-stream";
}
function fileInfo(path: string) {
return {
name: basename(path),
mime: mimeOrDefault(path),
size: lstatSync(path).size,
file: readFileSync(path),
};
}
Чтобы получить тип MIME, я использую MIME Упаковка NPM, но я мог бы сильно закодировать его, так как я в любом случае, что мне тяжело кодируйте файлы, но это было просто привычкой. В противном случае я использую lstatsync. и Readfilesync от узла ФС модуль.
И с тем, что выпуск создан, и пакеты доступны для людей вручную, если они не хотят использовать Nuget по какой-то причине.
Закрытие проблемы
Последнее, что я хотел автоматизировать, — это закрытие проблемы, используемую для управления рабочим процессом. К настоящему времени я был на рулоне создания пользовательских действий, поэтому я создал еще один (я также не нашел, чтобы просто закрыть проблему, все они были за несвежие проблемы или PR, но, возможно, я не выглядел достаточно сложным).
async function run() {
const token = core.getInput("token");
const octokit = new github.GitHub(token);
const context = github.context;
if (!context.payload.issue) {
throw new Error("This should not happen");
}
await octokit.issues.createComment({
...context.repo,
issue_number: context.payload.issue.number,
body: core.getInput("message"),
});
await octokit.issues.update({
...context.repo,
issue_number: context.payload.issue.number,
state: "closed",
});
}
Для удобства мы добавляем комментарий к проблеме, используя предоставленное сообщение, сделанное через otcokit.issues.createComment , а затем обновление статуса выпуска, используя otcokit.issues.update и установка государство: «Закрыто» Отказ Из нашего файла рабочего процесса мы можем использовать это так:
- name: Close issue
uses: ./.github/actions/close-issue
if: steps.check-issue.outputs.exists == 'true'
with:
token: ${{ secrets.GITHUB_TOKEN }}
message: |
The release has been approved and has been
* Deployed to NuGet
* Created as a Release on the repo
* Commit has been tagged
И с этим, как только проблема помечена Одобрено освобождение Мои взаимодействия сделаны!
Вывод
Моя цель в начале было создание рабочего процесса на основе одобрения с действиями GitHub, и я довольно рад, что я смог сделать это. Вы можете найти самые последние (на момент написания) построить и Выпуск Проходит, и если вы посмотрите в закрытые проблемы, вы найдете их там тоже.
Это немного громоблокируется, хотя, без встроенного одобрения поддержки было много пользовательских действий, которые я оказался письмом (мое репо, теперь докладывается 10% кодовой базы, является Typearcript 🤣) Поэтому я надеюсь, что это функция на их дорожной карте.
Кроме того, это не на 100% дурака. На данный момент я не проверяю этикетки правильно, он должен проверить на Одобрено освобождение этикетка, а также релиз-кандидат Потому что, если бы я поставил другой ярлык, это просто пробежит. Но так как я единственный вкладчик здесь, я меньше беспокоюсь об этом в данный момент.
В целом я доволен тем, как это работает, и я надеюсь, что это даст вам понимание того, как у вас тоже может быть рабочий процесс на основе одобрения, используя действия GitHub.
Оригинал: «https://dev.to/azure/approval-workflows-with-github-actions-3bob»