Creating Go Actions
Gitea's CI system, Gitea Actions, was recently released in version 1.19.0. Gitea Actions is an internal CI/CD system, offering near compatibility with GitHub Actions. One key difference is that Gitea supports both Go and JavaScript actions, while GitHub only supports JavaScript actions natively (though you can use any programming language for Docker container actions).
Go actions provide a way for gophers who are not familiar with JavaScript to create native actions. However, compared to JavaScript actions, Go actions may be slower because they need to be built as executables before running. But you don't need to put the generated javascript files into dist in a Go action.
In this guide, we'll show you how to create a Go action in Gitea. Before diving in, you should have a basic understanding of Gitea Actions. If you're not familiar with it, we recommend reading Hacking on Gitea Actions.
A Simple Go Action
First, let's create a simple Go action. You'll need to create a repository called simple-go-action
on your Gitea instance and clone it:
git clone <repo-url>
cd simple-go-action
# create the go.mod file
go mod init simple-go-action
The metadata file is essential for actions, and its filename must be either action.yml
or action.yaml
. Here's an example action.yml file:
name: 'Simple Go Action'
description: 'A simple Gitea action written in go'
runs:
using: 'go'
main: 'main.go'
To read more about the metadata syntax, you can see the Metadata syntax for GitHub Actions.
You may notice that we use using: 'go'
in the metadata, specifying Go as the runtime for this action. Since GitHub doesn't support Go actions natively, you won't find this in GitHub's documentation.
Next, add the main.go
file with a simple logic to print a "Hello world" string:
package main
import "fmt"
func main() {
fmt.Println("Hello world")
}
Now the action is ready to be tested. Commit and push the code to Gitea. To test the action, create another repository called test-simple-go-action
and add a workflow file using the following code.
Please replace <action-url>
with the URL of your action. The <action-url>
should be like http(s)://<your-gitea-instance-url>/<owner>/<repo>@<version>
, for example: https://gitea.com/Zettat123/simple-go-action@v1
. The <version>
could be a tag, a branch or a commit SHA. For more information, see using-release-management-for-actions.
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>
Since the action is written in Go and the runs-on
environment may not have Go installed, you'll need to set up a Go runtime before using the action. The setup-go
action works well in this case, but we'll discuss cases where it doesn't later. After pushing the workflow file, you can see the result in the Actions tab.
Inputs and Outputs
You can specify the data that the action will use through inputs parameters and the data that the action can provide to other actions through outputs parameters. For more information, see GitHub's documentation on inputs and outputs
To use inputs
and outputs
, you'll need to update the action.yml
file:
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'
You'll also need to update the main.go
file:
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
}
In the workflow that uses the action, you need to add the code related to inputs
and 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 }}'
After pushing the updated workflow file, you will see the result:
Good! You have successfully used inputs
and outputs
in your action.
You may find that the code for reading inputs
and writing outputs
looks complicated.
To simplify the code, you can use the 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"))
}
If you re-run the workflow, the result will be:
As shown in screenshot, the third-party packages need to be downloaded to build the executable.
If you don't want to download third-party packages every time, you can use go mod vendor
to create the vendor
directory to store packages.
Pre and Post
In the action.yml
file, we set the runs.main
to main.go
to specify the code that the action will run.
In addition to the main
, you can also specify the pre
and the post
for an action.
The pre
allows you to run some code before running the main
and the post
allows you to run some code at the end of the job.
If you need more information, please read GitHub's documentation on pre and post.
Let's add the pre
and the post
to the action.
Firstly, you need to create a pre
directory and add the pre.go
file in it:
package main
import "fmt"
func main() {
fmt.Println("Pre of Simple Go Action")
}
Next, create the post
directory and post.go
file in the similar way:
package main
import "fmt"
func main() {
fmt.Println("Post of Simple Go Action")
}
And you'll also need to update the action.yml
file:
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"
Now the structure of the directory should be like:
├── action.yml
├── go.mod
├── main.go
├── post
│ └── post.go