From 3c692ebd77107dafd93248eb698adb05c6cf3ef3 Mon Sep 17 00:00:00 2001 From: "Madhusudan.C.S" Date: Mon, 24 Oct 2016 21:21:08 -0700 Subject: [PATCH 1/2] [Federation] Implement the `kubefed` command. Also: 1. Add it to the client build targets list. 2. Register `kubefed join` and `kubefed unjoin` commands. --- federation/cmd/kubefed/BUILD | 18 +++++++ federation/cmd/kubefed/app/BUILD | 24 +++++++++ federation/cmd/kubefed/app/kubefed.go | 35 +++++++++++++ federation/cmd/kubefed/kubefed.go | 30 +++++++++++ federation/pkg/kubefed/BUILD | 2 + federation/pkg/kubefed/kubefed.go | 74 +++++++++++++++++++++++++++ hack/.linted_packages | 1 + hack/lib/golang.sh | 1 + 8 files changed, 185 insertions(+) create mode 100644 federation/cmd/kubefed/BUILD create mode 100644 federation/cmd/kubefed/app/BUILD create mode 100644 federation/cmd/kubefed/app/kubefed.go create mode 100644 federation/cmd/kubefed/kubefed.go create mode 100644 federation/pkg/kubefed/kubefed.go diff --git a/federation/cmd/kubefed/BUILD b/federation/cmd/kubefed/BUILD new file mode 100644 index 00000000000..f973c5723e9 --- /dev/null +++ b/federation/cmd/kubefed/BUILD @@ -0,0 +1,18 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_binary", + "go_library", + "go_test", + "cgo_library", +) + +go_binary( + name = "kubefed", + srcs = ["kubefed.go"], + tags = ["automanaged"], + deps = ["//federation/cmd/kubefed/app:go_default_library"], +) diff --git a/federation/cmd/kubefed/app/BUILD b/federation/cmd/kubefed/app/BUILD new file mode 100644 index 00000000000..7ba37ca64c0 --- /dev/null +++ b/federation/cmd/kubefed/app/BUILD @@ -0,0 +1,24 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_binary", + "go_library", + "go_test", + "cgo_library", +) + +go_library( + name = "go_default_library", + srcs = ["kubefed.go"], + tags = ["automanaged"], + deps = [ + "//federation/pkg/kubefed:go_default_library", + "//pkg/client/metrics/prometheus:go_default_library", + "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/util/logs:go_default_library", + "//pkg/version/prometheus:go_default_library", + ], +) diff --git a/federation/cmd/kubefed/app/kubefed.go b/federation/cmd/kubefed/app/kubefed.go new file mode 100644 index 00000000000..7feb5059d3f --- /dev/null +++ b/federation/cmd/kubefed/app/kubefed.go @@ -0,0 +1,35 @@ +/* +Copyright 2016 The Kubernetes 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 app + +import ( + "os" + + "k8s.io/kubernetes/federation/pkg/kubefed" + _ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/util/logs" + _ "k8s.io/kubernetes/pkg/version/prometheus" // for version metric registration +) + +func Run() error { + logs.InitLogs() + defer logs.FlushLogs() + + cmd := kubefed.NewKubeFedCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr) + return cmd.Execute() +} diff --git a/federation/cmd/kubefed/kubefed.go b/federation/cmd/kubefed/kubefed.go new file mode 100644 index 00000000000..72a03efc6fd --- /dev/null +++ b/federation/cmd/kubefed/kubefed.go @@ -0,0 +1,30 @@ +/* +Copyright 2016 The Kubernetes 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 main + +import ( + "os" + + "k8s.io/kubernetes/federation/cmd/kubefed/app" +) + +func main() { + if err := app.Run(); err != nil { + os.Exit(1) + } + os.Exit(0) +} diff --git a/federation/pkg/kubefed/BUILD b/federation/pkg/kubefed/BUILD index 9559f3d48a2..d63f19cbbe9 100644 --- a/federation/pkg/kubefed/BUILD +++ b/federation/pkg/kubefed/BUILD @@ -14,6 +14,7 @@ go_library( name = "go_default_library", srcs = [ "join.go", + "kubefed.go", "unjoin.go", ], tags = ["automanaged"], @@ -30,6 +31,7 @@ go_library( "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/resource:go_default_library", "//pkg/runtime:go_default_library", + "//pkg/util/flag:go_default_library", "//vendor:github.com/golang/glog", "//vendor:github.com/spf13/cobra", ], diff --git a/federation/pkg/kubefed/kubefed.go b/federation/pkg/kubefed/kubefed.go new file mode 100644 index 00000000000..497368c8c18 --- /dev/null +++ b/federation/pkg/kubefed/kubefed.go @@ -0,0 +1,74 @@ +/* +Copyright 2016 The Kubernetes 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 kubefed + +import ( + "io" + + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + kubectl "k8s.io/kubernetes/pkg/kubectl/cmd" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/util/flag" + + "github.com/spf13/cobra" +) + +// NewKubeFedCommand creates the `kubefed` command and its nested children. +func NewKubeFedCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command { + // Parent command to which all subcommands are added. + cmds := &cobra.Command{ + Use: "kubefed", + Short: "kubefed controls a Kubernetes Cluster Federation", + Long: templates.LongDesc(` + kubefed controls a Kubernetes Cluster Federation. + + Find more information at https://github.com/kubernetes/kubernetes.`), + Run: runHelp, + } + + f.BindFlags(cmds.PersistentFlags()) + f.BindExternalFlags(cmds.PersistentFlags()) + + // From this point and forward we get warnings on flags that contain "_" separators + cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc) + + groups := templates.CommandGroups{ + { + Message: "Basic Commands:", + Commands: []*cobra.Command{ + NewCmdJoin(f, out, NewJoinFederationConfig(clientcmd.NewDefaultPathOptions())), + NewCmdUnjoin(f, out, err, NewJoinFederationConfig(clientcmd.NewDefaultPathOptions())), + }, + }, + } + groups.Add(cmds) + + filters := []string{ + "options", + } + templates.ActsAsRootCommand(cmds, filters, groups...) + + cmds.AddCommand(kubectl.NewCmdVersion(f, out)) + cmds.AddCommand(kubectl.NewCmdOptions(out)) + + return cmds +} + +func runHelp(cmd *cobra.Command, args []string) { + cmd.Help() +} diff --git a/hack/.linted_packages b/hack/.linted_packages index 5556742c422..e518356d439 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -52,6 +52,7 @@ federation/apis/federation/install federation/cmd/federation-apiserver federation/cmd/federation-controller-manager federation/cmd/genfeddocs +federation/cmd/kubefed hack/boilerplate/test hack/cmd/teststale pkg/api diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh index 7a73b64691b..34177640206 100755 --- a/hack/lib/golang.sh +++ b/hack/lib/golang.sh @@ -95,6 +95,7 @@ fi # The set of client targets that we are building for all platforms readonly KUBE_CLIENT_TARGETS=( cmd/kubectl + federation/cmd/kubefed ) readonly KUBE_CLIENT_BINARIES=("${KUBE_CLIENT_TARGETS[@]##*/}") readonly KUBE_CLIENT_BINARIES_WIN=("${KUBE_CLIENT_BINARIES[@]/%/.exe}") From 9a0fc0c4f5186089fa383b24436332c10b6ce2a9 Mon Sep 17 00:00:00 2001 From: "Madhusudan.C.S" Date: Tue, 25 Oct 2016 23:32:48 -0700 Subject: [PATCH 2/2] [Federation][(Un)join-01] Refactor common functions and structs into a util package. --- federation/pkg/kubefed/BUILD | 2 + federation/pkg/kubefed/join.go | 106 +++------------------ federation/pkg/kubefed/join_test.go | 19 ++-- federation/pkg/kubefed/kubefed.go | 5 +- federation/pkg/kubefed/unjoin.go | 28 +++--- federation/pkg/kubefed/unjoin_test.go | 4 +- federation/pkg/kubefed/util/BUILD | 26 ++++++ federation/pkg/kubefed/util/util.go | 130 ++++++++++++++++++++++++++ 8 files changed, 203 insertions(+), 117 deletions(-) create mode 100644 federation/pkg/kubefed/util/BUILD create mode 100644 federation/pkg/kubefed/util/util.go diff --git a/federation/pkg/kubefed/BUILD b/federation/pkg/kubefed/BUILD index d63f19cbbe9..95714aa92ae 100644 --- a/federation/pkg/kubefed/BUILD +++ b/federation/pkg/kubefed/BUILD @@ -20,6 +20,7 @@ go_library( tags = ["automanaged"], deps = [ "//federation/apis/federation:go_default_library", + "//federation/pkg/kubefed/util:go_default_library", "//pkg/api:go_default_library", "//pkg/api/errors:go_default_library", "//pkg/api/unversioned:go_default_library", @@ -48,6 +49,7 @@ go_test( deps = [ "//federation/apis/federation:go_default_library", "//federation/apis/federation/v1beta1:go_default_library", + "//federation/pkg/kubefed/util:go_default_library", "//pkg/api:go_default_library", "//pkg/api/errors:go_default_library", "//pkg/api/testapi:go_default_library", diff --git a/federation/pkg/kubefed/join.go b/federation/pkg/kubefed/join.go index 83e8c7d8f9f..56536ce8c0c 100644 --- a/federation/pkg/kubefed/join.go +++ b/federation/pkg/kubefed/join.go @@ -21,8 +21,7 @@ import ( "io" "strings" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + "k8s.io/kubernetes/federation/pkg/kubefed/util" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/kubectl" kubectlcmd "k8s.io/kubernetes/pkg/kubectl/cmd" @@ -35,10 +34,6 @@ import ( ) const ( - // KubeconfigSecretDataKey is the key name used in the secret to - // stores a cluster's credentials. - KubeconfigSecretDataKey = "kubeconfig" - // defaultClusterCIDR is the default CIDR range accepted by the // joining API server. See `apis/federation.ClusterSpec` for // details. @@ -59,52 +54,9 @@ var ( kubectl join foo --host-cluster-context=bar`) ) -// JoinFederationConfig provides a filesystem based kubeconfig (via -// `PathOptions()`) and a mechanism to talk to the federation host -// cluster. -type JoinFederationConfig interface { - // PathOptions provides filesystem based kubeconfig access. - PathOptions() *clientcmd.PathOptions - // HostFactory provides a mechanism to communicate with the - // cluster where federation control plane is hosted. - HostFactory(host, kubeconfigPath string) cmdutil.Factory -} - -// joinFederationConfig implements JoinFederationConfig interface. -type joinFederationConfig struct { - pathOptions *clientcmd.PathOptions -} - -// Assert that `joinFederationConfig` implements the -// `JoinFederationConfig` interface. -var _ JoinFederationConfig = &joinFederationConfig{} - -func NewJoinFederationConfig(pathOptions *clientcmd.PathOptions) JoinFederationConfig { - return &joinFederationConfig{ - pathOptions: pathOptions, - } -} - -func (j *joinFederationConfig) PathOptions() *clientcmd.PathOptions { - return j.pathOptions -} - -func (j *joinFederationConfig) HostFactory(host, kubeconfigPath string) cmdutil.Factory { - loadingRules := *j.pathOptions.LoadingRules - loadingRules.Precedence = j.pathOptions.GetLoadingPrecedence() - loadingRules.ExplicitPath = kubeconfigPath - overrides := &clientcmd.ConfigOverrides{ - CurrentContext: host, - } - - hostClientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, overrides) - - return cmdutil.NewFactory(hostClientConfig) -} - // NewCmdJoin defines the `join` command that joins a cluster to a // federation. -func NewCmdJoin(f cmdutil.Factory, cmdOut io.Writer, config JoinFederationConfig) *cobra.Command { +func NewCmdJoin(f cmdutil.Factory, cmdOut io.Writer, config util.AdminConfig) *cobra.Command { cmd := &cobra.Command{ Use: "join CLUSTER_CONTEXT --host-cluster-context=HOST_CONTEXT", Short: "Join a cluster to a federation", @@ -120,36 +72,35 @@ func NewCmdJoin(f cmdutil.Factory, cmdOut io.Writer, config JoinFederationConfig cmdutil.AddValidateFlags(cmd) cmdutil.AddPrinterFlags(cmd) cmdutil.AddGeneratorFlags(cmd, cmdutil.ClusterV1Beta1GeneratorName) - addJoinFlags(cmd) + util.AddSubcommandFlags(cmd) return cmd } // joinFederation is the implementation of the `join federation` command. -func joinFederation(f cmdutil.Factory, cmdOut io.Writer, config JoinFederationConfig, cmd *cobra.Command, args []string) error { - name, err := kubectlcmd.NameFromCommandArgs(cmd, args) +func joinFederation(f cmdutil.Factory, cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Command, args []string) error { + joinFlags, err := util.GetSubcommandFlags(cmd, args) if err != nil { return err } - host := cmdutil.GetFlagString(cmd, "host-cluster-context") - hostSystemNamespace := cmdutil.GetFlagString(cmd, "host-system-namespace") - kubeconfig := cmdutil.GetFlagString(cmd, "kubeconfig") dryRun := cmdutil.GetDryRunFlag(cmd) - glog.V(2).Infof("Args and flags: name %s, host: %s, host-system-namespace: %s, kubeconfig: %s, dry-run: %s", name, host, hostSystemNamespace, kubeconfig, dryRun) + glog.V(2).Infof("Args and flags: name %s, host: %s, host-system-namespace: %s, kubeconfig: %s, dry-run: %s", joinFlags.Name, joinFlags.Host, joinFlags.HostSystemNamespace, joinFlags.Kubeconfig, dryRun) po := config.PathOptions() - po.LoadingRules.ExplicitPath = kubeconfig + po.LoadingRules.ExplicitPath = joinFlags.Kubeconfig clientConfig, err := po.GetStartingConfig() if err != nil { return err } - generator, err := clusterGenerator(clientConfig, name) + generator, err := clusterGenerator(clientConfig, joinFlags.Name) if err != nil { glog.V(2).Infof("Failed creating cluster generator: %v", err) return err } glog.V(2).Infof("Created cluster generator: %#v", generator) + hostFactory := config.HostFactory(joinFlags.Host, joinFlags.Kubeconfig) + // We are not using the `kubectl create secret` machinery through // `RunCreateSubcommand` as we do to the cluster resource below // because we have a bunch of requirements that the machinery does @@ -165,8 +116,7 @@ func joinFederation(f cmdutil.Factory, cmdOut io.Writer, config JoinFederationCo // don't have to print the created secret in the default case. // Having said that, secret generation machinery could be altered to // suit our needs, but it is far less invasive and readable this way. - hostFactory := config.HostFactory(host, kubeconfig) - _, err = createSecret(hostFactory, clientConfig, hostSystemNamespace, name, dryRun) + _, err = createSecret(hostFactory, clientConfig, joinFlags.HostSystemNamespace, joinFlags.Name, dryRun) if err != nil { glog.V(2).Infof("Failed creating the cluster credentials secret: %v", err) return err @@ -174,19 +124,13 @@ func joinFederation(f cmdutil.Factory, cmdOut io.Writer, config JoinFederationCo glog.V(2).Infof("Cluster credentials secret created") return kubectlcmd.RunCreateSubcommand(f, cmd, cmdOut, &kubectlcmd.CreateSubcommandOptions{ - Name: name, + Name: joinFlags.Name, StructuredGenerator: generator, DryRun: dryRun, OutputFormat: cmdutil.GetFlagString(cmd, "output"), }) } -func addJoinFlags(cmd *cobra.Command) { - cmd.Flags().String("kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") - cmd.Flags().String("host-cluster-context", "", "Host cluster context") - cmd.Flags().String("host-system-namespace", "federation-system", "Namespace in the host cluster where the federation system components are installed") -} - // minifyConfig is a wrapper around `clientcmdapi.MinifyConfig()` that // sets the current context to the given context before calling // `clientcmdapi.MinifyConfig()`. @@ -223,34 +167,14 @@ func createSecret(hostFactory cmdutil.Factory, clientConfig *clientcmdapi.Config return nil, err } - configBytes, err := clientcmd.Write(*newClientConfig) + // Boilerplate to create the secret in the host cluster. + clientset, err := hostFactory.ClientSet() if err != nil { glog.V(2).Infof("Failed to serialize the kubeconfig for the given context %q: %v", name, err) return nil, err } - // Build the secret object with the minified and flattened - // kubeconfig content. - secret := &api.Secret{ - ObjectMeta: api.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Data: map[string][]byte{ - KubeconfigSecretDataKey: configBytes, - }, - } - - if !dryRun { - // Boilerplate to create the secret in the host cluster. - clientset, err := hostFactory.ClientSet() - if err != nil { - glog.V(2).Infof("Failed to retrieve the cluster clientset: %v", err) - return nil, err - } - return clientset.Core().Secrets(namespace).Create(secret) - } - return secret, nil + return util.CreateKubeconfigSecret(clientset, newClientConfig, namespace, name, dryRun) } // clusterGenerator extracts the cluster information from the supplied diff --git a/federation/pkg/kubefed/join_test.go b/federation/pkg/kubefed/join_test.go index cfeb7ef23da..843de4841af 100644 --- a/federation/pkg/kubefed/join_test.go +++ b/federation/pkg/kubefed/join_test.go @@ -26,6 +26,7 @@ import ( "testing" federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" + "k8s.io/kubernetes/federation/pkg/kubefed/util" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" @@ -110,12 +111,12 @@ func TestJoinFederation(t *testing.T) { t.Fatalf("[%d] unexpected error: %v", i, err) } - joinConfig, err := newFakeJoinFederationConfig(hostFactory, tc.kubeconfigGlobal) + adminConfig, err := newFakeAdminConfig(hostFactory, tc.kubeconfigGlobal) if err != nil { t.Fatalf("[%d] unexpected error: %v", i, err) } - cmd := NewCmdJoin(f, buf, joinConfig) + cmd := NewCmdJoin(f, buf, adminConfig) cmd.Flags().Set("kubeconfig", tc.kubeconfigExplicit) cmd.Flags().Set("host", "substrate") @@ -171,28 +172,28 @@ func testJoinFederationFactory(name, server string) cmdutil.Factory { return f } -type fakeJoinFederationConfig struct { +type fakeAdminConfig struct { pathOptions *clientcmd.PathOptions hostFactory cmdutil.Factory } -func newFakeJoinFederationConfig(f cmdutil.Factory, kubeconfigGlobal string) (JoinFederationConfig, error) { +func newFakeAdminConfig(f cmdutil.Factory, kubeconfigGlobal string) (util.AdminConfig, error) { pathOptions := clientcmd.NewDefaultPathOptions() pathOptions.GlobalFile = kubeconfigGlobal pathOptions.EnvVar = "" - return &fakeJoinFederationConfig{ + return &fakeAdminConfig{ pathOptions: pathOptions, hostFactory: f, }, nil } -func (r *fakeJoinFederationConfig) PathOptions() *clientcmd.PathOptions { - return r.pathOptions +func (f *fakeAdminConfig) PathOptions() *clientcmd.PathOptions { + return f.pathOptions } -func (r *fakeJoinFederationConfig) HostFactory(host, kubeconfigPath string) cmdutil.Factory { - return r.hostFactory +func (f *fakeAdminConfig) HostFactory(host, kubeconfigPath string) cmdutil.Factory { + return f.hostFactory } func fakeJoinHostFactory(name, server, token string) (cmdutil.Factory, error) { diff --git a/federation/pkg/kubefed/kubefed.go b/federation/pkg/kubefed/kubefed.go index 497368c8c18..965e2a943a4 100644 --- a/federation/pkg/kubefed/kubefed.go +++ b/federation/pkg/kubefed/kubefed.go @@ -19,6 +19,7 @@ package kubefed import ( "io" + "k8s.io/kubernetes/federation/pkg/kubefed/util" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" kubectl "k8s.io/kubernetes/pkg/kubectl/cmd" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -51,8 +52,8 @@ func NewKubeFedCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob { Message: "Basic Commands:", Commands: []*cobra.Command{ - NewCmdJoin(f, out, NewJoinFederationConfig(clientcmd.NewDefaultPathOptions())), - NewCmdUnjoin(f, out, err, NewJoinFederationConfig(clientcmd.NewDefaultPathOptions())), + NewCmdJoin(f, out, util.NewAdminConfig(clientcmd.NewDefaultPathOptions())), + NewCmdUnjoin(f, out, err, util.NewAdminConfig(clientcmd.NewDefaultPathOptions())), }, }, } diff --git a/federation/pkg/kubefed/unjoin.go b/federation/pkg/kubefed/unjoin.go index 279cdc990a4..39674f63e89 100644 --- a/federation/pkg/kubefed/unjoin.go +++ b/federation/pkg/kubefed/unjoin.go @@ -22,10 +22,10 @@ import ( "net/url" federationapi "k8s.io/kubernetes/federation/apis/federation" + "k8s.io/kubernetes/federation/pkg/kubefed/util" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" - kubectlcmd "k8s.io/kubernetes/pkg/kubectl/cmd" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -49,7 +49,7 @@ var ( // NewCmdUnjoin defines the `unjoin` command that removes a cluster // from a federation. -func NewCmdUnjoin(f cmdutil.Factory, cmdOut, cmdErr io.Writer, config JoinFederationConfig) *cobra.Command { +func NewCmdUnjoin(f cmdutil.Factory, cmdOut, cmdErr io.Writer, config util.AdminConfig) *cobra.Command { cmd := &cobra.Command{ Use: "unjoin CLUSTER_NAME --host-cluster-context=HOST_CONTEXT", Short: "Unjoins a cluster from a federation", @@ -61,42 +61,41 @@ func NewCmdUnjoin(f cmdutil.Factory, cmdOut, cmdErr io.Writer, config JoinFedera }, } - addJoinFlags(cmd) + util.AddSubcommandFlags(cmd) return cmd } // unjoinFederation is the implementation of the `unjoin` command. -func unjoinFederation(f cmdutil.Factory, cmdOut, cmdErr io.Writer, config JoinFederationConfig, cmd *cobra.Command, args []string) error { - name, err := kubectlcmd.NameFromCommandArgs(cmd, args) +func unjoinFederation(f cmdutil.Factory, cmdOut, cmdErr io.Writer, config util.AdminConfig, cmd *cobra.Command, args []string) error { + unjoinFlags, err := util.GetSubcommandFlags(cmd, args) if err != nil { return err } - host := cmdutil.GetFlagString(cmd, "host-cluster-context") - hostSystemNamespace := cmdutil.GetFlagString(cmd, "host-system-namespace") - kubeconfig := cmdutil.GetFlagString(cmd, "kubeconfig") - cluster, err := popCluster(f, name) + cluster, err := popCluster(f, unjoinFlags.Name) if err != nil { return err } if cluster == nil { - fmt.Fprintf(cmdErr, "WARNING: cluster %q not found in federation, so its credentials' secret couldn't be deleted", name) + fmt.Fprintf(cmdErr, "WARNING: cluster %q not found in federation, so its credentials' secret couldn't be deleted", unjoinFlags.Name) return nil } // We want a separate client factory to communicate with the // federation host cluster. See join_federation.go for details. - hostFactory := config.HostFactory(host, kubeconfig) - err = deleteSecret(hostFactory, cluster.Spec.SecretRef.Name, hostSystemNamespace) + hostFactory := config.HostFactory(unjoinFlags.Host, unjoinFlags.Kubeconfig) + err = deleteSecret(hostFactory, cluster.Spec.SecretRef.Name, unjoinFlags.HostSystemNamespace) if isNotFound(err) { fmt.Fprintf(cmdErr, "WARNING: secret %q not found in the host cluster, so it couldn't be deleted", cluster.Spec.SecretRef.Name) } else if err != nil { return err } - _, err = fmt.Fprintf(cmdOut, "Successfully removed cluster %q from federation\n", name) + _, err = fmt.Fprintf(cmdOut, "Successfully removed cluster %q from federation\n", unjoinFlags.Name) return err } +// popCluster fetches the cluster object with the given name, deletes +// it and returns the deleted cluster object. func popCluster(f cmdutil.Factory, name string) (*federationapi.Cluster, error) { // Boilerplate to create the secret in the host cluster. mapper, typer := f.Object() @@ -133,6 +132,8 @@ func popCluster(f cmdutil.Factory, name string) (*federationapi.Cluster, error) return cluster, rh.Delete("", name) } +// deleteSecret deletes the secret with the given name from the host +// cluster. func deleteSecret(hostFactory cmdutil.Factory, name, namespace string) error { clientset, err := hostFactory.ClientSet() if err != nil { @@ -141,6 +142,7 @@ func deleteSecret(hostFactory cmdutil.Factory, name, namespace string) error { return clientset.Core().Secrets(namespace).Delete(name, &api.DeleteOptions{}) } +// isNotFound checks if the given error is a NotFound status error. func isNotFound(err error) bool { statusErr := err if urlErr, ok := err.(*url.Error); ok { diff --git a/federation/pkg/kubefed/unjoin_test.go b/federation/pkg/kubefed/unjoin_test.go index 9469d341dcd..eb9cedf326e 100644 --- a/federation/pkg/kubefed/unjoin_test.go +++ b/federation/pkg/kubefed/unjoin_test.go @@ -125,12 +125,12 @@ func TestUnjoinFederation(t *testing.T) { errBuf := bytes.NewBuffer([]byte{}) hostFactory := fakeUnjoinHostFactory(tc.cluster) - joinConfig, err := newFakeJoinFederationConfig(hostFactory, tc.kubeconfigGlobal) + adminConfig, err := newFakeAdminConfig(hostFactory, tc.kubeconfigGlobal) if err != nil { t.Fatalf("[%d] unexpected error: %v", i, err) } - cmd := NewCmdUnjoin(f, buf, errBuf, joinConfig) + cmd := NewCmdUnjoin(f, buf, errBuf, adminConfig) cmd.Flags().Set("kubeconfig", tc.kubeconfigExplicit) cmd.Flags().Set("host", "substrate") diff --git a/federation/pkg/kubefed/util/BUILD b/federation/pkg/kubefed/util/BUILD new file mode 100644 index 00000000000..29b9ab10022 --- /dev/null +++ b/federation/pkg/kubefed/util/BUILD @@ -0,0 +1,26 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_binary", + "go_library", + "go_test", + "cgo_library", +) + +go_library( + name = "go_default_library", + srcs = ["util.go"], + tags = ["automanaged"], + deps = [ + "//pkg/api:go_default_library", + "//pkg/client/clientset_generated/internalclientset:go_default_library", + "//pkg/client/unversioned/clientcmd:go_default_library", + "//pkg/client/unversioned/clientcmd/api:go_default_library", + "//pkg/kubectl/cmd:go_default_library", + "//pkg/kubectl/cmd/util:go_default_library", + "//vendor:github.com/spf13/cobra", + ], +) diff --git a/federation/pkg/kubefed/util/util.go b/federation/pkg/kubefed/util/util.go new file mode 100644 index 00000000000..2c231944550 --- /dev/null +++ b/federation/pkg/kubefed/util/util.go @@ -0,0 +1,130 @@ +/* +Copyright 2016 The Kubernetes 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 util + +import ( + "k8s.io/kubernetes/pkg/api" + client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" + kubectlcmd "k8s.io/kubernetes/pkg/kubectl/cmd" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + + "github.com/spf13/cobra" +) + +const ( + // KubeconfigSecretDataKey is the key name used in the secret to + // stores a cluster's credentials. + KubeconfigSecretDataKey = "kubeconfig" +) + +// AdminConfig provides a filesystem based kubeconfig (via +// `PathOptions()`) and a mechanism to talk to the federation +// host cluster. +type AdminConfig interface { + // PathOptions provides filesystem based kubeconfig access. + PathOptions() *clientcmd.PathOptions + // HostFactory provides a mechanism to communicate with the + // cluster where federation control plane is hosted. + HostFactory(host, kubeconfigPath string) cmdutil.Factory +} + +// adminConfig implements the AdminConfig interface. +type adminConfig struct { + pathOptions *clientcmd.PathOptions +} + +// NewAdminConfig creates an admin config for `kubefed` commands. +func NewAdminConfig(pathOptions *clientcmd.PathOptions) AdminConfig { + return &adminConfig{ + pathOptions: pathOptions, + } +} + +func (a *adminConfig) PathOptions() *clientcmd.PathOptions { + return a.pathOptions +} + +func (a *adminConfig) HostFactory(host, kubeconfigPath string) cmdutil.Factory { + loadingRules := *a.pathOptions.LoadingRules + loadingRules.Precedence = a.pathOptions.GetLoadingPrecedence() + loadingRules.ExplicitPath = kubeconfigPath + overrides := &clientcmd.ConfigOverrides{ + CurrentContext: host, + } + + hostClientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, overrides) + + return cmdutil.NewFactory(hostClientConfig) +} + +// SubcommandFlags holds the flags required by the subcommands of +// `kubefed`. +type SubcommandFlags struct { + Name string + Host string + HostSystemNamespace string + Kubeconfig string +} + +// AddSubcommandFlags adds the definition for `kubefed` subcommand +// flags. +func AddSubcommandFlags(cmd *cobra.Command) { + cmd.Flags().String("kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") + cmd.Flags().String("host", "", "Host cluster context") + cmd.Flags().String("host-system-namespace", "federation-system", "Namespace in the host cluster where the federation system components are installed") +} + +// GetSubcommandFlags retrieves the command line flag values for the +// `kubefed` subcommands. +func GetSubcommandFlags(cmd *cobra.Command, args []string) (*SubcommandFlags, error) { + name, err := kubectlcmd.NameFromCommandArgs(cmd, args) + if err != nil { + return nil, err + } + return &SubcommandFlags{ + Name: name, + Host: cmdutil.GetFlagString(cmd, "host"), + HostSystemNamespace: cmdutil.GetFlagString(cmd, "host-system-namespace"), + Kubeconfig: cmdutil.GetFlagString(cmd, "kubeconfig"), + }, nil +} + +func CreateKubeconfigSecret(clientset *client.Clientset, kubeconfig *clientcmdapi.Config, namespace, name string, dryRun bool) (*api.Secret, error) { + configBytes, err := clientcmd.Write(*kubeconfig) + if err != nil { + return nil, err + } + + // Build the secret object with the minified and flattened + // kubeconfig content. + secret := &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Data: map[string][]byte{ + KubeconfigSecretDataKey: configBytes, + }, + } + + if !dryRun { + return clientset.Core().Secrets(namespace).Create(secret) + } + return secret, nil +}