diff --git a/pkg/client/client.go b/pkg/client/client.go index 331b786f2..dccdc05fc 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -27,20 +27,15 @@ import ( "github.com/vmware-tanzu/velero/pkg/buildinfo" ) -func buildConfigFromFlags(context, kubeconfigPath string, precedence []string) (*rest.Config, error) { - return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath, Precedence: precedence}, - &clientcmd.ConfigOverrides{ - CurrentContext: context, - }).ClientConfig() -} - // Config returns a *rest.Config, using either the kubeconfig (if specified) or an in-cluster // configuration. func Config(kubeconfig, kubecontext, baseName string, qps float32, burst int) (*rest.Config, error) { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() loadingRules.ExplicitPath = kubeconfig - clientConfig, err := buildConfigFromFlags(kubecontext, kubeconfig, loadingRules.Precedence) + configOverrides := &clientcmd.ConfigOverrides{CurrentContext: kubecontext} + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) + + clientConfig, err := kubeConfig.ClientConfig() if err != nil { return nil, errors.Wrap(err, "error finding Kubernetes API server config in --kubeconfig, $KUBECONFIG, or in-cluster configuration") } diff --git a/pkg/client/factory.go b/pkg/client/factory.go index 3a0933af7..aa5119c26 100644 --- a/pkg/client/factory.go +++ b/pkg/client/factory.go @@ -77,11 +77,10 @@ type factory struct { } // NewFactory returns a Factory. -func NewFactory(baseName, kubecontext string, config VeleroConfig) Factory { +func NewFactory(baseName string, config VeleroConfig) Factory { f := &factory{ - flags: pflag.NewFlagSet("", pflag.ContinueOnError), - baseName: baseName, - kubecontext: kubecontext, + flags: pflag.NewFlagSet("", pflag.ContinueOnError), + baseName: baseName, } f.namespace = os.Getenv("VELERO_NAMESPACE") @@ -97,7 +96,7 @@ func NewFactory(baseName, kubecontext string, config VeleroConfig) Factory { 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 Velero should operate") - //f.flags.StringVar(&f.kubecontext, "kubecontext", "", "The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)") + f.flags.StringVar(&f.kubecontext, "kubecontext", "", "The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)") return f } @@ -128,6 +127,7 @@ func (f *factory) KubeClient() (kubernetes.Interface, error) { return nil, err } kubeClient, err := kubernetes.NewForConfig(clientConfig) + if err != nil { return nil, errors.WithStack(err) } diff --git a/pkg/client/factory_test.go b/pkg/client/factory_test.go index 1ea28cb87..33b04231a 100644 --- a/pkg/client/factory_test.go +++ b/pkg/client/factory_test.go @@ -31,14 +31,14 @@ func TestFactory(t *testing.T) { // Env variable should set the namespace if no config or argument are used os.Setenv("VELERO_NAMESPACE", "env-velero") - f := NewFactory("velero", "", make(map[string]interface{})) + f := NewFactory("velero", make(map[string]interface{})) assert.Equal(t, "env-velero", f.Namespace()) os.Unsetenv("VELERO_NAMESPACE") // Argument should change the namespace - f = NewFactory("velero", "", make(map[string]interface{})) + f = NewFactory("velero", make(map[string]interface{})) s := "flag-velero" flags := new(pflag.FlagSet) @@ -50,7 +50,7 @@ func TestFactory(t *testing.T) { // An argument overrides the env variable if both are set. os.Setenv("VELERO_NAMESPACE", "env-velero") - f = NewFactory("velero", "", make(map[string]interface{})) + f = NewFactory("velero", make(map[string]interface{})) flags = new(pflag.FlagSet) f.BindFlags(flags) diff --git a/pkg/cmd/velero/velero.go b/pkg/cmd/velero/velero.go index 982761d2a..b249416e1 100644 --- a/pkg/cmd/velero/velero.go +++ b/pkg/cmd/velero/velero.go @@ -91,7 +91,7 @@ operations can also be performed as 'velero backup get' and 'velero schedule cre }, } - f := client.NewFactory(name, "", config) + f := client.NewFactory(name, config) f.BindFlags(c.PersistentFlags()) // Bind features directly to the root command so it's available to all callers. diff --git a/pkg/restore/prioritize_group_version.go b/pkg/restore/prioritize_group_version.go index d4ad9331c..e3a01c03d 100644 --- a/pkg/restore/prioritize_group_version.go +++ b/pkg/restore/prioritize_group_version.go @@ -214,7 +214,7 @@ func userPriorityConfigMap() (*corev1.ConfigMap, error) { return nil, errors.Wrap(err, "reading client config file") } - fc := client.NewFactory("APIGroupVersionsRestore", "", cfg) + fc := client.NewFactory("APIGroupVersionsRestore", cfg) kc, err := fc.KubeClient() if err != nil { diff --git a/test/e2e/pkg/client/auth_providers.go b/test/e2e/pkg/client/auth_providers.go new file mode 100644 index 000000000..c12f842c1 --- /dev/null +++ b/test/e2e/pkg/client/auth_providers.go @@ -0,0 +1,25 @@ +/* +Copyright 2017 the Velero 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 + +// Make sure we import the client-go auth provider plugins. + +import ( + _ "k8s.io/client-go/plugin/pkg/client/auth/azure" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" +) diff --git a/test/e2e/pkg/client/client.go b/test/e2e/pkg/client/client.go new file mode 100644 index 000000000..331b786f2 --- /dev/null +++ b/test/e2e/pkg/client/client.go @@ -0,0 +1,70 @@ +/* +Copyright 2017, 2019 the Velero 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 ( + "fmt" + "runtime" + + "github.com/pkg/errors" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "github.com/vmware-tanzu/velero/pkg/buildinfo" +) + +func buildConfigFromFlags(context, kubeconfigPath string, precedence []string) (*rest.Config, error) { + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath, Precedence: precedence}, + &clientcmd.ConfigOverrides{ + CurrentContext: context, + }).ClientConfig() +} + +// Config returns a *rest.Config, using either the kubeconfig (if specified) or an in-cluster +// configuration. +func Config(kubeconfig, kubecontext, baseName string, qps float32, burst int) (*rest.Config, error) { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.ExplicitPath = kubeconfig + clientConfig, err := buildConfigFromFlags(kubecontext, kubeconfig, loadingRules.Precedence) + if err != nil { + return nil, errors.Wrap(err, "error finding Kubernetes API server config in --kubeconfig, $KUBECONFIG, or in-cluster configuration") + } + + if qps > 0.0 { + clientConfig.QPS = qps + } + if burst > 0 { + clientConfig.Burst = burst + } + + clientConfig.UserAgent = buildUserAgent( + baseName, + buildinfo.Version, + buildinfo.FormattedGitSHA(), + runtime.GOOS, + runtime.GOARCH, + ) + + return clientConfig, nil +} + +// buildUserAgent builds a User-Agent string from given args. +func buildUserAgent(command, version, formattedSha, os, arch string) string { + return fmt.Sprintf( + "%s/%s (%s/%s) %s", command, version, os, arch, formattedSha) +} diff --git a/test/e2e/pkg/client/client_test.go b/test/e2e/pkg/client/client_test.go new file mode 100644 index 000000000..e4f6f6a53 --- /dev/null +++ b/test/e2e/pkg/client/client_test.go @@ -0,0 +1,51 @@ +/* +Copyright 2018 the Velero 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 ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildUserAgent(t *testing.T) { + tests := []struct { + name string + command string + os string + arch string + gitSha string + version string + expected string + }{ + { + name: "Test general interpolation in correct order", + command: "velero", + os: "darwin", + arch: "amd64", + gitSha: "abc123", + version: "v0.1.1", + expected: "velero/v0.1.1 (darwin/amd64) abc123", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resp := buildUserAgent(test.command, test.version, test.gitSha, test.os, test.arch) + assert.Equal(t, resp, test.expected) + }) + } +} diff --git a/test/e2e/pkg/client/config.go b/test/e2e/pkg/client/config.go new file mode 100644 index 000000000..2302ca9c9 --- /dev/null +++ b/test/e2e/pkg/client/config.go @@ -0,0 +1,151 @@ +/* +Copyright 2021 the Velero 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" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +const ( + ConfigKeyNamespace = "namespace" + ConfigKeyFeatures = "features" + ConfigKeyCACert = "cacert" + ConfigKeyColorized = "colorized" +) + +// VeleroConfig is a map of strings to interface{} for deserializing Velero client config options. +// The alias is a way to attach type-asserting convenience methods. +type VeleroConfig map[string]interface{} + +// LoadConfig loads the Velero client configuration file and returns it as a VeleroConfig. If the +// file does not exist, an empty map is returned. +func LoadConfig() (VeleroConfig, error) { + fileName := configFileName() + + _, err := os.Stat(fileName) + if os.IsNotExist(err) { + // If the file isn't there, just return an empty map + return VeleroConfig{}, 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 VeleroConfig + 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 Velero client configuration file. +func SaveConfig(config VeleroConfig) error { + fileName := configFileName() + + // Try to make the directory in case it doesn't exist + dir := filepath.Dir(fileName) + if err := os.MkdirAll(dir, 0700); err != nil { + return errors.WithStack(err) + } + + configFile, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return errors.WithStack(err) + } + defer configFile.Close() + + return json.NewEncoder(configFile).Encode(&config) +} + +func (c VeleroConfig) Namespace() string { + val, ok := c[ConfigKeyNamespace] + if !ok { + return "" + } + + ns, ok := val.(string) + if !ok { + return "" + } + + return ns +} + +func (c VeleroConfig) Features() []string { + val, ok := c[ConfigKeyFeatures] + if !ok { + return []string{} + } + + features, ok := val.(string) + if !ok { + return []string{} + } + + return strings.Split(features, ",") +} + +func (c VeleroConfig) Colorized() bool { + val, ok := c[ConfigKeyColorized] + if !ok { + return true + } + + valString, ok := val.(string) + if !ok { + return true + } + + colorized, err := strconv.ParseBool(valString) + if err != nil { + return true + } + + return colorized + +} + +func (c VeleroConfig) CACertFile() string { + val, ok := c[ConfigKeyCACert] + if !ok { + return "" + } + caCertFile, ok := val.(string) + if !ok { + return "" + } + + return caCertFile +} + +func configFileName() string { + return filepath.Join(os.Getenv("HOME"), ".config", "velero", "config.json") +} diff --git a/test/e2e/pkg/client/config_test.go b/test/e2e/pkg/client/config_test.go new file mode 100644 index 000000000..e78a9b116 --- /dev/null +++ b/test/e2e/pkg/client/config_test.go @@ -0,0 +1,34 @@ +/* +Copyright 2021 the Velero 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 ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVeleroConfig(t *testing.T) { + c := VeleroConfig{ + "namespace": "foo", + "features": "feature1,feature2", + } + + assert.Equal(t, "foo", c.Namespace()) + assert.Equal(t, []string{"feature1", "feature2"}, c.Features()) + assert.Equal(t, true, c.Colorized()) +} diff --git a/test/e2e/pkg/client/dynamic.go b/test/e2e/pkg/client/dynamic.go new file mode 100644 index 000000000..8fcfab107 --- /dev/null +++ b/test/e2e/pkg/client/dynamic.go @@ -0,0 +1,141 @@ +/* +Copyright 2017 the Velero 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 ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" +) + +// DynamicFactory contains methods for retrieving dynamic clients for GroupVersionResources and +// GroupVersionKinds. +type DynamicFactory interface { + // ClientForGroupVersionResource returns a Dynamic client for the given group/version + // and resource for the given namespace. + ClientForGroupVersionResource(gv schema.GroupVersion, resource metav1.APIResource, namespace string) (Dynamic, error) +} + +// dynamicFactory implements DynamicFactory. +type dynamicFactory struct { + dynamicClient dynamic.Interface +} + +// NewDynamicFactory returns a new ClientPool-based dynamic factory. +func NewDynamicFactory(dynamicClient dynamic.Interface) DynamicFactory { + return &dynamicFactory{dynamicClient: dynamicClient} +} + +func (f *dynamicFactory) ClientForGroupVersionResource(gv schema.GroupVersion, resource metav1.APIResource, namespace string) (Dynamic, error) { + return &dynamicResourceClient{ + resourceClient: f.dynamicClient.Resource(gv.WithResource(resource.Name)).Namespace(namespace), + }, nil +} + +// Creator creates an object. +type Creator interface { + // Create creates an object. + Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) +} + +// Lister lists objects. +type Lister interface { + // List lists all the objects of a given resource. + List(metav1.ListOptions) (*unstructured.UnstructuredList, error) +} + +// Watcher watches objects. +type Watcher interface { + // Watch watches for changes to objects of a given resource. + Watch(metav1.ListOptions) (watch.Interface, error) +} + +// Getter gets an object. +type Getter interface { + // Get fetches an object by name. + Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) +} + +// Patcher patches an object. +type Patcher interface { + //Patch patches the named object using the provided patch bytes, which are expected to be in JSON merge patch format. The patched object is returned. + + Patch(name string, data []byte) (*unstructured.Unstructured, error) +} + +// Deletor deletes an object. +type Deletor interface { + //Patch patches the named object using the provided patch bytes, which are expected to be in JSON merge patch format. The patched object is returned. + + Delete(name string, opts metav1.DeleteOptions) error +} + +// StatusUpdater updates status field of a object +type StatusUpdater interface { + UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) +} + +// Dynamic contains client methods that Velero needs for backing up and restoring resources. +type Dynamic interface { + Creator + Lister + Watcher + Getter + Patcher + Deletor + StatusUpdater +} + +// dynamicResourceClient implements Dynamic. +type dynamicResourceClient struct { + resourceClient dynamic.ResourceInterface +} + +var _ Dynamic = &dynamicResourceClient{} + +func (d *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return d.resourceClient.Create(context.TODO(), obj, metav1.CreateOptions{}) +} + +func (d *dynamicResourceClient) List(options metav1.ListOptions) (*unstructured.UnstructuredList, error) { + return d.resourceClient.List(context.TODO(), options) +} + +func (d *dynamicResourceClient) Watch(options metav1.ListOptions) (watch.Interface, error) { + return d.resourceClient.Watch(context.TODO(), options) +} + +func (d *dynamicResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { + return d.resourceClient.Get(context.TODO(), name, opts) +} + +func (d *dynamicResourceClient) Patch(name string, data []byte) (*unstructured.Unstructured, error) { + return d.resourceClient.Patch(context.TODO(), name, types.MergePatchType, data, metav1.PatchOptions{}) +} + +func (d *dynamicResourceClient) Delete(name string, opts metav1.DeleteOptions) error { + return d.resourceClient.Delete(context.TODO(), name, opts) +} + +func (d *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) { + return d.resourceClient.UpdateStatus(context.TODO(), obj, opts) +} diff --git a/test/e2e/pkg/client/factory.go b/test/e2e/pkg/client/factory.go new file mode 100644 index 000000000..3a0933af7 --- /dev/null +++ b/test/e2e/pkg/client/factory.go @@ -0,0 +1,185 @@ +/* +Copyright 2017, 2019 the Velero 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 ( + "os" + + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + k8scheme "k8s.io/client-go/kubernetes/scheme" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/pkg/errors" + "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" +) + +// Factory knows how to create a VeleroClient and Kubernetes client. +type Factory interface { + // BindFlags binds common flags (--kubeconfig, --namespace) to the passed-in FlagSet. + BindFlags(flags *pflag.FlagSet) + // Client returns a VeleroClient. It uses the following priority to specify the cluster + // 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. + KubeClient() (kubernetes.Interface, error) + // DynamicClient returns a Kubernetes dynamic client. It uses the following priority to specify the cluster + // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. + DynamicClient() (dynamic.Interface, error) + // KubebuilderClient returns a client for the controller runtime framework. It adds Kubernetes and Velero + // types to its scheme. It uses the following priority to specify the cluster + // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. + KubebuilderClient() (kbclient.Client, error) + // SetBasename changes the basename for an already-constructed client. + // This is useful for generating clients that require a different user-agent string below the root `velero` + // command, such as the server subcommand. + SetBasename(string) + // SetClientQPS sets the Queries Per Second for a client. + SetClientQPS(float32) + // SetClientBurst sets the Burst for a client. + SetClientBurst(int) + // ClientConfig returns a rest.Config struct used for client-go clients. + ClientConfig() (*rest.Config, error) + // Namespace returns the namespace which the Factory will create clients for. + Namespace() string +} + +type factory struct { + flags *pflag.FlagSet + kubeconfig string + kubecontext string + baseName string + namespace string + clientQPS float32 + clientBurst int +} + +// NewFactory returns a Factory. +func NewFactory(baseName, kubecontext string, config VeleroConfig) Factory { + f := &factory{ + flags: pflag.NewFlagSet("", pflag.ContinueOnError), + baseName: baseName, + kubecontext: kubecontext, + } + + f.namespace = os.Getenv("VELERO_NAMESPACE") + if config.Namespace() != "" { + f.namespace = config.Namespace() + } + + // We didn't get the namespace via env var or config file, so use the default. + // Command line flags will override when BindFlags is called. + if f.namespace == "" { + f.namespace = velerov1api.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 Velero should operate") + //f.flags.StringVar(&f.kubecontext, "kubecontext", "", "The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)") + return f +} + +func (f *factory) BindFlags(flags *pflag.FlagSet) { + flags.AddFlagSet(f.flags) +} + +func (f *factory) ClientConfig() (*rest.Config, error) { + return Config(f.kubeconfig, f.kubecontext, f.baseName, f.clientQPS, f.clientBurst) +} + +func (f *factory) Client() (clientset.Interface, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + + veleroClient, err := clientset.NewForConfig(clientConfig) + if err != nil { + return nil, errors.WithStack(err) + } + return veleroClient, nil +} + +func (f *factory) KubeClient() (kubernetes.Interface, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + kubeClient, err := kubernetes.NewForConfig(clientConfig) + if err != nil { + return nil, errors.WithStack(err) + } + return kubeClient, nil +} + +func (f *factory) DynamicClient() (dynamic.Interface, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + dynamicClient, err := dynamic.NewForConfig(clientConfig) + if err != nil { + return nil, errors.WithStack(err) + } + return dynamicClient, nil +} + +func (f *factory) KubebuilderClient() (kbclient.Client, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + + scheme := runtime.NewScheme() + velerov1api.AddToScheme(scheme) + k8scheme.AddToScheme(scheme) + apiextv1beta1.AddToScheme(scheme) + apiextv1.AddToScheme(scheme) + kubebuilderClient, err := kbclient.New(clientConfig, kbclient.Options{ + Scheme: scheme, + }) + + if err != nil { + return nil, err + } + + return kubebuilderClient, nil +} + +func (f *factory) SetBasename(name string) { + f.baseName = name +} + +func (f *factory) SetClientQPS(qps float32) { + f.clientQPS = qps +} + +func (f *factory) SetClientBurst(burst int) { + f.clientBurst = burst +} + +func (f *factory) Namespace() string { + return f.namespace +} diff --git a/test/e2e/pkg/client/factory_test.go b/test/e2e/pkg/client/factory_test.go new file mode 100644 index 000000000..1ea28cb87 --- /dev/null +++ b/test/e2e/pkg/client/factory_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2019 the Velero 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 ( + "os" + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" +) + +// TestFactory tests the client.Factory interface. +func TestFactory(t *testing.T) { + // Velero client configuration is currently omitted due to requiring a + // test filesystem in pkg/test. This causes an import cycle as pkg/test + // uses pkg/client's interfaces to implement fakes + + // Env variable should set the namespace if no config or argument are used + os.Setenv("VELERO_NAMESPACE", "env-velero") + f := NewFactory("velero", "", make(map[string]interface{})) + + assert.Equal(t, "env-velero", f.Namespace()) + + os.Unsetenv("VELERO_NAMESPACE") + + // Argument should change the namespace + f = NewFactory("velero", "", make(map[string]interface{})) + s := "flag-velero" + flags := new(pflag.FlagSet) + + f.BindFlags(flags) + + flags.Parse([]string{"--namespace", s}) + + assert.Equal(t, s, f.Namespace()) + + // An argument overrides the env variable if both are set. + os.Setenv("VELERO_NAMESPACE", "env-velero") + f = NewFactory("velero", "", make(map[string]interface{})) + flags = new(pflag.FlagSet) + + f.BindFlags(flags) + flags.Parse([]string{"--namespace", s}) + assert.Equal(t, s, f.Namespace()) + + os.Unsetenv("VELERO_NAMESPACE") +} diff --git a/test/e2e/util/k8s/client.go b/test/e2e/util/k8s/client.go index 4601bae8f..0db9d3aaa 100644 --- a/test/e2e/util/k8s/client.go +++ b/test/e2e/util/k8s/client.go @@ -20,7 +20,7 @@ import ( "k8s.io/client-go/kubernetes" kbclient "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/vmware-tanzu/velero/pkg/client" + "github.com/vmware-tanzu/velero/test/e2e/pkg/client" ) // TestClient contains different API clients that are in use throughout diff --git a/test/e2e/util/kibishii/kibishii_utils.go b/test/e2e/util/kibishii/kibishii_utils.go index de5f2cf69..950599735 100644 --- a/test/e2e/util/kibishii/kibishii_utils.go +++ b/test/e2e/util/kibishii/kibishii_utils.go @@ -138,6 +138,8 @@ func RunKibishiiTests(veleroCfg VeleroConfig, backupName, restoreName, backupLoc // return errors.Wrapf(err, "Error get vSphere snapshot uploads") // } } else { + // wait for a period to confirm no snapshots exist for the backup + time.Sleep(5 * time.Minute) if strings.EqualFold(veleroFeatures, "EnableCSI") { _, err = GetSnapshotCheckPoint(*veleroCfg.ClientToInstallVelero, veleroCfg, 0, kibishiiNamespace, backupName, KibishiiPodNameList)