Clarify labels (#69)
The label will follow the format `label[:schema[:args]]`, and the schema will be `host` if it's omitted. So - `ubuntu:docker://node:18`: Run jobs with label `ubuntu` via docker with image `node:18` - `ubuntu:host`: Run jobs with label `ubuntu` on the host directly. - `ubuntu`: Same as `ubuntu:host`. - `ubuntu:vm:ubuntu-latest`: (Just a example, not Implemented) Run jobs with label `ubuntu` via virtual machine with iso `ubuntu-latest`. Reviewed-on: https://gitea.com/gitea/act_runner/pulls/69 Reviewed-by: Zettat123 <zettat123@noreply.gitea.io> Reviewed-by: wxiaoguang <wxiaoguang@noreply.gitea.io>
This commit is contained in:
parent
8d8a11052a
commit
616ad7c96a
6 changed files with 126 additions and 50 deletions
|
@ -3,7 +3,12 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"codeberg.org/forgejo/runner/artifactcache"
|
"codeberg.org/forgejo/runner/artifactcache"
|
||||||
"codeberg.org/forgejo/runner/client"
|
"codeberg.org/forgejo/runner/client"
|
||||||
|
@ -11,12 +16,6 @@ import (
|
||||||
"codeberg.org/forgejo/runner/engine"
|
"codeberg.org/forgejo/runner/engine"
|
||||||
"codeberg.org/forgejo/runner/poller"
|
"codeberg.org/forgejo/runner/poller"
|
||||||
"codeberg.org/forgejo/runner/runtime"
|
"codeberg.org/forgejo/runner/runtime"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/mattn/go-isatty"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func runDaemon(ctx context.Context, envFile string) func(cmd *cobra.Command, args []string) error {
|
func runDaemon(ctx context.Context, envFile string) func(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -35,8 +34,8 @@ func runDaemon(ctx context.Context, envFile string) func(cmd *cobra.Command, arg
|
||||||
// require docker if a runner label uses a docker backend
|
// require docker if a runner label uses a docker backend
|
||||||
needsDocker := false
|
needsDocker := false
|
||||||
for _, l := range cfg.Runner.Labels {
|
for _, l := range cfg.Runner.Labels {
|
||||||
splits := strings.SplitN(l, ":", 2)
|
_, schema, _, _ := runtime.ParseLabel(l)
|
||||||
if len(splits) == 2 && strings.HasPrefix(splits[1], "docker://") {
|
if schema == "docker" {
|
||||||
needsDocker = true
|
needsDocker = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -6,14 +9,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
goruntime "runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
pingv1 "code.gitea.io/actions-proto-go/ping/v1"
|
pingv1 "code.gitea.io/actions-proto-go/ping/v1"
|
||||||
|
|
||||||
"codeberg.org/forgejo/runner/client"
|
"codeberg.org/forgejo/runner/client"
|
||||||
"codeberg.org/forgejo/runner/config"
|
"codeberg.org/forgejo/runner/config"
|
||||||
"codeberg.org/forgejo/runner/register"
|
"codeberg.org/forgejo/runner/register"
|
||||||
|
"codeberg.org/forgejo/runner/runtime"
|
||||||
|
|
||||||
"github.com/bufbuild/connect-go"
|
"github.com/bufbuild/connect-go"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
@ -34,7 +39,7 @@ func runRegister(ctx context.Context, regArgs *registerArgs, envFile string) fun
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
log.Infof("Registering runner, arch=%s, os=%s, version=%s.",
|
log.Infof("Registering runner, arch=%s, os=%s, version=%s.",
|
||||||
runtime.GOARCH, runtime.GOOS, version)
|
goruntime.GOARCH, goruntime.GOOS, version)
|
||||||
|
|
||||||
// runner always needs root permission
|
// runner always needs root permission
|
||||||
if os.Getuid() != 0 {
|
if os.Getuid() != 0 {
|
||||||
|
@ -118,12 +123,9 @@ func (r *registerInputs) validate() error {
|
||||||
|
|
||||||
func validateLabels(labels []string) error {
|
func validateLabels(labels []string) error {
|
||||||
for _, label := range labels {
|
for _, label := range labels {
|
||||||
values := strings.SplitN(label, ":", 2)
|
if _, _, _, err := runtime.ParseLabel(label); err != nil {
|
||||||
if len(values) > 2 {
|
return err
|
||||||
return fmt.Errorf("Invalid label: %s", label)
|
|
||||||
}
|
}
|
||||||
// len(values) == 1, label for non docker execution environment
|
|
||||||
// TODO: validate value format, like docker://node:16-buster
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -164,7 +166,7 @@ func (r *registerInputs) assignToNext(stage registerStage, value string) registe
|
||||||
}
|
}
|
||||||
|
|
||||||
if validateLabels(r.CustomLabels) != nil {
|
if validateLabels(r.CustomLabels) != nil {
|
||||||
log.Infoln("Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster)")
|
log.Infoln("Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster,linux_arm:host)")
|
||||||
return StageInputCustomLabels
|
return StageInputCustomLabels
|
||||||
}
|
}
|
||||||
return StageWaitingForRegistration
|
return StageWaitingForRegistration
|
||||||
|
@ -221,14 +223,14 @@ func printStageHelp(stage registerStage) {
|
||||||
case StageOverwriteLocalConfig:
|
case StageOverwriteLocalConfig:
|
||||||
log.Infoln("Runner is already registered, overwrite local config? [y/N]")
|
log.Infoln("Runner is already registered, overwrite local config? [y/N]")
|
||||||
case StageInputInstance:
|
case StageInputInstance:
|
||||||
log.Infoln("Enter the Forgejo instance URL (for example, https://codeberg.org/):")
|
log.Infoln("Enter the Gitea instance URL (for example, https://gitea.com/):")
|
||||||
case StageInputToken:
|
case StageInputToken:
|
||||||
log.Infoln("Enter the runner token:")
|
log.Infoln("Enter the runner token:")
|
||||||
case StageInputRunnerName:
|
case StageInputRunnerName:
|
||||||
hostname, _ := os.Hostname()
|
hostname, _ := os.Hostname()
|
||||||
log.Infof("Enter the runner name (if set empty, use hostname:%s ):\n", hostname)
|
log.Infof("Enter the runner name (if set empty, use hostname: %s):\n", hostname)
|
||||||
case StageInputCustomLabels:
|
case StageInputCustomLabels:
|
||||||
log.Infoln("Enter the runner labels, leave blank to use the default labels (comma-separated, for example, self-hosted,ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster):")
|
log.Infoln("Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster,linux_arm:host):")
|
||||||
case StageWaitingForRegistration:
|
case StageWaitingForRegistration:
|
||||||
log.Infoln("Waiting for registration...")
|
log.Infoln("Waiting for registration...")
|
||||||
}
|
}
|
||||||
|
@ -290,11 +292,11 @@ func doRegister(cfg *config.Config, inputs *registerInputs) error {
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).
|
log.WithError(err).
|
||||||
Errorln("Cannot ping the Forgejo instance server")
|
Errorln("Cannot ping the Gitea instance server")
|
||||||
// TODO: if ping failed, retry or exit
|
// TODO: if ping failed, retry or exit
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
} else {
|
} else {
|
||||||
log.Debugln("Successfully pinged the Forgejo instance server")
|
log.Debugln("Successfully pinged the Gitea instance server")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestValidateLabels(t *testing.T) {
|
|
||||||
labels := []string{"ubuntu-latest:docker://node:16-buster", "self-hosted"}
|
|
||||||
if err := validateLabels(labels); err != nil {
|
|
||||||
t.Errorf("validateLabels() error = %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
26
runtime/label.go
Normal file
26
runtime/label.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseLabel(str string) (label, schema, arg string, err error) {
|
||||||
|
splits := strings.SplitN(str, ":", 3)
|
||||||
|
label = splits[0]
|
||||||
|
schema = "host"
|
||||||
|
arg = ""
|
||||||
|
if len(splits) >= 2 {
|
||||||
|
schema = splits[1]
|
||||||
|
}
|
||||||
|
if len(splits) >= 3 {
|
||||||
|
arg = splits[2]
|
||||||
|
}
|
||||||
|
if schema != "host" && schema != "docker" {
|
||||||
|
return "", "", "", fmt.Errorf("unsupported schema: %s", schema)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
60
runtime/label_test.go
Normal file
60
runtime/label_test.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestParseLabel(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
args string
|
||||||
|
wantLabel string
|
||||||
|
wantSchema string
|
||||||
|
wantArg string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
args: "ubuntu:docker://node:18",
|
||||||
|
wantLabel: "ubuntu",
|
||||||
|
wantSchema: "docker",
|
||||||
|
wantArg: "//node:18",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: "ubuntu:host",
|
||||||
|
wantLabel: "ubuntu",
|
||||||
|
wantSchema: "host",
|
||||||
|
wantArg: "",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: "ubuntu",
|
||||||
|
wantLabel: "ubuntu",
|
||||||
|
wantSchema: "host",
|
||||||
|
wantArg: "",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: "ubuntu:vm:ubuntu-18.04",
|
||||||
|
wantLabel: "",
|
||||||
|
wantSchema: "",
|
||||||
|
wantArg: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.args, func(t *testing.T) {
|
||||||
|
gotLabel, gotSchema, gotArg, err := ParseLabel(tt.args)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseLabel() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if gotLabel != tt.wantLabel {
|
||||||
|
t.Errorf("parseLabel() gotLabel = %v, want %v", gotLabel, tt.wantLabel)
|
||||||
|
}
|
||||||
|
if gotSchema != tt.wantSchema {
|
||||||
|
t.Errorf("parseLabel() gotSchema = %v, want %v", gotSchema, tt.wantSchema)
|
||||||
|
}
|
||||||
|
if gotArg != tt.wantArg {
|
||||||
|
t.Errorf("parseLabel() gotArg = %v, want %v", gotArg, tt.wantArg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import (
|
||||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
"codeberg.org/forgejo/runner/artifactcache"
|
"codeberg.org/forgejo/runner/artifactcache"
|
||||||
"codeberg.org/forgejo/runner/client"
|
"codeberg.org/forgejo/runner/client"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runner runs the pipeline.
|
// Runner runs the pipeline.
|
||||||
|
@ -31,28 +32,24 @@ func (s *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Runner) platformPicker(labels []string) string {
|
func (s *Runner) platformPicker(labels []string) string {
|
||||||
// "ubuntu-18.04:docker://node:16-buster"
|
platforms := make(map[string]string, len(s.Labels))
|
||||||
// "self-hosted"
|
|
||||||
|
|
||||||
platforms := make(map[string]string, len(labels))
|
|
||||||
for _, l := range s.Labels {
|
for _, l := range s.Labels {
|
||||||
// "ubuntu-18.04:docker://node:16-buster"
|
label, schema, arg, err := ParseLabel(l)
|
||||||
splits := strings.SplitN(l, ":", 2)
|
if err != nil {
|
||||||
if len(splits) == 1 {
|
log.Errorf("invaid label %q: %v", l, err)
|
||||||
// identifier for non docker execution environment
|
|
||||||
platforms[splits[0]] = "-self-hosted"
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// ["ubuntu-18.04", "docker://node:16-buster"]
|
|
||||||
k, v := splits[0], splits[1]
|
|
||||||
|
|
||||||
if prefix := "docker://"; !strings.HasPrefix(v, prefix) {
|
switch schema {
|
||||||
|
case "docker":
|
||||||
|
// TODO "//" will be ignored, maybe we should use 'ubuntu-18.04:docker:node:16-buster' instead
|
||||||
|
platforms[label] = strings.TrimPrefix(arg, "//")
|
||||||
|
case "host":
|
||||||
|
platforms[label] = "-self-hosted"
|
||||||
|
default:
|
||||||
|
// It should not happen, because ParseLabel has checked it.
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
v = strings.TrimPrefix(v, prefix)
|
|
||||||
}
|
}
|
||||||
// ubuntu-18.04 => node:16-buster
|
|
||||||
platforms[k] = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, label := range labels {
|
for _, label := range labels {
|
||||||
|
@ -67,6 +64,8 @@ func (s *Runner) platformPicker(labels []string) string {
|
||||||
// ["with-gpu"] => "linux:with-gpu"
|
// ["with-gpu"] => "linux:with-gpu"
|
||||||
// ["ubuntu-22.04", "with-gpu"] => "ubuntu:22.04_with-gpu"
|
// ["ubuntu-22.04", "with-gpu"] => "ubuntu:22.04_with-gpu"
|
||||||
|
|
||||||
// return default
|
// return default.
|
||||||
return "node:16-bullseye"
|
// So the runner receives a task with a label that the runner doesn't have,
|
||||||
|
// it happens when the user have edited the label of the runner in the web UI.
|
||||||
|
return "node:16-bullseye" // TODO: it may be not correct, what if the runner is used as host mode only?
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue