Skip to content

Commit

Permalink
Enh/target cluster by alias (#185)
Browse files Browse the repository at this point in the history
* enh: allow targeting by alias

* enh: autocompletion/suggestion also lists aliases

* enh: support alias for config set-garden

* enh: validate gardenctl config on load

* feat: PR feedback I

* enh: PR feedback II
  • Loading branch information
sven-petersen authored Dec 13, 2022
1 parent 7d03afd commit 49392a5
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 23 deletions.
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ Attention `brew` users: `gardenctl-v2` uses the same binary name as the legacy `

### Install from Github Release

If you install via GitHub releases, you need to
- put the `gardenctl` binary on your path
- and [install gardenlogin](https://github.com/gardener/gardenlogin#installation).
If you install via GitHub releases, you need to

- put the `gardenctl` binary on your path
- and [install gardenlogin](https://github.com/gardener/gardenlogin#installation).

The other install methods do this for you.

Expand Down Expand Up @@ -61,7 +62,8 @@ sudo mv "./gardenctl_v2_${os}_${arch}" /usr/local/bin/gardenctl
You can modify this file directly using the `gardenctl config` command. It allows adding, modifying and deleting gardens.

Example `config` command:
``` bash

```bash
# Adapt the path to your kubeconfig file for the garden cluster
export KUBECONFIG=~/relative/path/to/kubeconfig.yaml

Expand All @@ -80,15 +82,17 @@ cluster_identity=$(echo ${identity_status#"$prefix"})
# Configure garden cluster
gardenctl config set-garden $cluster_identity --kubeconfig $KUBECONFIG
```

This command will create or update a garden with the provided identity and kubeconfig path of your garden cluster.

### Example Config

```yaml
gardens:
- identity: landscape-dev # Unique identity of the garden cluster. See cluster-identity ConfigMap in kube-system namespace of the garden cluster
kubeconfig: ~/relative/path/to/kubeconfig.yaml
# context: different-context # Overrides the current-context of the garden cluster kubeconfig
- identity: landscape-dev # Unique identity of the garden cluster. See cluster-identity ConfigMap in kube-system namespace of the garden cluster
kubeconfig: ~/relative/path/to/kubeconfig.yaml
# name: my-name # An alternative, unique garden name for targeting
# context: different-context # Overrides the current-context of the garden cluster kubeconfig
# patterns: ~ # List of regex patterns for pattern targeting
```

Expand All @@ -115,23 +119,27 @@ this leads to an error and gardenctl cannot be executed. The `target.yaml` and t

You can make sure that `GCTL_SESSION_ID` or `TERM_SESSION_ID` is always present by adding
the following code to your terminal profile `~/.profile`, `~/.bashrc` or comparable file.

```sh
bash and zsh: [ -n "$GCTL_SESSION_ID" ] || [ -n "$TERM_SESSION_ID" ] || export GCTL_SESSION_ID=$(uuidgen)
```

```sh
fish: [ -n "$GCTL_SESSION_ID" ] || [ -n "$TERM_SESSION_ID" ] || set -gx GCTL_SESSION_ID (uuidgen)
```

```ps
powershell: if ( !(Test-Path Env:GCTL_SESSION_ID) -and !(Test-Path Env:TERM_SESSION_ID) ) { $Env:GCTL_SESSION_ID = [guid]::NewGuid().ToString() }
```

### Completion

Gardenctl supports completion that will help you working with the CLI and save you typing effort.
It will also help you find clusters by providing suggestions for gardener resources such as shoots or projects.
It will also help you find clusters by providing suggestions for gardener resources such as shoots or projects.
Completion is supported for `bash`, `zsh`, `fish` and `powershell`.
You will find more information on how to configure your shell completion for gardenctl by executing the help for
your shell completion command. Example:

```bash
gardenctl completion bash --help
```
Expand All @@ -145,29 +153,34 @@ You can set a target to use it in subsequent commands. You can also overwrite th
Note that this will not affect your KUBECONFIG env variable. To update the KUBECONFIG env for your current target see [Configure KUBECONFIG](#configure-kubeconfig-for-shoot-clusters) section

Example:

```bash
# target control plane
gardenctl target --garden landscape-dev --project my-project --shoot my-shoot --control-plane
```

Find more information in the [documentation](docs/usage/targeting.md).

### Configure KUBECONFIG for Shoot Clusters

Generate a script that points KUBECONFIG to the targeted cluster for the specified shell. Use together with `eval` to configure your shell. Example for `bash`:

```bash
eval $(gardenctl kubectl-env bash)
```

### Configure Cloud Provider CLIs

Generate the cloud provider CLI configuration script for the specified shell. Use together with `eval` to configure your shell. Example for `bash`:

```bash
eval $(gardenctl provider-env bash)
```

### SSH

Establish an SSH connection to a Shoot cluster's node.

```bash
gardenctl ssh my-node
```
1 change: 1 addition & 0 deletions docs/help/gardenctl_config_set-garden.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ gardenctl config set-garden my-garden --context garden-context --pattern "^(?:la
### Options

```
--alias string unique alias of this Garden that can be used instead of the name to target this Garden
--context string override the current-context of the garden cluster kubeconfig
-h, --help help for set-garden
--kubeconfig string path to kubeconfig file for this Garden cluster
Expand Down
9 changes: 9 additions & 0 deletions pkg/cmd/config/set_garden.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ type setGardenOptions struct {
Configuration *config.Config
// Name is a unique name of this Garden that can be used to target this Garden
Name string
// Alias is a unique alias of this Garden that can be used to target this Garden
// +optional
Alias flag.StringFlag
// KubeconfigFlag is the path to the kubeconfig file of the Garden cluster
KubeconfigFlag flag.StringFlag
// ContextFlag Overrides the current-context of the garden cluster kubeconfig
Expand Down Expand Up @@ -104,6 +107,7 @@ func (o *setGardenOptions) Validate() error {
func (o *setGardenOptions) AddFlags(flags *pflag.FlagSet) {
flags.Var(&o.KubeconfigFlag, "kubeconfig", "path to kubeconfig file for this Garden cluster")
flags.Var(&o.ContextFlag, "context", "override the current-context of the garden cluster kubeconfig")
flags.Var(&o.Alias, "alias", "unique alias of this Garden that can be used instead of the name to target this Garden")
flags.StringArrayVar(&o.Patterns, "pattern", nil, `define regex match patterns for this garden for custom input formats for targeting.
Use named capturing groups to match target values.
Supported capturing groups: project, namespace, shoot.
Expand All @@ -123,6 +127,10 @@ func (o *setGardenOptions) Run(_ util.Factory) error {
garden.Context = o.ContextFlag.Value()
}

if o.Alias.Provided() {
garden.Alias = o.Alias.Value()
}

if o.Patterns != nil {
firstPattern := o.Patterns[0]
if len(firstPattern) > 0 {
Expand All @@ -136,6 +144,7 @@ func (o *setGardenOptions) Run(_ util.Factory) error {
Name: o.Name,
Kubeconfig: o.KubeconfigFlag.Value(),
Context: o.ContextFlag.Value(),
Alias: o.Alias.Value(),
Patterns: o.Patterns,
})
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/config/set_garden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var _ = Describe("Config Subcommand SetGarden", func() {
Expect(cmd.Use).To(Equal("set-garden"))
Expect(cmd.ValidArgsFunction).NotTo(BeNil())
Expect(cmd.ValidArgs).To(BeNil())
assertAllFlagNames(cmd.Flags(), "context", "kubeconfig", "pattern")
assertAllFlagNames(cmd.Flags(), "alias", "context", "kubeconfig", "pattern")
})
})

Expand Down
61 changes: 56 additions & 5 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"gopkg.in/yaml.v3"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/klog/v2"

"github.com/gardener/gardenctl-v2/pkg/ac"
)
Expand All @@ -36,6 +37,9 @@ type Config struct {
type Garden struct {
// Name is a unique identifier of this Garden that can be used to target this Garden
Name string `yaml:"identity" json:"identity"`
// Alias is a unique identifier of this Garden that can be used as an alternate name to target this Garden
// +optional
Alias string `yaml:"name,omitempty" json:"name,omitempty"`
// Kubeconfig holds the path for the kubeconfig of the garden cluster
Kubeconfig string `yaml:"kubeconfig" json:"kubeconfig"`
// Context overrides the current-context of the garden cluster kubeconfig
Expand Down Expand Up @@ -96,9 +100,38 @@ func LoadFromFile(filename string) (*Config, error) {
config.LinkKubeconfig = &val
}

config.validate()

return config, nil
}

// validate checks the config for ambiguous definitions and prints warnings to the user.
func (config *Config) validate() {
seen := make(map[string]bool, len(config.Gardens))

for i := range config.Gardens {
garden := config.Gardens[i]

if logged, ok := seen[garden.Name]; ok && !logged {
klog.Warningf("identity and alias should be unique but %q was found multiple times in gardenctl configuration", garden.Name)

seen[garden.Name] = true
} else if !ok {
seen[garden.Name] = false
}

if garden.Alias != "" && garden.Alias != garden.Name {
if logged, ok := seen[garden.Alias]; ok && !logged {
klog.Warningf("identity and alias should be unique but %q was found multiple times in gardenctl configuration", garden.Alias)

seen[garden.Alias] = true
} else if !ok {
seen[garden.Alias] = false
}
}
}
}

// SymlinkTargetKubeconfig indicates if the kubeconfig of the current target should be always symlinked.
func (config *Config) SymlinkTargetKubeconfig() bool {
return config.LinkKubeconfig == nil || *config.LinkKubeconfig
Expand Down Expand Up @@ -148,14 +181,32 @@ func (config *Config) GardenNames() []string {
return names
}

// Garden returns a Garden cluster from the list of configured Gardens.
// Garden returns the matching Garden cluster by name (identity or alias) from the list
// of configured Gardens. In case of ambigous names the first match is returned and identity is
// preferred over alias.
func (config *Config) Garden(name string) (*Garden, error) {
i, ok := config.IndexOfGarden(name)
if !ok {
return nil, fmt.Errorf("garden %q is not defined in gardenctl configuration", name)
if name == "" {
return nil, fmt.Errorf("garden name or alias cannot be empty")
}

var firstMatchByAlias *Garden

for idx := range config.Gardens {
cfg := &config.Gardens[idx]
if name == cfg.Name {
return cfg, nil
}

if firstMatchByAlias == nil && name == cfg.Alias {
firstMatchByAlias = cfg
}
}

if firstMatchByAlias != nil {
return firstMatchByAlias, nil
}

return &config.Gardens[i], nil
return nil, fmt.Errorf("garden %q is not defined in gardenctl configuration", name)
}

// ClientConfig returns a deferred loading client config for a configured garden cluster.
Expand Down
19 changes: 13 additions & 6 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ var _ = Describe("Config", func() {
var (
clusterIdentity1 = "garden1"
clusterIdentity2 = "garden2"
clusterAlias1 = "gardenalias1"
clusterAlias2 = "gardenalias2"
fooIdentity = "fooGarden"
project = "fooProject"
shoot = "fooShoot"
Expand All @@ -34,14 +36,16 @@ var _ = Describe("Config", func() {
LinkKubeconfig: pointer.Bool(false),
Gardens: []config.Garden{
{
Name: clusterIdentity1,
Name: clusterIdentity1,
Alias: clusterAlias1,
Patterns: []string{
fmt.Sprintf("^%s/shoot--(?P<project>.+)--(?P<shoot>.+)$", clusterIdentity1),
"^shoot--(?P<project>.+)--(?P<shoot>.+)$",
},
},
{
Name: clusterIdentity2,
Name: clusterIdentity2,
Alias: clusterAlias2,
Patterns: []string{
fmt.Sprintf("^(%s/)?shoot--(?P<project>.+)--(?P<shoot>.+)$", clusterIdentity2),
},
Expand Down Expand Up @@ -122,11 +126,14 @@ var _ = Describe("Config", func() {
fmt.Sprintf("garden %q is not defined in gardenctl configuration", fooIdentity)),
)

It("should find garden by identity", func() {
garden, err := cfg.Garden(clusterIdentity1)
DescribeTable("Should find garden by identity and alias", func(name, identityOrAlias string) {
garden, err := cfg.Garden(name)
Expect(err).NotTo(HaveOccurred())
Expect(garden.Name).Should(Equal(clusterIdentity1))
})
Expect(garden.Name).Should(Equal(identityOrAlias))
},
Entry("should find garden by identity", clusterIdentity1, clusterIdentity1),
Entry("should find garden by alias", clusterAlias2, clusterIdentity2),
)

It("should throw an error if garden not found", func() {
_, err := cfg.Garden("foobar")
Expand Down
15 changes: 12 additions & 3 deletions pkg/target/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type FlagCompletors interface {
// ProjectNames returns all projects for the currently targeted garden.
// The current target must at least point to a garden.
ProjectNames(ctx context.Context) ([]string, error)
// GardenNames returns all names of configured Gardens.
// GardenNames returns all identities and aliases of configured Gardens.
GardenNames() ([]string, error)
}

Expand Down Expand Up @@ -132,7 +132,12 @@ func newGardenClient(name string, config *config.Config, provider ClientProvider
return nil, err
}

return gardenclient.NewGardenClient(client, name), nil
garden, err := config.Garden(name)
if err != nil {
return nil, err
}

return gardenclient.NewGardenClient(client, garden.Name), nil
}

// NewManager returns a new manager.
Expand Down Expand Up @@ -737,7 +742,7 @@ func (m *managerImpl) ProjectNames(ctx context.Context) ([]string, error) {
return names.List(), nil
}

// GardenNames returns all names of configured Gardens.
// GardenNames returns all identities and aliases of configured Gardens.
func (m *managerImpl) GardenNames() ([]string, error) {
config := m.Configuration()
if config == nil {
Expand All @@ -747,6 +752,10 @@ func (m *managerImpl) GardenNames() ([]string, error) {
names := sets.NewString()
for _, garden := range config.Gardens {
names.Insert(garden.Name)

if garden.Alias != "" {
names.Insert(garden.Alias)
}
}

return names.List(), nil
Expand Down

0 comments on commit 49392a5

Please sign in to comment.