Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip Loginview create when credentials already exist #275

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3fe814a
update readme (#233)
Vad1mo Nov 1, 2024
08d7811
Fixed the registry info issue (#237)
Althaf66 Nov 1, 2024
b3ccfba
fixed username validator (#239)
Roaster05 Nov 11, 2024
3f0b376
Revert "fixed username validator (#239)" (#247)
Vad1mo Nov 11, 2024
26ea446
AutoGenerate credential name in login (#250)
bupd Nov 14, 2024
71bebe8
print test output to screen (#254)
Vad1mo Nov 14, 2024
7af2b83
modified project list cmd (#260)
Althaf66 Nov 19, 2024
523faba
Added flags for user list cmd (#238)
Althaf66 Nov 19, 2024
c377368
search command for project and repository (#174)
Althaf66 Nov 22, 2024
185ff88
feat: Config flag error ignored during login #251 (#259)
qcserestipy Nov 26, 2024
3b79257
add '--force' flag to delete non-empty projects (#252)
adwait-godbole Nov 26, 2024
8c4970e
Support yaml output (#241)
Standing-Man Nov 26, 2024
d867478
Add: created schedule cmd (#121)
Althaf66 Nov 26, 2024
c16640d
fix-username validation (#265)
bupd Nov 26, 2024
8837cd0
Added check for existing credentials in present config for login; Ski…
qcserestipy Nov 30, 2024
fb877e7
Added config and data path management description to readme
qcserestipy Dec 1, 2024
0f5115e
Added harbor-config docs; Add
qcserestipy Dec 1, 2024
f185533
Added pwd encryption and key ring usage. The encryption key is stored…
qcserestipy Dec 2, 2024
9502ce1
Fix: compare existing pwd hash with encrypted pwd
qcserestipy Dec 2, 2024
3a5676a
Fix: Ineffective use of err in view.go
qcserestipy Dec 2, 2024
d911fc8
Added interface for keyring provider; added mockKeyRing provider; Add…
qcserestipy Dec 4, 2024
f408403
build(deps): bump github.com/charmbracelet/bubbles from 0.18.0 to 0.2…
dependabot[bot] Dec 3, 2024
6bae96d
Explicitely set mock keyring in sub tests
qcserestipy Dec 4, 2024
e93a24c
Explicitely set mock keyring in sub tests
qcserestipy Dec 4, 2024
0eb90cc
Added config sub command; added functions for set, get, list and clea…
qcserestipy Dec 21, 2024
d2916be
bug: fix push-latest-images and publish-release (#279)
Standing-Man Dec 9, 2024
7bee62c
Add: label commands (#95)
Althaf66 Dec 10, 2024
9d440eb
feat: modified registy update command (#88)
Althaf66 Dec 10, 2024
2f936b0
build(deps): bump github.com/charmbracelet/huh from 0.5.2 to 0.6.0 (#…
dependabot[bot] Dec 20, 2024
3ddbf3d
feature: Support YAML output for newly added commands (#268)
Standing-Man Dec 20, 2024
77f3a44
Added error propagation to config sub commands; Added tests for subco…
qcserestipy Dec 22, 2024
98eae89
Added --name flag for credential selection; Added subcommand tests
qcserestipy Dec 22, 2024
2cf64a2
Did sign off rebase and mod tidy
qcserestipy Dec 22, 2024
0210af8
Explicitely set mock keyring in sub tests
qcserestipy Dec 4, 2024
660695d
Explicitely set mock keyring in sub tests
qcserestipy Dec 4, 2024
297cadf
Added config sub command; added functions for set, get, list and clea…
qcserestipy Dec 21, 2024
a50950f
Added --name flag for credential selection; Added subcommand tests
qcserestipy Dec 22, 2024
08f66f3
Resolved merge conflicts with main
qcserestipy Dec 22, 2024
748893c
Updated cli auto docs
qcserestipy Dec 22, 2024
87e6492
Fix bug to add new credentials in case others already exist
qcserestipy Dec 22, 2024
49f1d5b
Fix bug to add new credentials in case others already exist; fix bug …
qcserestipy Dec 22, 2024
b9d3754
Fix for login command with not all flags; Added automatic context swi…
qcserestipy Dec 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Available Commands:
version Version of Harbor CLI

Flags:
--config string config file (default is $HOME/.harbor/config.yaml) (default "/Users/vadim/.harbor/config.yaml")
-c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml)
-h, --help help for harbor
-o, --output-format string Output format. One of: json|yaml
-v, --verbose verbose output
Expand All @@ -100,8 +100,32 @@ Use "harbor [command] --help" for more information about a command.

```



#### Config Management

##### Hierachy
Use the `--config` flag to specify a custom configuration file path (highest priority).
```bash
harbor --config /path/to/custom/config.yaml artifact list
```
If `--config` is not provided, Harbor CLI checks the `HARBOR_CLI_CONFIG` environment variable for the config file path.
```bash
export HARBOR_CLI_CONFIG=/path/to/custom/config.yaml
harbor artifact list
```
If neither is set, it defaults to `$XDG_CONFIG_HOME/harbor-cli/config.yaml` or `$HOME/.config/harbor-cli/config.yaml` if `XDG_CONFIG_HOME` is unset.
```bash
harbor artifact list
```

##### Data Path
- Data paths are determined by the `XDG_DATA_HOME` environment variable.
- If `XDG_DATA_HOME` is not set, it defaults to `$HOME/.local/share/harbor-cli/data.yaml`.
- The data file always contains the path of the latest config used.

##### Config TL;DR
- `--config` flag > `HARBOR_CLI_CONFIG` environment variable > default XDG config paths.
- Environment variables override default settings, and the `--config` flag takes precedence over both environment variables and defaults.
- The data file always contains the path of the latest config used.


#### Log in to Harbor Registry
Expand Down
2 changes: 2 additions & 0 deletions cmd/harbor/root/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/goharbor/harbor-cli/cmd/harbor/root/artifact"
"github.com/goharbor/harbor-cli/cmd/harbor/root/config"
"github.com/goharbor/harbor-cli/cmd/harbor/root/labels"
"github.com/goharbor/harbor-cli/cmd/harbor/root/project"
"github.com/goharbor/harbor-cli/cmd/harbor/root/registry"
Expand Down Expand Up @@ -61,6 +62,7 @@ harbor help
root.AddCommand(
versionCommand(),
LoginCommand(),
config.Config(),
project.Project(),
registry.Registry(),
repositry.Repository(),
Expand Down
20 changes: 20 additions & 0 deletions cmd/harbor/root/config/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package config

import "github.com/spf13/cobra"

func Config() *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Manage the config of the Harbor Cli",
Long: `Manage repositories in Harbor config`,
}
cmd.AddCommand(
ListConfigCommand(),
GetConfigItemCommand(),
SetConfigItemCommand(),
DeleteConfigItemCommand(),
)

return cmd

}
180 changes: 180 additions & 0 deletions cmd/harbor/root/config/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package config

import (
"fmt"
"reflect"
"strings"

"github.com/goharbor/harbor-cli/pkg/utils"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

// DeleteConfigItemCommand creates the 'harbor config delete' subcommand,
// allowing you to do: harbor config delete <item>
func DeleteConfigItemCommand() *cobra.Command {
var credentialName string

cmd := &cobra.Command{
Use: "delete <item>",
Short: "Delete (clear) a specific config item",
Example: `
# Clear the current credential's password
harbor config delete credentials.password

# Clear a specific credential's password using --name
harbor config delete credentials.password --name harbor-cli@http://demo.goharbor.io
`,
Long: `Clear the value of a specific CLI config item by setting it to its zero value.
Case-insensitive field lookup, but uses the canonical (Go) field name internally.
If you specify --name, that credential (rather than the "current" one) will be used.`,
Args: cobra.ExactArgs(1),

// Use RunE so we can propagate errors
RunE: func(cmd *cobra.Command, args []string) error {
// 1. Load the current config
config, err := utils.GetCurrentHarborConfig()
if err != nil {
return fmt.Errorf("failed to load Harbor config: %w", err)
}

// 2. Parse the user-supplied item path (e.g., "credentials.password")
itemPath := strings.Split(args[0], ".")

// 3. Reflection-based delete (zero out)
actualSegments := []string{}
if err := deleteValueInConfig(config, itemPath, &actualSegments, credentialName); err != nil {
return fmt.Errorf("failed to delete value in config: %w", err)
}

// 4. Persist the updated config to disk
if err := utils.UpdateConfigFile(config); err != nil {
return fmt.Errorf("failed to save updated config: %w", err)
}

// 5. Confirm to the user (no error here)
canonicalPath := strings.Join(actualSegments, ".")
logrus.Infof("Successfully cleared %s", canonicalPath)

return nil
},
}

// Add --name / -n to let the user pick a specific credential
cmd.Flags().StringVarP(
&credentialName,
"name",
"n",
"",
"Name of the credential to delete fields from (default: the current credential)",
)

return cmd
}

// deleteValueInConfig checks whether the user is deleting something
// under "credentials" (i.e., *a* credential) or a top-level field.
//
// If the user says "credentials.*" AND provides --name, we'll look
// up that specific credential by name. Otherwise, we use CurrentCredentialName.
func deleteValueInConfig(
config *utils.HarborConfig,
path []string,
actualSegments *[]string,
credentialName string,
) error {
if len(path) == 0 {
return fmt.Errorf("no config item specified")
}

// If the first segment is "credentials", pivot to the chosen credential.
if strings.EqualFold(path[0], "credentials") {
*actualSegments = append(*actualSegments, "Credentials")

// Figure out which credential name to use
credName := config.CurrentCredentialName
if credentialName != "" {
credName = credentialName
}

// Find the matching credential
var targetCred *utils.Credential
for i := range config.Credentials {
if strings.EqualFold(config.Credentials[i].Name, credName) {
targetCred = &config.Credentials[i]
break
}
}
if targetCred == nil {
return fmt.Errorf("no matching credential found for '%s'", credName)
}

// Remove "credentials" from path, delete the value in that credential
return deleteNestedValue(targetCred, path[1:], actualSegments)
}

// Otherwise, we delete a field in the main HarborConfig struct
return deleteNestedValue(config, path, actualSegments)
}

// deleteNestedValue navigates a pointer to a struct, following the path segments
// in a case-insensitive manner, until the last segment, where it sets the field
// to its zero value.
func deleteNestedValue(obj interface{}, path []string, actualSegments *[]string) error {
// We require obj to be a pointer to a struct so we can modify it.
val := reflect.ValueOf(obj)
if val.Kind() != reflect.Ptr {
return fmt.Errorf("object must be a pointer to a struct, got %s", val.Kind())
}
val = val.Elem() // dereference pointer

for i, segment := range path {
if val.Kind() != reflect.Struct {
return fmt.Errorf("cannot traverse non-struct for segment '%s'", segment)
}
t := val.Type()

// Case-insensitive field lookup
fieldIndex := -1
for j := 0; j < val.NumField(); j++ {
if strings.EqualFold(t.Field(j).Name, segment) {
fieldIndex = j
break
}
}
if fieldIndex < 0 {
return fmt.Errorf("config item '%s' does not exist", segment)
}

field := t.Field(fieldIndex)
fieldValue := val.Field(fieldIndex)

// Record the actual field name
*actualSegments = append(*actualSegments, field.Name)

// If this is NOT the last path segment, move deeper
if i < len(path)-1 {
// If the field is a pointer and nil, we can't go deeper
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
return fmt.Errorf("field '%s' is nil and cannot be traversed", field.Name)
}
// Descend
val = fieldValue
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
continue
}

// If this is the last segment, set the field to zero value
if !fieldValue.CanSet() {
return fmt.Errorf("cannot set field '%s' to zero value", field.Name)
}

// The zero value for that field can be obtained with reflect.Zero().
zeroVal := reflect.Zero(fieldValue.Type())
fieldValue.Set(zeroVal)
}

return nil
}
Loading
Loading