-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds the `login` command for the user to log in to the server address passed. This will enable users to login to multiple registeries and specific which set of credentials to use for other subcommands via cli or env var else it uses the current credential set in the config file. This commit also adds the utils functions that will be used by successive commits. Signed-off-by: Akshat <akshat25iiit@gmail.com>
- Loading branch information
1 parent
81b403e
commit 49ee28f
Showing
8 changed files
with
688 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package constants | ||
|
||
const ( | ||
HarborCredentialName = "HARBORCREDENTIALNAME" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package login | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/akshatdalton/harbor-cli/cmd/utils" | ||
"github.com/goharbor/go-client/pkg/harbor" | ||
"github.com/goharbor/go-client/pkg/sdk/v2.0/client/user" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type loginOptions struct { | ||
name string | ||
serverAddress string | ||
username string | ||
password string | ||
} | ||
|
||
// NewLoginCommand creates a new `harbor login` command | ||
func NewLoginCommand() *cobra.Command { | ||
var opts loginOptions | ||
|
||
cmd := &cobra.Command{ | ||
Use: "login [SERVER]", | ||
Short: "Log in to Harbor registry", | ||
Long: "Authenticate with Harbor Registry.", | ||
Args: cobra.ExactArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
opts.serverAddress = args[0] | ||
return runLogin(opts) | ||
}, | ||
} | ||
|
||
flags := cmd.Flags() | ||
flags.StringVarP(&opts.name, "name", "", "", "name for the set of credentials") | ||
|
||
flags.StringVarP(&opts.username, "username", "u", "", "Username") | ||
if err := cmd.MarkFlagRequired("username"); err != nil { | ||
panic(err) | ||
} | ||
flags.StringVarP(&opts.password, "password", "p", "", "Password") | ||
if err := cmd.MarkFlagRequired("password"); err != nil { | ||
panic(err) | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
func runLogin(opts loginOptions) error { | ||
clientConfig := &harbor.ClientSetConfig{ | ||
URL: opts.serverAddress, | ||
Username: opts.username, | ||
Password: opts.password, | ||
} | ||
client := utils.GetClientByConfig(clientConfig) | ||
|
||
ctx := context.Background() | ||
_, err := client.User.GetCurrentUserInfo(ctx, &user.GetCurrentUserInfoParams{}) | ||
if err != nil { | ||
return fmt.Errorf("login failed, please check your credentials: %s", err) | ||
} | ||
|
||
cred := utils.Credential{ | ||
Name: opts.name, | ||
Username: opts.username, | ||
Password: opts.password, | ||
ServerAddress: opts.serverAddress, | ||
} | ||
|
||
if err = utils.StoreCredential(cred, true); err != nil { | ||
return fmt.Errorf("Failed to store the credential: %s", err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package cmd | ||
|
||
import ( | ||
"github.com/akshatdalton/harbor-cli/cmd/login" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func addCommands(cmd *cobra.Command) { | ||
cmd.AddCommand(login.NewLoginCommand()) | ||
} | ||
|
||
// CreateHarborCLI creates a new Harbor CLI | ||
func CreateHarborCLI() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "harbor", | ||
Short: "Official Harbor CLI", | ||
} | ||
|
||
addCommands(cmd) | ||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package utils | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"net/url" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/adrg/xdg" | ||
"github.com/akshatdalton/harbor-cli/cmd/constants" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
var configFile = filepath.Join(xdg.Home, ".harbor", "config") | ||
|
||
type Credential struct { | ||
Name string `yaml:"name"` | ||
Username string `yaml:"username"` | ||
Password string `yaml:"password"` | ||
ServerAddress string `yaml:"serveraddress"` | ||
} | ||
|
||
type CredentialStore struct { | ||
CurrentCredentialName string `yaml:"current-credential-name"` | ||
Credentials []Credential `yaml:"credentials"` | ||
} | ||
|
||
func checkAndUpdateCredentialName(credential *Credential) { | ||
if credential.Name != "" { | ||
return | ||
} | ||
|
||
parsedUrl, err := url.Parse(credential.ServerAddress) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
credential.Name = parsedUrl.Hostname() + "-" + credential.Username | ||
log.Println("credential name not specified, storing the credential with the name as:", credential.Name) | ||
return | ||
} | ||
|
||
func readCredentialStore() (CredentialStore, error) { | ||
configInfo, err := ioutil.ReadFile(configFile) | ||
if err != nil { | ||
return CredentialStore{}, err | ||
} | ||
|
||
var credentialStore CredentialStore | ||
if err := yaml.Unmarshal(configInfo, &credentialStore); err != nil { | ||
return CredentialStore{}, err | ||
} | ||
return credentialStore, nil | ||
} | ||
|
||
func checkAndCreateConfigFile() { | ||
if _, err := os.Stat(configFile); os.IsNotExist(err) { | ||
// Create the parent directory if it doesn't exist | ||
if err := os.MkdirAll(filepath.Dir(configFile), os.ModePerm); err != nil { | ||
panic(err) | ||
} | ||
|
||
if _, err := os.Create(configFile); err != nil { | ||
panic(err) | ||
} | ||
} else if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func StoreCredential(credential Credential, setAsCurrentCredential bool) error { | ||
checkAndUpdateCredentialName(&credential) | ||
checkAndCreateConfigFile() | ||
credentialStore, err := readCredentialStore() | ||
if err != nil { | ||
return fmt.Errorf("failed to read credential store: %s", err) | ||
} | ||
|
||
// Check and remove the credential with same username and serveraddress. | ||
removeIndex := -1 | ||
for i, cred := range credentialStore.Credentials { | ||
if cred.Username == credential.Username && cred.ServerAddress == credential.ServerAddress { | ||
removeIndex = i | ||
break | ||
} | ||
} | ||
|
||
if removeIndex != -1 { | ||
credentialStore.Credentials = append(credentialStore.Credentials[:removeIndex], credentialStore.Credentials[removeIndex+1:]...) | ||
} | ||
|
||
credentialStore.Credentials = append(credentialStore.Credentials, credential) | ||
if setAsCurrentCredential { | ||
credentialStore.CurrentCredentialName = credential.Name | ||
} | ||
|
||
bytes, err := yaml.Marshal(credentialStore) | ||
if err != nil { | ||
return err | ||
} | ||
if err = ioutil.WriteFile(configFile, bytes, 0600); err != nil { | ||
return err | ||
} | ||
log.Println("Saving credentials to:", configFile) | ||
return nil | ||
} | ||
|
||
// resolveCredential resolves the credential in the following priority order: | ||
// 1. credentialName specified by the user via CLI argument | ||
// 2. credentialName specified by the user via environment variable | ||
// 3. current active credential | ||
func resolveCredential(credentialName string) (Credential, error) { | ||
credentialStore, err := readCredentialStore() | ||
if err != nil { | ||
panic(fmt.Sprintf("failed to read credential store: %s", err)) | ||
} | ||
|
||
// If credentialName is not specified, check environment variable | ||
if credentialName == "" { | ||
credentialName = os.Getenv(constants.HarborCredentialName) | ||
} | ||
|
||
// If user has not specified the credential to use, use the current active credential | ||
if credentialName == "" { | ||
credentialName = credentialStore.CurrentCredentialName | ||
} | ||
|
||
if credentialName == "" { | ||
return Credential{}, fmt.Errorf("current credential name not set, please login again") | ||
} | ||
|
||
// Look for the credential with the given name | ||
for _, cred := range credentialStore.Credentials { | ||
if cred.Name == credentialName { | ||
return cred, nil | ||
} | ||
} | ||
|
||
return Credential{}, fmt.Errorf("no credential found for the name: %s, please login again with the credential name: %s", credentialName, credentialName) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package utils | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/goharbor/go-client/pkg/harbor" | ||
v2client "github.com/goharbor/go-client/pkg/sdk/v2.0/client" | ||
) | ||
|
||
// Returns Harbor v2 client for given clientConfig | ||
func GetClientByConfig(clientConfig *harbor.ClientSetConfig) *v2client.HarborAPI { | ||
cs, err := harbor.NewClientSet(clientConfig) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return cs.V2() | ||
} | ||
|
||
// Returns Harbor v2 client after resolving the credential name | ||
func GetClientByCredentialName(credentialName string) *v2client.HarborAPI { | ||
credential, err := resolveCredential(credentialName) | ||
if err != nil { | ||
panic(err) | ||
} | ||
clientConfig := &harbor.ClientSetConfig{ | ||
URL: credential.ServerAddress, | ||
Username: credential.Username, | ||
Password: credential.Password, | ||
} | ||
return GetClientByConfig(clientConfig) | ||
} | ||
|
||
func PrintPayloadInJSONFormat(payload any) { | ||
if payload == nil { | ||
return | ||
} | ||
|
||
jsonStr, err := json.MarshalIndent(payload, "", " ") | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
fmt.Println(string(jsonStr)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
module github.com/akshatdalton/harbor-cli | ||
|
||
go 1.20 | ||
|
||
require github.com/spf13/cobra v1.7.0 | ||
|
||
require ( | ||
github.com/google/go-cmp v0.5.9 // indirect | ||
github.com/stretchr/testify v1.8.1 // indirect | ||
) | ||
|
||
require ( | ||
github.com/PuerkitoBio/purell v1.1.1 // indirect | ||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect | ||
github.com/adrg/xdg v0.4.0 | ||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect | ||
github.com/go-openapi/analysis v0.20.1 // indirect | ||
github.com/go-openapi/errors v0.20.1 // indirect | ||
github.com/go-openapi/jsonpointer v0.19.5 // indirect | ||
github.com/go-openapi/jsonreference v0.19.6 // indirect | ||
github.com/go-openapi/loads v0.21.0 // indirect | ||
github.com/go-openapi/runtime v0.21.0 // indirect | ||
github.com/go-openapi/spec v0.20.4 // indirect | ||
github.com/go-openapi/strfmt v0.21.0 // indirect | ||
github.com/go-openapi/swag v0.19.15 // indirect | ||
github.com/go-openapi/validate v0.20.3 // indirect | ||
github.com/go-stack/stack v1.8.0 // indirect | ||
github.com/goharbor/go-client v0.26.2 | ||
github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||
github.com/josharian/intern v1.0.0 // indirect | ||
github.com/mailru/easyjson v0.7.6 // indirect | ||
github.com/mitchellh/mapstructure v1.5.0 // indirect | ||
github.com/oklog/ulid v1.3.1 // indirect | ||
github.com/opentracing/opentracing-go v1.2.0 // indirect | ||
github.com/spf13/pflag v1.0.5 // indirect | ||
go.mongodb.org/mongo-driver v1.7.3 // indirect | ||
golang.org/x/net v0.4.0 // indirect | ||
golang.org/x/sys v0.3.0 // indirect | ||
golang.org/x/text v0.5.0 // indirect | ||
gopkg.in/yaml.v2 v2.4.0 | ||
) |
Oops, something went wrong.