Skip to main content

Creating Go Actions

· 7 min read
Zetta
Gitea maintainer
techknowlogick
Gitea maintainer
pat-s
Gitea maintainer

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.

use-go-action-main

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:

use-go-action-input-and-output

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:

use-sdk

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
└── pre
└── pre.go

Everything looks ok! But when you re-run the workflow, you may see an error: "go": executable file not found in $PATH. The reason is that the code in pre will be executed before calling the setup-go action, and the Go runtime has not yet been set up. In this case, you'll need to use an image with Go installed to run this workflow and you won't need to call the setup-go action since the image already has the Go runtime. You can use container.image to specify the image (replace <image-with-go> with an image with go installed):

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 }}'

Then the workflow will work. You'll see the output of the pre in Set up job:

go-action-pre

and the output of the post in Complete job:

go-action-post


Now you should have a basic understanding of Gitea's Go actions. If you have any ideas or encounter any bugs, please feel free to create issues or pull requests. We can work together to make Gitea Actions even better.


You can find the demo code in the following repositories:

And there is also a usable go action to publish release to Gitea https://gitea.com/actions/release-action

An icon showing wave propagation

Join our community

Gitea is open source. Star our GitHub repo, and join our community on Discord!

An icon showing a paper plane

Subscribe to our newsletter

Stay up to date with all things Gitea