Add more CLI commands

This commit is contained in:
Gabriel Adrian Samfira 2022-05-03 12:40:59 +00:00
parent 475d424f32
commit 8ceafff09b
21 changed files with 995 additions and 107 deletions

View file

@ -95,9 +95,7 @@ func (a *APIController) handleWorkflowJobEvent(w http.ResponseWriter, r *http.Re
func (a *APIController) CatchAll(w http.ResponseWriter, r *http.Request) {
headers := r.Header.Clone()
// for key, val := range headers {
// fmt.Printf("%s --> %v\n", key, val)
// }
event := runnerParams.Event(headers.Get("X-Github-Event"))
switch event {
case runnerParams.WorkflowJobEvent:
@ -115,6 +113,7 @@ func (a *APIController) NotFoundHandler(w http.ResponseWriter, r *http.Request)
Error: "Not found",
}
w.WriteHeader(http.StatusNotFound)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(apiErr)
}
@ -180,6 +179,7 @@ func (a *APIController) ListCredentials(w http.ResponseWriter, r *http.Request)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(creds)
}
@ -191,6 +191,7 @@ func (a *APIController) ListProviders(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(providers)
}

View file

@ -98,7 +98,9 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl
apiRouter.Handle("/organizations", log(os.Stdout, http.HandlerFunc(han.CatchAll))).Methods("POST", "OPTIONS")
// Credentials and providers
apiRouter.Handle("/credentials/", log(os.Stdout, http.HandlerFunc(han.ListCredentials))).Methods("GET", "OPTIONS")
apiRouter.Handle("/credentials", log(os.Stdout, http.HandlerFunc(han.ListCredentials))).Methods("GET", "OPTIONS")
apiRouter.Handle("/providers/", log(os.Stdout, http.HandlerFunc(han.ListProviders))).Methods("GET", "OPTIONS")
apiRouter.Handle("/providers", log(os.Stdout, http.HandlerFunc(han.ListProviders))).Methods("GET", "OPTIONS")
return router

View file

@ -115,7 +115,7 @@ func (a *Authenticator) AuthenticateUser(ctx context.Context, info params.Passwo
user, err := a.store.GetUser(ctx, info.Username)
if err != nil {
if err == runnerErrors.ErrNotFound {
if errors.Is(err, runnerErrors.ErrNotFound) {
return ctx, runnerErrors.ErrUnauthorized
}
return ctx, errors.Wrap(err, "authenticating")

View file

@ -12,8 +12,14 @@ import (
"github.com/pkg/errors"
)
func NewClient(name string, cfg config.Manager) *Client {
func NewClient(name string, cfg config.Manager, debug bool) *Client {
cli := resty.New()
if cfg.Token != "" {
cli = cli.SetAuthToken(cfg.Token)
}
cli = cli.
SetHeader("Accept", "application/json").
SetDebug(debug)
return &Client{
ManagerName: name,
Config: cfg,
@ -45,7 +51,6 @@ func (c *Client) InitManager(url string, param params.NewUserParams) (params.Use
var response params.User
resp, err := c.client.R().
SetHeader("Content-Type", "application/json").
SetBody(body).
SetResult(&response).
Post(url)
@ -69,17 +74,210 @@ func (c *Client) Login(url string, param params.PasswordLoginParams) (string, er
var response params.JWTResponse
resp, err := c.client.R().
SetHeader("Content-Type", "application/json").
SetBody(body).
SetResult(&response).
Post(url)
if err != nil {
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return "", errors.Wrap(err, "sending request")
return "", errors.Wrap(decErr, "sending request")
}
return "", fmt.Errorf("error running init: %s", apiErr.Details)
return "", fmt.Errorf("error performing login: %s", apiErr.Details)
}
return response.Token, nil
}
func (c *Client) ListCredentials() ([]params.GithubCredentials, error) {
var ghCreds []params.GithubCredentials
url := fmt.Sprintf("%s/api/v1/credentials", c.Config.BaseURL)
resp, err := c.client.R().
SetResult(&ghCreds).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return nil, errors.Wrap(decErr, "sending request")
}
return nil, fmt.Errorf("error fetching credentials: %s", apiErr.Details)
}
return ghCreds, nil
}
func (c *Client) ListProviders() ([]params.Provider, error) {
var providers []params.Provider
url := fmt.Sprintf("%s/api/v1/providers", c.Config.BaseURL)
resp, err := c.client.R().
SetResult(&providers).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return nil, errors.Wrap(decErr, "sending request")
}
return nil, fmt.Errorf("error fetching providers: %s", apiErr.Details)
}
return providers, nil
}
func (c *Client) ListRepositories() ([]params.Repository, error) {
var repos []params.Repository
url := fmt.Sprintf("%s/api/v1/repositories", c.Config.BaseURL)
resp, err := c.client.R().
SetResult(&repos).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return nil, errors.Wrap(decErr, "sending request")
}
return nil, fmt.Errorf("error fetching repos: %s", apiErr.Details)
}
return repos, nil
}
func (c *Client) CreateRepository(param params.CreateRepoParams) (params.Repository, error) {
var response params.Repository
url := fmt.Sprintf("%s/api/v1/repositories", c.Config.BaseURL)
body, err := json.Marshal(param)
if err != nil {
return params.Repository{}, err
}
resp, err := c.client.R().
SetBody(body).
SetResult(&response).
Post(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error performing login: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) GetRepository(repoID string) (params.Repository, error) {
var response params.Repository
url := fmt.Sprintf("%s/api/v1/repositories/%s", c.Config.BaseURL, repoID)
resp, err := c.client.R().
SetResult(&response).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error fetching repos: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) DeleteRepository(repoID string) error {
url := fmt.Sprintf("%s/api/v1/repositories/%s", c.Config.BaseURL, repoID)
resp, err := c.client.R().
Delete(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return errors.Wrap(decErr, "sending request")
}
return fmt.Errorf("error fetching repos: %s", apiErr.Details)
}
return nil
}
func (c *Client) CreateRepoPool(repoID string, param params.CreatePoolParams) (params.Pool, error) {
url := fmt.Sprintf("%s/api/v1/repositories/%s/pools", c.Config.BaseURL, repoID)
var response params.Pool
body, err := json.Marshal(param)
if err != nil {
return response, err
}
resp, err := c.client.R().
SetBody(body).
SetResult(&response).
Post(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error performing login: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) ListRepoPools(repoID string) ([]params.Pool, error) {
url := fmt.Sprintf("%s/api/v1/repositories/%s/pools", c.Config.BaseURL, repoID)
var response []params.Pool
resp, err := c.client.R().
SetResult(&response).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error performing login: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) GetRepoPool(repoID, poolID string) (params.Pool, error) {
url := fmt.Sprintf("%s/api/v1/repositories/%s/pools/%s", c.Config.BaseURL, repoID, poolID)
var response params.Pool
resp, err := c.client.R().
SetResult(&response).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error performing login: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) DeleteRepoPool(repoID, poolID string) error {
url := fmt.Sprintf("%s/api/v1/repositories/%s/pools/%s", c.Config.BaseURL, repoID, poolID)
resp, err := c.client.R().
Delete(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return errors.Wrap(decErr, "sending request")
}
return fmt.Errorf("error performing login: %s", apiErr.Details)
}
return nil
}
func (c *Client) UpdateRepoPool(repoID, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
url := fmt.Sprintf("%s/api/v1/repositories/%s/pools/%s", c.Config.BaseURL, repoID, poolID)
var response params.Pool
body, err := json.Marshal(param)
if err != nil {
return response, err
}
resp, err := c.client.R().
SetBody(body).
SetResult(&response).
Put(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error performing login: %s", apiErr.Details)
}
return response, nil
}

View file

@ -0,0 +1,63 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"runner-manager/params"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
// credentialsCmd represents the credentials command
var credentialsCmd = &cobra.Command{
Use: "credentials",
Aliases: []string{"creds"},
Short: "List configured credentials",
Long: `List all available credentials configured in the service
config file.
Currently, github personal tokens are configured statically in the config file
of the runner-manager service. This command lists the names of those credentials,
which in turn can be used to define pools of runners withing repositories.`,
Run: nil,
}
func init() {
credentialsCmd.AddCommand(
&cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List configured github credentials",
Long: `List the names of the github personal access tokens availabe to the runner-manager.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
creds, err := cli.ListCredentials()
if err != nil {
return err
}
formatGithubCredentials(creds)
return nil
},
})
rootCmd.AddCommand(credentialsCmd)
}
func formatGithubCredentials(creds []params.GithubCredentials) {
t := table.NewWriter()
header := table.Row{"Name", "Description"}
t.AppendHeader(header)
for _, val := range creds {
t.AppendRow(table.Row{val.Name, val.Description})
t.AppendSeparator()
}
fmt.Println(t.Render())
}

View file

@ -6,7 +6,9 @@ package cmd
import (
"fmt"
"strings"
"runner-manager/cmd/run-cli/common"
"runner-manager/cmd/run-cli/config"
"runner-manager/params"
@ -33,18 +35,24 @@ run-cli login --name=dev --url=https://runner.example.com --username=admin --pas
`,
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)
if cfg.HasManager(loginProfileName) {
return fmt.Errorf("a manager with name %s already exists in your local config", loginProfileName)
}
}
if err := promptUnsetInitVariables(); err != nil {
return err
}
newUser := params.NewUserParams{
Username: loginUserName,
Password: loginPassword,
FullName: loginFullName,
Email: loginEmail,
}
response, err := cli.InitManager(loginURL, newUser)
url := strings.TrimSuffix(loginURL, "/")
response, err := cli.InitManager(url, newUser)
if err != nil {
return errors.Wrap(err, "initializing manager")
}
@ -54,25 +62,18 @@ run-cli login --name=dev --url=https://runner.example.com --username=admin --pas
Password: loginPassword,
}
token, err := cli.Login(loginURL, loginParams)
token, err := cli.Login(url, 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,
Name: loginProfileName,
BaseURL: url,
Token: token,
})
cfg.ActiveManager = loginName
cfg.ActiveManager = loginProfileName
if err := cfg.SaveConfig(); err != nil {
return errors.Wrap(err, "saving config")
@ -83,21 +84,42 @@ run-cli login --name=dev --url=https://runner.example.com --username=admin --pas
},
}
func promptUnsetInitVariables() error {
var err error
if loginUserName == "" {
loginUserName, err = common.PromptString("Username")
if err != nil {
return err
}
}
if loginEmail == "" {
loginEmail, err = common.PromptString("Email")
if err != nil {
return err
}
}
if loginPassword == "" {
loginPassword, err = common.PromptPassword("Password")
if err != nil {
return err
}
}
return nil
}
func init() {
rootCmd.AddCommand(initCmd)
initCmd.Flags().StringVarP(&loginName, "name", "n", "", "A name for this runner manager")
initCmd.Flags().StringVarP(&loginProfileName, "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.Flags().StringVarP(&loginFullName, "full-name", "f", "", "Full name of the user")
initCmd.Flags().StringVarP(&loginPassword, "password", "p", "", "The admin password")
initCmd.MarkFlagRequired("name")
initCmd.MarkFlagRequired("url")
initCmd.MarkFlagRequired("username")
initCmd.MarkFlagRequired("password")
initCmd.MarkFlagRequired("full-name")
initCmd.MarkFlagRequired("email")
}
func renderUserTable(user params.User) {

View file

@ -6,17 +6,21 @@ package cmd
import (
"fmt"
"runner-manager/cmd/run-cli/common"
"runner-manager/cmd/run-cli/config"
"runner-manager/params"
"strings"
"github.com/spf13/cobra"
)
var (
loginName string
loginURL string
loginPassword string
loginUserName string
loginFullName string
loginEmail string
loginProfileName string
loginURL string
loginPassword string
loginUserName string
loginFullName string
loginEmail string
)
// loginCmd represents the login command
@ -27,16 +31,68 @@ var loginCmd = &cobra.Command{
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)
RunE: func(cmd *cobra.Command, args []string) error {
if cfg != nil {
if cfg.HasManager(loginProfileName) {
return fmt.Errorf("a manager with name %s already exists in your local config", loginProfileName)
}
}
if err := promptUnsetLoginVariables(); err != nil {
return err
}
url := strings.TrimSuffix(loginURL, "/")
loginParams := params.PasswordLoginParams{
Username: loginUserName,
Password: loginPassword,
}
resp, err := cli.Login(url, loginParams)
if err != nil {
return err
}
cfg.Managers = append(cfg.Managers, config.Manager{
Name: loginProfileName,
BaseURL: url,
Token: resp,
})
cfg.ActiveManager = loginProfileName
if err := cfg.SaveConfig(); err != nil {
return err
}
return nil
},
}
func promptUnsetLoginVariables() error {
var err error
if loginUserName == "" {
loginUserName, err = common.PromptString("Username")
if err != nil {
return err
}
}
if loginPassword == "" {
loginPassword, err = common.PromptPassword("Password")
if err != nil {
return err
}
}
return nil
}
func init() {
rootCmd.AddCommand(loginCmd)
loginCmd.Flags().StringVarP(&loginName, "name", "n", "", "A name for this runner manager")
loginCmd.Flags().StringVarP(&loginProfileName, "name", "n", "", "A name for this runner manager")
loginCmd.Flags().StringVarP(&loginURL, "url", "a", "", "The base URL for the runner manager API")
loginCmd.Flags().StringVarP(&loginUserName, "username", "u", "", "Username to log in as")
loginCmd.Flags().StringVarP(&loginPassword, "password", "p", "", "The user passowrd")
loginCmd.MarkFlagRequired("name")
loginCmd.MarkFlagRequired("url")
}

View file

@ -14,6 +14,7 @@ import (
var organizationCmd = &cobra.Command{
Use: "organization",
SilenceUsage: true,
Aliases: []string{"org"},
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:

View file

@ -6,7 +6,9 @@ package cmd
import (
"fmt"
"runner-manager/params"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
@ -32,10 +34,29 @@ func init() {
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 :(")
if needsInit {
return needsInitError
}
providers, err := cli.ListProviders()
if err != nil {
return err
}
formatProviders(providers)
return nil
},
})
rootCmd.AddCommand(providerCmd)
}
func formatProviders(providers []params.Provider) {
t := table.NewWriter()
header := table.Row{"Name", "Description"}
t.AppendHeader(header)
for _, val := range providers {
t.AppendRow(table.Row{val.Name, val.ProviderType})
t.AppendSeparator()
}
fmt.Println(t.Render())
}

View file

@ -0,0 +1,309 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"runner-manager/config"
"runner-manager/params"
"strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
var (
poolProvider string
poolMaxRunners uint
poolMinIdleRunners uint
poolImage string
poolFlavor string
poolOSType string
poolOSArch string
poolTags string
poolEnabled bool
)
// repoPoolCmd represents the pool command
var repoPoolCmd = &cobra.Command{
Use: "pool",
SilenceUsage: true,
Aliases: []string{"pools"},
Short: "Manage pools",
Long: `Manage pools for a repository.
Repositories and organizations can define multiple pools with different
characteristics, which in turn will spawn github self hosted runners on
compute instances that reflect those characteristics.
For example, one pool can define a runner with tags "GPU,ML" which will
spin up instances with access to a GPU, on the desired provider.`,
Run: nil,
}
var poolAddCmd = &cobra.Command{
Use: "add",
Aliases: []string{"create"},
Short: "Add pool",
Long: `Add a new pool repository to the manager.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a repository ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
tags := strings.Split(poolTags, ",")
newPoolParams := params.CreatePoolParams{
ProviderName: poolProvider,
MaxRunners: poolMaxRunners,
MinIdleRunners: poolMinIdleRunners,
Image: poolImage,
Flavor: poolFlavor,
OSType: config.OSType(poolOSType),
OSArch: config.OSArch(poolOSArch),
Tags: tags,
Enabled: poolEnabled,
}
if err := newPoolParams.Validate(); err != nil {
return err
}
pool, err := cli.CreateRepoPool(args[0], newPoolParams)
if err != nil {
return err
}
formatOnePool(pool)
return nil
},
}
var poolListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List repository pools",
Long: `List all configured pools for a given repository.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a repository ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
pools, err := cli.ListRepoPools(args[0])
if err != nil {
return err
}
formatPools(pools)
return nil
},
}
var poolShowCmd = &cobra.Command{
Use: "show",
Short: "Show details for one pool",
Long: `Displays detailed information about a single pool.`,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) < 2 || len(args) > 2 {
return fmt.Errorf("command requires repoID and poolID")
}
pool, err := cli.GetRepoPool(args[0], args[1])
if err != nil {
return err
}
formatOnePool(pool)
return nil
},
}
var poolDeleteCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm", "del"},
Short: "Removes one pool",
Long: `Delete one repository pool from the manager.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) < 2 || len(args) > 2 {
return fmt.Errorf("command requires repoID and poolID")
}
if err := cli.DeleteRepoPool(args[0], args[1]); err != nil {
return err
}
return nil
},
}
var poolUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update one pool",
Long: `Updates pool characteristics.
This command updates the pool characteristics. Runners already created prior to updating
the pool, will not be recreated. IF they no longer suit your needs, you will need to
explicitly remove them using the runner delete command.
`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) < 2 || len(args) > 2 {
return fmt.Errorf("command requires repoID and poolID")
}
poolUpdateParams := params.UpdatePoolParams{}
if cmd.Flags().Changed("image") {
poolUpdateParams.Image = poolImage
}
if cmd.Flags().Changed("flavor") {
poolUpdateParams.Flavor = poolFlavor
}
if cmd.Flags().Changed("tags") {
poolUpdateParams.Tags = strings.Split(poolTags, ",")
}
if cmd.Flags().Changed("os-type") {
poolUpdateParams.OSType = config.OSType(poolOSType)
}
if cmd.Flags().Changed("os-arch") {
poolUpdateParams.OSArch = config.OSArch(poolOSArch)
}
if cmd.Flags().Changed("max-runners") {
poolUpdateParams.MaxRunners = &poolMaxRunners
}
if cmd.Flags().Changed("min-idle-runners") {
poolUpdateParams.MinIdleRunners = &poolMinIdleRunners
}
if cmd.Flags().Changed("enabled") {
poolUpdateParams.Enabled = &poolEnabled
}
pool, err := cli.UpdateRepoPool(args[0], args[1], poolUpdateParams)
if err != nil {
return err
}
formatOnePool(pool)
return nil
},
}
func init() {
poolAddCmd.Flags().StringVar(&poolProvider, "provider-name", "", "The name of the provider where runners will be created.")
poolAddCmd.Flags().StringVar(&poolImage, "image", "", "The provider-specific image name to use for runners in this pool.")
poolAddCmd.Flags().StringVar(&poolFlavor, "flavor", "", "The flavor to use for this runner.")
poolAddCmd.Flags().StringVar(&poolTags, "tags", "", "A comma separated list of tags to assign to this runner.")
poolAddCmd.Flags().StringVar(&poolOSType, "os-type", "linux", "Operating system type (windows, linux, etc).")
poolAddCmd.Flags().StringVar(&poolOSArch, "os-arch", "amd64", "Operating system architecture (amd64, arm, etc).")
poolAddCmd.Flags().UintVar(&poolMaxRunners, "max-runners", 5, "The maximum number of runner this pool will create.")
poolAddCmd.Flags().UintVar(&poolMinIdleRunners, "min-idle-runners", 1, "Attempt to maintain a minimum of idle self-hosted runners of this type.")
poolAddCmd.Flags().BoolVar(&poolEnabled, "enabled", false, "Enable this pool.")
poolAddCmd.MarkFlagRequired("provider-name")
poolAddCmd.MarkFlagRequired("image")
poolAddCmd.MarkFlagRequired("flavor")
poolAddCmd.MarkFlagRequired("tags")
poolUpdateCmd.Flags().StringVar(&poolImage, "image", "", "The provider-specific image name to use for runners in this pool.")
poolUpdateCmd.Flags().StringVar(&poolFlavor, "flavor", "", "The flavor to use for this runner.")
poolUpdateCmd.Flags().StringVar(&poolTags, "tags", "", "A comma separated list of tags to assign to this runner.")
poolUpdateCmd.Flags().StringVar(&poolOSType, "os-type", "linux", "Operating system type (windows, linux, etc).")
poolUpdateCmd.Flags().StringVar(&poolOSArch, "os-arch", "amd64", "Operating system architecture (amd64, arm, etc).")
poolUpdateCmd.Flags().UintVar(&poolMaxRunners, "max-runners", 5, "The maximum number of runner this pool will create.")
poolUpdateCmd.Flags().UintVar(&poolMinIdleRunners, "min-idle-runners", 1, "Attempt to maintain a minimum of idle self-hosted runners of this type.")
poolUpdateCmd.Flags().BoolVar(&poolEnabled, "enabled", false, "Enable this pool.")
repoPoolCmd.AddCommand(
poolListCmd,
poolAddCmd,
poolShowCmd,
poolDeleteCmd,
poolUpdateCmd,
)
repositoryCmd.AddCommand(repoPoolCmd)
}
func formatPools(pools []params.Pool) {
t := table.NewWriter()
header := table.Row{"ID", "Image", "Flavor", "Tags", "Enabled"}
t.AppendHeader(header)
for _, pool := range pools {
tags := []string{}
for _, tag := range pool.Tags {
tags = append(tags, tag.Name)
}
t.AppendRow(table.Row{pool.ID, pool.Image, pool.Flavor, strings.Join(tags, " "), pool.Enabled})
t.AppendSeparator()
}
fmt.Println(t.Render())
}
func formatOnePool(pool params.Pool) {
t := table.NewWriter()
rowConfigAutoMerge := table.RowConfig{AutoMerge: true}
header := table.Row{"Field", "Value"}
tags := []string{}
for _, tag := range pool.Tags {
tags = append(tags, tag.Name)
}
t.AppendHeader(header)
t.AppendRow(table.Row{"ID", pool.ID})
t.AppendRow(table.Row{"Provider Name", pool.ProviderName})
t.AppendRow(table.Row{"Image", pool.Image})
t.AppendRow(table.Row{"Flavor", pool.Flavor})
t.AppendRow(table.Row{"OS Type", pool.OSType})
t.AppendRow(table.Row{"OS Architecture", pool.OSArch})
t.AppendRow(table.Row{"Max Runners", pool.MaxRunners})
t.AppendRow(table.Row{"Min Idle Runners", pool.MinIdleRunners})
t.AppendRow(table.Row{"Tags", strings.Join(tags, ", ")})
t.AppendRow(table.Row{"Enabled", pool.Enabled})
if len(pool.Instances) > 0 {
for _, instance := range pool.Instances {
t.AppendRow(table.Row{"Instances", fmt.Sprintf("%s (%s)", instance.Name, instance.ID)}, rowConfigAutoMerge)
}
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true},
})
fmt.Println(t.Render())
}

View file

@ -6,36 +6,170 @@ package cmd
import (
"fmt"
"runner-manager/params"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
var (
repoOwner string
repoName string
repoWebhookSecret string
repoCreds string
)
// repositoryCmd represents the repository command
var repositoryCmd = &cobra.Command{
Use: "repository",
Aliases: []string{"repo"},
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:
Short: "Manage repositories",
Long: `Add, remove or update repositories for which we manage
self hosted runners.
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")
This command allows you to define a new repository or manage an existing
repository for which the runner-manager maintains pools of self hosted runners.`,
Run: nil,
}
var repoAddCmd = &cobra.Command{
Use: "add",
Aliases: []string{"create"},
Short: "Add repository",
Long: `Add a new repository to the manager.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
newRepoReq := params.CreateRepoParams{
Owner: repoOwner,
Name: repoName,
WebhookSecret: repoWebhookSecret,
CredentialsName: repoCreds,
}
repo, err := cli.CreateRepository(newRepoReq)
if err != nil {
return err
}
formatOneRepository(repo)
return nil
},
}
var repoListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List repositories",
Long: `List all configured respositories that are currently managed.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
repos, err := cli.ListRepositories()
if err != nil {
return err
}
formatRepositories(repos)
return nil
},
}
var repoShowCmd = &cobra.Command{
Use: "show",
Short: "Show details for one repository",
Long: `Displays detailed information about a single repository.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a repository ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
repo, err := cli.GetRepository(args[0])
if err != nil {
return err
}
formatOneRepository(repo)
return nil
},
}
var repoDeleteCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm", "del"},
Short: "Removes one repository",
Long: `Delete one repository from the manager.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a repository ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
if err := cli.DeleteRepository(args[0]); err != nil {
return err
}
return nil
},
}
func init() {
repoAddCmd.Flags().StringVar(&repoOwner, "owner", "", "The owner of this repository")
repoAddCmd.Flags().StringVar(&repoName, "name", "", "The name of the repository")
repoAddCmd.Flags().StringVar(&repoWebhookSecret, "webhook-secret", "", "The webhook secret for this repository")
repoAddCmd.Flags().StringVar(&repoCreds, "credentials", "", "Credentials name. See credentials list.")
repoAddCmd.MarkFlagRequired("credentials")
repoAddCmd.MarkFlagRequired("owner")
repoAddCmd.MarkFlagRequired("name")
repositoryCmd.AddCommand(
repoListCmd,
repoAddCmd,
repoShowCmd,
repoDeleteCmd,
)
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")
}
func formatRepositories(repos []params.Repository) {
t := table.NewWriter()
header := table.Row{"ID", "Owner", "Name", "Credentials name"}
t.AppendHeader(header)
for _, val := range repos {
t.AppendRow(table.Row{val.ID, val.Owner, val.Name, val.CredentialsName})
t.AppendSeparator()
}
fmt.Println(t.Render())
}
func formatOneRepository(repo params.Repository) {
t := table.NewWriter()
header := table.Row{"Field", "Value"}
t.AppendHeader(header)
t.AppendRow(table.Row{"ID", repo.ID})
t.AppendRow(table.Row{"Owner", repo.Owner})
t.AppendRow(table.Row{"Name", repo.Name})
t.AppendRow(table.Row{"Credentials", repo.CredentialsName})
if len(repo.Pools) > 0 {
for _, pool := range repo.Pools {
t.AppendRow(table.Row{"Pools", pool.ID})
}
}
fmt.Println(t.Render())
}

View file

@ -5,6 +5,7 @@ Copyright © 2022 NAME HERE <EMAIL ADDRESS>
package cmd
import (
"fmt"
"os"
"runner-manager/cmd/run-cli/client"
"runner-manager/cmd/run-cli/config"
@ -12,11 +13,15 @@ import (
"github.com/spf13/cobra"
)
var cfg *config.Config
var mgr config.Manager
var configErr error
var cli *client.Client
var active string
var (
cfg *config.Config
mgr config.Manager
cli *client.Client
active string
needsInit bool
debug bool
needsInitError = fmt.Errorf("Please log into a runner-manager first")
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
@ -28,6 +33,7 @@ var rootCmd = &cobra.Command{
// 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() {
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug on all API calls")
cobra.OnInitialize(initConfig)
err := rootCmd.Execute()
@ -37,9 +43,21 @@ func Execute() {
}
func initConfig() {
cfg, configErr = config.LoadConfig()
if configErr == nil {
mgr, _ = cfg.GetActiveConfig()
var err error
cfg, err = config.LoadConfig()
if err != nil {
fmt.Printf("Failed to load config: %s", err)
os.Exit(1)
}
cli = client.NewClient(active, mgr)
if len(cfg.Managers) == 0 {
// config is empty.
needsInit = true
} else {
mgr, err = cfg.GetActiveConfig()
if err != nil {
mgr = cfg.Managers[0]
}
active = mgr.Name
}
cli = client.NewClient(active, mgr, debug)
}

View file

@ -0,0 +1,53 @@
package common
import (
"errors"
"github.com/manifoldco/promptui"
"github.com/nbutton23/zxcvbn-go"
)
func PromptPassword(label string) (string, error) {
if label == "" {
label = "Password"
}
validate := func(input string) error {
passwordStenght := zxcvbn.PasswordStrength(input, nil)
if passwordStenght.Score < 4 {
return errors.New("password is too weak")
}
return nil
}
prompt := promptui.Prompt{
Label: label,
Validate: validate,
Mask: '*',
}
result, err := prompt.Run()
if err != nil {
return "", err
}
return result, nil
}
func PromptString(label string) (string, error) {
validate := func(input string) error {
if len(input) == 0 {
return errors.New("empty input not allowed")
}
return nil
}
prompt := promptui.Prompt{
Label: label,
Validate: validate,
}
result, err := prompt.Run()
if err != nil {
return "", err
}
return result, nil
}

View file

@ -35,6 +35,14 @@ func LoadConfig() (*Config, error) {
return nil, errors.Wrap(err, "fetching config")
}
if _, err := os.Stat(cfgFile); err != nil {
if errors.Is(err, os.ErrNotExist) {
// return empty config
return &Config{}, nil
}
return nil, errors.Wrap(err, "accessing config file")
}
var config Config
if _, err := toml.DecodeFile(cfgFile, &config); err != nil {
return nil, errors.Wrap(err, "decoding toml")

View file

@ -4,16 +4,17 @@
package config
import (
"fmt"
"os"
"path/filepath"
"github.com/pkg/errors"
)
func getHomeDir() (string, error) {
home := os.Getenv("HOME")
home, err := os.UserHomeDir()
if home == "" {
return "", fmt.Errorf("failed to get home folder")
if err != nil {
return "", errors.Wrap(err, "fetching home dir")
}
return filepath.Join(home, ".local", "share", DefaultAppFolder), nil

View file

@ -75,12 +75,17 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) params.Pool {
OSType: pool.OSType,
Enabled: pool.Enabled,
Tags: make([]params.Tag, len(pool.Tags)),
Instances: make([]params.Instance, len(pool.Instances)),
}
for idx, val := range pool.Tags {
ret.Tags[idx] = s.sqlToCommonTags(*val)
}
for idx, inst := range pool.Instances {
ret.Instances[idx] = s.sqlToParamsInstance(inst)
}
return ret
}
@ -273,9 +278,6 @@ func (s *sqlDatabase) ListRepositories(ctx context.Context) ([]params.Repository
func (s *sqlDatabase) DeleteRepository(ctx context.Context, repoID string, hardDelete bool) error {
repo, err := s.getRepoByID(ctx, repoID)
if err != nil {
if err == runnerErrors.ErrNotFound {
return nil
}
return errors.Wrap(err, "fetching repo")
}
@ -391,13 +393,10 @@ func (s *sqlDatabase) ListOrganizations(ctx context.Context) ([]params.Organizat
func (s *sqlDatabase) DeleteOrganization(ctx context.Context, name string) error {
org, err := s.getOrg(ctx, name, false)
if err != nil {
if err == runnerErrors.ErrNotFound {
return nil
}
return errors.Wrap(err, "fetching repo")
}
q := s.conn.Delete(&org)
q := s.conn.Unscoped().Delete(&org)
if q.Error != nil && !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return errors.Wrap(q.Error, "deleting org")
}
@ -579,7 +578,7 @@ func (s *sqlDatabase) ListOrgPools(ctx context.Context, orgID string) ([]params.
return ret, nil
}
func (s *sqlDatabase) getRepoPool(ctx context.Context, repoID, poolID string) (Pool, error) {
func (s *sqlDatabase) getRepoPool(ctx context.Context, repoID, poolID string, preload ...string) (Pool, error) {
repo, err := s.getRepoByID(ctx, repoID)
if err != nil {
return Pool{}, errors.Wrap(err, "fetching repo")
@ -589,8 +588,16 @@ func (s *sqlDatabase) getRepoPool(ctx context.Context, repoID, poolID string) (P
if err != nil {
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
q := s.conn
if len(preload) > 0 {
for _, item := range preload {
q = q.Preload(item)
}
}
var pool []Pool
err = s.conn.Model(&repo).Association("Pools").Find(&pool, "id = ?", u)
err = q.Model(&repo).Association("Pools").Find(&pool, "id = ?", u)
if err != nil {
return Pool{}, errors.Wrap(err, "fetching pool")
}
@ -602,7 +609,7 @@ func (s *sqlDatabase) getRepoPool(ctx context.Context, repoID, poolID string) (P
}
func (s *sqlDatabase) GetRepositoryPool(ctx context.Context, repoID, poolID string) (params.Pool, error) {
pool, err := s.getRepoPool(ctx, repoID, poolID)
pool, err := s.getRepoPool(ctx, repoID, poolID, "Tags", "Instances")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
@ -670,12 +677,9 @@ func (s *sqlDatabase) GetOrganizationPool(ctx context.Context, orgID, poolID str
func (s *sqlDatabase) DeleteRepositoryPool(ctx context.Context, repoID, poolID string) error {
pool, err := s.getRepoPool(ctx, repoID, poolID)
if err != nil {
if errors.Is(err, runnerErrors.ErrNotFound) {
return nil
}
return errors.Wrap(err, "looking up repo pool")
}
q := s.conn.Delete(&pool)
q := s.conn.Unscoped().Delete(&pool)
if q.Error != nil && !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return errors.Wrap(q.Error, "deleting pool")
}
@ -685,12 +689,9 @@ func (s *sqlDatabase) DeleteRepositoryPool(ctx context.Context, repoID, poolID s
func (s *sqlDatabase) DeleteOrganizationPool(ctx context.Context, orgID, poolID string) error {
pool, err := s.getOrgPool(ctx, orgID, poolID, false)
if err != nil {
if errors.Is(err, runnerErrors.ErrNotFound) {
return nil
}
return errors.Wrap(err, "looking up repo pool")
}
q := s.conn.Delete(&pool)
q := s.conn.Unscoped().Delete(&pool)
if q.Error != nil && !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return errors.Wrap(q.Error, "deleting pool")
}
@ -866,12 +867,9 @@ func (s *sqlDatabase) GetInstanceByName(ctx context.Context, instanceName string
func (s *sqlDatabase) DeleteInstance(ctx context.Context, poolID string, instanceName string) error {
instance, err := s.getPoolInstanceByName(ctx, poolID, instanceName)
if err != nil {
if errors.Is(err, runnerErrors.ErrNotFound) {
return nil
}
return errors.Wrap(err, "deleting instance")
}
if q := s.conn.Delete(&instance); q.Error != nil {
if q := s.conn.Unscoped().Delete(&instance); q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return nil
}
@ -1005,7 +1003,7 @@ func (s *sqlDatabase) updatePool(pool Pool, param params.UpdatePoolParams) (para
tags := make([]Tag, len(param.Tags))
for idx, t := range param.Tags {
tags[idx] = Tag{
Name: t.Name,
Name: t,
}
}
@ -1018,7 +1016,7 @@ func (s *sqlDatabase) updatePool(pool Pool, param params.UpdatePoolParams) (para
}
func (s *sqlDatabase) UpdateRepositoryPool(ctx context.Context, repoID, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
pool, err := s.getRepoPool(ctx, repoID, poolID)
pool, err := s.getRepoPool(ctx, repoID, poolID, "Tags")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}

2
go.mod
View file

@ -12,6 +12,7 @@ require (
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/manifoldco/promptui v0.9.0
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
@ -26,6 +27,7 @@ require (
)
require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 // indirect
github.com/go-macaroon-bakery/macaroonpb v1.0.0 // indirect

6
go.sum
View file

@ -35,8 +35,11 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
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=
@ -181,6 +184,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/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
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=
@ -319,6 +324,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
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-20181122145206-62eef0e2fa9b/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=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View file

@ -95,6 +95,7 @@ type Pool struct {
OSArch config.OSArch `json:"os_arch"`
Tags []Tag `json:"tags"`
Enabled bool `json:"enabled"`
Instances []Instance `json:"instances"`
}
type Internal struct {

View file

@ -47,7 +47,7 @@ type NewUserParams struct {
}
type UpdatePoolParams struct {
Tags []Tag `json:"tags"`
Tags []string `json:"tags"`
Enabled *bool `json:"enabled"`
MaxRunners *uint `json:"max_runners"`
MinIdleRunners *uint `json:"min_idle_runners"`

View file

@ -248,10 +248,7 @@ func (r *Runner) DeleteRepoPool(ctx context.Context, repoID, poolID string) erro
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
return errors.Wrap(err, "fetching pool")
}
instances, err := r.store.ListInstances(ctx, pool.ID)
@ -269,10 +266,7 @@ func (r *Runner) DeleteRepoPool(ctx context.Context, repoID, poolID string) erro
}
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 errors.Wrap(err, "deleting pool")
}
return nil
}
@ -341,7 +335,7 @@ func (r *Runner) UpdateRepoPool(ctx context.Context, repoID, poolID string, para
minIdleRunners = *param.MinIdleRunners
}
if minIdleRunners < maxRunners {
if minIdleRunners > maxRunners {
return params.Pool{}, runnerErrors.NewBadRequestError("min_idle_runners cannot be larger than max_runners")
}