Add profile management in the CLI
This commit is contained in:
parent
c089217a53
commit
a8274dcc02
5 changed files with 328 additions and 111 deletions
|
|
@ -37,11 +37,11 @@ var initCmd = &cobra.Command{
|
|||
A newly installed runner manager needs to be initialized to become
|
||||
functional. This command sets the administrative user and password,
|
||||
generates a controller UUID which is used internally to identify runners
|
||||
created by the manager and enables the service.
|
||||
created by the manager and adds the profile to the local client config.
|
||||
|
||||
Example usage:
|
||||
|
||||
garm-cli login --name=dev --url=https://runner.example.com --username=admin --password=superSecretPassword
|
||||
garm-cli init --name=dev --url=https://runner.example.com --username=admin --password=superSecretPassword
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if cfg != nil {
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
// Copyright 2022 Cloudbase Solutions SRL
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"garm/cmd/garm-cli/common"
|
||||
"garm/cmd/garm-cli/config"
|
||||
"garm/params"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
loginProfileName string
|
||||
loginURL string
|
||||
loginPassword string
|
||||
loginUserName string
|
||||
loginFullName string
|
||||
loginEmail string
|
||||
)
|
||||
|
||||
// loginCmd represents the login command
|
||||
var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
SilenceUsage: true,
|
||||
Short: "Log into a manager",
|
||||
Long: `Performs login for this machine on a remote garm:
|
||||
|
||||
garm-cli login --name=dev --url=https://runner.example.com`,
|
||||
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(&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")
|
||||
}
|
||||
267
cmd/garm-cli/cmd/profile.go
Normal file
267
cmd/garm-cli/cmd/profile.go
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
// Copyright 2022 Cloudbase Solutions SRL
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"garm/cmd/garm-cli/common"
|
||||
"garm/cmd/garm-cli/config"
|
||||
"garm/params"
|
||||
"strings"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
loginProfileName string
|
||||
loginURL string
|
||||
loginPassword string
|
||||
loginUserName string
|
||||
loginFullName string
|
||||
loginEmail string
|
||||
)
|
||||
|
||||
// runnerCmd represents the runner command
|
||||
var profileCmd = &cobra.Command{
|
||||
Use: "profile",
|
||||
SilenceUsage: false,
|
||||
Short: "Add, delete or update profiles",
|
||||
Long: `Creates, deletes or updates bearer tokens for profiles.`,
|
||||
Run: nil,
|
||||
}
|
||||
|
||||
var profileListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List profiles",
|
||||
Long: `List profiles.
|
||||
|
||||
This command will list all currently defined profiles in the local configuration
|
||||
file of the garm client.
|
||||
`,
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if needsInit {
|
||||
return needsInitError
|
||||
}
|
||||
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
formatProfiles(cfg.Managers)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var profileDeleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Aliases: []string{"remove", "rm", "del"},
|
||||
Short: "Delete profile",
|
||||
Long: `Delete a profile from the local CLI configuration.`,
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if needsInit {
|
||||
return needsInitError
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("requires a profile name")
|
||||
}
|
||||
|
||||
if err := cfg.DeleteProfile(args[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cfg.SaveConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var poolSwitchCmd = &cobra.Command{
|
||||
Use: "switch",
|
||||
Short: "Switch to a different profile",
|
||||
Long: `Switch the CLI to a different profile.`,
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if needsInit {
|
||||
return needsInitError
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("requires a profile name")
|
||||
}
|
||||
|
||||
if cfg != nil {
|
||||
if !cfg.HasManager(args[0]) {
|
||||
return fmt.Errorf("a profile with name %s does not exist", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
cfg.ActiveManager = args[0]
|
||||
|
||||
if err := cfg.SaveConfig(); err != nil {
|
||||
return fmt.Errorf("error saving config: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var profileAddCmd = &cobra.Command{
|
||||
Use: "add",
|
||||
Aliases: []string{"create"},
|
||||
Short: "Add profile",
|
||||
Long: `Create a profile for a new garm installation.`,
|
||||
SilenceUsage: true,
|
||||
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
|
||||
},
|
||||
}
|
||||
|
||||
var profileLoginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Refresh bearer token for profile",
|
||||
Long: `Logs into an existing garm installation.
|
||||
|
||||
This command will refresh the bearer token associated with an already defined garm
|
||||
installation, by performing a login.
|
||||
`,
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if needsInit {
|
||||
return needsInitError
|
||||
}
|
||||
|
||||
if cfg == nil {
|
||||
// We should probably error out here
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := promptUnsetLoginVariables(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loginParams := params.PasswordLoginParams{
|
||||
Username: loginUserName,
|
||||
Password: loginPassword,
|
||||
}
|
||||
|
||||
resp, err := cli.Login(mgr.BaseURL, loginParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cfg.SetManagerToken(mgr.Name, resp); err != nil {
|
||||
return fmt.Errorf("error saving new token: %s", err)
|
||||
}
|
||||
|
||||
if err := cfg.SaveConfig(); err != nil {
|
||||
return fmt.Errorf("error saving config: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
profileLoginCmd.Flags().StringVarP(&loginUserName, "username", "u", "", "Username to log in as")
|
||||
profileLoginCmd.Flags().StringVarP(&loginPassword, "password", "p", "", "The user passowrd")
|
||||
|
||||
profileAddCmd.Flags().StringVarP(&loginProfileName, "name", "n", "", "A name for this runner manager")
|
||||
profileAddCmd.Flags().StringVarP(&loginURL, "url", "a", "", "The base URL for the runner manager API")
|
||||
profileAddCmd.Flags().StringVarP(&loginUserName, "username", "u", "", "Username to log in as")
|
||||
profileAddCmd.Flags().StringVarP(&loginPassword, "password", "p", "", "The user passowrd")
|
||||
profileAddCmd.MarkFlagRequired("name")
|
||||
profileAddCmd.MarkFlagRequired("url")
|
||||
|
||||
profileCmd.AddCommand(
|
||||
profileListCmd,
|
||||
profileLoginCmd,
|
||||
poolSwitchCmd,
|
||||
profileDeleteCmd,
|
||||
profileAddCmd,
|
||||
)
|
||||
|
||||
rootCmd.AddCommand(profileCmd)
|
||||
}
|
||||
|
||||
func formatProfiles(profiles []config.Manager) {
|
||||
t := table.NewWriter()
|
||||
header := table.Row{"Name", "Base URL"}
|
||||
t.AppendHeader(header)
|
||||
|
||||
for _, profile := range profiles {
|
||||
name := profile.Name
|
||||
if profile.Name == mgr.Name {
|
||||
name = fmt.Sprintf("%s (current)", name)
|
||||
}
|
||||
t.AppendRow(table.Row{name, profile.BaseURL})
|
||||
t.AppendSeparator()
|
||||
}
|
||||
fmt.Println(t.Render())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ var (
|
|||
active string
|
||||
needsInit bool
|
||||
debug bool
|
||||
needsInitError = fmt.Errorf("Please log into a garm first")
|
||||
needsInitError = fmt.Errorf("Please log into a garm installation first")
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -66,11 +68,14 @@ func LoadConfig() (*Config, error) {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
mux sync.Mutex
|
||||
Managers []Manager `toml:"manager"`
|
||||
ActiveManager string `toml:"active_manager"`
|
||||
}
|
||||
|
||||
func (c *Config) HasManager(mgr string) bool {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
if mgr == "" {
|
||||
return false
|
||||
}
|
||||
|
|
@ -82,7 +87,58 @@ func (c *Config) HasManager(mgr string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (c *Config) SetManagerToken(name, token string) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
found := false
|
||||
newManagerList := []Manager{}
|
||||
for _, mgr := range c.Managers {
|
||||
newMgr := Manager{
|
||||
Name: mgr.Name,
|
||||
BaseURL: mgr.BaseURL,
|
||||
Token: mgr.Token,
|
||||
}
|
||||
if mgr.Name == name {
|
||||
found = true
|
||||
newMgr.Token = token
|
||||
}
|
||||
newManagerList = append(newManagerList, newMgr)
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("profile %s not found", name)
|
||||
}
|
||||
c.Managers = newManagerList
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) DeleteProfile(name string) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
newManagers := []Manager{}
|
||||
for _, val := range c.Managers {
|
||||
if val.Name == name {
|
||||
continue
|
||||
}
|
||||
newManagers = append(newManagers, Manager{
|
||||
Name: val.Name,
|
||||
BaseURL: val.BaseURL,
|
||||
Token: val.Token,
|
||||
})
|
||||
}
|
||||
c.Managers = newManagers
|
||||
if c.ActiveManager == name {
|
||||
if len(c.Managers) > 0 {
|
||||
c.ActiveManager = c.Managers[0].Name
|
||||
} else {
|
||||
c.ActiveManager = ""
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) GetActiveConfig() (Manager, error) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
if c.ActiveManager == "" {
|
||||
return Manager{}, runnerErrors.ErrNotFound
|
||||
}
|
||||
|
|
@ -96,6 +152,8 @@ func (c *Config) GetActiveConfig() (Manager, error) {
|
|||
}
|
||||
|
||||
func (c *Config) SaveConfig() error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
cfgFile, err := getConfigFilePath()
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue