diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000000000000000000000000000000000000..dc5caf361dfc4f17754835a7b35d9a1e204e6887 --- /dev/null +++ b/client/client.go @@ -0,0 +1,207 @@ +// Copyright 2017 The ksonnet authors +// +// +// 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" + "os" + "reflect" + + "github.com/ksonnet/ksonnet/metadata" + str "github.com/ksonnet/ksonnet/strings" + "github.com/ksonnet/ksonnet/utils" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/tools/clientcmd" +) + +// Config is a wrapper around client-go's ClientConfig +type Config struct { + Overrides *clientcmd.ConfigOverrides + LoadingRules *clientcmd.ClientConfigLoadingRules + + Config clientcmd.ClientConfig +} + +// NewClientConfig initializes a new client.Config with the provided loading rules and overrides. +func NewClientConfig(overrides clientcmd.ConfigOverrides, loadingRules clientcmd.ClientConfigLoadingRules) *Config { + config := clientcmd.NewInteractiveDeferredLoadingClientConfig(&loadingRules, &overrides, os.Stdin) + return &Config{Overrides: &overrides, LoadingRules: &loadingRules, Config: config} +} + +// NewDefaultClientConfig initializes a new ClientConfig with default loading rules and no overrides. +func NewDefaultClientConfig() *Config { + overrides := clientcmd.ConfigOverrides{} + loadingRules := *clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig + config := clientcmd.NewInteractiveDeferredLoadingClientConfig(&loadingRules, &overrides, os.Stdin) + + return &Config{Overrides: &overrides, LoadingRules: &loadingRules, Config: config} +} + +// InitClient initializes a new ClientConfig given the specified environment +// spec and returns the ClientPool, DiscoveryInterface, and namespace. +func InitClient(env string) (dynamic.ClientPool, discovery.DiscoveryInterface, string, error) { + clientConfig := NewDefaultClientConfig() + return clientConfig.RestClient(&env) +} + +// Namespace returns the namespace for the provided ClientConfig. +func (c *Config) Namespace() (string, error) { + ns, _, err := c.Config.Namespace() + return ns, err +} + +// RestClient returns the ClientPool, DiscoveryInterface, and Namespace based on the environment spec. +func (c *Config) RestClient(envName *string) (dynamic.ClientPool, discovery.DiscoveryInterface, string, error) { + if envName != nil { + err := c.overrideCluster(*envName) + if err != nil { + return nil, nil, "", err + } + } + + conf, err := c.Config.ClientConfig() + if err != nil { + return nil, nil, "", err + } + + disco, err := discovery.NewDiscoveryClientForConfig(conf) + if err != nil { + return nil, nil, "", err + } + + discoCache := utils.NewMemcachedDiscoveryClient(disco) + mapper := discovery.NewDeferredDiscoveryRESTMapper(discoCache, dynamic.VersionInterfaces) + pathresolver := dynamic.LegacyAPIPathResolverFunc + + pool := dynamic.NewClientPool(conf, mapper, pathresolver) + + ns, err := c.Namespace() + if err != nil { + return nil, nil, "", err + } + + return pool, discoCache, ns, nil +} + +// BindClientGoFlags binds client-go flags to the specified command. This way +// any overrides to client-go flags will automatically update the client config. +func (c *Config) BindClientGoFlags(cmd *cobra.Command) { + cmd.PersistentFlags().StringVar(&c.LoadingRules.ExplicitPath, "kubeconfig", "", "Path to a kubeconfig file. Alternative to env var $KUBECONFIG.") + clientcmd.BindOverrideFlags(c.Overrides, cmd.PersistentFlags(), clientcmd.RecommendedConfigOverrideFlags("")) +} + +// ResolveContext returns the server and namespace of the cluster at the +// provided context. If the context string is empty, the "default" context is +// used. +func (c *Config) ResolveContext(context string) (server, namespace string, err error) { + rawConfig, err := c.Config.RawConfig() + if err != nil { + return "", "", err + } + + // use the default context where context is empty + if context == "" { + if rawConfig.CurrentContext == "" && len(rawConfig.Clusters) == 0 { + // User likely does not have a kubeconfig file. + return "", "", fmt.Errorf("No current context found. Make sure a kubeconfig file is present") + } + // Note: "" is a valid rawConfig.CurrentContext + context = rawConfig.CurrentContext + } + + ctx := rawConfig.Contexts[context] + if ctx == nil { + return "", "", fmt.Errorf("context '%s' does not exist in the kubeconfig file", context) + } + + log.Infof("Using context '%s' from the kubeconfig file specified at the environment variable $KUBECONFIG", context) + cluster, exists := rawConfig.Clusters[ctx.Cluster] + if !exists { + return "", "", fmt.Errorf("No cluster with name '%s' exists", ctx.Cluster) + } + + return cluster.Server, ctx.Namespace, nil +} + +// overrideCluster ensures that the server specified in the environment is +// associated in the user's kubeconfig file during deployment to a ksonnet +// environment. We will error out if it is not. +// +// If the environment server the user is attempting to deploy to is not the current +// kubeconfig context, we must manually override the client-go --cluster flag +// to ensure we are deploying to the correct cluster. +func (c *Config) overrideCluster(envName string) error { + cwd, err := os.Getwd() + if err != nil { + return err + } + + metadataManager, err := metadata.Find(cwd) + if err != nil { + return err + } + + rawConfig, err := c.Config.RawConfig() + if err != nil { + return err + } + + var servers = make(map[string]string) + for name, cluster := range rawConfig.Clusters { + server, err := str.NormalizeURL(cluster.Server) + if err != nil { + return err + } + + servers[server] = name + } + + // + // check to ensure that the environment we are trying to deploy to is + // created, and that the server is located in kubeconfig. + // + + log.Debugf("Validating deployment at '%s' with server '%v'", envName, reflect.ValueOf(servers).MapKeys()) + env, err := metadataManager.GetEnvironment(envName) + if err != nil { + return err + } + + // TODO support multi-cluster deployment. + server, err := str.NormalizeURL(env.Destinations[0].Server) + if err != nil { + return err + } + + if _, ok := servers[server]; ok { + clusterName := servers[server] + if c.Overrides.Context.Cluster == "" { + log.Debugf("Overwriting --cluster flag with '%s'", clusterName) + c.Overrides.Context.Cluster = clusterName + } + if c.Overrides.Context.Namespace == "" { + log.Debugf("Overwriting --namespace flag with '%s'", env.Destinations[0].Namespace) + c.Overrides.Context.Namespace = env.Destinations[0].Namespace + } + return nil + } + + return fmt.Errorf("Attempting to deploy to environment '%s' at '%s', but cannot locate a server at that address", envName, env.Destinations[0].Server) +} diff --git a/cmd/apply.go b/cmd/apply.go index e04c08fe0d4dae4727c553006c8468e2e565ee94..86504b0340fa5723b69531e0565fa8989bb8ee83 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -21,9 +21,14 @@ import ( "github.com/spf13/cobra" + "github.com/ksonnet/ksonnet/client" "github.com/ksonnet/ksonnet/pkg/kubecfg" ) +var ( + applyClientConfig *client.Config +) + const ( flagCreate = "create" flagSkipGc = "skip-gc" @@ -52,7 +57,8 @@ func init() { RootCmd.AddCommand(applyCmd) addEnvCmdFlags(applyCmd) - bindClientGoFlags(applyCmd) + applyClientConfig = client.NewDefaultClientConfig() + applyClientConfig.BindClientGoFlags(applyCmd) bindJsonnetFlags(applyCmd) applyCmd.PersistentFlags().Bool(flagCreate, true, "Option to create resources if they do not already exist on the cluster") applyCmd.PersistentFlags().Bool(flagSkipGc, false, "Option to skip garbage collection, even with --"+flagGcTag+" specified") @@ -94,6 +100,9 @@ var applyCmd = &cobra.Command{ return err } + c.ClientConfig = applyClientConfig + c.Env = env + componentNames, err := flags.GetStringArray(flagComponent) if err != nil { return err @@ -104,16 +113,6 @@ var applyCmd = &cobra.Command{ return err } - c.ClientPool, c.Discovery, err = restClientPool(cmd, &env) - if err != nil { - return err - } - - c.Namespace, err = namespace() - if err != nil { - return err - } - te := newCmdObjExpander(cmdObjExpanderConfig{ cmd: cmd, env: env, diff --git a/cmd/delete.go b/cmd/delete.go index 1f91e4314fe225c8cf9337330ac7a9828e6e363f..e5123d4bcd56db0764e3c8cee756aacd89b3daf8 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" + "github.com/ksonnet/ksonnet/client" "github.com/ksonnet/ksonnet/pkg/kubecfg" ) @@ -29,10 +30,15 @@ const ( deleteShortDesc = "Remove component-specified Kubernetes resources from remote clusters" ) +var ( + deleteClientConfig *client.Config +) + func init() { RootCmd.AddCommand(deleteCmd) addEnvCmdFlags(deleteCmd) - bindClientGoFlags(deleteCmd) + deleteClientConfig = client.NewDefaultClientConfig() + deleteClientConfig.BindClientGoFlags(deleteCmd) bindJsonnetFlags(deleteCmd) deleteCmd.PersistentFlags().Int64(flagGracePeriod, -1, "Number of seconds given to resources to terminate gracefully. A negative value is ignored") } @@ -66,15 +72,8 @@ var deleteCmd = &cobra.Command{ return err } - c.ClientPool, c.Discovery, err = restClientPool(cmd, &env) - if err != nil { - return err - } - - c.Namespace, err = namespace() - if err != nil { - return err - } + c.ClientConfig = deleteClientConfig + c.Env = env te := newCmdObjExpander(cmdObjExpanderConfig{ cmd: cmd, diff --git a/cmd/diff.go b/cmd/diff.go index 13bf1b8128c4bd27faac1c9f44f953eb71bb8e7b..7c2a6e14692d76fb5ff5daa3c5e47f67db59b270 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -20,14 +20,11 @@ import ( "os" "strings" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - "github.com/spf13/afero" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/tools/clientcmd" + "github.com/ksonnet/ksonnet/client" "github.com/ksonnet/ksonnet/metadata" "github.com/ksonnet/ksonnet/pkg/kubecfg" ) @@ -206,12 +203,7 @@ func initDiffSingleEnv(fs afero.Fs, env, diffStrategy string, files []string, cm return nil, err } - c.Client.ClientPool, c.Client.Discovery, err = restClientPool(cmd, &env) - if err != nil { - return nil, err - } - - c.Client.Namespace, err = namespace() + c.Client.ClientPool, c.Client.Discovery, c.Client.Namespace, err = client.InitClient(env) if err != nil { return nil, err } @@ -263,11 +255,12 @@ func initDiffRemotesCmd(fs afero.Fs, env1, env2, diffStrategy string, cmd *cobra return nil, err } - c.ClientA.ClientPool, c.ClientA.Discovery, c.ClientA.Namespace, err = setupClientConfig(&c.ClientA.Name, cmd) + c.ClientA.ClientPool, c.ClientA.Discovery, c.ClientA.Namespace, err = client.InitClient(c.ClientA.Name) if err != nil { return nil, err } - c.ClientB.ClientPool, c.ClientB.Discovery, c.ClientB.Namespace, err = setupClientConfig(&c.ClientB.Name, cmd) + + c.ClientB.ClientPool, c.ClientB.Discovery, c.ClientB.Namespace, err = client.InitClient(c.ClientB.Name) if err != nil { return nil, err } @@ -287,7 +280,7 @@ func initDiffRemoteCmd(fs afero.Fs, localEnv, remoteEnv, diffStrategy string, cm return nil, err } - c.Client.ClientPool, c.Client.Discovery, c.Client.Namespace, err = setupClientConfig(&remoteEnv, cmd) + c.Client.ClientPool, c.Client.Discovery, c.Client.Namespace, err = client.InitClient(remoteEnv) if err != nil { return nil, err } @@ -295,25 +288,6 @@ func initDiffRemoteCmd(fs afero.Fs, localEnv, remoteEnv, diffStrategy string, cm return &c, nil } -func setupClientConfig(env *string, cmd *cobra.Command) (dynamic.ClientPool, discovery.DiscoveryInterface, string, error) { - overrides := &clientcmd.ConfigOverrides{} - loadingRules := *clientcmd.NewDefaultClientConfigLoadingRules() - loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig - config := clientcmd.NewInteractiveDeferredLoadingClientConfig(&loadingRules, overrides, os.Stdin) - - clientPool, discovery, err := restClient(cmd, env, config, overrides) - if err != nil { - return nil, nil, "", err - } - - namespace, err := namespaceFor(config, overrides) - if err != nil { - return nil, nil, "", err - } - - return clientPool, discovery, namespace, nil -} - // expandEnvObjs finds and expands templates for an environment func expandEnvObjs(fs afero.Fs, cmd *cobra.Command, env string, manager metadata.Manager) ([]*unstructured.Unstructured, error) { expander, err := newExpander(fs, cmd) diff --git a/cmd/env.go b/cmd/env.go index ca74f4986315c9f57b855cbe44e6641974705e77..830a61803c2fdeb3b02463eed06ae2d7f3828195 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + "github.com/ksonnet/ksonnet/client" "github.com/ksonnet/ksonnet/metadata" "github.com/ksonnet/ksonnet/pkg/kubecfg" ) @@ -34,16 +35,20 @@ const ( flagEnvContext = "context" ) -var envShortDesc = map[string]string{ - "add": "Add a new environment to a ksonnet application", - "list": "List all environments in a ksonnet application", - "rm": "Delete an environment from a ksonnet application", - "set": "Set environment-specific fields (name, namespace, server)", -} +var ( + envClientConfig *client.Config + envShortDesc = map[string]string{ + "add": "Add a new environment to a ksonnet application", + "list": "List all environments in a ksonnet application", + "rm": "Delete an environment from a ksonnet application", + "set": "Set environment-specific fields (name, namespace, server)", + } +) func init() { RootCmd.AddCommand(envCmd) - bindClientGoFlags(envCmd) + envClientConfig = client.NewDefaultClientConfig() + envClientConfig.BindClientGoFlags(envCmd) envCmd.AddCommand(envAddCmd) envCmd.AddCommand(envRmCmd) @@ -368,7 +373,7 @@ func resolveEnvFlags(flags *pflag.FlagSet) (string, string, error) { if server == "" { // server is not provided -- use the context. - server, defaultNamespace, err = resolveContext(context) + server, defaultNamespace, err = envClientConfig.ResolveContext(context) if err != nil { return "", "", err } diff --git a/cmd/init.go b/cmd/init.go index b8361112e8ae4f5df52b37754b01b78e34a444ba..52bd3dfd9f3ec4cbf344d36a30f37d9a59ba7194 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -21,6 +21,7 @@ import ( "os" "path/filepath" + "github.com/ksonnet/ksonnet/client" "github.com/ksonnet/ksonnet/pkg/kubecfg" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -31,13 +32,18 @@ const ( initShortDesc = "Initialize a ksonnet application" ) +var ( + initClientConfig *client.Config +) + func init() { RootCmd.AddCommand(initCmd) // TODO: We need to make this default to checking the `kubeconfig` file. initCmd.PersistentFlags().String(flagAPISpec, "version:v1.7.0", "Manually specified Kubernetes API version. The corresponding OpenAPI spec is used to generate ksonnet's Kubernetes libraries") - bindClientGoFlags(initCmd) + initClientConfig = client.NewDefaultClientConfig() + initClientConfig.BindClientGoFlags(initCmd) initCmd.Flags().String(flagInitDir, "", "Ksonnet application directory") } diff --git a/cmd/root.go b/cmd/root.go index 4311834cbac1f56f7455566053e8bd53801c1626..06faf1bd0109367a4747dc342f93381876c72f77 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -24,25 +24,19 @@ import ( "os" "path" "path/filepath" - "reflect" "strings" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/ksonnet/ksonnet/component" + "github.com/ksonnet/ksonnet/metadata" + str "github.com/ksonnet/ksonnet/strings" + "github.com/ksonnet/ksonnet/template" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/spf13/afero" "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/tools/clientcmd" - - "github.com/ksonnet/ksonnet/component" - "github.com/ksonnet/ksonnet/metadata" - str "github.com/ksonnet/ksonnet/strings" - "github.com/ksonnet/ksonnet/template" - "github.com/ksonnet/ksonnet/utils" // Register auth plugins _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -66,22 +60,13 @@ const ( ) var ( - clientConfig clientcmd.ClientConfig - overrides clientcmd.ConfigOverrides - loadingRules clientcmd.ClientConfigLoadingRules - appFs afero.Fs + appFs afero.Fs ) func init() { appFs = afero.NewOsFs() RootCmd.PersistentFlags().CountP(flagVerbose, "v", "Increase verbosity. May be given multiple times.") - - // The "usual" clientcmd/kubectl flags - loadingRules = *clientcmd.NewDefaultClientConfigLoadingRules() - loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig - clientConfig = clientcmd.NewInteractiveDeferredLoadingClientConfig(&loadingRules, &overrides, os.Stdin) - RootCmd.PersistentFlags().Set("logtostderr", "true") } @@ -95,13 +80,6 @@ func bindJsonnetFlags(cmd *cobra.Command) { cmd.PersistentFlags().String(flagResolvFail, "warn", "Action when resolveImage fails. One of ignore,warn,error") } -func bindClientGoFlags(cmd *cobra.Command) { - kflags := clientcmd.RecommendedConfigOverrideFlags("") - ep := &loadingRules.ExplicitPath - cmd.PersistentFlags().StringVar(ep, "kubeconfig", "", "Path to a kubeconfig file. Alternative to env var $KUBECONFIG.") - clientcmd.BindOverrideFlags(&overrides, cmd.PersistentFlags(), kflags) -} - // RootCmd is the root of cobra subcommand tree var RootCmd = &cobra.Command{ Use: "ks", @@ -133,53 +111,6 @@ application configuration to remote clusters. }, } -// clientConfig.Namespace() is broken in client-go 3.0: -// namespace in config erroneously overrides explicit --namespace -func namespace() (string, error) { - return namespaceFor(clientConfig, &overrides) -} - -func namespaceFor(c clientcmd.ClientConfig, overrides *clientcmd.ConfigOverrides) (string, error) { - if overrides.Context.Namespace != "" { - return overrides.Context.Namespace, nil - } - ns, _, err := clientConfig.Namespace() - return ns, err -} - -// resolveContext returns the server and namespace of the cluster at the -// provided context. If the context string is empty, the default context is -// used. -func resolveContext(context string) (server, namespace string, err error) { - rawConfig, err := clientConfig.RawConfig() - if err != nil { - return "", "", err - } - - // use the default context where context is empty - if context == "" { - if rawConfig.CurrentContext == "" && len(rawConfig.Clusters) == 0 { - // User likely does not have a kubeconfig file. - return "", "", fmt.Errorf("No current context found. Make sure a kubeconfig file is present") - } - // Note: "" is a valid rawConfig.CurrentContext - context = rawConfig.CurrentContext - } - - ctx := rawConfig.Contexts[context] - if ctx == nil { - return "", "", fmt.Errorf("context '%s' does not exist in the kubeconfig file", context) - } - - log.Infof("Using context '%s' from the kubeconfig file specified at the environment variable $KUBECONFIG", context) - cluster, exists := rawConfig.Clusters[ctx.Cluster] - if !exists { - return "", "", fmt.Errorf("No cluster with name '%s' exists", ctx.Cluster) - } - - return cluster.Server, ctx.Namespace, nil -} - func logLevel(verbosity int) log.Level { switch verbosity { case 0: @@ -286,108 +217,12 @@ func dumpJSON(v interface{}) string { return string(buf.Bytes()) } -func restClient(cmd *cobra.Command, envName *string, config clientcmd.ClientConfig, overrides *clientcmd.ConfigOverrides) (dynamic.ClientPool, discovery.DiscoveryInterface, error) { - if envName != nil { - err := overrideCluster(*envName, config, overrides) - if err != nil { - return nil, nil, err - } - } - - conf, err := config.ClientConfig() - if err != nil { - return nil, nil, err - } - - disco, err := discovery.NewDiscoveryClientForConfig(conf) - if err != nil { - return nil, nil, err - } - - discoCache := utils.NewMemcachedDiscoveryClient(disco) - mapper := discovery.NewDeferredDiscoveryRESTMapper(discoCache, dynamic.VersionInterfaces) - pathresolver := dynamic.LegacyAPIPathResolverFunc - - pool := dynamic.NewClientPool(conf, mapper, pathresolver) - return pool, discoCache, nil -} - -func restClientPool(cmd *cobra.Command, envName *string) (dynamic.ClientPool, discovery.DiscoveryInterface, error) { - return restClient(cmd, envName, clientConfig, &overrides) -} - // addEnvCmdFlags adds the flags that are common to the family of commands // whose form is `[<env>|-f <file-name>]`, e.g., `apply` and `delete`. func addEnvCmdFlags(cmd *cobra.Command) { cmd.PersistentFlags().StringArrayP(flagComponent, flagComponentShort, nil, "Name of a specific component (multiple -c flags accepted, allows YAML, JSON, and Jsonnet)") } -// overrideCluster ensures that the server specified in the environment is -// associated in the user's kubeconfig file during deployment to a ksonnet -// environment. We will error out if it is not. -// -// If the environment server the user is attempting to deploy to is not the current -// kubeconfig context, we must manually override the client-go --cluster flag -// to ensure we are deploying to the correct cluster. -func overrideCluster(envName string, clientConfig clientcmd.ClientConfig, overrides *clientcmd.ConfigOverrides) error { - cwd, err := os.Getwd() - if err != nil { - return err - } - - metadataManager, err := metadata.Find(cwd) - if err != nil { - return err - } - - rawConfig, err := clientConfig.RawConfig() - if err != nil { - return err - } - - var servers = make(map[string]string) - for name, cluster := range rawConfig.Clusters { - server, err := str.NormalizeURL(cluster.Server) - if err != nil { - return err - } - - servers[server] = name - } - - // - // check to ensure that the environment we are trying to deploy to is - // created, and that the server is located in kubeconfig. - // - - log.Debugf("Validating deployment at '%s' with server '%v'", envName, reflect.ValueOf(servers).MapKeys()) - env, err := metadataManager.GetEnvironment(envName) - if err != nil { - return err - } - - // TODO support multi-cluster deployment. - server, err := str.NormalizeURL(env.Destinations[0].Server) - if err != nil { - return err - } - - if _, ok := servers[server]; ok { - clusterName := servers[server] - if overrides.Context.Cluster == "" { - log.Debugf("Overwriting --cluster flag with '%s'", clusterName) - overrides.Context.Cluster = clusterName - } - if overrides.Context.Namespace == "" { - log.Debugf("Overwriting --namespace flag with '%s'", env.Destinations[0].Namespace) - overrides.Context.Namespace = env.Destinations[0].Namespace - } - return nil - } - - return fmt.Errorf("Attempting to deploy to environment '%s' at '%s', but cannot locate a server at that address", envName, env.Destinations[0].Server) -} - type cmdObjExpanderConfig struct { fs afero.Fs cmd *cobra.Command diff --git a/cmd/validate.go b/cmd/validate.go index f84b4cb6bd892ddf3b06a6408d42bfc1c3d2b64e..73fdbe02e165d2f0db67e1aedcf8b431965e3e42 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" + "github.com/ksonnet/ksonnet/client" "github.com/ksonnet/ksonnet/pkg/kubecfg" ) @@ -28,11 +29,16 @@ const ( valShortDesc = "Check generated component manifests against the server's API" ) +var ( + validateClientConfig *client.Config +) + func init() { RootCmd.AddCommand(validateCmd) addEnvCmdFlags(validateCmd) bindJsonnetFlags(validateCmd) - bindClientGoFlags(validateCmd) + validateClientConfig = client.NewDefaultClientConfig() + validateClientConfig.BindClientGoFlags(validateCmd) } var validateCmd = &cobra.Command{ @@ -59,10 +65,8 @@ var validateCmd = &cobra.Command{ return err } - _, c.Discovery, err = restClientPool(cmd, nil) - if err != nil { - return err - } + c.ClientConfig = validateClientConfig + c.Env = env te := newCmdObjExpander(cmdObjExpanderConfig{ cmd: cmd, diff --git a/pkg/kubecfg/apply.go b/pkg/kubecfg/apply.go index df6b3dcf0de95a0250a1695f2a4ae9abe21bcf37..6e49db65ff171f87f52ed30246c51f8730024c08 100644 --- a/pkg/kubecfg/apply.go +++ b/pkg/kubecfg/apply.go @@ -5,6 +5,7 @@ import ( "fmt" "sort" + "github.com/ksonnet/ksonnet/client" "github.com/ksonnet/ksonnet/utils" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" @@ -39,18 +40,21 @@ const ( // ApplyCmd represents the apply subcommand type ApplyCmd struct { - ClientPool dynamic.ClientPool - Discovery discovery.DiscoveryInterface - Namespace string - - Create bool - GcTag string - SkipGc bool - DryRun bool + ClientConfig *client.Config + Env string + Create bool + GcTag string + SkipGc bool + DryRun bool } // Run applies the components to the designated environment cluster. func (c ApplyCmd) Run(apiObjects []*unstructured.Unstructured, wd string) error { + clientPool, discovery, namespace, err := c.ClientConfig.RestClient(&c.Env) + if err != nil { + return err + } + dryRunText := "" if c.DryRun { dryRunText = " (dry-run)" @@ -65,10 +69,10 @@ func (c ApplyCmd) Run(apiObjects []*unstructured.Unstructured, wd string) error utils.SetMetaDataAnnotation(obj, AnnotationGcTag, c.GcTag) } - desc := fmt.Sprintf("%s %s", utils.ResourceNameFor(c.Discovery, obj), utils.FqName(obj)) + desc := fmt.Sprintf("%s %s", utils.ResourceNameFor(discovery, obj), utils.FqName(obj)) log.Info("Updating ", desc, dryRunText) - rc, err := utils.ClientForResource(c.ClientPool, c.Discovery, obj, c.Namespace) + rc, err := utils.ClientForResource(clientPool, discovery, obj, namespace) if err != nil { return err } @@ -110,23 +114,23 @@ func (c ApplyCmd) Run(apiObjects []*unstructured.Unstructured, wd string) error } if c.GcTag != "" && !c.SkipGc { - version, err := utils.FetchVersion(c.Discovery) + version, err := utils.FetchVersion(discovery) if err != nil { return err } - err = walkObjects(c.ClientPool, c.Discovery, metav1.ListOptions{}, func(o runtime.Object) error { + err = walkObjects(clientPool, discovery, metav1.ListOptions{}, func(o runtime.Object) error { meta, err := meta.Accessor(o) if err != nil { return err } gvk := o.GetObjectKind().GroupVersionKind() - desc := fmt.Sprintf("%s %s (%s)", utils.ResourceNameFor(c.Discovery, o), utils.FqName(meta), gvk.GroupVersion()) + desc := fmt.Sprintf("%s %s (%s)", utils.ResourceNameFor(discovery, o), utils.FqName(meta), gvk.GroupVersion()) log.Debugf("Considering %v for gc", desc) if eligibleForGc(meta, c.GcTag) && !seenUids.Has(string(meta.GetUID())) { log.Info("Garbage collecting ", desc, dryRunText) if !c.DryRun { - err := gcDelete(c.ClientPool, c.Discovery, &version, o) + err := gcDelete(clientPool, discovery, &version, o) if err != nil { return err } diff --git a/pkg/kubecfg/delete.go b/pkg/kubecfg/delete.go index b76841fc23983e727f738a2b0a9aadf45de0c2d5..d1f68bf333082b23794300668322a8a6c404b2cc 100644 --- a/pkg/kubecfg/delete.go +++ b/pkg/kubecfg/delete.go @@ -23,23 +23,25 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" + "github.com/ksonnet/ksonnet/client" "github.com/ksonnet/ksonnet/utils" ) // DeleteCmd represents the delete subcommand type DeleteCmd struct { - ClientPool dynamic.ClientPool - Discovery discovery.DiscoveryInterface - Namespace string - - GracePeriod int64 + ClientConfig *client.Config + Env string + GracePeriod int64 } func (c DeleteCmd) Run(apiObjects []*unstructured.Unstructured) error { - version, err := utils.FetchVersion(c.Discovery) + clientPool, discovery, namespace, err := c.ClientConfig.RestClient(&c.Env) + if err != nil { + return err + } + + version, err := utils.FetchVersion(discovery) if err != nil { return err } @@ -61,10 +63,10 @@ func (c DeleteCmd) Run(apiObjects []*unstructured.Unstructured) error { } for _, obj := range apiObjects { - desc := fmt.Sprintf("%s %s", utils.ResourceNameFor(c.Discovery, obj), utils.FqName(obj)) + desc := fmt.Sprintf("%s %s", utils.ResourceNameFor(discovery, obj), utils.FqName(obj)) log.Info("Deleting ", desc) - client, err := utils.ClientForResource(c.ClientPool, c.Discovery, obj, c.Namespace) + client, err := utils.ClientForResource(clientPool, discovery, obj, namespace) if err != nil { return err } diff --git a/pkg/kubecfg/validate.go b/pkg/kubecfg/validate.go index 4ef32dc04c7a8f5dc7aa2a4ea2cac976e587d476..275f780fa125925a83ac71a3aaf730445a2c66f7 100644 --- a/pkg/kubecfg/validate.go +++ b/pkg/kubecfg/validate.go @@ -21,26 +21,31 @@ import ( log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/discovery" + "github.com/ksonnet/ksonnet/client" "github.com/ksonnet/ksonnet/utils" ) // ValidateCmd represents the validate subcommand type ValidateCmd struct { - Discovery discovery.DiscoveryInterface + ClientConfig *client.Config + Env string } func (c ValidateCmd) Run(apiObjects []*unstructured.Unstructured, out io.Writer) error { + _, discovery, _, err := client.InitClient(c.Env) + if err != nil { + return err + } hasError := false for _, obj := range apiObjects { - desc := fmt.Sprintf("%s %s", utils.ResourceNameFor(c.Discovery, obj), utils.FqName(obj)) + desc := fmt.Sprintf("%s %s", utils.ResourceNameFor(discovery, obj), utils.FqName(obj)) log.Info("Validating ", desc) var allErrs []error - schema, err := utils.NewSwaggerSchemaFor(c.Discovery, obj.GroupVersionKind().GroupVersion()) + schema, err := utils.NewSwaggerSchemaFor(discovery, obj.GroupVersionKind().GroupVersion()) if err != nil { allErrs = append(allErrs, fmt.Errorf("Unable to fetch schema: %v", err)) } else {