mirror of https://code.forgejo.org/forgejo/runner
[FORGEJO] add the create-runner-file
parent
e6f0b3b5b8
commit
5a1ea04ce8
|
@ -4,21 +4,52 @@ on:
|
|||
- push
|
||||
|
||||
env:
|
||||
FORGEJO_HOST_PORT: 'forgejo:3000'
|
||||
FORGEJO_ADMIN_USER: 'root'
|
||||
FORGEJO_ADMIN_PASSWORD: 'admin1234'
|
||||
FORGEJO_RUNNER_SECRET: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
|
||||
FORGEJO_SCRIPT: |
|
||||
/bin/s6-svscan /etc/s6 & sleep 10 ; su -c "forgejo admin user create --admin --username $FORGEJO_ADMIN_USER --password $FORGEJO_ADMIN_PASSWORD --email root@example.com" git && su -c "forgejo forgejo-cli actions register --labels docker --name therunner --secret $FORGEJO_RUNNER_SECRET" git && sleep infinity
|
||||
GOPROXY: https://goproxy.io,direct
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
tests:
|
||||
name: check and test
|
||||
if: github.repository_owner != 'forgejo-integration' && github.repository_owner != 'forgejo-experimental' && github.repository_owner != 'forgejo-release'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: docker
|
||||
|
||||
services:
|
||||
forgejo:
|
||||
image: codeberg.org/forgejo-integration/forgejo:1.20.0-4-rc2
|
||||
env:
|
||||
FORGEJO__security__INSTALL_LOCK: "true"
|
||||
FORGEJO__log__LEVEL: "debug"
|
||||
FORGEJO__actions__ENABLED: "true"
|
||||
FORGEJO_ADMIN_USER: ${{ env.FORGEJO_ADMIN_USER }}
|
||||
FORGEJO_ADMIN_PASSWORD: ${{ env.FORGEJO_ADMIN_PASSWORD }}
|
||||
FORGEJO_RUNNER_SECRET: ${{ env.FORGEJO_RUNNER_SECRET }}
|
||||
cmd:
|
||||
- 'bash'
|
||||
- '-c'
|
||||
- ${{ env.FORGEJO_SCRIPT }}
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.20
|
||||
# pin because of https://github.com/nektos/act/issues/1908
|
||||
go-version: 1.20.5
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- name: vet checks
|
||||
run: make vet
|
||||
- name: build
|
||||
run: make build
|
||||
- name: test
|
||||
run: make test
|
||||
|
||||
- run: make vet
|
||||
|
||||
- run: make build
|
||||
|
||||
- name: check the forgejo server is responding
|
||||
run: |
|
||||
set -x
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq jq curl
|
||||
test $FORGEJO_ADMIN_USER = $(curl -sS http://$FORGEJO_ADMIN_USER:$FORGEJO_ADMIN_PASSWORD@$FORGEJO_HOST_PORT/api/v1/user | jq --raw-output .login)
|
||||
|
||||
- run: make FORGEJO_URL=http://$FORGEJO_HOST_PORT test
|
||||
|
|
1
go.mod
1
go.mod
|
@ -8,6 +8,7 @@ require (
|
|||
github.com/avast/retry-go/v4 v4.5.0
|
||||
github.com/bufbuild/connect-go v1.10.0
|
||||
github.com/docker/docker v24.0.5+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/nektos/act v0.2.49
|
||||
|
|
2
go.sum
2
go.sum
|
@ -83,6 +83,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
|
|
|
@ -41,6 +41,8 @@ func Execute(ctx context.Context) {
|
|||
registerCmd.Flags().StringVar(®Args.Labels, "labels", "", "Runner tags, comma separated")
|
||||
rootCmd.AddCommand(registerCmd)
|
||||
|
||||
rootCmd.AddCommand(createRunnerFileCmd(ctx, &configFile))
|
||||
|
||||
// ./act_runner daemon
|
||||
daemonCmd := &cobra.Command{
|
||||
Use: "daemon",
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
pingv1 "code.gitea.io/actions-proto-go/ping/v1"
|
||||
"github.com/bufbuild/connect-go"
|
||||
gouuid "github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"gitea.com/gitea/act_runner/internal/app/run"
|
||||
"gitea.com/gitea/act_runner/internal/pkg/client"
|
||||
"gitea.com/gitea/act_runner/internal/pkg/config"
|
||||
"gitea.com/gitea/act_runner/internal/pkg/ver"
|
||||
)
|
||||
|
||||
type createRunnerFileArgs struct {
|
||||
Connect bool
|
||||
InstanceAddr string
|
||||
Secret string
|
||||
Name string
|
||||
}
|
||||
|
||||
func createRunnerFileCmd(ctx context.Context, configFile *string) *cobra.Command {
|
||||
var argsVar createRunnerFileArgs
|
||||
cmd := &cobra.Command{
|
||||
Use: "create-runner-file",
|
||||
Short: "Create a runner file using a shared secret used to pre-register the runner on the Forgejo instance",
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
RunE: runCreateRunnerFile(ctx, &argsVar, configFile),
|
||||
}
|
||||
cmd.Flags().BoolVar(&argsVar.Connect, "connect", false, "tries to connect to the instance using the secret (Forgejo v1.21 instance or greater)")
|
||||
cmd.Flags().StringVar(&argsVar.InstanceAddr, "instance", "", "Forgejo instance address")
|
||||
cmd.MarkFlagRequired("instance")
|
||||
cmd.Flags().StringVar(&argsVar.Secret, "secret", "", "secret shared with the Frogejo instance via forgejo-cli actions register")
|
||||
cmd.MarkFlagRequired("secret")
|
||||
cmd.Flags().StringVar(&argsVar.Name, "name", "", "Runner name")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// must be exactly the same as fogejo/models/actions/forgejo.go
|
||||
func uuidFromSecret(secret string) (string, error) {
|
||||
uuid, err := gouuid.FromBytes([]byte(secret[:16]))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("gouuid.FromBytes %v", err)
|
||||
}
|
||||
return uuid.String(), nil
|
||||
}
|
||||
|
||||
// should be exactly the same as forgejo/cmd/forgejo/actions.go
|
||||
func validateSecret(secret string) error {
|
||||
secretLen := len(secret)
|
||||
if secretLen != 40 {
|
||||
return fmt.Errorf("the secret must be exactly 40 characters long, not %d", secretLen)
|
||||
}
|
||||
if _, err := hex.DecodeString(secret); err != nil {
|
||||
return fmt.Errorf("the secret must be an hexadecimal string: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ping(cfg *config.Config, reg *config.Registration) error {
|
||||
// initial http client
|
||||
cli := client.New(
|
||||
reg.Address,
|
||||
cfg.Runner.Insecure,
|
||||
"",
|
||||
"",
|
||||
ver.Version(),
|
||||
)
|
||||
|
||||
_, err := cli.Ping(context.Background(), connect.NewRequest(&pingv1.PingRequest{
|
||||
Data: reg.UUID,
|
||||
}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ping %s failed %w", reg.Address, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCreateRunnerFile(ctx context.Context, args *createRunnerFileArgs, configFile *string) func(cmd *cobra.Command, args []string) error {
|
||||
return func(*cobra.Command, []string) error {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.Info("Creating runner file")
|
||||
|
||||
//
|
||||
// Prepare the registration data
|
||||
//
|
||||
cfg, err := config.LoadDefault(*configFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid configuration: %w", err)
|
||||
}
|
||||
|
||||
if err := validateSecret(args.Secret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uuid, err := uuidFromSecret(args.Secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := args.Name
|
||||
if name == "" {
|
||||
name, _ = os.Hostname()
|
||||
log.Infof("Runner name is empty, use hostname '%s'.", name)
|
||||
}
|
||||
|
||||
reg := &config.Registration{
|
||||
Name: name,
|
||||
UUID: uuid,
|
||||
Token: args.Secret,
|
||||
Address: args.InstanceAddr,
|
||||
}
|
||||
|
||||
//
|
||||
// Verify the Forgejo instance is reachable
|
||||
//
|
||||
if err := ping(cfg, reg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// Save the registration file
|
||||
//
|
||||
if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil {
|
||||
return fmt.Errorf("failed to save runner config to %s: %w", cfg.Runner.File, err)
|
||||
}
|
||||
|
||||
//
|
||||
// Verify the secret works
|
||||
//
|
||||
if args.Connect {
|
||||
cli := client.New(
|
||||
reg.Address,
|
||||
cfg.Runner.Insecure,
|
||||
reg.UUID,
|
||||
reg.Token,
|
||||
ver.Version(),
|
||||
)
|
||||
|
||||
runner := run.NewRunner(cfg, reg, cli)
|
||||
resp, err := runner.Declare(ctx, cfg.Runner.Labels)
|
||||
|
||||
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {
|
||||
log.Warn("Cannot verify the connection because the Forgejo instance is lower than v1.21")
|
||||
} else if err != nil {
|
||||
log.WithError(err).Error("fail to invoke Declare")
|
||||
return err
|
||||
} else {
|
||||
log.Infof("connection successful: %s, with version: %s, with labels: %v",
|
||||
resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
"gitea.com/gitea/act_runner/internal/pkg/client"
|
||||
"gitea.com/gitea/act_runner/internal/pkg/config"
|
||||
"gitea.com/gitea/act_runner/internal/pkg/ver"
|
||||
"github.com/bufbuild/connect-go"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func executeCommand(ctx context.Context, cmd *cobra.Command, args ...string) (string, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.SetOut(buf)
|
||||
cmd.SetErr(buf)
|
||||
cmd.SetArgs(args)
|
||||
|
||||
err := cmd.ExecuteContext(ctx)
|
||||
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
func Test_createRunnerFileCmd(t *testing.T) {
|
||||
configFile := "config.yml"
|
||||
ctx := context.Background()
|
||||
cmd := createRunnerFileCmd(ctx, &configFile)
|
||||
output, err := executeCommand(ctx, cmd)
|
||||
assert.ErrorContains(t, err, `required flag(s) "instance", "secret" not set`)
|
||||
assert.Contains(t, output, "Usage:")
|
||||
}
|
||||
|
||||
func Test_validateSecret(t *testing.T) {
|
||||
assert.ErrorContains(t, validateSecret("abc"), "exactly 40 characters")
|
||||
assert.ErrorContains(t, validateSecret("ZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), "must be an hexadecimal")
|
||||
}
|
||||
|
||||
func Test_uuidFromSecret(t *testing.T) {
|
||||
uuid, err := uuidFromSecret("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, uuid, "41414141-4141-4141-4141-414141414141")
|
||||
}
|
||||
|
||||
func Test_ping(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
address := os.Getenv("FORGEJO_URL")
|
||||
if address == "" {
|
||||
address = "https://code.forgejo.org"
|
||||
}
|
||||
reg := &config.Registration{
|
||||
Address: address,
|
||||
UUID: "create-runner-file_test.go",
|
||||
}
|
||||
assert.NoError(t, ping(cfg, reg))
|
||||
}
|
||||
|
||||
func Test_runCreateRunnerFile(t *testing.T) {
|
||||
//
|
||||
// Set the .runner file to be in a temporary directory
|
||||
//
|
||||
dir := t.TempDir()
|
||||
configFile := dir + "/config.yml"
|
||||
runnerFile := dir + "/.runner"
|
||||
cfg, err := config.LoadDefault("")
|
||||
cfg.Runner.File = runnerFile
|
||||
yamlData, err := yaml.Marshal(cfg)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(configFile, yamlData, 0o666))
|
||||
|
||||
instance, has := os.LookupEnv("FORGEJO_URL")
|
||||
if !has {
|
||||
instance = "https://code.forgejo.org"
|
||||
}
|
||||
secret, has := os.LookupEnv("FORGEJO_RUNNER_SECRET")
|
||||
assert.True(t, has)
|
||||
name := "testrunner"
|
||||
|
||||
//
|
||||
// Run create-runner-file
|
||||
//
|
||||
ctx := context.Background()
|
||||
cmd := createRunnerFileCmd(ctx, &configFile)
|
||||
output, err := executeCommand(ctx, cmd, "--connect", "--secret", secret, "--instance", instance, "--name", name)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "", output)
|
||||
|
||||
//
|
||||
// Read back the runner file and verify its content
|
||||
//
|
||||
reg, err := config.LoadRegistration(runnerFile)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, secret, reg.Token)
|
||||
assert.EqualValues(t, instance, reg.Address)
|
||||
|
||||
//
|
||||
// Verify that fetching a task successfully returns there is
|
||||
// no task for this runner
|
||||
//
|
||||
cli := client.New(
|
||||
reg.Address,
|
||||
cfg.Runner.Insecure,
|
||||
reg.UUID,
|
||||
reg.Token,
|
||||
ver.Version(),
|
||||
)
|
||||
resp, err := cli.FetchTask(ctx, connect.NewRequest(&runnerv1.FetchTaskRequest{}))
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, resp.Msg.Task)
|
||||
}
|
|
@ -101,7 +101,7 @@ func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command,
|
|||
resp, err := runner.Declare(ctx, ls.Names())
|
||||
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {
|
||||
// Gitea instance is older version. skip declare step.
|
||||
log.Warn("Because the Gitea instance is an old version, skip declare labels and version.")
|
||||
log.Warn("Because the Forgejo instance is an old version, skip declare labels and version.")
|
||||
} else if err != nil {
|
||||
log.WithError(err).Error("fail to invoke Declare")
|
||||
return err
|
||||
|
|
Loading…
Reference in New Issue