mirror of
https://code.forgejo.org/forgejo/runner.git
synced 2024-12-22 13:54:24 +03:00
165 lines
4.3 KiB
Go
165 lines
4.3 KiB
Go
|
// 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
|
||
|
}
|
||
|
}
|