Add a basic CLI

This commit is contained in:
Gabriel Adrian Samfira 2022-05-02 17:55:29 +00:00
parent 7ec937a138
commit 475d424f32
26 changed files with 930 additions and 102 deletions

View file

@ -340,3 +340,111 @@ func (a *APIController) CreateRepoPoolHandler(w http.ResponseWriter, r *http.Req
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pool)
}
func (a *APIController) ListRepoPoolsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
repoID, ok := vars["repoID"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No repo ID specified",
})
return
}
pools, err := a.r.ListRepoPools(ctx, repoID)
if err != nil {
log.Printf("listing pools: %+v", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pools)
}
func (a *APIController) GetRepoPoolHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
repoID, repoOk := vars["repoID"]
poolID, poolOk := vars["poolID"]
if !repoOk || !poolOk {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No repo or pool ID specified",
})
return
}
pool, err := a.r.GetRepoPoolByID(ctx, repoID, poolID)
if err != nil {
log.Printf("listing pools: %+v", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pool)
}
func (a *APIController) DeleteRepoPoolHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
repoID, repoOk := vars["repoID"]
poolID, poolOk := vars["poolID"]
if !repoOk || !poolOk {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No repo or pool ID specified",
})
return
}
if err := a.r.DeleteRepoPool(ctx, repoID, poolID); err != nil {
log.Printf("removing pool: %+v", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
}
func (a *APIController) UpdateRepoPoolHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
repoID, repoOk := vars["repoID"]
poolID, poolOk := vars["poolID"]
if !repoOk || !poolOk {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No repo or pool ID specified",
})
return
}
var poolData runnerParams.UpdatePoolParams
if err := json.NewDecoder(r.Body).Decode(&poolData); err != nil {
log.Printf("failed to decode: %+v", err)
handleError(w, gErrors.ErrBadRequest)
return
}
pool, err := a.r.UpdateRepoPool(ctx, repoID, poolID, poolData)
if err != nil {
log.Printf("error creating repository pool: %+v", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pool)
}

View file

