跳到主要内容

创建 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 和 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 动作

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

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

元数据文件对动作来说是必不可少的,其文件名必须是 action.ymlaction.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 指定为此动作的运行环境。由于 GitHub Actions 不原生支持 Go 动作,所以这在 GitHub 的文档中是找不到的。

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

package main

import "fmt"

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

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

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>

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

使用 go 动作的主要部分

输入与输出

你可以通过输入参数指定该动作将要使用的数据,通过输出参数指定该动作可以提供给其他动作的数据。更多信息,请参阅 GitHub 关于输入输出的文档。

要使用 inputsoutputs,你需要更新 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
}

在使用该动作的工作流中,你需要添加与 inputsoutputs 相关的代码:

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

很好!你已经成功在动作中使用了 inputsoutputs。 你可能会发现,读取 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 目录,以存储这些软件包。

Pre 和 Post

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

我们为 action 添加 prepost。 首先,需要创建一个 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 action 之前执行,而此时 Go 运行时尚未设置好。 在这种情况下,需要使用已安装 Go 的镜像来运行此工作流,并且无需调用 setup-go action,因为该镜像已经包含了 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 actions 有了基本的了解。 如果你有任何想法或遇到任何 bug,请随时创建工单或合并请求。 我们可以共同努力,让 Gitea Actions 变得更好。


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

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