跳到主要内容

创建 Go Actions

· 阅读需 8 分钟
Zetta
Technical Oversight Committee
techknowlogick
Technical Oversight Committee
pat-s
Gitea maintainer
本文由 AI 翻译,如有歧义请以 英文原文 为准。

Gitea 的 CI 系统 Gitea Actions 最近在 1.19.0 版本中发布。Gitea Actions 是一个内置的 CI/CD 系统,与 GitHub Actions 几乎兼容。一个关键区别是,Gitea 同时支持 Go action 和 JavaScript action,而 GitHub 仅原生支持 JavaScript action(不过你可以使用任何编程语言来编写 Docker 容器 action)。

Go action 为不熟悉 JavaScript 的 Go 开发者提供了一种创建原生 action 的方法。 然而,与 JavaScript action 相比,Go action 可能更慢,因为它们在运行前需要被构建为可执行文件。但你无需在 Go action 中将生成的 JavaScript 文件放入 dist。

在本指南中,我们将展示如何在 Gitea 中创建 Go action。在开始之前,你应该对 Gitea Actions 有基本的了解。如果你还不熟悉,建议阅读 深入 Gitea Actions。

一个简单的 Go Action​

首先,让我们创建一个简单的 Go action。你需要在你的 Gitea 实例上创建一个名为 simple-go-action 的仓库并克隆它:

git clone <repo-url>
cd simple-go-action
# create the go.mod file
go mod init simple-go-action

元数据文件对于 action 至关重要,其文件名必须是 action.yml 或 action.yaml。下面是一个 action.yml 文件示例:

name: 'Simple Go Action'
description: 'A simple Gitea action written in go'
runs:
using: 'go'
main: 'main.go'

要了解更多元数据语法,可以查看 GitHub Actions 的元数据语法。

你可能会注意到,我们在元数据中使用了 using: 'go',指定 Go 作为此 action 的运行时。由于 GitHub 不原生支持 Go action,你不会在 GitHub 的文档中找到这一点。

接下来,添加 main.go 文件,其中包含打印 "Hello world" 字符串的简单逻辑:

package main

import "fmt"

func main() {
fmt.Println("Hello world")
}

现在 action 已准备好进行测试。提交代码并推送到 Gitea。要测试该 action,请创建另一个名为 test-simple-go-action 的仓库,并使用以下代码添加一个工作流文件。 请将 <action-url> 替换为你的 action 的 URL。<action-url> 应该类似于 http(s)://<your-gitea-instance-url>/<owner>/<repo>@<version>,例如:https://gitea.com/Zettat123/simple-go-action@v1。<version> 可以是标签、分支或提交 SHA。更多信息请参见 使用发布管理 action。

name: 'Test Go Action'
on: [push]
jobs:
use-go-action:
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.20'

- name: Use Go Action
uses: <action-url>

由于该 action 是用 Go 编写的,而 runs-on 环境可能没有安装 Go,你需要在使用该 action 之前设置 Go 运行时。setup-go action 在这种情况下效果很好,但我们稍后会讨论不适用的情况。推送工作流文件后,你可以在 Actions 标签页中查看结果。

use-go-action-main

输入与输出​

你可以通过 inputs 参数指定操作将使用的数据,并通过 outputs 参数指定操作可以提供给其他操作的数据。更多信息,请参阅 GitHub 文档中的 inputs 和 outputs。

要使用 inputs 和 outputs,你需要更新 action.yml 文件:

name: 'Simple Go Action'
description: 'A simple Gitea action written in go'
inputs:
username:
description: 'The username to print'
required: true
outputs:
time:
description: 'The time when the action was called'
runs:
using: 'go'
main: 'main.go'

你还需要更新 main.go 文件:

package main

import (
"fmt"
"os"
"time"
)

func main() {
username := readInputs()
fmt.Printf("username is %s\n", username)

err := writeOutputs("time", time.Now().Format("2006-01-02 15:04:05"))
if err != nil {
panic(err)
}
}

func readInputs() string {
username := os.Getenv("INPUT_USERNAME")
return username
}