@ -41,12 +41,17 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl
// Repos and pools //
/////////////////////
// Get pool
apiRouter.Handle("/repositories/{repoID}/pools/{poolID:poolID\\/?}", log(os.Stdout, http.HandlerFunc(han.CatchAll))).Methods("GET", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools/{poolID}/", log(os.Stdout, http.HandlerFunc(han.GetRepoPoolHandler))).Methods("GET", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools/{poolID}", log(os.Stdout, http.HandlerFunc(han.GetRepoPoolHandler))).Methods("GET", "OPTIONS")
// Delete pool
apiRouter.Handle("/repositories/{repoID}/pools/{poolID:poolID\\/?}", log(os.Stdout, http.HandlerFunc(han.CatchAll))).Methods("DELETE", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools/{poolID}/", log(os.Stdout, http.HandlerFunc(han.DeleteRepoPoolHandler))).Methods("DELETE", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools/{poolID}", log(os.Stdout, http.HandlerFunc(han.DeleteRepoPoolHandler))).Methods("DELETE", "OPTIONS")
// Update pool
apiRouter.Handle("/repositories/{repoID}/pools/{poolID}/", log(os.Stdout, http.HandlerFunc(han.UpdateRepoPoolHandler))).Methods("PUT", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools/{poolID}", log(os.Stdout, http.HandlerFunc(han.UpdateRepoPoolHandler))).Methods("PUT", "OPTIONS")
// List pools
apiRouter.Handle("/repositories/{repoID}/pools/", log(os.Stdout, http.HandlerFunc(han.CatchAll))).Methods("GET", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools", log(os.Stdout, http.HandlerFunc(han.CatchAll))).Methods("GET", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools/", log(os.Stdout, http.HandlerFunc(han.ListRepoPoolsHandler))).Methods("GET", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools", log(os.Stdout, http.HandlerFunc(han.ListRepoPoolsHandler))).Methods("GET", "OPTIONS")
// Create pool
apiRouter.Handle("/repositories/{repoID}/pools/", log(os.Stdout, http.HandlerFunc(han.CreateRepoPoolHandler))).Methods("POST", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools", log(os.Stdout, http.HandlerFunc(han.CreateRepoPoolHandler))).Methods("POST", "OPTIONS")

View file

@ -28,16 +28,11 @@ type Authenticator struct {
}
func (a *Authenticator) IsInitialized() bool {
info, err := a.store.ControllerInfo()
if err != nil {
return false
if a.store.HasAdminUser(context.Background()) {
return true
}
if info.ControllerID.String() == "" {
return false
}
return true
return false
}
func (a *Authenticator) GetJWTToken(ctx context.Context) (string, error) {
@ -105,9 +100,6 @@ func (a *Authenticator) InitController(ctx context.Context, param params.NewUser
param.Password = hashed
if _, err := a.store.InitController(); err != nil {
return params.User{}, errors.Wrap(err, "initializing controller")
}
return a.store.CreateUser(ctx, param)
}

0
cmd/run-cli/LICENSE Normal file
View file

View file

@ -0,0 +1,85 @@
package client
import (
"encoding/json"
"fmt"
apiParams "runner-manager/apiserver/params"
"runner-manager/cmd/run-cli/config"
"runner-manager/params"
"github.com/go-resty/resty/v2"
"github.com/pkg/errors"
)
func NewClient(name string, cfg config.Manager) *Client {
cli := resty.New()
return &Client{
ManagerName: name,
Config: cfg,
client: cli,
}
}
type Client struct {
ManagerName string
Config config.Manager
client *resty.Client
}
func (c *Client) decodeAPIError(body []byte) (apiParams.APIErrorResponse, error) {
var errDetails apiParams.APIErrorResponse
if err := json.Unmarshal(body, &errDetails); err != nil {
return apiParams.APIErrorResponse{}, errors.Wrap(err, "decoding response")
}
return errDetails, fmt.Errorf("error in API call: %s", errDetails.Details)
}
func (c *Client) InitManager(url string, param params.NewUserParams) (params.User, error) {
body, err := json.Marshal(param)
if err != nil {
return params.User{}, errors.Wrap(err, "marshaling body")
}
url = fmt.Sprintf("%s/api/v1/first-run/", url)
var response params.User
resp, err := c.client.R().
SetHeader("Content-Type", "application/json").
SetBody(body).
SetResult(&response).
Post(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return params.User{}, errors.Wrap(decErr, "sending request")
}
return params.User{}, fmt.Errorf("error running init: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) Login(url string, param params.PasswordLoginParams) (string, error) {
body, err := json.Marshal(param)
if err != nil {
return "", errors.Wrap(err, "marshaling body")
}
url = fmt.Sprintf("%s/api/v1/auth/login", url)
var response params.JWTResponse
resp, err := c.client.R().
SetHeader("Content-Type", "application/json").
SetBody(body).
SetResult(&response).
Post(url)
if err != nil {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return "", errors.Wrap(err, "sending request")
}
return "", fmt.Errorf("error running init: %s", apiErr.Details)
}
return response.Token, nil
}

113
cmd/run-cli/cmd/init.go Normal file
View file

@ -0,0 +1,113 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"runner-manager/cmd/run-cli/config"
"runner-manager/params"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// initCmd represents the init command
var initCmd = &cobra.Command{
Use: "init",
SilenceUsage: true,
Short: "Initialize a newly installed runner-manager",
Long: `Initiallize a new installation of runner-manager.
A newly installed runner manager needs to be initialized to become
functional. This command sets the administrative user and password,
generates a controller UUID which is used internally to identify runners
created by the manager and enables the service.
Example usage:
run-cli login --name=dev --url=https://runner.example.com --username=admin --password=superSecretPassword
`,
RunE: func(cmd *cobra.Command, args []string) error {
if cfg != nil {
if cfg.HasManager(loginName) {
return fmt.Errorf("a manager with name %s already exists in your local config", loginName)
}
}
newUser := params.NewUserParams{
Username: loginUserName,
Password: loginPassword,
FullName: loginFullName,
Email: loginEmail,
}
response, err := cli.InitManager(loginURL, newUser)
if err != nil {
return errors.Wrap(err, "initializing manager")
}
loginParams := params.PasswordLoginParams{
Username: loginUserName,
Password: loginPassword,
}
token, err := cli.Login(loginURL, loginParams)
if err != nil {
return errors.Wrap(err, "authenticating")
}
if cfg == nil {
// we're creating a new config
cfg = &config.Config{
Managers: []config.Manager{},
}
}
cfg.Managers = append(cfg.Managers, config.Manager{
Name: loginName,
BaseURL: loginURL,
Token: token,
})
cfg.ActiveManager = loginName
if err := cfg.SaveConfig(); err != nil {
return errors.Wrap(err, "saving config")
}
renderUserTable(response)
return nil
},
}
func init() {
rootCmd.AddCommand(initCmd)
initCmd.Flags().StringVarP(&loginName, "name", "n", "", "A name for this runner manager")
initCmd.Flags().StringVarP(&loginURL, "url", "a", "", "The base URL for the runner manager API")
initCmd.Flags().StringVarP(&loginUserName, "username", "u", "", "The desired administrative username")
initCmd.Flags().StringVarP(&loginPassword, "password", "p", "", "The admin password")
initCmd.Flags().StringVarP(&loginFullName, "full-name", "f", "", "Full name of the user")
initCmd.Flags().StringVarP(&loginEmail, "email", "e", "", "Email address")
initCmd.MarkFlagRequired("name")
initCmd.MarkFlagRequired("url")
initCmd.MarkFlagRequired("username")
initCmd.MarkFlagRequired("password")
initCmd.MarkFlagRequired("full-name")
initCmd.MarkFlagRequired("email")
}
func renderUserTable(user params.User) {
t := table.NewWriter()
header := table.Row{"Field", "Value"}
t.AppendHeader(header)
t.AppendRow(table.Row{"ID", user.ID})
t.AppendRow(table.Row{"Username", user.Username})
t.AppendRow(table.Row{"Email", user.Email})
t.AppendRow(table.Row{"Enabled", user.Enabled})
fmt.Println(t.Render())
}

42
cmd/run-cli/cmd/login.go Normal file
View file

@ -0,0 +1,42 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
loginName string
loginURL string
loginPassword string
loginUserName string
loginFullName string
loginEmail string
)
// loginCmd represents the login command
var loginCmd = &cobra.Command{
Use: "login",
SilenceUsage: true,
Short: "Log into a manager",
Long: `Performs login for this machine on a remote runner-manager:
run-cli login --name=dev --url=https://runner.example.com`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("--> %v %v\n", cfg, configErr)
},
}
func init() {
rootCmd.AddCommand(loginCmd)
loginCmd.Flags().StringVarP(&loginName, "name", "n", "", "A name for this runner manager")
loginCmd.Flags().StringVarP(&loginURL, "url", "a", "", "The base URL for the runner manager API")
loginCmd.MarkFlagRequired("name")
loginCmd.MarkFlagRequired("url")
}

View file

@ -0,0 +1,41 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// organizationCmd represents the organization command
var organizationCmd = &cobra.Command{
Use: "organization",
SilenceUsage: true,
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("organization called")
},
}
func init() {
rootCmd.AddCommand(organizationCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// organizationCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// organizationCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View file

@ -0,0 +1,41 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// providerCmd represents the provider command
var providerCmd = &cobra.Command{
Use: "provider",
SilenceUsage: true,
Short: "Interacts with the providers API resource.",
Long: `Run operations on the provider resource.
Currently this command only lists all available configured
providers. Providers are added to the configuration file of
the service and are referenced by name when adding repositories
and organizations. Runners will be created in these environments.`,
Run: nil,
}
func init() {
providerCmd.AddCommand(
&cobra.Command{
Use: "list",
Short: "List all configured providers",
Long: `List all cloud providers configured with the service.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("provider list called")
return fmt.Errorf("I failed :(")
},
})
rootCmd.AddCommand(providerCmd)
}

View file

@ -0,0 +1,41 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// repositoryCmd represents the repository command
var repositoryCmd = &cobra.Command{
Use: "repository",
SilenceUsage: true,
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("repository called")
},
}
func init() {
rootCmd.AddCommand(repositoryCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// repositoryCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// repositoryCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

45
cmd/run-cli/cmd/root.go Normal file
View file

@ -0,0 +1,45 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"os"
"runner-manager/cmd/run-cli/client"
"runner-manager/cmd/run-cli/config"
"github.com/spf13/cobra"
)
var cfg *config.Config
var mgr config.Manager
var configErr error
var cli *client.Client
var active string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "run-cli",
Short: "Runner manager CLI app",
Long: `CLI for the github self hosted runners manager.`,
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
cobra.OnInitialize(initConfig)
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func initConfig() {
cfg, configErr = config.LoadConfig()
if configErr == nil {
mgr, _ = cfg.GetActiveConfig()
}
cli = client.NewClient(active, mgr)
}

41
cmd/run-cli/cmd/runner.go Normal file
View file

@ -0,0 +1,41 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// runnerCmd represents the runner command
var runnerCmd = &cobra.Command{
Use: "runner",
SilenceUsage: true,
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("runner called")
},
}
func init() {
rootCmd.AddCommand(runnerCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// runnerCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// runnerCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View file

@ -0,0 +1,100 @@
package config
import (
"os"
"path/filepath"
"github.com/BurntSushi/toml"
"github.com/pkg/errors"
runnerErrors "runner-manager/errors"
)
const (
DefaultAppFolder = "run-cli"
DefaultConfigFileName = "config.toml"
)
func getConfigFilePath() (string, error) {
configDir, err := getHomeDir()
if err != nil {
return "", errors.Wrap(err, "fetching home folder")
}
if err := ensureHomeDir(configDir); err != nil {
return "", errors.Wrap(err, "ensuring config dir")
}
cfgFile := filepath.Join(configDir, DefaultConfigFileName)
return cfgFile, nil
}
func LoadConfig() (*Config, error) {
cfgFile, err := getConfigFilePath()
if err != nil {
return nil, errors.Wrap(err, "fetching config")
}
var config Config
if _, err := toml.DecodeFile(cfgFile, &config); err != nil {
return nil, errors.Wrap(err, "decoding toml")
}
return &config, nil
}
type Config struct {
Managers []Manager `toml:"manager"`
ActiveManager string `toml:"active_manager"`
}
func (c *Config) HasManager(mgr string) bool {
if mgr == "" {
return false
}
for _, val := range c.Managers {
if val.Name == mgr {
return true
}
}
return false
}
func (c *Config) GetActiveConfig() (Manager, error) {
if c.ActiveManager == "" {
return Manager{}, runnerErrors.ErrNotFound
}
for _, val := range c.Managers {
if val.Name == c.ActiveManager {
return val, nil
}
}
return Manager{}, runnerErrors.ErrNotFound
}
func (c *Config) SaveConfig() error {
cfgFile, err := getConfigFilePath()
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return errors.Wrap(err, "getting config")
}
}
cfgHandle, err := os.Create(cfgFile)
if err != nil {
errors.Wrap(err, "getting file handle")
}
encoder := toml.NewEncoder(cfgHandle)
if err := encoder.Encode(c); err != nil {
return errors.Wrap(err, "saving config")
}
return nil
}
type Manager struct {
Name string `toml:"name"`
BaseURL string `toml:"base_url"`
Token string `toml:"bearer_token"`
}

View file

@ -0,0 +1,21 @@
package config
import (
"os"
"github.com/pkg/errors"
)
func ensureHomeDir(folder string) error {
if _, err := os.Stat(folder); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return errors.Wrap(err, "checking home dir")
}
if err := os.MkdirAll(folder, 0o710); err != nil {
return errors.Wrapf(err, "creating %s", folder)
}
}
return nil
}

View file

@ -0,0 +1,20 @@
//go:build !windows
// +build !windows
package config
import (
"fmt"
"os"
"path/filepath"
)
func getHomeDir() (string, error) {
home := os.Getenv("HOME")
if home == "" {
return "", fmt.Errorf("failed to get home folder")
}
return filepath.Join(home, ".local", "share", DefaultAppFolder), nil
}

View file

@ -0,0 +1,18 @@
//go:build windows && !linux
// +build windows,!linux
package config
import (
"os"
)
func getHomeDir() (string, error) {
appData := os.Getenv("APPDATA")
if appData == "" {
return "", fmt.Errorf("failed to get home folder")
}
return filepath.Join(appData, DefaultAppFolder), nil
}

11
cmd/run-cli/main.go Normal file
View file

@ -0,0 +1,11 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package main
import "runner-manager/cmd/run-cli/cmd"
func main() {
cmd.Execute()
}

View file

@ -14,8 +14,11 @@ import (
"runner-manager/auth"
"runner-manager/config"
"runner-manager/database"
"runner-manager/database/common"
"runner-manager/runner"
"runner-manager/util"
"github.com/pkg/errors"
// "github.com/google/go-github/v43/github"
// "golang.org/x/oauth2"
// "gopkg.in/yaml.v3"
@ -28,7 +31,17 @@ var (
var Version string
// var token = "super secret token"
func maybeInitController(db common.Store) error {
if _, err := db.ControllerInfo(); err == nil {
return nil
}
if _, err := db.InitController(); err != nil {
return errors.Wrap(err, "initializing controller")
}
return nil
}
func main() {
flag.Parse()
@ -51,6 +64,15 @@ func main() {
}
log.SetOutput(logWriter)
db, err := database.NewDatabase(ctx, cfg.Database)
if err != nil {
log.Fatal(err)
}
if err := maybeInitController(db); err != nil {
log.Fatal(err)
}
runner, err := runner.NewRunner(ctx, *cfg)
if err != nil {
log.Fatalf("failed to create controller: %+v", err)
@ -62,11 +84,6 @@ func main() {
log.Fatal(err)
}
db, err := database.NewDatabase(ctx, cfg.Database)
if err != nil {
log.Fatal(err)
}
authenticator := auth.NewAuthenticator(cfg.JWTAuth, db)
controller, err := controllers.NewAPIController(runner, authenticator)
if err != nil {

View file

@ -88,8 +88,8 @@ type Address struct {
type Instance struct {
Base
ProviderID string `gorm:"uniqueIndex"`
Name string `gorm:"uniqueIndex"`
ProviderID *string `gorm:"uniqueIndex"`
Name string `gorm:"uniqueIndex"`
OSType config.OSType
OSArch config.OSArch
OSName string

View file

@ -749,9 +749,13 @@ func (s *sqlDatabase) sqlAddressToParamsAddress(addr Address) params.Address {
}
func (s *sqlDatabase) sqlToParamsInstance(instance Instance) params.Instance {
var id string
if instance.ProviderID != nil {
id = *instance.ProviderID
}
ret := params.Instance{
ID: instance.ID.String(),
ProviderID: instance.ProviderID,
ProviderID: id,
Name: instance.Name,
OSType: instance.OSType,
OSName: instance.OSName,
@ -883,7 +887,7 @@ func (s *sqlDatabase) UpdateInstance(ctx context.Context, instanceID string, par
}
if param.ProviderID != "" {
instance.ProviderID = param.ProviderID
instance.ProviderID = &param.ProviderID
}
if param.OSName != "" {
@ -997,7 +1001,7 @@ func (s *sqlDatabase) updatePool(pool Pool, param params.UpdatePoolParams) (para
return params.Pool{}, errors.Wrap(q.Error, "saving database entry")
}
if len(param.Tags) > 0 {
if param.Tags != nil && len(param.Tags) > 0 {
tags := make([]Tag, len(param.Tags))
for idx, t := range param.Tags {
tags[idx] = Tag{
@ -1099,7 +1103,7 @@ func (s *sqlDatabase) CreateUser(ctx context.Context, user params.NewUserParams)
if q.Error != nil {
return params.User{}, errors.Wrap(q.Error, "creating user")
}
return params.User{}, nil
return s.sqlToParamsUser(newUser), nil
}
func (s *sqlDatabase) HasAdminUser(ctx context.Context) bool {

7
go.mod
View file

@ -4,15 +4,18 @@ go 1.18
require (
github.com/BurntSushi/toml v0.3.1
github.com/go-resty/resty/v2 v2.7.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/go-github/v43 v43.0.0
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/jedib0t/go-pretty/v6 v6.3.1
github.com/lxc/lxd v0.0.0-20220415052741-1170f2806124
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
github.com/pkg/errors v0.9.1
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
github.com/spf13/cobra v1.4.0
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
gopkg.in/natefinch/lumberjack.v2 v2.0.0
@ -30,18 +33,22 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/juju/webbrowser v1.0.0 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.12 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pkg/sftp v1.13.4 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20220325170049-de3da57026de // indirect
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect

22
go.sum
View file

@ -40,6 +40,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -64,6 +65,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-macaroon-bakery/macaroonpb v1.0.0 h1:It9exBaRMZ9iix1iJ6gwzfwsDE6ExNuwtAJ9e09v6XE=
github.com/go-macaroon-bakery/macaroonpb v1.0.0/go.mod h1:UzrGOcbiwTXISFP2XDLDPjfhMINZa+fX/7A2lMd31zc=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
@ -138,6 +141,10 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jedib0t/go-pretty/v6 v6.3.1 h1:aOXiD9oqiuLH8btPQW6SfgtQN5zwhyfzZls8a6sPJ/I=
github.com/jedib0t/go-pretty/v6 v6.3.1/go.mod h1:FMkOpgGD3EZ91cW8g/96RfxoV7bdeJyzXPYgz1L1ln0=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@ -174,6 +181,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lxc/lxd v0.0.0-20220415052741-1170f2806124 h1:EmjWCASxSUz+ymsEJfiWN3yx3yTypoKJrnOSSzAWYds=
github.com/lxc/lxd v0.0.0-20220415052741-1170f2806124/go.mod h1:T4xjj62BmFg1L5JfY2wRyPZtKbBeTFgo/GLwV8DFZ8M=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
@ -184,21 +193,29 @@ github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -282,6 +299,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -299,6 +317,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -327,6 +346,7 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -338,6 +358,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -492,6 +513,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,7 +1,6 @@
package common
import (
"context"
"runner-manager/params"
)
@ -16,7 +15,7 @@ type PoolManager interface {
WebhookSecret() string
HandleWorkflowJob(job params.WorkflowJob) error
RefreshState(cfg params.Repository) error
AddPool(ctx context.Context, pool params.Pool) error
// AddPool(ctx context.Context, pool params.Pool) error
// PoolManager lifecycle functions. Start/stop pool.
Start() error

View file

@ -30,11 +30,7 @@ func NewRepositoryPoolManager(ctx context.Context, cfg params.Repository, provid
if err != nil {
return nil, errors.Wrap(err, "getting github client")
}
pools := map[string]params.Pool{}
for _, val := range cfg.Pools {
pools[val.ID] = val
}
repo := &Repository{
ctx: ctx,
cfg: cfg,
@ -42,16 +38,11 @@ func NewRepositoryPoolManager(ctx context.Context, cfg params.Repository, provid
id: cfg.ID,
store: store,
providers: providers,
pools: pools,
controllerID: cfg.Internal.ControllerID,
quit: make(chan struct{}),
done: make(chan struct{}),
}
if err := repo.loadPools(); err != nil {
return nil, errors.Wrap(err, "loading pools")
}
return repo, nil
}
@ -66,7 +57,6 @@ type Repository struct {
quit chan struct{}
done chan struct{}
id string
pools map[string]params.Pool
mux sync.Mutex
}
@ -84,32 +74,6 @@ func (r *Repository) RefreshState(cfg params.Repository) error {
return nil
}
func (r *Repository) loadPools() error {
pools, err := r.store.ListRepoPools(r.ctx, r.id)
if err != nil {
return errors.Wrap(err, "fetching pools")
}
for _, pool := range pools {
if err := r.AddPool(r.ctx, pool); err != nil {
return errors.Wrap(err, "adding pool")
}
}
return nil
}
func (r *Repository) AddPool(ctx context.Context, pool params.Pool) error {
r.mux.Lock()
defer r.mux.Unlock()
if _, ok := r.pools[pool.ID]; ok {
return nil
}
r.pools[pool.ID] = pool
return nil
}
func (r *Repository) getGithubRunners() ([]*github.Runner, error) {
runners, _, err := r.ghcli.Actions.ListRunners(r.ctx, r.cfg.Owner, r.cfg.Name, nil)
if err != nil {
@ -249,9 +213,12 @@ func (r *Repository) cleanupOrphanedGithubRunners(runners []*github.Runner) erro
continue
}
pool, ok := r.pools[poolID]
if !ok {
// not a pool we manage.
pool, err := r.store.GetRepositoryPool(r.ctx, r.id, poolID)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "fetching pool")
}
// not pool we manage.
continue
}
@ -340,21 +307,26 @@ func (r *Repository) cleanupOrphanedProviderRunners(runners []*github.Runner) er
}
func (r *Repository) ensureMinIdleRunners() {
for poolID, pool := range r.pools {
pools, err := r.store.ListRepoPools(r.ctx, r.id)
if err != nil {
log.Printf("error listing pools: %s", err)
return
}
for _, pool := range pools {
if !pool.Enabled {
log.Printf("pool %s is disabled, skipping", pool.ID)
continue
}
existingInstances, err := r.store.ListInstances(r.ctx, poolID)
existingInstances, err := r.store.ListInstances(r.ctx, pool.ID)
if err != nil {
log.Printf("failed to ensure minimum idle workers for pool %s: %s", poolID, err)
log.Printf("failed to ensure minimum idle workers for pool %s: %s", pool.ID, err)
return
}
// asJs, _ := json.MarshalIndent(existingInstances, "", " ")
// log.Printf(">>> %s", string(asJs))
if uint(len(existingInstances)) >= pool.MaxRunners {
log.Printf("max workers (%d) reached for pool %s, skipping idle worker creation", pool.MaxRunners, poolID)
log.Printf("max workers (%d) reached for pool %s, skipping idle worker creation", pool.MaxRunners, pool.ID)
continue
}
@ -379,9 +351,9 @@ func (r *Repository) ensureMinIdleRunners() {
}
for i := 0; i < required; i++ {
log.Printf("addind new idle worker to pool %s", poolID)
if err := r.AddRunner(r.ctx, poolID); err != nil {
log.Printf("failed to add new instance for pool %s: %s", poolID, err)
log.Printf("addind new idle worker to pool %s", pool.ID)
if err := r.AddRunner(r.ctx, pool.ID); err != nil {
log.Printf("failed to add new instance for pool %s: %s", pool.ID, err)
}
}
}
@ -418,9 +390,9 @@ func (r *Repository) updateArgsFromProviderInstance(providerInstance params.Inst
}
func (r *Repository) deleteInstanceFromProvider(instance params.Instance) error {
pool, ok := r.pools[instance.PoolID]
if !ok {
return runnerErrors.NewNotFoundError("invalid pool ID")
pool, err := r.store.GetRepositoryPool(r.ctx, r.id, instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
}
provider, ok := r.providers[pool.ProviderName]
@ -439,9 +411,9 @@ func (r *Repository) deleteInstanceFromProvider(instance params.Instance) error
}
func (r *Repository) addInstanceToProvider(instance params.Instance) error {
pool, ok := r.pools[instance.PoolID]
if !ok {
return runnerErrors.NewNotFoundError("invalid pool ID: %s", instance.PoolID)
pool, err := r.store.GetRepositoryPool(r.ctx, r.id, instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
}
provider, ok := r.providers[pool.ProviderName]
@ -495,9 +467,9 @@ func (r *Repository) addInstanceToProvider(instance params.Instance) error {
// TODO: add function to set runner status to idle when instance calls home on callback url
func (r *Repository) AddRunner(ctx context.Context, poolID string) error {
pool, ok := r.pools[poolID]
if !ok {
return runnerErrors.NewNotFoundError("invalid provider ID")
pool, err := r.store.GetRepositoryPool(r.ctx, r.id, poolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
}
name := fmt.Sprintf("runner-manager-%s", uuid.New())
@ -512,7 +484,7 @@ func (r *Repository) AddRunner(ctx context.Context, poolID string) error {
CallbackURL: r.cfg.Internal.InstanceCallbackURL,
}
_, err := r.store.CreateInstance(r.ctx, poolID, createParams)
_, err = r.store.CreateInstance(r.ctx, poolID, createParams)
if err != nil {
return errors.Wrap(err, "creating instance")
}
@ -592,6 +564,7 @@ func (r *Repository) setInstanceRunnerStatus(job params.WorkflowJob, status prov
RunnerStatus: status,
}
log.Printf("setting runner status for %s to %s", runner.Name, status)
if _, err := r.store.UpdateInstance(r.ctx, runner.ID, updateParams); err != nil {
return errors.Wrap(err, "updating runner state")
}
@ -625,9 +598,10 @@ func (r *Repository) acquireNewInstance(job params.WorkflowJob) error {
if err != nil {
return errors.Wrap(err, "fetching suitable pool")
}
log.Printf("adding new runner with requested tags %s in pool %s", strings.Join(job.WorkflowJob.Labels, ", "), pool.ID)
if !pool.Enabled {
log.Printf("selecte pool (%s) is disabled", pool.ID)
log.Printf("selected pool (%s) is disabled", pool.ID)
return nil
}

View file

@ -14,7 +14,6 @@ import (
lxd "github.com/lxc/lxd/client"
"github.com/lxc/lxd/shared/api"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)
var _ common.Provider = &LXD{}
@ -159,8 +158,8 @@ func (l *LXD) getTools(image *api.Image, tools []*github.RunnerApplicationDownlo
continue
}
fmt.Println(*tool.Architecture, *tool.OS)
fmt.Printf("image arch: %s --> osType: %s\n", image.Architecture, string(osType))
// fmt.Println(*tool.Architecture, *tool.OS)
// fmt.Printf("image arch: %s --> osType: %s\n", image.Architecture, string(osType))
if *tool.Architecture == image.Architecture && *tool.OS == string(osType) {
return *tool, nil
}
@ -278,8 +277,8 @@ func (l *LXD) CreateInstance(ctx context.Context, bootstrapParams params.Bootstr
return params.Instance{}, errors.Wrap(err, "fetching create args")
}
asJs, err := yaml.Marshal(args)
fmt.Println(string(asJs), err)
// asJs, err := yaml.Marshal(args)
// fmt.Println(string(asJs), err)
if err := l.launchInstance(args); err != nil {
return params.Instance{}, errors.Wrap(err, "creating instance")
}

View file

@ -174,7 +174,7 @@ func (r *Runner) CreateRepoPool(ctx context.Context, repoID string, param params
r.mux.Lock()
defer r.mux.Unlock()
repo, ok := r.repositories[repoID]
_, ok := r.repositories[repoID]
if !ok {
return params.Pool{}, runnerErrors.ErrNotFound
}
@ -196,6 +196,13 @@ func (r *Runner) CreateRepoPool(ctx context.Context, repoID string, param params
return params.Pool{}, runnerErrors.NewBadRequestError("no such provider %s", param.ProviderName)
}
// github automatically adds the "self-hosted" tag as well as the OS type (linux, windows, etc)
// and architecture (arm, x64, etc) to all self hosted runners. When a workflow job comes in, we try
// to find a pool based on the labels that are set in the workflow. If we don't explicitly define these
// default tags for each pool, and the user targets these labels, we won't be able to match any pools.
// The downside is that all pools with the same OS and arch will have these default labels. Users should
// set distinct and unique labels on each pool, and explicitly target those labels, or risk assigning
// the job to the wrong worker type.
ghArch, err := util.ResolveToGithubArch(string(param.OSArch))
if err != nil {
return params.Pool{}, errors.Wrap(err, "invalid arch")
@ -219,23 +226,67 @@ func (r *Runner) CreateRepoPool(ctx context.Context, repoID string, param params
return params.Pool{}, errors.Wrap(err, "creating pool")
}
if err := repo.AddPool(ctx, pool); err != nil {
return params.Pool{}, errors.Wrap(err, "adding pool to manager")
}
return pool, nil
}
func (r *Runner) DeleteRepoPool(ctx context.Context) error {
func (r *Runner) GetRepoPoolByID(ctx context.Context, repoID, poolID string) (params.Pool, error) {
if !auth.IsAdmin(ctx) {
return params.Pool{}, runnerErrors.ErrUnauthorized
}
pool, err := r.store.GetRepositoryPool(ctx, repoID, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
return pool, nil
}
func (r *Runner) DeleteRepoPool(ctx context.Context, repoID, poolID string) error {
if !auth.IsAdmin(ctx) {
return runnerErrors.ErrUnauthorized
}
pool, err := r.store.GetRepositoryPool(ctx, repoID, poolID)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "fetching pool")
}
return nil
}
instances, err := r.store.ListInstances(ctx, pool.ID)
if err != nil {
return errors.Wrap(err, "fetching instances")
}
// TODO: implement a count function
if len(instances) > 0 {
runnerIDs := []string{}
for _, run := range instances {
runnerIDs = append(runnerIDs, run.ID)
}
return runnerErrors.NewBadRequestError("pool has runners: %s", strings.Join(runnerIDs, ", "))
}
if err := r.store.DeleteRepositoryPool(ctx, repoID, poolID); err != nil {
// deleted by some othe call?
if !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "deleting pool")
}
}
return nil
}
func (r *Runner) ListRepoPools(ctx context.Context) error {
return nil
}
func (r *Runner) ListRepoPools(ctx context.Context, repoID string) ([]params.Pool, error) {
if !auth.IsAdmin(ctx) {
return []params.Pool{}, runnerErrors.ErrUnauthorized
}
func (r *Runner) UpdateRepoPool(ctx context.Context) error {
return nil
pools, err := r.store.ListRepoPools(ctx, repoID)
if err != nil {
return nil, errors.Wrap(err, "fetching pools")
}
return pools, nil
}
func (r *Runner) ListPoolInstances(ctx context.Context) error {
@ -269,3 +320,34 @@ func (r *Runner) findRepoPoolManager(owner, name string) (common.PoolManager, er
}
return nil, errors.Wrapf(runnerErrors.ErrNotFound, "repository %s/%s not configured", owner, name)
}
func (r *Runner) UpdateRepoPool(ctx context.Context, repoID, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
if !auth.IsAdmin(ctx) {
return params.Pool{}, runnerErrors.ErrUnauthorized
}
pool, err := r.store.GetRepositoryPool(ctx, repoID, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
maxRunners := pool.MaxRunners
minIdleRunners := pool.MinIdleRunners
if param.MaxRunners != nil {
maxRunners = *param.MaxRunners
}
if param.MinIdleRunners != nil {
minIdleRunners = *param.MinIdleRunners
}
if minIdleRunners < maxRunners {
return params.Pool{}, runnerErrors.NewBadRequestError("min_idle_runners cannot be larger than max_runners")
}
newPool, err := r.store.UpdateRepositoryPool(ctx, repoID, poolID, param)
if err != nil {
return params.Pool{}, errors.Wrap(err, "updating pool")
}
return newPool, nil
}