diff --git a/docs/help/gardenctl.md b/docs/help/gardenctl.md index dea7fbe8..2f565ae1 100644 --- a/docs/help/gardenctl.md +++ b/docs/help/gardenctl.md @@ -52,6 +52,7 @@ Find more information at: https://github.com/gardener/gardenctl-v2/blob/master/R ### SEE ALSO * [gardenctl config](gardenctl_config.md) - Modify gardenctl configuration file using subcommands +* [gardenctl history-env](gardenctl_history-env.md) - Generate a script Fuzzy search the target history * [gardenctl kubeconfig](gardenctl_kubeconfig.md) - Print the kubeconfig for the current target * [gardenctl kubectl-env](gardenctl_kubectl-env.md) - Generate a script that points KUBECONFIG to the targeted cluster for the specified shell * [gardenctl provider-env](gardenctl_provider-env.md) - Generate the cloud provider CLI configuration script for the specified shell diff --git a/docs/help/gardenctl_history-env.md b/docs/help/gardenctl_history-env.md new file mode 100644 index 00000000..c1b08390 --- /dev/null +++ b/docs/help/gardenctl_history-env.md @@ -0,0 +1,50 @@ +## gardenctl history-env + +Generate a script Fuzzy search the target history + +### Synopsis + +Generate a script Fuzzy search the target history + +The fuzzy finder must be installed. +Please refer to the installation instructions of the 3rd party tools: +* fuzzy finder - https://github.com/junegunn/fzf, + +Generate a script that fuzzy search the target history for the specified shell. +See each sub-command's help for details on how to use the generated script. + + + +### Options + +``` + -h, --help help for history-env +``` + +### Options inherited from parent commands + +``` + --add-dir-header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --config string config file (default is ~/.garden/gardenctl-v2.yaml) + --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log-dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log-file string If non-empty, use this log file (no effect when -logtostderr=true) + --log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --skip-headers If true, avoid header prefixes in the log messages + --skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [gardenctl](gardenctl.md) - Gardenctl is a utility to interact with Gardener installations +* [gardenctl history-env bash](gardenctl_history-env_bash.md) - Generate a script that fuzzy search the target history for bash +* [gardenctl history-env fish](gardenctl_history-env_fish.md) - Generate a script that fuzzy search the target history for fish +* [gardenctl history-env powershell](gardenctl_history-env_powershell.md) - Generate a script that fuzzy search the target history for powershell +* [gardenctl history-env zsh](gardenctl_history-env_zsh.md) - Generate a script that fuzzy search the target history for zsh + diff --git a/docs/help/gardenctl_history-env_bash.md b/docs/help/gardenctl_history-env_bash.md new file mode 100644 index 00000000..d8f3db7d --- /dev/null +++ b/docs/help/gardenctl_history-env_bash.md @@ -0,0 +1,45 @@ +## gardenctl history-env bash + +Generate a script that fuzzy search the target history for bash + +### Synopsis + +Generate a script that fuzzy search the target history for bash. + +To load the fuzzy search the target history script in your current shell session: +$ eval "$(gardenctl history-env bash)" + + +``` +gardenctl history-env bash [flags] +``` + +### Options + +``` + -h, --help help for bash +``` + +### Options inherited from parent commands + +``` + --add-dir-header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --config string config file (default is ~/.garden/gardenctl-v2.yaml) + --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log-dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log-file string If non-empty, use this log file (no effect when -logtostderr=true) + --log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --skip-headers If true, avoid header prefixes in the log messages + --skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [gardenctl history-env](gardenctl_history-env.md) - Generate a script Fuzzy search the target history + diff --git a/docs/help/gardenctl_history-env_fish.md b/docs/help/gardenctl_history-env_fish.md new file mode 100644 index 00000000..c4294d8f --- /dev/null +++ b/docs/help/gardenctl_history-env_fish.md @@ -0,0 +1,45 @@ +## gardenctl history-env fish + +Generate a script that fuzzy search the target history for fish + +### Synopsis + +Generate a script that fuzzy search the target history for fish. + +To load the fuzzy search the target history script in your current shell session: +$ eval (gardenctl history-env fish) + + +``` +gardenctl history-env fish [flags] +``` + +### Options + +``` + -h, --help help for fish +``` + +### Options inherited from parent commands + +``` + --add-dir-header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --config string config file (default is ~/.garden/gardenctl-v2.yaml) + --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log-dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log-file string If non-empty, use this log file (no effect when -logtostderr=true) + --log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --skip-headers If true, avoid header prefixes in the log messages + --skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [gardenctl history-env](gardenctl_history-env.md) - Generate a script Fuzzy search the target history + diff --git a/docs/help/gardenctl_history-env_powershell.md b/docs/help/gardenctl_history-env_powershell.md new file mode 100644 index 00000000..fa1e5616 --- /dev/null +++ b/docs/help/gardenctl_history-env_powershell.md @@ -0,0 +1,45 @@ +## gardenctl history-env powershell + +Generate a script that fuzzy search the target history for powershell + +### Synopsis + +Generate a script that fuzzy search the target history for powershell. + +To load the fuzzy search the target history script in your current shell session: +PS /> & gardenctl history-env powershell | Invoke-Expression + + +``` +gardenctl history-env powershell [flags] +``` + +### Options + +``` + -h, --help help for powershell +``` + +### Options inherited from parent commands + +``` + --add-dir-header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --config string config file (default is ~/.garden/gardenctl-v2.yaml) + --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log-dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log-file string If non-empty, use this log file (no effect when -logtostderr=true) + --log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --skip-headers If true, avoid header prefixes in the log messages + --skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [gardenctl history-env](gardenctl_history-env.md) - Generate a script Fuzzy search the target history + diff --git a/docs/help/gardenctl_history-env_zsh.md b/docs/help/gardenctl_history-env_zsh.md new file mode 100644 index 00000000..b2f16ed1 --- /dev/null +++ b/docs/help/gardenctl_history-env_zsh.md @@ -0,0 +1,45 @@ +## gardenctl history-env zsh + +Generate a script that fuzzy search the target history for zsh + +### Synopsis + +Generate a script that fuzzy search the target history for zsh. + +To load the fuzzy search the target history script in your current shell session: +$ eval "$(gardenctl history-env zsh)" + + +``` +gardenctl history-env zsh [flags] +``` + +### Options + +``` + -h, --help help for zsh +``` + +### Options inherited from parent commands + +``` + --add-dir-header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --config string config file (default is ~/.garden/gardenctl-v2.yaml) + --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log-dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log-file string If non-empty, use this log file (no effect when -logtostderr=true) + --log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --skip-headers If true, avoid header prefixes in the log messages + --skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [gardenctl history-env](gardenctl_history-env.md) - Generate a script Fuzzy search the target history + diff --git a/docs/help/gardenctl_target.md b/docs/help/gardenctl_target.md index 2695a37a..4adbb7c0 100644 --- a/docs/help/gardenctl_target.md +++ b/docs/help/gardenctl_target.md @@ -55,6 +55,7 @@ gardenctl target value/that/matches/pattern --control-plane * [gardenctl](gardenctl.md) - Gardenctl is a utility to interact with Gardener installations * [gardenctl target control-plane](gardenctl_target_control-plane.md) - Target the control plane of the shoot * [gardenctl target garden](gardenctl_target_garden.md) - Target a garden +* [gardenctl target history](gardenctl_target_history.md) - Print the target history * [gardenctl target project](gardenctl_target_project.md) - Target a project * [gardenctl target seed](gardenctl_target_seed.md) - Target a seed * [gardenctl target shoot](gardenctl_target_shoot.md) - Target a shoot diff --git a/docs/help/gardenctl_target_history.md b/docs/help/gardenctl_target_history.md new file mode 100644 index 00000000..3ca0a1e7 --- /dev/null +++ b/docs/help/gardenctl_target_history.md @@ -0,0 +1,46 @@ +## gardenctl target history + +Print the target history + +### Synopsis + +Print the target history + +``` +gardenctl target history [flags] +``` + +### Options + +``` + -h, --help help for history +``` + +### Options inherited from parent commands + +``` + --add-dir-header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --config string config file (default is ~/.garden/gardenctl-v2.yaml) + --control-plane target control plane of shoot, use together with shoot argument + --garden string target the given garden cluster + --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log-dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log-file string If non-empty, use this log file (no effect when -logtostderr=true) + --log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --project string target the given project + --seed string target the given seed cluster + --shoot string target the given shoot cluster + --skip-headers If true, avoid header prefixes in the log messages + --skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [gardenctl target](gardenctl_target.md) - Set scope for next operations, using subcommands or pattern + diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 76b8112f..1e40d694 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -122,6 +122,7 @@ Find more information at: https://github.com/gardener/gardenctl-v2/blob/master/R cmd.AddCommand(cmdconfig.NewCmdConfig(f, ioStreams)) cmd.AddCommand(cmdenv.NewCmdProviderEnv(f, ioStreams)) cmd.AddCommand(cmdenv.NewCmdKubectlEnv(f, ioStreams)) + cmd.AddCommand(cmdenv.NewCmdHistoryEnv(f, ioStreams)) cmd.AddCommand(cmdenv.NewCmdRC(f, ioStreams)) cmd.AddCommand(kubeconfig.NewCmdKubeconfig(f, ioStreams)) diff --git a/pkg/cmd/env/env.go b/pkg/cmd/env/env.go index 636efec8..2c2305ca 100644 --- a/pkg/cmd/env/env.go +++ b/pkg/cmd/env/env.go @@ -112,3 +112,43 @@ The generated script points the KUBECONFIG environment variable to the currently return cmd } + +// NewCmdHistoryEnv returns a new history-env command. +func NewCmdHistoryEnv(f util.Factory, ioStreams util.IOStreams) *cobra.Command { + o := &options{ + Options: base.Options{ + IOStreams: ioStreams, + }, + ProviderType: "history", + } + runE := base.WrapRunE(o, f) + cmd := &cobra.Command{ + Use: "history-env", + Short: "Generate a script Fuzzy search the target history", + Long: `Generate a script Fuzzy search the target history + +The fuzzy finder must be installed. +Please refer to the installation instructions of the 3rd party tools: +* fuzzy finder - https://github.com/junegunn/fzf, + +Generate a script that fuzzy search the target history for the specified shell. +See each sub-command's help for details on how to use the generated script. + +`, + DisableFlagParsing: true, + } + + for _, s := range validShells { + cmd.AddCommand(&cobra.Command{ + Use: string(s), + Short: fmt.Sprintf("Generate a script that fuzzy search the target history for %s", s), + Long: fmt.Sprintf("Generate a script that fuzzy search the target history for %s.\n\n"+ + "To load the fuzzy search the target history script in your current shell session:\n%s\n", + s, s.Prompt(runtime.GOOS)+s.EvalCommand(fmt.Sprintf("%s %s %s", "gardenctl", cmd.Use, s)), + ), + RunE: runE, + }) + } + + return cmd +} diff --git a/pkg/cmd/env/env_test.go b/pkg/cmd/env/env_test.go index 26d3e208..dc73b035 100644 --- a/pkg/cmd/env/env_test.go +++ b/pkg/cmd/env/env_test.go @@ -89,4 +89,21 @@ var _ = Describe("Env Commands", func() { } }) }) + + Describe("given a HistoryEnv instance", func() { + BeforeEach(func() { + cmd = env.NewCmdHistoryEnv(factory, streams) + }) + + It("should have Use, Flags and SubCommands", func() { + Expect(cmd.Use).To(Equal("history-env")) + Expect(cmd.Flag("output")).To(BeNil()) + subCmds := cmd.Commands() + Expect(len(subCmds)).To(Equal(4)) + for _, c := range subCmds { + s := env.Shell(c.Name()) + Expect(s).To(BeElementOf(env.ValidShells)) + } + }) + }) }) diff --git a/pkg/cmd/env/options.go b/pkg/cmd/env/options.go index 5e37a7b8..1787da48 100644 --- a/pkg/cmd/env/options.go +++ b/pkg/cmd/env/options.go @@ -64,13 +64,17 @@ func (o *options) Complete(f util.Factory, cmd *cobra.Command, args []string) er o.GardenDir = f.GardenHomeDir() o.Template = newTemplate("helpers") - //nolint:gocritic // accept singleCaseSwitch to be consistent with rest of the file. Will be resolved once we refactor to have own options for each provider type switch o.ProviderType { case "kubernetes": filename := filepath.Join(o.GardenDir, "templates", "kubernetes.tmpl") if err := o.Template.ParseFiles(filename); err != nil { return err } + case "history": + filename := filepath.Join(o.GardenDir, "templates", "history.tmpl") + if err := o.Template.ParseFiles(filename); err != nil { + return err + } } manager, err := f.Manager() @@ -137,6 +141,8 @@ func (o *options) Run(f util.Factory) error { } return o.runKubernetes(ctx, manager) + case "history": + return o.runHistory(ctx, manager) default: if o.Target.GardenName() == "" { return target.ErrNoGardenTargeted @@ -234,6 +240,14 @@ func (o *options) run(ctx context.Context, manager target.Manager) error { return execTmpl(o, shoot, secret, cloudProfile, messages) } +func (o *options) runHistory(ctx context.Context, manager target.Manager) error { + data := map[string]interface{}{ + "__meta": generateMetadata(o), + } + + return o.Template.ExecuteTemplate(o.IOStreams.Out, o.Shell, data) +} + func execTmpl(o *options, shoot *gardencorev1beta1.Shoot, secret *corev1.Secret, cloudProfile *gardencorev1beta1.CloudProfile, messages ac.AccessRestrictionMessages) error { o.ProviderType = shoot.Spec.Provider.Type diff --git a/pkg/cmd/env/rc_test.go b/pkg/cmd/env/rc_test.go index 8e6a198a..6fccae59 100644 --- a/pkg/cmd/env/rc_test.go +++ b/pkg/cmd/env/rc_test.go @@ -72,6 +72,7 @@ alias gtc-='gardenctl target unset control-plane' alias gk='eval "$(gardenctl kubectl-env bash)"' alias gp='eval "$(gardenctl provider-env bash)"' alias gcv='gardenctl config view -o yaml' +alias ghh='eval "$(gardenctl history-env bash)"' source <(gardenctl completion bash) complete -o default -F __start_gardenctl g gk @@ -91,6 +92,7 @@ alias gtc-='gardenctl target unset control-plane' alias gk='eval "$(gardenctl kubectl-env zsh)"' alias gp='eval "$(gardenctl provider-env zsh)"' alias gcv='gardenctl config view -o yaml' +alias ghh='eval "$(gardenctl history-env zsh)"' if (( $+commands[gardenctl] )); then if [ -d "$ZSH_CACHE_DIR/completions" ] && (($fpath[(Ie)$ZSH_CACHE_DIR/completions])); then GCTL_COMPLETION_FILE="$ZSH_CACHE_DIR/completions/_gardenctl" @@ -122,6 +124,7 @@ alias gtc-='gardenctl target unset control-plane' alias gk='eval (gardenctl kubectl-env fish)' alias gp='eval (gardenctl provider-env fish)' alias gcv='gardenctl config view -o yaml' +alias ghh='eval "$(gardenctl history-env fish)"' gardenctl completion fish | source complete -c g -w gardenctl gk @@ -159,6 +162,10 @@ function Gardenctl-Config-View { gardenctl config view -o yaml } Set-Alias -Name gcv -Value Gardenctl-Config-View -Option AllScope -Force +function Gardenctl-HistoryEnv { + gardenctl history-env powershell | Out-String | Invoke-Expression +} +Set-Alias -Name ghh -Value Gardenctl-HistoryEnv -Option AllScope -Force function Gardenctl-Completion-Powershell { $s = (gardenctl completion powershell) @( diff --git a/pkg/cmd/env/template_test.go b/pkg/cmd/env/template_test.go index 019b67c6..f84fb1ff 100644 --- a/pkg/cmd/env/template_test.go +++ b/pkg/cmd/env/template_test.go @@ -76,7 +76,7 @@ var _ = Describe("Env Commands - Template", func() { Entry("shell is bash", "bash", "eval $(%s)"), Entry("shell is zsh", "zsh", "eval $(%s)"), Entry("shell is fish", "fish", "eval (%s)"), - Entry("shell is powershell", "powershell", "& %s | Invoke-Expression"), + Entry("shell is powershell", "powershell", "& %s | Out-String | Invoke-Expression"), ) DescribeTable("executing the usage-hint template", @@ -189,9 +189,9 @@ $Env:AZURE_SUBSCRIPTION_ID = '%[5]s'; $Env:AZURE_CONFIG_DIR = '%[6]s'; az login --service-principal --username "$Env:AZURE_CLIENT_ID" --password "$Env:AZURE_CLIENT_SECRET" --tenant "$Env:AZURE_TENANT_ID"; az account set --subscription "$Env:AZURE_SUBSCRIPTION_ID"; -printf 'Run the following command to log out and remove access to Azure subscriptions:\n$ & gardenctl provider-env --garden garden --project project --shoot shoot -u %[1]s | Invoke-Expression\n'; +printf 'Run the following command to log out and remove access to Azure subscriptions:\n$ & gardenctl provider-env --garden garden --project project --shoot shoot -u %[1]s | Out-String | Invoke-Expression\n'; # Run this command to configure az for your shell: -# & gardenctl provider-env %[1]s | Invoke-Expression +# & gardenctl provider-env %[1]s | Out-String | Invoke-Expression ` const unsetFormat = `az logout --username "$Env:AZURE_CLIENT_ID"; @@ -201,7 +201,7 @@ Remove-Item -ErrorAction SilentlyContinue Env:\AZURE_TENANT_ID; Remove-Item -ErrorAction SilentlyContinue Env:\AZURE_SUBSCRIPTION_ID; Remove-Item -ErrorAction SilentlyContinue Env:\AZURE_CONFIG_DIR; # Run this command to reset the az configuration for your shell: -# & gardenctl provider-env -u %[1]s | Invoke-Expression +# & gardenctl provider-env -u %[1]s | Out-String | Invoke-Expression ` var ( clientID = "client" diff --git a/pkg/cmd/env/templates/helpers.tmpl b/pkg/cmd/env/templates/helpers.tmpl index ca09f225..855a7407 100644 --- a/pkg/cmd/env/templates/helpers.tmpl +++ b/pkg/cmd/env/templates/helpers.tmpl @@ -16,6 +16,6 @@ # {{template "eval-cmd" dict "shell" .shell "cmd" (printf "%s -u %s" .commandPath .shell)}} {{end}} -{{define "eval-cmd"}}{{if eq .shell "powershell"}}& {{.cmd}} | Invoke-Expression{{else if eq .shell "fish" -}}eval ({{.cmd}}){{else}}eval $({{.cmd}}){{end}}{{end}} +{{define "eval-cmd"}}{{if eq .shell "powershell"}}& {{.cmd}} | Out-String | Invoke-Expression{{else if eq .shell "fish" -}}eval ({{.cmd}}){{else}}eval $({{.cmd}}){{end}}{{end}} {{define "printf"}}{{if .format}}printf {{.format | replace "\n" "\\n" | shellEscape}}{{range .arguments}} {{. | shellEscape}}{{end}}{{end}}{{end}} diff --git a/pkg/cmd/env/templates/history.tmpl b/pkg/cmd/env/templates/history.tmpl new file mode 100644 index 00000000..caa86bcd --- /dev/null +++ b/pkg/cmd/env/templates/history.tmpl @@ -0,0 +1,15 @@ +{{define "bash"}} +{{template "eval-cmd" dict "shell" .shell "cmd" "gardenctl target history | fzf --tac --no-sort --height 40% --layout reverse --info inline --border --preview 'echo {}' --preview-window up,1,border-horizontal"}} +{{template "usage-hint" .__meta}}{{end}} + +{{define "zsh"}} +{{template "eval-cmd" dict "shell" .shell "cmd" "gardenctl target history | fzf --tac --no-sort --height 40% --layout reverse --info inline --border --preview 'echo {}' --preview-window up,1,border-horizontal"}} +{{template "usage-hint" .__meta}}{{end}} + +{{define "fish"}} +{{template "eval-cmd" dict "shell" .shell "cmd" "gardenctl target history | fzf --tac --no-sort --height 40% --layout reverse --info inline --border --preview 'echo {}' --preview-window up,1,border-horizontal"}} +{{template "usage-hint" .__meta}}{{end}} + +{{define "powershell"}} +{{"gardenctl target history | fzf --tac --no-sort --height 40% --layout reverse --info inline --border --preview 'echo {}' --preview-window up,1,border-horizontal | Out-String | Invoke-Expression" }} +{{template "usage-hint" .__meta}}{{end}} \ No newline at end of file diff --git a/pkg/cmd/env/templates/rc.tmpl b/pkg/cmd/env/templates/rc.tmpl index 1bf817e7..ba33f393 100644 --- a/pkg/cmd/env/templates/rc.tmpl +++ b/pkg/cmd/env/templates/rc.tmpl @@ -9,6 +9,7 @@ alias {{.prefix}}tc-='gardenctl target unset control-plane' alias {{.prefix}}k='eval "$(gardenctl kubectl-env {{.shell}})"' alias {{.prefix}}p='eval "$(gardenctl provider-env {{.shell}})"' alias {{.prefix}}cv='gardenctl config view -o yaml' +alias {{.prefix}}hh='eval "$(gardenctl history-env {{.shell}})"' {{if not .noCompletion -}} source <(gardenctl completion {{.shell}}) complete -o default -F __start_gardenctl {{.prefix}} @@ -29,6 +30,7 @@ alias {{.prefix}}tc-='gardenctl target unset control-plane' alias {{.prefix}}k='eval "$(gardenctl kubectl-env {{.shell}})"' alias {{.prefix}}p='eval "$(gardenctl provider-env {{.shell}})"' alias {{.prefix}}cv='gardenctl config view -o yaml' +alias {{.prefix}}hh='eval "$(gardenctl history-env {{.shell}})"' {{if not .noCompletion -}} if (( $+commands[gardenctl] )); then if [ -d "$ZSH_CACHE_DIR/completions" ] && (($fpath[(Ie)$ZSH_CACHE_DIR/completions])); then @@ -61,6 +63,7 @@ alias {{.prefix}}tc-='gardenctl target unset control-plane' alias {{.prefix}}k='eval (gardenctl kubectl-env {{.shell}})' alias {{.prefix}}p='eval (gardenctl provider-env {{.shell}})' alias {{.prefix}}cv='gardenctl config view -o yaml' +alias {{.prefix}}hh='eval "$(gardenctl history-env {{.shell}})"' {{if not .noCompletion -}} gardenctl completion {{.shell}} | source complete -c {{.prefix}} -w gardenctl @@ -99,6 +102,10 @@ function Gardenctl-Config-View { gardenctl config view -o yaml } Set-Alias -Name {{.prefix}}cv -Value Gardenctl-Config-View -Option AllScope -Force +function Gardenctl-HistoryEnv { + gardenctl history-env {{.shell}} | Out-String | Invoke-Expression +} +Set-Alias -Name {{.prefix}}hh -Value Gardenctl-HistoryEnv -Option AllScope -Force {{if not .noCompletion -}} function Gardenctl-Completion-Powershell { $s = (gardenctl completion {{.shell}}) diff --git a/pkg/cmd/env/testdata/gcp/unset.pwsh b/pkg/cmd/env/testdata/gcp/unset.pwsh index 5ae65edd..3a8172d9 100644 --- a/pkg/cmd/env/testdata/gcp/unset.pwsh +++ b/pkg/cmd/env/testdata/gcp/unset.pwsh @@ -4,4 +4,4 @@ Remove-Item -ErrorAction SilentlyContinue Env:\CLOUDSDK_CORE_PROJECT; Remove-Item -ErrorAction SilentlyContinue Env:\CLOUDSDK_COMPUTE_REGION; Remove-Item -ErrorAction SilentlyContinue Env:\CLOUDSDK_CONFIG; # Run this command to reset the gcloud configuration for your shell: -# & gardenctl provider-env -u powershell | Invoke-Expression +# & gardenctl provider-env -u powershell | Out-String | Invoke-Expression diff --git a/pkg/cmd/target/export_test.go b/pkg/cmd/target/export_test.go index 8be90c8c..a76c3f45 100644 --- a/pkg/cmd/target/export_test.go +++ b/pkg/cmd/target/export_test.go @@ -6,4 +6,9 @@ SPDX-License-Identifier: Apache-2.0 package target -var ValidTargetArgsFunction = validTargetArgsFunction +var ( + ValidTargetArgsFunction = validTargetArgsFunction + ToHistoryOutput = toHistoryOutput + ToCommand = toCommand + HistoryWrite = historyWrite +) diff --git a/pkg/cmd/target/history.go b/pkg/cmd/target/history.go new file mode 100644 index 00000000..7e51ccfa --- /dev/null +++ b/pkg/cmd/target/history.go @@ -0,0 +1,164 @@ +/* +SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Gardener contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package target + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + "github.com/gardener/gardenctl-v2/internal/util" + "github.com/gardener/gardenctl-v2/pkg/cmd/base" + "github.com/gardener/gardenctl-v2/pkg/target" +) + +const ( + historyFile = "history" +) + +// NewCmdHistory returns a new target history command. +func NewCmdHistory(f util.Factory, ioStreams util.IOStreams) *cobra.Command { + o := &HistoryOptions{ + Options: base.Options{ + IOStreams: ioStreams, + }, + } + cmd := &cobra.Command{ + Use: "history", + Short: "Print the target history", + Long: "Print the target history", + RunE: base.WrapRunE(o, f), + } + + return cmd +} + +// HistoryOptions is a struct to support target history command. +type HistoryOptions struct { + base.Options + path string +} + +// HistoryWriteOptions is a struct to support target history write command. +type HistoryWriteOptions struct { + base.Options + calledAs string + path string +} + +// NewHistoryWriteOptions returns initialized HistoryWriteOptions. +func NewHistoryWriteOptions(ioStreams util.IOStreams) *HistoryWriteOptions { + return &HistoryWriteOptions{ + Options: base.Options{ + IOStreams: ioStreams, + }, + } +} + +// Run does the actual work of the command. +func (o *HistoryOptions) Run(f util.Factory) error { + if err := toHistoryOutput(o.path, o.Options); err != nil { + return err + } + + return nil +} + +// historyWrite executes history file write. +func historyWrite(path string, s string) error { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666) + if err != nil { + return fmt.Errorf("history file open error %s", path) + } + defer f.Close() + + if _, err := f.WriteString(s); err != nil { + return fmt.Errorf("history file write error %s", path) + } + + return nil +} + +// toHistoryOutput executes history output. +func toHistoryOutput(path string, o base.Options) error { + content, err := os.ReadFile(path) + if err != nil { + return err + } + + _, err = fmt.Fprintf(o.IOStreams.Out, "%s", content) + + return err +} + +// toCommand executes target history parse from current target. +func toCommand(currentTarget target.Target) (string, error) { + var flags []string + + if currentTarget.GardenName() != "" { + flags = append(flags, "--garden", currentTarget.GardenName()) + } + + if currentTarget.ProjectName() != "" { + flags = append(flags, "--project", currentTarget.ProjectName()) + } + + if currentTarget.SeedName() != "" { + flags = append(flags, "--seed", currentTarget.SeedName()) + } + + if currentTarget.ShootName() != "" { + flags = append(flags, "--shoot", currentTarget.ShootName()) + } + + if currentTarget.ControlPlane() { + flags = append(flags, "--control-plane") + } + + return fmt.Sprintln(os.Args[0], "target", strings.Join(flags, " ")), nil +} + +// Run does the actual work of the command. +func (o *HistoryWriteOptions) Run(f util.Factory) error { + // keep gardenctl target unset history + if o.calledAs == "view" || o.calledAs == "history" { + return nil + } + + manager, err := f.Manager() + if err != nil { + return err + } + + currentTarget, err := manager.CurrentTarget() + if err != nil { + return fmt.Errorf("failed to get current target: %w", err) + } + + command, err := toCommand(currentTarget) + if err != nil { + return err + } + + return historyWrite(o.path, command) +} + +// Complete adapts from the command line args to the data required. +func (o *HistoryWriteOptions) Complete(f util.Factory, cmd *cobra.Command, args []string) error { + o.calledAs = cmd.CalledAs() + o.path = filepath.Join(f.GardenHomeDir(), historyFile) + + return nil +} + +// Complete adapts from the command line args to the data required. +func (o *HistoryOptions) Complete(f util.Factory, cmd *cobra.Command, args []string) error { + o.path = filepath.Join(f.GardenHomeDir(), historyFile) + return nil +} diff --git a/pkg/cmd/target/history_test.go b/pkg/cmd/target/history_test.go new file mode 100644 index 00000000..5d61d6df --- /dev/null +++ b/pkg/cmd/target/history_test.go @@ -0,0 +1,88 @@ +/* +SPDX-FileCopyrightText: 2021 SAP SE or an SAP affiliate company and Gardener contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package target_test + +import ( + "os" + + internalfake "github.com/gardener/gardenctl-v2/internal/fake" + "github.com/gardener/gardenctl-v2/internal/util" + "github.com/gardener/gardenctl-v2/pkg/cmd/base" + cmdtarget "github.com/gardener/gardenctl-v2/pkg/cmd/target" + "github.com/gardener/gardenctl-v2/pkg/target" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("history Command", func() { + const ( + historyPath = "./history" + gardenName = "mygarden" + projectName = "myproject" + shootName = "myshoot" + ) + + var ( + streams util.IOStreams + options *base.Options + out *util.SafeBytesBuffer + currentTarget target.Target + factory *internalfake.Factory + targetProvider *internalfake.TargetProvider + ) + + BeforeEach(func() { + streams, _, out, _ = util.NewTestIOStreams() + options = base.NewOptions(streams) + currentTarget = target.NewTarget(gardenName, projectName, "", shootName) + }) + + JustBeforeEach(func() { + targetProvider = internalfake.NewFakeTargetProvider(currentTarget) + factory = internalfake.NewFakeFactory(nil, nil, nil, targetProvider) + }) + + AfterSuite(func() { + err := os.Remove(historyPath) + Expect(err).NotTo(HaveOccurred()) + }) + + Describe("PersistentPostRunE", func() { + It("should PersistentPostRunE succeed", func() { + cmd := cmdtarget.NewCmdTarget(factory, streams) + Expect(cmd.PersistentPostRunE(cmd, nil)).To(Succeed()) + }) + }) + + Describe("#HistoryWrite and ToHistoryOutput ", func() { + It("should write history file", func() { + err := cmdtarget.HistoryWrite(historyPath, "hello") + Expect(err).NotTo(HaveOccurred()) + }) + It("should print history output", func() { + err := cmdtarget.ToHistoryOutput(historyPath, *options) + Expect(err).NotTo(HaveOccurred()) + Expect(out.String()).Should(ContainSubstring("hello")) + }) + }) + + Describe("#ToHistoryOutput", func() { + It("should print history output from command level", func() { + cmd := cmdtarget.NewCmdHistory(factory, streams) + Expect(cmd.RunE(cmd, nil)).To(Succeed()) + Expect(out.String()).Should(ContainSubstring("target --garden mygarden --project myproject --shoot myshoot")) + }) + }) + + Describe("#toCommand", func() { + It("should succeed execute history parse", func() { + currentTarget, err := cmdtarget.ToCommand(currentTarget) + Expect(err).NotTo(HaveOccurred()) + Expect(currentTarget).Should((ContainSubstring("target --garden mygarden --project myproject --shoot myshoot"))) + }) + }) +}) diff --git a/pkg/cmd/target/target.go b/pkg/cmd/target/target.go index 79e25dea..02bded53 100644 --- a/pkg/cmd/target/target.go +++ b/pkg/cmd/target/target.go @@ -27,6 +27,7 @@ import ( // NewCmdTarget returns a new target command. func NewCmdTarget(f util.Factory, ioStreams util.IOStreams) *cobra.Command { o := NewTargetOptions(ioStreams) + h := NewHistoryWriteOptions(ioStreams) cmd := &cobra.Command{ Use: "target", Short: "Set scope for next operations, using subcommands or pattern", @@ -38,7 +39,8 @@ gardenctl target shoot my-shoot # Target shoot control-plane using values that match a pattern defined for a specific garden gardenctl target value/that/matches/pattern --control-plane`, - RunE: base.WrapRunE(o, f), + RunE: base.WrapRunE(o, f), + PersistentPostRunE: base.WrapRunE(h, f), } cmd.AddCommand(NewCmdTargetGarden(f, ioStreams)) @@ -49,7 +51,7 @@ gardenctl target value/that/matches/pattern --control-plane`, cmd.AddCommand(NewCmdUnset(f, ioStreams)) cmd.AddCommand(NewCmdView(f, ioStreams)) - + cmd.AddCommand(NewCmdHistory(f, ioStreams)) o.AddFlags(cmd.Flags()) manager, err := f.Manager() diff --git a/pkg/target/mocks/mock_manager.go b/pkg/target/mocks/mock_manager.go index 39d68d23..e5975c32 100644 --- a/pkg/target/mocks/mock_manager.go +++ b/pkg/target/mocks/mock_manager.go @@ -11,7 +11,7 @@ import ( gomock "github.com/golang/mock/gomock" clientcmd "k8s.io/client-go/tools/clientcmd" client "sigs.k8s.io/controller-runtime/pkg/client" - + gardenclient "github.com/gardener/gardenctl-v2/internal/gardenclient" config "github.com/gardener/gardenctl-v2/pkg/config" target "github.com/gardener/gardenctl-v2/pkg/target"