summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEarl Warren <contact@earl-warren.org>2023-07-11 22:45:49 +0200
committerEarl Warren <contact@earl-warren.org>2023-08-23 16:03:12 +0200
commit5a1ea04ce8f41ecc7ff6b83cc93de020b0bd852b (patch)
tree8517c32c07b9fa05ba5e7abef4a4680b9dc264ff
parent[FORGEJO] use go vet without any third party addition (diff)
downloadforgejo-runner-5a1ea04ce8f41ecc7ff6b83cc93de020b0bd852b.tar.xz
forgejo-runner-5a1ea04ce8f41ecc7ff6b83cc93de020b0bd852b.zip
[FORGEJO] add the create-runner-file
-rw-r--r--.forgejo/workflows/test.yml49
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--internal/app/cmd/cmd.go2
-rw-r--r--internal/app/cmd/create-runner-file.go164
-rw-r--r--internal/app/cmd/create-runner-file_test.go118
-rw-r--r--internal/app/cmd/daemon.go2
7 files changed, 328 insertions, 10 deletions
diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml
index 636c4ad..48cca2a 100644
--- a/.forgejo/workflows/test.yml
+++ b/.forgejo/workflows/test.yml
@@ -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
diff --git a/go.mod b/go.mod
index 6fb7a68..931a625 100644
--- a/go.mod
+++ b/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
diff --git a/go.sum b/go.sum
index 263fe26..c938229 100644
--- a/go.sum
+++ b/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=
diff --git a/internal/app/cmd/cmd.go b/internal/app/cmd/cmd.go
index 3af4442..48341dc 100644
--- a/internal/app/cmd/cmd.go
+++ b/internal/app/cmd/cmd.go
@@ -41,6 +41,8 @@ func Execute(ctx context.Context) {
registerCmd.Flags().StringVar(&regArgs.Labels, "labels", "", "Runner tags, comma separated")
rootCmd.AddCommand(registerCmd)
+ rootCmd.AddCommand(createRunnerFileCmd(ctx, &configFile))
+
// ./act_runner daemon
daemonCmd := &cobra.Command{
Use: "daemon",
diff --git a/internal/app/cmd/create-runner-file.go b/internal/app/cmd/create-runner-file.go
new file mode 100644
index 0000000..c28a74b
--- /dev/null
+++ b/internal/app/cmd/create-runner-file.go
@@ -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
+ }
+}
diff --git a/internal/app/cmd/create-runner-file_test.go b/internal/app/cmd/create-runner-file_test.go
new file mode 100644
index 0000000..e55a3d7
--- /dev/null
+++ b/internal/app/cmd/create-runner-file_test.go
@@ -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)
+}
diff --git a/internal/app/cmd/daemon.go b/internal/app/cmd/daemon.go
index e8e0917..a2a3974 100644
--- a/internal/app/cmd/daemon.go
+++ b/internal/app/cmd/daemon.go
@@ -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