Add a basic CLI
This commit is contained in:
parent
7ec937a138
commit
475d424f32
26 changed files with 930 additions and 102 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
14
auth/auth.go
14
auth/auth.go
|
|
@ -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
0
cmd/run-cli/LICENSE
Normal file
85
cmd/run-cli/client/client.go
Normal file
85
cmd/run-cli/client/client.go
Normal 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
113
cmd/run-cli/cmd/init.go
Normal 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
42
cmd/run-cli/cmd/login.go
Normal 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")
|
||||
}
|
||||
41
cmd/run-cli/cmd/organization.go
Normal file
41
cmd/run-cli/cmd/organization.go
Normal 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")
|
||||
}
|
||||
41
cmd/run-cli/cmd/provider.go
Normal file
41
cmd/run-cli/cmd/provider.go
Normal 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)
|
||||
}
|
||||
41
cmd/run-cli/cmd/repository.go
Normal file
41
cmd/run-cli/cmd/repository.go
Normal 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
45
cmd/run-cli/cmd/root.go
Normal 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
41
cmd/run-cli/cmd/runner.go
Normal 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")
|
||||
}
|
||||
100
cmd/run-cli/config/config.go
Normal file
100
cmd/run-cli/config/config.go
Normal 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"`
|
||||
}
|
||||
21
cmd/run-cli/config/home.go
Normal file
21
cmd/run-cli/config/home.go
Normal 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
|
||||
}
|
||||
20
cmd/run-cli/config/home_nix.go
Normal file
20
cmd/run-cli/config/home_nix.go
Normal 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
|
||||
}
|
||||
18
cmd/run-cli/config/home_windows.go
Normal file
18
cmd/run-cli/config/home_windows.go
Normal 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
11
cmd/run-cli/main.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||
|
||||
*/
|
||||
package main
|
||||
|
||||
import "runner-manager/cmd/run-cli/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = ¶m.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
7
go.mod
|
|
@ -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
22
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue