Add more CLI commands
This commit is contained in:
parent
475d424f32
commit
8ceafff09b
21 changed files with 995 additions and 107 deletions
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
63
cmd/run-cli/cmd/credentials.go
Normal file
63
cmd/run-cli/cmd/credentials.go
Normal 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())
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
309
cmd/run-cli/cmd/repo_pool.go
Normal file
309
cmd/run-cli/cmd/repo_pool.go
Normal 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())
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
53
cmd/run-cli/common/common.go
Normal file
53
cmd/run-cli/common/common.go
Normal 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
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
2
go.mod
|
|
@ -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
6
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue