Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'cluster', 'namespace', and 'user' options to delete command. Enhance delete to load provider components from cluster. #384

Merged
merged 1 commit into from
Jun 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clusterctl/clusterdeployer/clientfactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ func (f *clientFactory) NewClusterClientFromKubeconfig(kubeconfig string) (Clust
}

func (f *clientFactory) NewCoreClientsetFromKubeconfigFile(kubeconfigPath string) (*kubernetes.Clientset, error) {
return clientcmd.NewCoreClientSetForDefaultSearchPath(kubeconfigPath)
return clientcmd.NewCoreClientSetForDefaultSearchPath(kubeconfigPath, clientcmd.NewConfigOverrides())
}
49 changes: 33 additions & 16 deletions clusterctl/clusterdeployer/clusterclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/golang/glog"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
tcmd "k8s.io/client-go/tools/clientcmd"
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
"sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset"
"sigs.k8s.io/cluster-api/pkg/clientcmd"
Expand All @@ -44,9 +45,10 @@ const (
)

type clusterClient struct {
clientSet clientset.Interface
kubeconfigFile string
closeFn func() error
clientSet clientset.Interface
kubeconfigFile string
configOverrides tcmd.ConfigOverrides
closeFn func() error
}

// NewClusterClient creates and returns the address of a clusterClient, the kubeconfig argument is expected to be the string represenattion
Expand All @@ -57,7 +59,7 @@ func NewClusterClient(kubeconfig string) (*clusterClient, error) {
return nil, err
}
defer ifErrRemove(&err, f)
c, err := NewClusterClientFromFile(f)
c, err := NewClusterClientFromDefaultSearchPath(f, clientcmd.NewConfigOverrides())
if err != nil {
return nil, err
}
Expand All @@ -69,17 +71,18 @@ func (c *clusterClient) removeKubeconfigFile() error {
return os.Remove(c.kubeconfigFile)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does createTempFile handle removing the file automatically? I'm guessing not since we call os.Remove here. Which means that if there is an error creating the client we leak the file. We need to defer removing the file so that it gets cleaned up even on the error return paths.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a PR here: #390, which addresses this issue in two places. Note that there is still an opportunity to leak files if the user prematurely terminates the program or an automated system kills the VM / container. So in any sort of automated system we will need cleanup processes that handle leaked files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #390 merged and I have rebased this commit on top.

}

// NewClusterClientFromFile creates and returns the address of a clusterClient, the kubeconfigFile argument is expected to be the path to a
// NewClusterClientFromDefaultSearchPath creates and returns the address of a clusterClient, the kubeconfigFile argument is expected to be the path to a
// valid kubeconfig file.
func NewClusterClientFromFile(kubeconfigFile string) (*clusterClient, error) {
c, err := clientcmd.NewClusterApiClientForDefaultSearchPath(kubeconfigFile)
func NewClusterClientFromDefaultSearchPath(kubeconfigFile string, overrides tcmd.ConfigOverrides) (*clusterClient, error) {
c, err := clientcmd.NewClusterApiClientForDefaultSearchPath(kubeconfigFile, overrides)
if err != nil {
return nil, err
}

return &clusterClient{
kubeconfigFile: kubeconfigFile,
clientSet: c,
kubeconfigFile: kubeconfigFile,
clientSet: c,
configOverrides: overrides,
}, nil
}

Expand Down Expand Up @@ -169,16 +172,30 @@ func (c *clusterClient) WaitForClusterV1alpha1Ready() error {
}

func (c *clusterClient) kubectlApply(manifest string) error {
r := strings.NewReader(manifest)
cmd := exec.Command("kubectl", "apply", "--kubeconfig", c.kubeconfigFile, "-f", "-")
cmd.Stdin = r

cmd := exec.Command("kubectl", c.buildKubectlArgs("apply")...)
cmd.Stdin = strings.NewReader(manifest)
out, err := cmd.CombinedOutput()
if err == nil {
return nil
} else {
if err != nil {
return fmt.Errorf("couldn't kubectl apply: %v, output: %s", err, string(out))
}
return nil
}

func (c *clusterClient) buildKubectlArgs(commandName string) []string {
args := []string{commandName}
if c.kubeconfigFile != "" {
args = append(args, "--kubeconfig", c.kubeconfigFile)
}
if c.configOverrides.Context.Cluster != "" {
args = append(args, "--cluster", c.configOverrides.Context.Cluster)
}
if c.configOverrides.Context.Namespace != "" {
args = append(args, "--namespace", c.configOverrides.Context.Namespace)
}
if c.configOverrides.Context.AuthInfo != "" {
args = append(args, "--user", c.configOverrides.Context.AuthInfo)
}
return append(args, "-f", "-")
}

func (c *clusterClient) waitForKubectlApply(manifest string) error {
Expand Down
40 changes: 34 additions & 6 deletions clusterctl/cmd/delete_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,22 @@ limitations under the License.
package cmd

import (
"fmt"

"k8s.io/api/core/v1"
tcmd "k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/cluster-api/clusterctl/providercomponents"
"sigs.k8s.io/cluster-api/pkg/clientcmd"
"sigs.k8s.io/cluster-api/pkg/errors"

"github.com/golang/glog"
"github.com/spf13/cobra"
"sigs.k8s.io/cluster-api/pkg/errors"
)

type DeleteOptions struct {
ClusterName string
ProviderComponents string
KubeconfigPath string
ProviderComponents string
KubeconfigOverrides tcmd.ConfigOverrides
}

var do = &DeleteOptions{}
Expand All @@ -34,20 +42,40 @@ var deleteClusterCmd = &cobra.Command{
Short: "Delete kubernetes cluster",
Long: `Delete a kubernetes cluster with one command`,
Run: func(cmd *cobra.Command, args []string) {
if do.ClusterName == "" {
exitWithHelp(cmd, "Please provide cluster name.")
}
if err := RunDelete(); err != nil {
glog.Exit(err)
}
},
}

func init() {
deleteClusterCmd.Flags().StringVarP(&do.KubeconfigPath, "kubeconfig", "", "", "Path to the kubeconfig file to use for connecting to the cluster to be deleted, if empty, the default KUBECONFIG load path is used.")
deleteClusterCmd.Flags().StringVarP(&do.ProviderComponents, "provider-components", "p", "", "A yaml file containing cluster api provider controllers and supporting objects, if empty the value is loaded from the cluster's configuration store.")
// BindContextFlags will bind the flags cluster, namespace, and user
tcmd.BindContextFlags(&do.KubeconfigOverrides.Context, deleteClusterCmd.Flags(), tcmd.RecommendedContextOverrideFlags(""))
deleteCmd.AddCommand(deleteClusterCmd)
}

func RunDelete() error {
_, err := loadProviderComponents()
if err != nil {
return err
}
return errors.NotImplementedError
}

func loadProviderComponents() (string, error) {
coreClients, err := clientcmd.NewCoreClientSetForDefaultSearchPath(do.KubeconfigPath, do.KubeconfigOverrides)
if err != nil {
return "", fmt.Errorf("error creating core clients: %v", err)
}
pcStore := providercomponents.Store{
ExplicitPath: do.ProviderComponents,
ConfigMap: coreClients.CoreV1().ConfigMaps(v1.NamespaceDefault),
}
providerComponents, err := pcStore.Load()
if err != nil {
return "", fmt.Errorf("error when loading provider components: %v", err)
}
return providerComponents, nil
}
1 change: 0 additions & 1 deletion clusterctl/main_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func TestEmptyAndInvalidArgs(t *testing.T) {
{"create cluster with no arguments with invalid flag", []string{"create", "cluster", "--invalid-flag"}, 1, "create-cluster-no-args-invalid-flag.golden"},
{"delete with no arguments", []string{"delete"}, 0, "delete-no-args.golden"},
{"delete with no arguments with invalid flag", []string{"delete", "--invalid-flag"}, 1, "delete-no-args-invalid-flag.golden"},
{"delete cluster with no arguments", []string{"delete", "cluster"}, 1, "delete-cluster-no-args.golden"},
{"delete cluster with no arguments with invalid flag", []string{"delete", "cluster", "--invalid-flag"}, 1, "delete-cluster-no-args-invalid-flag.golden"},
{"validate with no arguments", []string{"validate"}, 0, "validate-no-args.golden"},
{"validate with no arguments with invalid flag", []string{"validate", "--invalid-flag"}, 1, "validate-no-args-invalid-flag.golden"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ Usage:
clusterctl delete cluster [flags]

Flags:
--cluster string The name of the kubeconfig cluster to use
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This help text isn't super clear without reading the code. Maybe "The name of the cluster to use in the kubeconfig file"?

Copy link
Contributor Author

@spew spew Jun 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We get this help text for 'free' from the libraries so it matches what kubectl has for help text (and actually the entire ecosystem of CLIs that are using the same client-go libraries). I do agree the message isn't super clear. However, there are some nice things about providing a consistent experience as users who are familiar with kubectl would know exactly what this means. So I see three options:

  1. Improve the message and diverge from what kubectl prints out, open PR upstream in client-go to improve the kubectl string.
  2. Leave message as it is (no custom code / message). Open PR to improve the messaging in client-go, wait for that to get vendored in.
  3. Run with our own message and diverge completely from the rest of the kubernetes community.

Of these options I think I like #2 the best as it is the least work and the only users right now are early adopters. It will be less work because we don't have to remember to remove our custom message later once client-go picks up the new messaging. However, I'm happy to do #1 if you think that is best. What do you think? (or do you have another idea?).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your second option seems reasonable to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will follow up with another PR in kube core -- I have an open issue here: #397

-h, --help help for cluster
--kubeconfig string Path to the kubeconfig file to use for connecting to the cluster to be deleted, if empty, the default KUBECONFIG load path is used.
-n, --namespace string If present, the namespace scope for this CLI request
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is namespace for the kubectl apply commands, which isn't super clear from the help text.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is for kubectl apply true, but will also be the namespace for everything clusterctl delete cluster does as the clients will be configured for this namespace.

-p, --provider-components string A yaml file containing cluster api provider controllers and supporting objects, if empty the value is loaded from the cluster's configuration store.
--user string The name of the kubeconfig user to use

Global Flags:
--alsologtostderr log to standard error as well as files
Expand Down
19 changes: 0 additions & 19 deletions clusterctl/testdata/delete-cluster-no-args.golden

This file was deleted.

6 changes: 1 addition & 5 deletions gcp-deployer/deploy/deploy_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,7 @@ func (d *deployer) copyKubeConfig(cluster *clusterv1.Cluster, master *clusterv1.
}

func (d *deployer) initApiClient() error {
c, err := clientcmd.NewClusterApiClientForDefaultSearchPath(d.configPath)
if err != nil {
return err
}
kubernetesClientSet, err := clientcmd.NewCoreClientSetForDefaultSearchPath(d.configPath)
kubernetesClientSet, c, err := clientcmd.NewClientsForDefaultSearchpath(d.configPath, clientcmd.NewConfigOverrides())
if err != nil {
return err
}
Expand Down
44 changes: 23 additions & 21 deletions pkg/clientcmd/configutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@ import (
"sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset"
)

// This is a convienience method to prevent the need of importing both this version of clientcmd and the client-go version
func NewConfigOverrides() clientcmd.ConfigOverrides {
return clientcmd.ConfigOverrides{}
}

// NewCoreClientSetForDefaultSearchPath creates a core kubernetes clientset. If the kubeconfigPath is specified then the configuration is loaded from that path.
// Otherwise the default kubeconfig search path is used.
func NewCoreClientSetForDefaultSearchPath(kubeconfigPath string) (*kubernetes.Clientset, error) {
config, err := newRestConfigForDefaultSearchPath(kubeconfigPath)
// The overrides parameter is used to select a specific context of the config, for example, select the context with a given cluster name or namespace.
func NewCoreClientSetForDefaultSearchPath(kubeconfigPath string, overrides clientcmd.ConfigOverrides) (*kubernetes.Clientset, error) {
config, err := newRestConfigForDefaultSearchPath(kubeconfigPath, overrides)
if err != nil {
return nil, err
}
Expand All @@ -48,8 +54,9 @@ func NewCoreClientSetForKubeconfig(kubeconfig string) (*kubernetes.Clientset, er

// NewClusterApiClientForDefaultSearchPath creates a Cluster API clientset. If the kubeconfigPath is specified then the configuration is loaded from that path.
// Otherwise the default kubeconfig search path is used.
func NewClusterApiClientForDefaultSearchPath(kubeconfigPath string) (*clientset.Clientset, error) {
config, err := newRestConfigForDefaultSearchPath(kubeconfigPath)
// The overrides parameter is used to select a specific context of the config, for example, select the context with a given cluster name or namespace.
func NewClusterApiClientForDefaultSearchPath(kubeconfigPath string, overrides clientcmd.ConfigOverrides) (*clientset.Clientset, error) {
config, err := newRestConfigForDefaultSearchPath(kubeconfigPath, overrides)
if err != nil {
return nil, err
}
Expand All @@ -67,17 +74,9 @@ func NewClusterApiClientForKubeconfig(kubeconfig string) (*clientset.Clientset,

// NewClientsForDefaultSearchpath creates both a core kubernetes clientset and a cluster-api clientset. If the kubeconfigPath
// is specified then the configuration is loaded from that path. Otherwise the default kubeconfig search path is used.
func NewClientsForDefaultSearchpath(kubeconfigPath string) (*kubernetes.Clientset, *clientset.Clientset, error) {
config, err := newRestConfigForDefaultSearchPath(kubeconfigPath)
if err != nil {
return nil, nil, err
}
return newClientsFromRestConfig(config)
}

// NewClientsForKubeconfig creates both a core kubernetes clientset and a cluster-api clientset.
func NewClientsForKubeconfig(kubeconfig string) (*kubernetes.Clientset, *clientset.Clientset, error) {
config, err := newRestConfigForKubeconfig(kubeconfig)
// The overrides parameter is used to select a specific context of the config, for example, select the context with a given cluster name or namespace.
func NewClientsForDefaultSearchpath(kubeconfigPath string, overrides clientcmd.ConfigOverrides) (*kubernetes.Clientset, *clientset.Clientset, error) {
config, err := newRestConfigForDefaultSearchPath(kubeconfigPath, overrides)
if err != nil {
return nil, nil, err
}
Expand All @@ -98,14 +97,17 @@ func newClientsFromRestConfig(config *rest.Config) (*kubernetes.Clientset, *clie
}

// newRestConfig creates a rest.Config for the given apiConfig
func newRestConfig(apiConfig *api.Config) (*rest.Config, error) {
return clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{}).ClientConfig()
// The overrides parameter is used to select a specific context of the config, for example, select the context with a given cluster name or namespace.
func newRestConfig(apiConfig *api.Config, overrides clientcmd.ConfigOverrides) (*rest.Config, error) {
return clientcmd.NewDefaultClientConfig(*apiConfig, &overrides).ClientConfig()
}

// newRestConfigForDefaultSearchPath creates a rest.Config by searching for the kubeconfig on the default search path. If an override 'kubeconfigPath' is
// given then that path is used instead of the default path. If no override is given, an attempt is made to load the
// 'in cluster' config. If this fails, then the default search path is used.
func newRestConfigForDefaultSearchPath(kubeconfigPath string) (*rest.Config, error) {
//
// The overrides parameter is used to select a specific context of the config, for example, select the context with a given cluster name or namespace.
func newRestConfigForDefaultSearchPath(kubeconfigPath string, overrides clientcmd.ConfigOverrides) (*rest.Config, error) {
if kubeconfigPath == "" {
config, err := rest.InClusterConfig()
// if there is no err, continue because InClusterConfig is only expected to succeed if running inside of a pod.
Expand All @@ -117,16 +119,16 @@ func newRestConfigForDefaultSearchPath(kubeconfigPath string) (*rest.Config, err
if err != nil {
return nil, err
}
return newRestConfig(apiConfig)
return newRestConfig(apiConfig, overrides)
}

// newRestConfigForKubeconfig creates a rest.Config for a given kubeconfig string.
func newRestConfigForKubeconfig(kubeconfig string) (*rest.Config, error) {
apiConfig, err := newApiConfigForDefaultSearchPath(kubeconfig)
apiConfig, err := newApiConfigForKubeconfig(kubeconfig)
if err != nil {
return nil, err
}
return newRestConfig(apiConfig)
return newRestConfig(apiConfig, clientcmd.ConfigOverrides{})
}

// newApiConfigForDefaultSearchPath creates an api.Config by searching for the kubeconfig on the default search path. If an override 'kubeconfigPath' is
Expand Down
2 changes: 1 addition & 1 deletion tools/repair/util/repair.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func NewRepairer(dryRun bool, configPath string) (*repairer, error) {
configPath = util.GetDefaultKubeConfigPath()
}

c, err := clientcmd.NewClusterApiClientForDefaultSearchPath(configPath)
c, err := clientcmd.NewClusterApiClientForDefaultSearchPath(configPath, clientcmd.NewConfigOverrides())
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion tools/upgrader/util/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func initClient(kubeconfig string) error {
if kubeconfig == "" {
kubeconfig = util.GetDefaultKubeConfigPath()
}
coreClientset, clusterapiClientset, err := clientcmd.NewClientsForDefaultSearchpath(kubeconfig)
coreClientset, clusterapiClientset, err := clientcmd.NewClientsForDefaultSearchpath(kubeconfig, clientcmd.NewConfigOverrides())
if err != nil {
glog.Fatalf("Error creating rest config: %v", err)
return err
Expand Down