1// SPDX-License-Identifier: MIT23package cmd45import (6 "context"7 "encoding/hex"8 "fmt"9 "os"1011 pingv1 "code.gitea.io/actions-proto-go/ping/v1"12 "connectrpc.com/connect"13 gouuid "github.com/google/uuid"14 log "github.com/sirupsen/logrus"15 "github.com/spf13/cobra"1617 "gitea.com/gitea/act_runner/internal/app/run"18 "gitea.com/gitea/act_runner/internal/pkg/client"19 "gitea.com/gitea/act_runner/internal/pkg/config"20 "gitea.com/gitea/act_runner/internal/pkg/ver"21)2223type createRunnerFileArgs struct {24 Connect bool25 InstanceAddr string26 Secret string27 Name string28}2930func createRunnerFileCmd(ctx context.Context, configFile *string) *cobra.Command {31 var argsVar createRunnerFileArgs32 cmd := &cobra.Command{33 Use: "create-runner-file",34 Short: "Create a runner file using a shared secret used to pre-register the runner on the Forgejo instance",35 Args: cobra.MaximumNArgs(0),36 RunE: runCreateRunnerFile(ctx, &argsVar, configFile),37 }38 cmd.Flags().BoolVar(&argsVar.Connect, "connect", false, "tries to connect to the instance using the secret (Forgejo v1.21 instance or greater)")39 cmd.Flags().StringVar(&argsVar.InstanceAddr, "instance", "", "Forgejo instance address")40 cmd.MarkFlagRequired("instance")41 cmd.Flags().StringVar(&argsVar.Secret, "secret", "", "secret shared with the Forgejo instance via forgejo-cli actions register")42 cmd.MarkFlagRequired("secret")43 cmd.Flags().StringVar(&argsVar.Name, "name", "", "Runner name")4445 return cmd46}4748// must be exactly the same as fogejo/models/actions/forgejo.go49func uuidFromSecret(secret string) (string, error) {50 uuid, err := gouuid.FromBytes([]byte(secret[:16]))51 if err != nil {52 return "", fmt.Errorf("gouuid.FromBytes %v", err)53 }54 return uuid.String(), nil55}5657// should be exactly the same as forgejo/cmd/forgejo/actions.go58func validateSecret(secret string) error {59 secretLen := len(secret)60 if secretLen != 40 {61 return fmt.Errorf("the secret must be exactly 40 characters long, not %d", secretLen)62 }63 if _, err := hex.DecodeString(secret); err != nil {64 return fmt.Errorf("the secret must be an hexadecimal string: %w", err)65 }66 return nil67}6869func ping(cfg *config.Config, reg *config.Registration) error {70 // initial http client71 cli := client.New(72 reg.Address,73 cfg.Runner.Insecure,74 "",75 "",76 ver.Version(),77 )7879 _, err := cli.Ping(context.Background(), connect.NewRequest(&pingv1.PingRequest{80 Data: reg.UUID,81 }))82 if err != nil {83 return fmt.Errorf("ping %s failed %w", reg.Address, err)84 }85 return nil86}8788func runCreateRunnerFile(ctx context.Context, args *createRunnerFileArgs, configFile *string) func(cmd *cobra.Command, args []string) error {89 return func(*cobra.Command, []string) error {90 log.SetLevel(log.DebugLevel)91 log.Info("Creating runner file")9293 //94 // Prepare the registration data95 //96 cfg, err := config.LoadDefault(*configFile)97 if err != nil {98 return fmt.Errorf("invalid configuration: %w", err)99 }100101 if err := validateSecret(args.Secret); err != nil {102 return err103 }104105 uuid, err := uuidFromSecret(args.Secret)106 if err != nil {107 return err108 }109110 name := args.Name111 if name == "" {112 name, _ = os.Hostname()113 log.Infof("Runner name is empty, use hostname '%s'.", name)114 }115116 reg := &config.Registration{117 Name: name,118 UUID: uuid,119 Token: args.Secret,120 Address: args.InstanceAddr,121 }122123 //124 // Verify the Forgejo instance is reachable125 //126 if err := ping(cfg, reg); err != nil {127 return err128 }129130 //131 // Save the registration file132 //133 if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil {134 return fmt.Errorf("failed to save runner config to %s: %w", cfg.Runner.File, err)135 }136137 //138 // Verify the secret works139 //140 if args.Connect {141 cli := client.New(142 reg.Address,143 cfg.Runner.Insecure,144 reg.UUID,145 reg.Token,146 ver.Version(),147 )148149 runner := run.NewRunner(cfg, reg, cli)150 resp, err := runner.Declare(ctx, cfg.Runner.Labels)151152 if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {153 log.Warn("Cannot verify the connection because the Forgejo instance is lower than v1.21")154 } else if err != nil {155 log.WithError(err).Error("fail to invoke Declare")156 return err157 } else {158 log.Infof("connection successful: %s, with version: %s, with labels: %v",159 resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels)160 }161 }162 return nil163 }164}