func writeOutputs(k, v string) (err error) {
msg := fmt.Sprintf("%s=%s", k, v)
outputFilepath := os.Getenv("GITHUB_OUTPUT")
f, err := os.OpenFile(outputFilepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return
}
defer func() {
if cErr := f.Close(); cErr != nil && err == nil {
err = cErr
}
}()
if _, err = f.Write([]byte(msg)); err != nil {
return
}
return
}

在使用该操作的工作流中,你需要添加与 inputs 和 outputs 相关的代码:

name: 'Test Go Action'
on: [push]
jobs:
use-go-action:
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.20'

- name: Use Go Action
id: use-go-action
uses: <action-url>
with:
username: foo

- name: Print Output
run: echo 'output time is ${{ steps.use-go-action.outputs.time }}'

推送更新后的工作流文件后,你将看到结果:

use-go-action-input-and-output

很好!你已成功在你的操作中使用了 inputs 和 outputs。

你可能会发现,用于读取 inputs 和写入 outputs 的代码看起来有些复杂。为了简化代码,你可以使用 go-githubactions SDK:

package main

import (
"fmt"
"time"

gha "github.com/sethvargo/go-githubactions"
)

func main() {
username := gha.GetInput("username")
fmt.Printf("username is %s\n", username)

gha.SetOutput("time", time.Now().Format("2006-01-02 15:04:05"))
}

如果重新运行工作流,结果将如下:

use-sdk

如截图所示,构建可执行文件需要下载第三方软件包。如果你不想每次都下载第三方软件包,可以使用 go mod vendor 创建 vendor 目录来存储这些软件包。

前置与后置​

在 action.yml 文件中,我们将 runs.main 设置为 main.go,以指定该操作将要运行的代码。 除了 main 之外,你还可以为操作指定 pre 和 post。 pre 允许你在运行 main 之前执行一些代码,而 post 允许你在作业结束时执行一些代码。 如果你需要更多信息,请阅读 GitHub 关于 pre 和 post 的文档。

让我们将 pre 和 post 添加到该操作中。 首先,你需要创建一个 pre 目录,并在其中添加 pre.go 文件:

package main

import "fmt"

func main() {
fmt.Println("Pre of Simple Go Action")
}

接下来,以类似的方式创建 post 目录和 post.go 文件:

package main

import "fmt"

func main() {
fmt.Println("Post of Simple Go Action")
}

你还需要更新 action.yml 文件:

name: 'Simple Go Action'
description: 'A simple Gitea action written in go'
inputs:
username:
description: 'The username to print'
required: true
outputs:
time:
description: 'The time when the action was called'
runs:
using: 'go'
main: 'main.go'
pre: "pre/pre.go"
post: "post/post.go"

现在目录结构应该如下:

├── action.yml
├── go.mod
├── main.go
├── post
│   └── post.go
└── pre
└── pre.go

一切看起来没问题!但当你重新运行工作流时,可能会看到一个错误:"go": executable file not found in $PATH。 原因是 pre 中的代码会在调用 setup-go 操作之前执行,而 Go 运行时尚未安装。 在这种情况下,你需要使用已安装 Go 的镜像来运行此工作流,并且由于镜像已包含 Go 运行时,因此无需调用 setup-go 操作。 你可以使用 container.image 指定镜像(将 <image-with-go> 替换为已安装 Go 的镜像):

name: 'Test Go Action'
on: [push]
jobs:
use-go-action:
runs-on: ubuntu-latest
container:
image: <image-with-go>
steps:
- name: Use Go Action
id: use-go-action
uses: <action-url>
with:
username: foo

- name: Print Output
run: echo 'output time is ${{ steps.use-go-action.outputs.time }}'

这样工作流就会正常运行。你将在 Set up job 中看到 pre 的输出:

go-action-pre

并在 Complete job 中看到 post 的输出:

go-action-post


现在你应该对 Gitea 的 Go 操作有了基本了解。 如果你有任何想法或遇到任何错误,请随时创建工单或合并请求。 我们可以一起努力让 Gitea Actions 变得更好。


你可以在以下仓库中找到演示代码:

此外,还有一个可用的 Go 操作,用于向 Gitea 发布版本 https://gitea.com/actions/release-action