From 816f14c0b46cb58d879cca5471122d1ce7d93330 Mon Sep 17 00:00:00 2001 From: Andy Goldstein Date: Fri, 22 Dec 2017 09:43:44 -0500 Subject: [PATCH] Support running in any namespace Add the ability for the Ark server to run in any namespace. Add `ark client config get/set` for manipulating the new client configuration file in $HOME/.config/ark/config.json. This holds client defaults, such as the Ark server's namespace (to avoid having to specify the --namespace flag all the time). Add a --namespace flag to all client commands. Signed-off-by: Andy Goldstein --- pkg/client/config.go | 75 +++++++++++++++++++ pkg/client/factory.go | 28 ++++++- pkg/cmd/ark/ark.go | 2 + pkg/cmd/cli/backup/create.go | 2 +- pkg/cmd/cli/backup/delete.go | 3 +- pkg/cmd/cli/backup/describe.go | 4 +- pkg/cmd/cli/backup/download.go | 2 +- pkg/cmd/cli/backup/get.go | 4 +- pkg/cmd/cli/backup/logs.go | 2 +- .../cli/{config/get.go => client/client.go} | 18 +++-- pkg/cmd/cli/{ => client}/config/config.go | 11 +-- pkg/cmd/cli/client/config/get.go | 60 +++++++++++++++ pkg/cmd/cli/client/config/set.go | 62 +++++++++++++++ pkg/cmd/cli/config/set.go | 29 ------- pkg/cmd/cli/plugin/add.go | 5 +- pkg/cmd/cli/plugin/remove.go | 5 +- pkg/cmd/cli/restore/create.go | 2 +- pkg/cmd/cli/restore/delete.go | 3 +- pkg/cmd/cli/restore/describe.go | 4 +- pkg/cmd/cli/restore/get.go | 4 +- pkg/cmd/cli/restore/logs.go | 2 +- pkg/cmd/cli/schedule/create.go | 2 +- pkg/cmd/cli/schedule/delete.go | 3 +- pkg/cmd/cli/schedule/describe.go | 4 +- pkg/cmd/cli/schedule/get.go | 4 +- pkg/cmd/server/server.go | 50 ++++++++++--- .../util/downloadrequest/downloadrequest.go | 15 ++-- .../downloadrequest/downloadrequest_test.go | 2 +- pkg/cmd/util/output/restore_describer.go | 2 +- pkg/controller/backup_controller.go | 2 +- pkg/controller/download_request_controller.go | 2 +- pkg/controller/restore_controller.go | 11 ++- pkg/controller/restore_controller_test.go | 2 + pkg/controller/schedule_controller.go | 7 +- pkg/controller/schedule_controller_test.go | 1 + 35 files changed, 335 insertions(+), 99 deletions(-) create mode 100644 pkg/client/config.go rename pkg/cmd/cli/{config/get.go => client/client.go} (68%) rename pkg/cmd/cli/{ => client}/config/config.go (79%) create mode 100644 pkg/cmd/cli/client/config/get.go create mode 100644 pkg/cmd/cli/client/config/set.go delete mode 100644 pkg/cmd/cli/config/set.go diff --git a/pkg/client/config.go b/pkg/client/config.go new file mode 100644 index 000000000..8f5d428b5 --- /dev/null +++ b/pkg/client/config.go @@ -0,0 +1,75 @@ +/* +Copyright 2018 the Heptio Ark contributors. + +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 client + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +const ( + ConfigKeyNamespace = "namespace" +) + +// LoadConfig loads the Ark client configuration file and returns it as a map[string]string. If the +// file does not exist, an empty map is returned. +func LoadConfig() (map[string]string, error) { + fileName := configFileName() + + _, err := os.Stat(fileName) + if os.IsNotExist(err) { + // If the file isn't there, just return an empty map + return map[string]string{}, nil + } + if err != nil { + // For any other Stat() error, return it + return nil, errors.WithStack(err) + } + + configFile, err := os.Open(fileName) + if err != nil { + return nil, errors.WithStack(err) + } + defer configFile.Close() + + var config map[string]string + if err := json.NewDecoder(configFile).Decode(&config); err != nil { + return nil, errors.WithStack(err) + } + + return config, nil +} + +// SaveConfig saves the passed in config map to the Ark client configuration file. +func SaveConfig(config map[string]string) error { + fileName := configFileName() + + configFile, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return errors.WithStack(err) + } + defer configFile.Close() + + return json.NewEncoder(configFile).Encode(&config) +} + +func configFileName() string { + return filepath.Join(os.Getenv("HOME"), ".config", "ark", "config.json") +} diff --git a/pkg/client/factory.go b/pkg/client/factory.go index ec504bc1d..236e53e03 100644 --- a/pkg/client/factory.go +++ b/pkg/client/factory.go @@ -17,30 +17,36 @@ limitations under the License. package client import ( + "fmt" + "os" + "github.com/pkg/errors" "github.com/spf13/pflag" "k8s.io/client-go/kubernetes" + "github.com/heptio/ark/pkg/apis/ark/v1" clientset "github.com/heptio/ark/pkg/generated/clientset/versioned" ) // Factory knows how to create an ArkClient and Kubernetes client. type Factory interface { - // BindFlags binds common flags such as --kubeconfig to the passed-in FlagSet. + // BindFlags binds common flags (--kubeconfig, --namespace) to the passed-in FlagSet. BindFlags(flags *pflag.FlagSet) // Client returns an ArkClient. It uses the following priority to specify the cluster - // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. + // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. Client() (clientset.Interface, error) // KubeClient returns a Kubernetes client. It uses the following priority to specify the cluster - // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. + // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. KubeClient() (kubernetes.Interface, error) + Namespace() string } type factory struct { flags *pflag.FlagSet kubeconfig string baseName string + namespace string } // NewFactory returns a Factory. @@ -49,7 +55,19 @@ func NewFactory(baseName string) Factory { flags: pflag.NewFlagSet("", pflag.ContinueOnError), baseName: baseName, } + + if config, err := LoadConfig(); err == nil { + f.namespace = config[ConfigKeyNamespace] + } else { + fmt.Fprintf(os.Stderr, "WARNING: error retrieving namespace from config file: %v\n", err) + } + + if f.namespace == "" { + f.namespace = v1.DefaultNamespace + } + f.flags.StringVar(&f.kubeconfig, "kubeconfig", "", "Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration") + f.flags.StringVarP(&f.namespace, "namespace", "n", f.namespace, "The namespace in which Ark should operate") return f } @@ -83,3 +101,7 @@ func (f *factory) KubeClient() (kubernetes.Interface, error) { } return kubeClient, nil } + +func (f *factory) Namespace() string { + return f.namespace +} diff --git a/pkg/cmd/ark/ark.go b/pkg/cmd/ark/ark.go index c0456aa02..db0d461bf 100644 --- a/pkg/cmd/ark/ark.go +++ b/pkg/cmd/ark/ark.go @@ -23,6 +23,7 @@ import ( "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cmd/cli/backup" + cliclient "github.com/heptio/ark/pkg/cmd/cli/client" "github.com/heptio/ark/pkg/cmd/cli/create" "github.com/heptio/ark/pkg/cmd/cli/delete" "github.com/heptio/ark/pkg/cmd/cli/describe" @@ -63,6 +64,7 @@ operations can also be performed as 'ark backup get' and 'ark schedule create'.` runplugin.NewCommand(), plugin.NewCommand(f), delete.NewCommand(f), + cliclient.NewCommand(), ) // add the glog flags diff --git a/pkg/cmd/cli/backup/create.go b/pkg/cmd/cli/backup/create.go index e0fa10aff..954ede3fd 100644 --- a/pkg/cmd/cli/backup/create.go +++ b/pkg/cmd/cli/backup/create.go @@ -118,7 +118,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { backup := &api.Backup{ ObjectMeta: metav1.ObjectMeta{ - Namespace: api.DefaultNamespace, + Namespace: f.Namespace(), Name: o.Name, Labels: o.Labels.Data(), }, diff --git a/pkg/cmd/cli/backup/delete.go b/pkg/cmd/cli/backup/delete.go index a3bd59689..861d3bd75 100644 --- a/pkg/cmd/cli/backup/delete.go +++ b/pkg/cmd/cli/backup/delete.go @@ -23,7 +23,6 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - api "github.com/heptio/ark/pkg/apis/ark/v1" "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cmd" "github.com/heptio/ark/pkg/controller" @@ -55,7 +54,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { backupName := args[0] - err = arkClient.ArkV1().Backups(api.DefaultNamespace).Delete(backupName, nil) + err = arkClient.ArkV1().Backups(f.Namespace()).Delete(backupName, nil) cmd.CheckError(err) fmt.Printf("Backup %q deleted\n", backupName) diff --git a/pkg/cmd/cli/backup/describe.go b/pkg/cmd/cli/backup/describe.go index 2ea59a7a8..cb9f3f4d5 100644 --- a/pkg/cmd/cli/backup/describe.go +++ b/pkg/cmd/cli/backup/describe.go @@ -42,12 +42,12 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { if len(args) > 0 { backups = new(v1.BackupList) for _, name := range args { - backup, err := arkClient.Ark().Backups(v1.DefaultNamespace).Get(name, metav1.GetOptions{}) + backup, err := arkClient.Ark().Backups(f.Namespace()).Get(name, metav1.GetOptions{}) cmd.CheckError(err) backups.Items = append(backups.Items, *backup) } } else { - backups, err = arkClient.ArkV1().Backups(v1.DefaultNamespace).List(listOptions) + backups, err = arkClient.ArkV1().Backups(f.Namespace()).List(listOptions) cmd.CheckError(err) } diff --git a/pkg/cmd/cli/backup/download.go b/pkg/cmd/cli/backup/download.go index 229adb721..d353c97af 100644 --- a/pkg/cmd/cli/backup/download.go +++ b/pkg/cmd/cli/backup/download.go @@ -106,7 +106,7 @@ func (o *DownloadOptions) Run(c *cobra.Command, f client.Factory) error { } defer backupDest.Close() - err = downloadrequest.Stream(arkClient.ArkV1(), o.Name, v1.DownloadTargetKindBackupContents, backupDest, o.Timeout) + err = downloadrequest.Stream(arkClient.ArkV1(), f.Namespace(), o.Name, v1.DownloadTargetKindBackupContents, backupDest, o.Timeout) if err != nil { os.Remove(o.Output) cmd.CheckError(err) diff --git a/pkg/cmd/cli/backup/get.go b/pkg/cmd/cli/backup/get.go index a5b621641..ff2317a3a 100644 --- a/pkg/cmd/cli/backup/get.go +++ b/pkg/cmd/cli/backup/get.go @@ -44,12 +44,12 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { if len(args) > 0 { backups = new(api.BackupList) for _, name := range args { - backup, err := arkClient.Ark().Backups(api.DefaultNamespace).Get(name, metav1.GetOptions{}) + backup, err := arkClient.Ark().Backups(f.Namespace()).Get(name, metav1.GetOptions{}) cmd.CheckError(err) backups.Items = append(backups.Items, *backup) } } else { - backups, err = arkClient.ArkV1().Backups(api.DefaultNamespace).List(listOptions) + backups, err = arkClient.ArkV1().Backups(f.Namespace()).List(listOptions) cmd.CheckError(err) } diff --git a/pkg/cmd/cli/backup/logs.go b/pkg/cmd/cli/backup/logs.go index 8a0dd50c3..880fbc5ed 100644 --- a/pkg/cmd/cli/backup/logs.go +++ b/pkg/cmd/cli/backup/logs.go @@ -44,7 +44,7 @@ func NewLogsCommand(f client.Factory) *cobra.Command { arkClient, err := f.Client() cmd.CheckError(err) - err = downloadrequest.Stream(arkClient.ArkV1(), args[0], v1.DownloadTargetKindBackupLog, os.Stdout, timeout) + err = downloadrequest.Stream(arkClient.ArkV1(), f.Namespace(), args[0], v1.DownloadTargetKindBackupLog, os.Stdout, timeout) cmd.CheckError(err) }, } diff --git a/pkg/cmd/cli/config/get.go b/pkg/cmd/cli/client/client.go similarity index 68% rename from pkg/cmd/cli/config/get.go rename to pkg/cmd/cli/client/client.go index 954fe8fdc..c8d623227 100644 --- a/pkg/cmd/cli/config/get.go +++ b/pkg/cmd/cli/client/client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 the Heptio Ark contributors. +Copyright 2018 the Heptio Ark contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,16 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -package config +package client import ( + "github.com/heptio/ark/pkg/cmd/cli/client/config" "github.com/spf13/cobra" - - "github.com/heptio/ark/pkg/client" ) -func NewGetCommand(f client.Factory) *cobra.Command { - c := &cobra.Command{} +func NewCommand() *cobra.Command { + c := &cobra.Command{ + Use: "client", + Short: "Ark client related commands", + } + + c.AddCommand( + config.NewCommand(), + ) return c } diff --git a/pkg/cmd/cli/config/config.go b/pkg/cmd/cli/client/config/config.go similarity index 79% rename from pkg/cmd/cli/config/config.go rename to pkg/cmd/cli/client/config/config.go index d17858287..210529ce3 100644 --- a/pkg/cmd/cli/config/config.go +++ b/pkg/cmd/cli/client/config/config.go @@ -18,20 +18,17 @@ package config import ( "github.com/spf13/cobra" - - "github.com/heptio/ark/pkg/client" ) -func NewCommand(f client.Factory) *cobra.Command { +func NewCommand() *cobra.Command { c := &cobra.Command{ Use: "config", - Short: "Work with config", - Long: "Work with config", + Short: "Get and set client configuration file values", } c.AddCommand( - NewGetCommand(f), - NewSetCommand(f), + NewGetCommand(), + NewSetCommand(), ) return c diff --git a/pkg/cmd/cli/client/config/get.go b/pkg/cmd/cli/client/config/get.go new file mode 100644 index 000000000..ce6ccc658 --- /dev/null +++ b/pkg/cmd/cli/client/config/get.go @@ -0,0 +1,60 @@ +/* +Copyright 2018 the Heptio Ark contributors. + +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 config + +import ( + "fmt" + "sort" + + "github.com/heptio/ark/pkg/client" + "github.com/heptio/ark/pkg/cmd" + "github.com/spf13/cobra" +) + +func NewGetCommand() *cobra.Command { + c := &cobra.Command{ + Use: "get [KEY 1] [KEY 2] [...]", + Short: "Get client configuration file values", + Run: func(c *cobra.Command, args []string) { + config, err := client.LoadConfig() + cmd.CheckError(err) + + if len(args) == 0 { + keys := make([]string, 0, len(config)) + for key := range config { + keys = append(keys, key) + } + + sort.Strings(keys) + + for _, key := range keys { + fmt.Printf("%s: %s\n", key, config[key]) + } + } else { + for _, key := range args { + value, found := config[key] + if !found { + value = "" + } + fmt.Printf("%s: %s\n", key, value) + } + } + }, + } + + return c +} diff --git a/pkg/cmd/cli/client/config/set.go b/pkg/cmd/cli/client/config/set.go new file mode 100644 index 000000000..a95bbeba3 --- /dev/null +++ b/pkg/cmd/cli/client/config/set.go @@ -0,0 +1,62 @@ +/* +Copyright 2018 the Heptio Ark contributors. + +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 config + +import ( + "fmt" + "os" + "strings" + + "github.com/heptio/ark/pkg/client" + "github.com/heptio/ark/pkg/cmd" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func NewSetCommand() *cobra.Command { + c := &cobra.Command{ + Use: "set KEY=VALUE [KEY=VALUE]...", + Short: "Set client configuration file values", + Run: func(c *cobra.Command, args []string) { + if len(args) < 1 { + cmd.CheckError(errors.Errorf("At least one KEY=VALUE argument is required")) + } + + config, err := client.LoadConfig() + cmd.CheckError(err) + + for _, arg := range args { + pair := strings.Split(arg, "=") + if len(pair) != 2 { + fmt.Fprintf(os.Stderr, "WARNING: invalid KEY=VALUE: %q\n", arg) + continue + } + key, value := pair[0], pair[1] + + if value == "" { + delete(config, key) + } else { + config[key] = value + } + } + + cmd.CheckError(client.SaveConfig(config)) + }, + } + + return c +} diff --git a/pkg/cmd/cli/config/set.go b/pkg/cmd/cli/config/set.go deleted file mode 100644 index 61ab0852c..000000000 --- a/pkg/cmd/cli/config/set.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2017 the Heptio Ark contributors. - -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 config - -import ( - "github.com/spf13/cobra" - - "github.com/heptio/ark/pkg/client" -) - -func NewSetCommand(f client.Factory) *cobra.Command { - c := &cobra.Command{} - - return c -} diff --git a/pkg/cmd/cli/plugin/add.go b/pkg/cmd/cli/plugin/add.go index f278b24d3..b5c9c6a6a 100644 --- a/pkg/cmd/cli/plugin/add.go +++ b/pkg/cmd/cli/plugin/add.go @@ -30,7 +30,6 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" - ark "github.com/heptio/ark/pkg/apis/ark/v1" "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cmd" "github.com/heptio/ark/pkg/cmd/util/flag" @@ -61,7 +60,7 @@ func NewAddCommand(f client.Factory) *cobra.Command { cmd.CheckError(err) } - arkDeploy, err := kubeClient.AppsV1beta1().Deployments(ark.DefaultNamespace).Get(arkDeployment, metav1.GetOptions{}) + arkDeploy, err := kubeClient.AppsV1beta1().Deployments(f.Namespace()).Get(arkDeployment, metav1.GetOptions{}) if err != nil { cmd.CheckError(err) } @@ -131,7 +130,7 @@ func NewAddCommand(f client.Factory) *cobra.Command { patchBytes, err := strategicpatch.CreateTwoWayMergePatch(original, updated, v1beta1.Deployment{}) cmd.CheckError(err) - _, err = kubeClient.AppsV1beta1().Deployments(ark.DefaultNamespace).Patch(arkDeploy.Name, types.StrategicMergePatchType, patchBytes) + _, err = kubeClient.AppsV1beta1().Deployments(arkDeploy.Namespace).Patch(arkDeploy.Name, types.StrategicMergePatchType, patchBytes) cmd.CheckError(err) }, } diff --git a/pkg/cmd/cli/plugin/remove.go b/pkg/cmd/cli/plugin/remove.go index 2e608da09..3f3c3f15b 100644 --- a/pkg/cmd/cli/plugin/remove.go +++ b/pkg/cmd/cli/plugin/remove.go @@ -27,7 +27,6 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" - ark "github.com/heptio/ark/pkg/apis/ark/v1" "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cmd" ) @@ -46,7 +45,7 @@ func NewRemoveCommand(f client.Factory) *cobra.Command { cmd.CheckError(err) } - arkDeploy, err := kubeClient.AppsV1beta1().Deployments(ark.DefaultNamespace).Get(arkDeployment, metav1.GetOptions{}) + arkDeploy, err := kubeClient.AppsV1beta1().Deployments(f.Namespace()).Get(arkDeployment, metav1.GetOptions{}) if err != nil { cmd.CheckError(err) } @@ -78,7 +77,7 @@ func NewRemoveCommand(f client.Factory) *cobra.Command { patchBytes, err := strategicpatch.CreateTwoWayMergePatch(original, updated, v1beta1.Deployment{}) cmd.CheckError(err) - _, err = kubeClient.AppsV1beta1().Deployments(ark.DefaultNamespace).Patch(arkDeploy.Name, types.StrategicMergePatchType, patchBytes) + _, err = kubeClient.AppsV1beta1().Deployments(arkDeploy.Namespace).Patch(arkDeploy.Name, types.StrategicMergePatchType, patchBytes) cmd.CheckError(err) }, } diff --git a/pkg/cmd/cli/restore/create.go b/pkg/cmd/cli/restore/create.go index a1f0f345e..b52470b54 100644 --- a/pkg/cmd/cli/restore/create.go +++ b/pkg/cmd/cli/restore/create.go @@ -118,7 +118,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { restore := &api.Restore{ ObjectMeta: metav1.ObjectMeta{ - Namespace: api.DefaultNamespace, + Namespace: f.Namespace(), Name: fmt.Sprintf("%s-%s", o.BackupName, time.Now().Format("20060102150405")), Labels: o.Labels.Data(), }, diff --git a/pkg/cmd/cli/restore/delete.go b/pkg/cmd/cli/restore/delete.go index 958a6ae97..a390385ab 100644 --- a/pkg/cmd/cli/restore/delete.go +++ b/pkg/cmd/cli/restore/delete.go @@ -22,7 +22,6 @@ import ( "github.com/spf13/cobra" - api "github.com/heptio/ark/pkg/apis/ark/v1" "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cmd" ) @@ -42,7 +41,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { name := args[0] - err = arkClient.ArkV1().Restores(api.DefaultNamespace).Delete(name, nil) + err = arkClient.ArkV1().Restores(f.Namespace()).Delete(name, nil) cmd.CheckError(err) fmt.Printf("Restore %q deleted\n", name) diff --git a/pkg/cmd/cli/restore/describe.go b/pkg/cmd/cli/restore/describe.go index 769fdc6df..41572b936 100644 --- a/pkg/cmd/cli/restore/describe.go +++ b/pkg/cmd/cli/restore/describe.go @@ -42,12 +42,12 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { if len(args) > 0 { restores = new(api.RestoreList) for _, name := range args { - restore, err := arkClient.Ark().Restores(api.DefaultNamespace).Get(name, metav1.GetOptions{}) + restore, err := arkClient.Ark().Restores(f.Namespace()).Get(name, metav1.GetOptions{}) cmd.CheckError(err) restores.Items = append(restores.Items, *restore) } } else { - restores, err = arkClient.ArkV1().Restores(api.DefaultNamespace).List(listOptions) + restores, err = arkClient.ArkV1().Restores(f.Namespace()).List(listOptions) cmd.CheckError(err) } diff --git a/pkg/cmd/cli/restore/get.go b/pkg/cmd/cli/restore/get.go index 2007aa73b..bc55aba2f 100644 --- a/pkg/cmd/cli/restore/get.go +++ b/pkg/cmd/cli/restore/get.go @@ -44,12 +44,12 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { if len(args) > 0 { restores = new(api.RestoreList) for _, name := range args { - restore, err := arkClient.Ark().Restores(api.DefaultNamespace).Get(name, metav1.GetOptions{}) + restore, err := arkClient.Ark().Restores(f.Namespace()).Get(name, metav1.GetOptions{}) cmd.CheckError(err) restores.Items = append(restores.Items, *restore) } } else { - restores, err = arkClient.ArkV1().Restores(api.DefaultNamespace).List(listOptions) + restores, err = arkClient.ArkV1().Restores(f.Namespace()).List(listOptions) cmd.CheckError(err) } diff --git a/pkg/cmd/cli/restore/logs.go b/pkg/cmd/cli/restore/logs.go index f44959194..ec65112cd 100644 --- a/pkg/cmd/cli/restore/logs.go +++ b/pkg/cmd/cli/restore/logs.go @@ -44,7 +44,7 @@ func NewLogsCommand(f client.Factory) *cobra.Command { arkClient, err := f.Client() cmd.CheckError(err) - err = downloadrequest.Stream(arkClient.ArkV1(), args[0], v1.DownloadTargetKindRestoreLog, os.Stdout, timeout) + err = downloadrequest.Stream(arkClient.ArkV1(), f.Namespace(), args[0], v1.DownloadTargetKindRestoreLog, os.Stdout, timeout) cmd.CheckError(err) }, } diff --git a/pkg/cmd/cli/schedule/create.go b/pkg/cmd/cli/schedule/create.go index a33ae99eb..3de434307 100644 --- a/pkg/cmd/cli/schedule/create.go +++ b/pkg/cmd/cli/schedule/create.go @@ -93,7 +93,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { schedule := &api.Schedule{ ObjectMeta: metav1.ObjectMeta{ - Namespace: api.DefaultNamespace, + Namespace: f.Namespace(), Name: o.BackupOptions.Name, }, Spec: api.ScheduleSpec{ diff --git a/pkg/cmd/cli/schedule/delete.go b/pkg/cmd/cli/schedule/delete.go index 4f2dd59fa..de27a864d 100644 --- a/pkg/cmd/cli/schedule/delete.go +++ b/pkg/cmd/cli/schedule/delete.go @@ -22,7 +22,6 @@ import ( "github.com/spf13/cobra" - api "github.com/heptio/ark/pkg/apis/ark/v1" "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cmd" ) @@ -42,7 +41,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command { name := args[0] - err = arkClient.ArkV1().Schedules(api.DefaultNamespace).Delete(name, nil) + err = arkClient.ArkV1().Schedules(f.Namespace()).Delete(name, nil) cmd.CheckError(err) fmt.Printf("Schedule %q deleted\n", name) diff --git a/pkg/cmd/cli/schedule/describe.go b/pkg/cmd/cli/schedule/describe.go index c54c02662..b375b13a0 100644 --- a/pkg/cmd/cli/schedule/describe.go +++ b/pkg/cmd/cli/schedule/describe.go @@ -42,12 +42,12 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { if len(args) > 0 { schedules = new(v1.ScheduleList) for _, name := range args { - schedule, err := arkClient.Ark().Schedules(v1.DefaultNamespace).Get(name, metav1.GetOptions{}) + schedule, err := arkClient.Ark().Schedules(f.Namespace()).Get(name, metav1.GetOptions{}) cmd.CheckError(err) schedules.Items = append(schedules.Items, *schedule) } } else { - schedules, err = arkClient.ArkV1().Schedules(v1.DefaultNamespace).List(listOptions) + schedules, err = arkClient.ArkV1().Schedules(f.Namespace()).List(listOptions) cmd.CheckError(err) } diff --git a/pkg/cmd/cli/schedule/get.go b/pkg/cmd/cli/schedule/get.go index 7fa0f21d0..dc6e0ba98 100644 --- a/pkg/cmd/cli/schedule/get.go +++ b/pkg/cmd/cli/schedule/get.go @@ -44,12 +44,12 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { if len(args) > 0 { schedules = new(api.ScheduleList) for _, name := range args { - schedule, err := arkClient.Ark().Schedules(api.DefaultNamespace).Get(name, metav1.GetOptions{}) + schedule, err := arkClient.Ark().Schedules(f.Namespace()).Get(name, metav1.GetOptions{}) cmd.CheckError(err) schedules.Items = append(schedules.Items, *schedule) } } else { - schedules, err = arkClient.ArkV1().Schedules(api.DefaultNamespace).List(listOptions) + schedules, err = arkClient.ArkV1().Schedules(f.Namespace()).List(listOptions) cmd.CheckError(err) } diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 3aa794f97..534c8d9da 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -19,6 +19,7 @@ package server import ( "context" "fmt" + "io/ioutil" "reflect" "sort" "strings" @@ -29,6 +30,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -61,7 +63,6 @@ import ( func NewCommand() *cobra.Command { var ( - kubeconfig string sortedLogLevels = getSortedLogLevels() logLevelFlag = flag.NewEnum(logrus.InfoLevel.String(), sortedLogLevels...) ) @@ -86,7 +87,21 @@ func NewCommand() *cobra.Command { logger := newLogger(logLevel, &logging.ErrorLocationHook{}, &logging.LogLocationHook{}) logger.Infof("Starting Ark server %s", buildinfo.FormattedGitSHA()) - s, err := newServer(kubeconfig, fmt.Sprintf("%s-%s", c.Parent().Name(), c.Name()), logger) + // NOTE: the namespace flag is bound to ark's persistent flags when the root ark command + // creates the client Factory and binds the Factory's flags. We're not using a Factory here in + // the server because the Factory gets its basename set at creation time, and the basename is + // used to construct the user-agent for clients. Also, the Factory's Namespace() method uses + // the client config file to determine the appropriate namespace to use, and that isn't + // applicable to the server (it uses the method directly below instead). We could potentially + // add a SetBasename() method to the Factory, and tweak how Namespace() works, if we wanted to + // have the server use the Factory. + namespaceFlag := c.Flag("namespace") + if namespaceFlag == nil { + cmd.CheckError(errors.New("unable to look up namespace flag")) + } + namespace := getServerNamespace(namespaceFlag) + + s, err := newServer(namespace, fmt.Sprintf("%s-%s", c.Parent().Name(), c.Name()), logger) cmd.CheckError(err) @@ -95,11 +110,24 @@ func NewCommand() *cobra.Command { } command.Flags().Var(logLevelFlag, "log-level", fmt.Sprintf("the level at which to log. Valid values are %s.", strings.Join(sortedLogLevels, ", "))) - command.Flags().StringVar(&kubeconfig, "kubeconfig", "", "Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration") return command } +func getServerNamespace(namespaceFlag *pflag.Flag) string { + if namespaceFlag.Changed { + return namespaceFlag.Value.String() + } + + if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { + if ns := strings.TrimSpace(string(data)); len(ns) > 0 { + return ns + } + } + + return api.DefaultNamespace +} + func newLogger(level logrus.Level, hooks ...logrus.Hook) *logrus.Logger { logger := logrus.New() logger.Level = level @@ -132,6 +160,7 @@ func getSortedLogLevels() []string { } type server struct { + namespace string kubeClientConfig *rest.Config kubeClient kubernetes.Interface arkClient clientset.Interface @@ -146,8 +175,8 @@ type server struct { pluginManager plugin.Manager } -func newServer(kubeconfig, baseName string, logger *logrus.Logger) (*server, error) { - clientConfig, err := client.Config(kubeconfig, baseName) +func newServer(namespace, baseName string, logger *logrus.Logger) (*server, error) { + clientConfig, err := client.Config("", baseName) if err != nil { return nil, err } @@ -170,12 +199,13 @@ func newServer(kubeconfig, baseName string, logger *logrus.Logger) (*server, err ctx, cancelFunc := context.WithCancel(context.Background()) s := &server{ + namespace: namespace, kubeClientConfig: clientConfig, kubeClient: kubeClient, arkClient: arkClient, discoveryClient: arkClient.Discovery(), clientPool: dynamic.NewDynamicClientPool(clientConfig), - sharedInformerFactory: informers.NewSharedInformerFactory(arkClient, 0), + sharedInformerFactory: informers.NewFilteredSharedInformerFactory(arkClient, 0, namespace, nil), ctx: ctx, cancelFunc: cancelFunc, logger: logger, @@ -218,12 +248,12 @@ func (s *server) run() error { } func (s *server) ensureArkNamespace() error { - logContext := s.logger.WithField("namespace", api.DefaultNamespace) + logContext := s.logger.WithField("namespace", s.namespace) logContext.Info("Ensuring namespace exists for backups") defaultNamespace := v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ - Name: api.DefaultNamespace, + Name: s.namespace, }, } @@ -243,7 +273,7 @@ func (s *server) loadConfig() (*api.Config, error) { err error ) for { - config, err = s.arkClient.ArkV1().Configs(api.DefaultNamespace).Get("default", metav1.GetOptions{}) + config, err = s.arkClient.ArkV1().Configs(s.namespace).Get("default", metav1.GetOptions{}) if err == nil { break } @@ -459,6 +489,7 @@ func (s *server) runControllers(config *api.Config) error { }() scheduleController := controller.NewScheduleController( + s.namespace, s.arkClient.ArkV1(), s.arkClient.ArkV1(), s.sharedInformerFactory.Ark().V1().Schedules(), @@ -511,6 +542,7 @@ func (s *server) runControllers(config *api.Config) error { cmd.CheckError(err) restoreController := controller.NewRestoreController( + s.namespace, s.sharedInformerFactory.Ark().V1().Restores(), s.arkClient.ArkV1(), s.arkClient.ArkV1(), diff --git a/pkg/cmd/util/downloadrequest/downloadrequest.go b/pkg/cmd/util/downloadrequest/downloadrequest.go index d579563f0..2d2891303 100644 --- a/pkg/cmd/util/downloadrequest/downloadrequest.go +++ b/pkg/cmd/util/downloadrequest/downloadrequest.go @@ -33,10 +33,10 @@ import ( arkclientv1 "github.com/heptio/ark/pkg/generated/clientset/versioned/typed/ark/v1" ) -func Stream(client arkclientv1.DownloadRequestsGetter, name string, kind v1.DownloadTargetKind, w io.Writer, timeout time.Duration) error { +func Stream(client arkclientv1.DownloadRequestsGetter, namespace, name string, kind v1.DownloadTargetKind, w io.Writer, timeout time.Duration) error { req := &v1.DownloadRequest{ ObjectMeta: metav1.ObjectMeta{ - Namespace: v1.DefaultNamespace, + Namespace: namespace, Name: fmt.Sprintf("%s-%s", name, time.Now().Format("20060102150405")), }, Spec: v1.DownloadRequestSpec{ @@ -47,18 +47,19 @@ func Stream(client arkclientv1.DownloadRequestsGetter, name string, kind v1.Down }, } - req, err := client.DownloadRequests(v1.DefaultNamespace).Create(req) + req, err := client.DownloadRequests(namespace).Create(req) if err != nil { return errors.WithStack(err) } - defer client.DownloadRequests(v1.DefaultNamespace).Delete(req.Name, nil) + defer client.DownloadRequests(namespace).Delete(req.Name, nil) listOptions := metav1.ListOptions{ - //TODO: once kube-apiserver http://issue.k8s.io/51046 is fixed, uncomment + // TODO: once the minimum supported Kubernetes version is v1.9.0, uncomment the following line. + // See http://issue.k8s.io/51046 for details. //FieldSelector: "metadata.name=" + req.Name ResourceVersion: req.ResourceVersion, } - watcher, err := client.DownloadRequests(v1.DefaultNamespace).Watch(listOptions) + watcher, err := client.DownloadRequests(namespace).Watch(listOptions) if err != nil { return errors.WithStack(err) } @@ -78,6 +79,8 @@ Loop: return errors.Errorf("unexpected type %T", e.Object) } + // TODO: once the minimum supported Kubernetes version is v1.9.0, remove the following check. + // See http://issue.k8s.io/51046 for details. if updated.Name != req.Name { continue } diff --git a/pkg/cmd/util/downloadrequest/downloadrequest_test.go b/pkg/cmd/util/downloadrequest/downloadrequest_test.go index 4b85d02d5..0266a6a76 100644 --- a/pkg/cmd/util/downloadrequest/downloadrequest_test.go +++ b/pkg/cmd/util/downloadrequest/downloadrequest_test.go @@ -150,7 +150,7 @@ func TestStream(t *testing.T) { output := new(bytes.Buffer) errCh := make(chan error) go func() { - err := Stream(client.ArkV1(), "name", test.kind, output, timeout) + err := Stream(client.ArkV1(), "namespace", "name", test.kind, output, timeout) errCh <- err }() diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go index ab6ad304e..6251de411 100644 --- a/pkg/cmd/util/output/restore_describer.go +++ b/pkg/cmd/util/output/restore_describer.go @@ -108,7 +108,7 @@ func describeRestoreResults(d *Describer, restore *v1.Restore, arkClient clients var buf bytes.Buffer var resultMap map[string]v1.RestoreResult - if err := downloadrequest.Stream(arkClient.ArkV1(), restore.Name, v1.DownloadTargetKindRestoreResults, &buf, 30*time.Second); err != nil { + if err := downloadrequest.Stream(arkClient.ArkV1(), restore.Namespace, restore.Name, v1.DownloadTargetKindRestoreResults, &buf, 30*time.Second); err != nil { d.Printf("Warnings:\t\n\nErrors:\t\n", err, err) return } diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index 031628dcf..5b1a4a510 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -297,7 +297,7 @@ func patchBackup(original, updated *api.Backup, client arkv1client.BackupsGetter return nil, errors.Wrap(err, "error creating two-way merge patch for backup") } - res, err := client.Backups(api.DefaultNamespace).Patch(original.Name, types.MergePatchType, patchBytes) + res, err := client.Backups(original.Namespace).Patch(original.Name, types.MergePatchType, patchBytes) if err != nil { return nil, errors.Wrap(err, "error patching backup") } diff --git a/pkg/controller/download_request_controller.go b/pkg/controller/download_request_controller.go index 003483e37..0634d96dc 100644 --- a/pkg/controller/download_request_controller.go +++ b/pkg/controller/download_request_controller.go @@ -276,7 +276,7 @@ func patchDownloadRequest(original, updated *v1.DownloadRequest, client arkv1cli return nil, errors.Wrap(err, "error creating two-way merge patch for download request") } - res, err := client.DownloadRequests(v1.DefaultNamespace).Patch(original.Name, types.MergePatchType, patchBytes) + res, err := client.DownloadRequests(original.Namespace).Patch(original.Name, types.MergePatchType, patchBytes) if err != nil { return nil, errors.Wrap(err, "error patching download request") } diff --git a/pkg/controller/restore_controller.go b/pkg/controller/restore_controller.go index 8524943fb..804d2b467 100644 --- a/pkg/controller/restore_controller.go +++ b/pkg/controller/restore_controller.go @@ -54,6 +54,7 @@ import ( var nonRestorableResources = []string{"nodes"} type restoreController struct { + namespace string restoreClient arkv1client.RestoresGetter backupClient arkv1client.BackupsGetter restorer restore.Restorer @@ -71,6 +72,7 @@ type restoreController struct { } func NewRestoreController( + namespace string, restoreInformer informers.RestoreInformer, restoreClient arkv1client.RestoresGetter, backupClient arkv1client.BackupsGetter, @@ -83,6 +85,7 @@ func NewRestoreController( pluginManager plugin.Manager, ) Interface { c := &restoreController{ + namespace: namespace, restoreClient: restoreClient, backupClient: backupClient, restorer: restorer, @@ -319,7 +322,7 @@ func (controller *restoreController) getValidationErrors(itm *api.Restore) []str } func (controller *restoreController) fetchBackup(bucket, name string) (*api.Backup, error) { - backup, err := controller.backupLister.Backups(api.DefaultNamespace).Get(name) + backup, err := controller.backupLister.Backups(controller.namespace).Get(name) if err == nil { return backup, nil } @@ -338,8 +341,10 @@ func (controller *restoreController) fetchBackup(bucket, name string) (*api.Back // ResourceVersion needs to be cleared in order to create the object in the API backup.ResourceVersion = "" + // Clear out the namespace too, just in case + backup.Namespace = "" - created, createErr := controller.backupClient.Backups(api.DefaultNamespace).Create(backup) + created, createErr := controller.backupClient.Backups(controller.namespace).Create(backup) if createErr != nil { logContext.WithError(errors.WithStack(createErr)).Error("Unable to create API object for Backup") } else { @@ -495,7 +500,7 @@ func patchRestore(original, updated *api.Restore, client arkv1client.RestoresGet return nil, errors.Wrap(err, "error creating two-way merge patch for restore") } - res, err := client.Restores(api.DefaultNamespace).Patch(original.Name, types.MergePatchType, patchBytes) + res, err := client.Restores(original.Namespace).Patch(original.Name, types.MergePatchType, patchBytes) if err != nil { return nil, errors.Wrap(err, "error patching restore") } diff --git a/pkg/controller/restore_controller_test.go b/pkg/controller/restore_controller_test.go index 40ff8c2bc..0462a55ac 100644 --- a/pkg/controller/restore_controller_test.go +++ b/pkg/controller/restore_controller_test.go @@ -82,6 +82,7 @@ func TestFetchBackup(t *testing.T) { ) c := NewRestoreController( + api.DefaultNamespace, sharedInformers.Ark().V1().Restores(), client.ArkV1(), client.ArkV1(), @@ -249,6 +250,7 @@ func TestProcessRestore(t *testing.T) { defer backupSvc.AssertExpectations(t) c := NewRestoreController( + api.DefaultNamespace, sharedInformers.Ark().V1().Restores(), client.ArkV1(), client.ArkV1(), diff --git a/pkg/controller/schedule_controller.go b/pkg/controller/schedule_controller.go index 497900a44..4a1da386d 100644 --- a/pkg/controller/schedule_controller.go +++ b/pkg/controller/schedule_controller.go @@ -45,6 +45,7 @@ import ( ) type scheduleController struct { + namespace string schedulesClient arkv1client.SchedulesGetter backupsClient arkv1client.BackupsGetter schedulesLister listers.ScheduleLister @@ -57,6 +58,7 @@ type scheduleController struct { } func NewScheduleController( + namespace string, schedulesClient arkv1client.SchedulesGetter, backupsClient arkv1client.BackupsGetter, schedulesInformer informers.ScheduleInformer, @@ -69,6 +71,7 @@ func NewScheduleController( } c := &scheduleController{ + namespace: namespace, schedulesClient: schedulesClient, backupsClient: backupsClient, schedulesLister: schedulesInformer.Lister(), @@ -153,7 +156,7 @@ func (controller *scheduleController) Run(ctx context.Context, numWorkers int) e } func (controller *scheduleController) enqueueAllEnabledSchedules() { - schedules, err := controller.schedulesLister.Schedules(api.DefaultNamespace).List(labels.NewSelector()) + schedules, err := controller.schedulesLister.Schedules(controller.namespace).List(labels.NewSelector()) if err != nil { controller.logger.WithError(errors.WithStack(err)).Error("Error listing Schedules") return @@ -388,7 +391,7 @@ func patchSchedule(original, updated *api.Schedule, client arkv1client.Schedules return nil, errors.Wrap(err, "error creating two-way merge patch for schedule") } - res, err := client.Schedules(api.DefaultNamespace).Patch(original.Name, types.MergePatchType, patchBytes) + res, err := client.Schedules(original.Namespace).Patch(original.Name, types.MergePatchType, patchBytes) if err != nil { return nil, errors.Wrap(err, "error patching schedule") } diff --git a/pkg/controller/schedule_controller_test.go b/pkg/controller/schedule_controller_test.go index 2bbae2ce7..cdd90f398 100644 --- a/pkg/controller/schedule_controller_test.go +++ b/pkg/controller/schedule_controller_test.go @@ -124,6 +124,7 @@ func TestProcessSchedule(t *testing.T) { ) c := NewScheduleController( + "namespace", client.ArkV1(), client.ArkV1(), sharedInformers.Ark().V1().Schedules(),