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>
pull/25/head
Jason Song 2023-03-23 20:48:33 +08:00
parent 4d5a35ac65
commit 90b8cc6a7a
7 changed files with 123 additions and 54 deletions

View File

@ -45,7 +45,7 @@ INFO Enter the runner token:
fe884e8027dc292970d4e0303fe82b14xxxxxxxx fe884e8027dc292970d4e0303fe82b14xxxxxxxx
INFO Enter the runner name (if set empty, use hostname: Test.local): INFO Enter the runner name (if set empty, use hostname: Test.local):
INFO 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): INFO 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):
INFO Registering runner, name=Test.local, instance=http://192.168.8.8:3000/, labels=[ubuntu-latest:docker://node:16-bullseye ubuntu-22.04:docker://node:16-bullseye ubuntu-20.04:docker://node:16-bullseye ubuntu-18.04:docker://node:16-buster]. INFO Registering runner, name=Test.local, instance=http://192.168.8.8:3000/, labels=[ubuntu-latest:docker://node:16-bullseye ubuntu-22.04:docker://node:16-bullseye ubuntu-20.04:docker://node:16-bullseye ubuntu-18.04:docker://node:16-buster].
DEBU Successfully pinged the Gitea instance server DEBU Successfully pinged the Gitea instance server

View File

@ -6,7 +6,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"
"gitea.com/gitea/act_runner/artifactcache" "gitea.com/gitea/act_runner/artifactcache"
"gitea.com/gitea/act_runner/client" "gitea.com/gitea/act_runner/client"
@ -14,12 +19,6 @@ import (
"gitea.com/gitea/act_runner/engine" "gitea.com/gitea/act_runner/engine"
"gitea.com/gitea/act_runner/poller" "gitea.com/gitea/act_runner/poller"
"gitea.com/gitea/act_runner/runtime" "gitea.com/gitea/act_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 {
@ -38,8 +37,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
} }

View File

@ -9,20 +9,21 @@ 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"
"gitea.com/gitea/act_runner/client"
"gitea.com/gitea/act_runner/config"
"gitea.com/gitea/act_runner/register"
"github.com/bufbuild/connect-go" "github.com/bufbuild/connect-go"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitea.com/gitea/act_runner/client"
"gitea.com/gitea/act_runner/config"
"gitea.com/gitea/act_runner/register"
"gitea.com/gitea/act_runner/runtime"
) )
// runRegister registers a runner to the server // runRegister registers a runner to the server
@ -37,7 +38,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 {
@ -121,12 +122,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
} }
@ -167,7 +165,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
@ -231,7 +229,7 @@ func printStageHelp(stage registerStage) {
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...")
} }

View File

@ -1,13 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
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
View 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
View 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)
}
})
}
}

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1" runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
log "github.com/sirupsen/logrus"
"gitea.com/gitea/act_runner/artifactcache" "gitea.com/gitea/act_runner/artifactcache"
"gitea.com/gitea/act_runner/client" "gitea.com/gitea/act_runner/client"
@ -35,28 +36,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 {
@ -71,6 +68,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?
} }