diff --git a/.gitignore b/.gitignore index 6f68fe36..ad35f9b9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,6 @@ site/ # Editors .vscode/ -.local/ \ No newline at end of file +.local/ +.idea/ +*.iml \ No newline at end of file diff --git a/Makefile b/Makefile index f108f7b9..8eea3767 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ TAGS := TESTS := . TESTFLAGS := LDFLAGS := -w -s -X github.com/rancher/k3d/version.Version=${GIT_TAG} -X github.com/rancher/k3d/version.K3sVersion=${K3S_TAG} +GCFLAGS := GOFLAGS := BINDIR := $(CURDIR)/bin BINARIES := k3d @@ -68,8 +69,11 @@ LINT_DIRS := $(DIRS) $(foreach dir,$(REC_DIRS),$(dir)/...) all: clean fmt check build +build-debug: GCFLAGS+="all=-N -l" +build-debug: build + build: - CGO_ENABLED=0 $(GO) build $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)/$(BINARIES)' + CGO_ENABLED=0 $(GO) build $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -gcflags '$(GCFLAGS)' -o '$(BINDIR)/$(BINARIES)' build-cross: LDFLAGS += -extldflags "-static" build-cross: diff --git a/cmd/create/createCluster.go b/cmd/create/createCluster.go index a0a62904..f05e2c57 100644 --- a/cmd/create/createCluster.go +++ b/cmd/create/createCluster.go @@ -115,7 +115,7 @@ func NewCmdCreateCluster() *cobra.Command { cmd.Flags().IntP("workers", "w", 0, "Specify how many workers you want to create") cmd.Flags().StringP("image", "i", fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)), "Specify k3s image that you want to use for the nodes") cmd.Flags().String("network", "", "Join an existing network") - cmd.Flags().String("secret", "", "Specify a cluster secret. By default, we generate one.") + cmd.Flags().String("token", "", "Specify a cluster token. By default, we generate one.") cmd.Flags().StringArrayP("volume", "v", nil, "Mount volumes into the nodes (Format: `--volume [SOURCE:]DEST[@NODEFILTER[;NODEFILTER...]]`\n - Example: `k3d create -w 2 -v /my/path@worker[0,1] -v /tmp/test:/tmp/other@master[0]`") cmd.Flags().StringArrayP("port", "p", nil, "Map ports from the node containers to the host (Format: `[HOST:][HOSTPORT:]CONTAINERPORT[/PROTOCOL][@NODEFILTER]`)\n - Example: `k3d create -w 2 -p 8080:80@worker[0] -p 8081@worker[1]`") cmd.Flags().BoolVar(&createClusterOpts.WaitForMaster, "wait", false, "Wait for the master(s) to be ready before returning. Use '--timeout DURATION' to not wait forever.") @@ -206,8 +206,8 @@ func parseCreateClusterCmd(cmd *cobra.Command, args []string, createClusterOpts log.Fatalln("Can only run a single node in hostnetwork mode") } - // --secret - secret, err := cmd.Flags().GetString("secret") + // --token + token, err := cmd.Flags().GetString("token") if err != nil { log.Fatalln(err) } @@ -309,7 +309,7 @@ func parseCreateClusterCmd(cmd *cobra.Command, args []string, createClusterOpts cluster := &k3d.Cluster{ Name: clustername, Network: network, - Secret: secret, + Token: token, CreateClusterOpts: createClusterOpts, ExposeAPI: exposeAPI, } diff --git a/cmd/get/getCluster.go b/cmd/get/getCluster.go index 1063001c..e2708fc5 100644 --- a/cmd/get/getCluster.go +++ b/cmd/get/getCluster.go @@ -22,9 +22,9 @@ THE SOFTWARE. package get import ( + "context" "fmt" "os" - "sort" "strings" k3cluster "github.com/rancher/k3d/pkg/cluster" @@ -37,41 +37,32 @@ import ( "github.com/liggitt/tabwriter" ) +// TODO : deal with --all flag to manage differentiate started cluster and stopped cluster like `docker ps` and `docker ps -a` +type clusterFlags struct { + noHeader bool + token bool +} + // NewCmdGetCluster returns a new cobra command func NewCmdGetCluster() *cobra.Command { + clusterFlags := clusterFlags{} + // create new command cmd := &cobra.Command{ Use: "cluster [NAME [NAME...]]", Aliases: []string{"clusters"}, Short: "Get cluster(s)", Long: `Get cluster(s).`, - Args: cobra.MinimumNArgs(0), // 0 or more; 0 = all Run: func(cmd *cobra.Command, args []string) { - clusters, headersOff := parseGetClusterCmd(cmd, args) - var existingClusters []*k3d.Cluster - if len(clusters) == 0 { // Option a) no cluster name specified -> get all clusters - found, err := k3cluster.GetClusters(cmd.Context(), runtimes.SelectedRuntime) - if err != nil { - log.Fatalln(err) - } - existingClusters = append(existingClusters, found...) - } else { // Option b) cluster name specified -> get specific cluster - for _, cluster := range clusters { - found, err := k3cluster.GetCluster(cmd.Context(), runtimes.SelectedRuntime, cluster) - if err != nil { - log.Fatalln(err) - } - existingClusters = append(existingClusters, found) - } - } - // print existing clusters - printClusters(existingClusters, headersOff) + clusters := buildClusterList(cmd.Context(), args) + PrintClusters(clusters, clusterFlags) }, } // add flags - cmd.Flags().Bool("no-headers", false, "Disable headers") + cmd.Flags().BoolVar(&clusterFlags.noHeader, "no-headers", false, "Disable headers") + cmd.Flags().BoolVar(&clusterFlags.token, "token", false, "Print k3s cluster token") // add subcommands @@ -79,54 +70,57 @@ func NewCmdGetCluster() *cobra.Command { return cmd } -func parseGetClusterCmd(cmd *cobra.Command, args []string) ([]*k3d.Cluster, bool) { +func buildClusterList(ctx context.Context, args []string) []*k3d.Cluster { + var clusters []*k3d.Cluster + var err error - // --no-headers - headersOff, err := cmd.Flags().GetBool("no-headers") - if err != nil { - log.Fatalln(err) - } - - // Args = cluster name if len(args) == 0 { - return nil, headersOff + // cluster name not specified : get all clusters + clusters, err = k3cluster.GetClusters(ctx, runtimes.SelectedRuntime) + if err != nil { + log.Fatalln(err) + } + } else { + for _, clusterName := range args { + // cluster name specified : get specific cluster + retrievedCluster, err := k3cluster.GetCluster(ctx, runtimes.SelectedRuntime, &k3d.Cluster{Name: clusterName}) + if err != nil { + log.Fatalln(err) + } + clusters = append(clusters, retrievedCluster) + } } - clusters := []*k3d.Cluster{} - for _, name := range args { - clusters = append(clusters, &k3d.Cluster{Name: name}) - } - - return clusters, headersOff + return clusters } -func printClusters(clusters []*k3d.Cluster, headersOff bool) { +// PrintPrintClusters : display list of cluster +func PrintClusters(clusters []*k3d.Cluster, flags clusterFlags) { tabwriter := tabwriter.NewWriter(os.Stdout, 6, 4, 3, ' ', tabwriter.RememberWidths) defer tabwriter.Flush() - if !headersOff { + if !flags.noHeader { headers := []string{"NAME", "MASTERS", "WORKERS"} // TODO: getCluster: add status column + if flags.token { + headers = append(headers, "TOKEN") + } _, err := fmt.Fprintf(tabwriter, "%s\n", strings.Join(headers, "\t")) if err != nil { log.Fatalln("Failed to print headers") } } - sort.Slice(clusters, func(i, j int) bool { - return clusters[i].Name < clusters[j].Name - }) + k3cluster.SortClusters(clusters) for _, cluster := range clusters { - masterCount := 0 - workerCount := 0 - for _, node := range cluster.Nodes { - if node.Role == k3d.MasterRole { - masterCount++ - } else if node.Role == k3d.WorkerRole { - workerCount++ - } + masterCount := cluster.MasterCount() + workerCount := cluster.WorkerCount() + + if flags.token { + fmt.Fprintf(tabwriter, "%s\t%d\t%d\t%s\n", cluster.Name, masterCount, workerCount, cluster.Token) + } else { + fmt.Fprintf(tabwriter, "%s\t%d\t%d\n", cluster.Name, masterCount, workerCount) } - fmt.Fprintf(tabwriter, "%s\t%d\t%d\n", cluster.Name, masterCount, workerCount) } } diff --git a/cmd/get/getKubeconfig.go b/cmd/get/getKubeconfig.go index 2be6cc97..9ec20596 100644 --- a/cmd/get/getKubeconfig.go +++ b/cmd/get/getKubeconfig.go @@ -69,7 +69,11 @@ func NewCmdGetKubeconfig() *cobra.Command { } } else { for _, clusterName := range args { - clusters = append(clusters, &k3d.Cluster{Name: clusterName}) + retrievedCluster, err := cluster.GetCluster(cmd.Context(), runtimes.SelectedRuntime, &k3d.Cluster{Name: clusterName}) + if err != nil { + log.Fatalln(err) + } + clusters = append(clusters, retrievedCluster) } } diff --git a/cmd/root.go b/cmd/root.go index 3190234c..8173c561 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -65,6 +65,10 @@ All Nodes of a k3d cluster are part of the same docker network.`, Run: func(cmd *cobra.Command, args []string) { if flags.version { printVersion() + } else { + if err := cmd.Usage(); err != nil { + log.Fatalln(err) + } } }, } @@ -73,8 +77,7 @@ All Nodes of a k3d cluster are part of the same docker network.`, // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { if err := rootCmd.Execute(); err != nil { - log.Errorln(err) - os.Exit(1) + log.Fatalln(err) } } diff --git a/docs/usage/commands.md b/docs/usage/commands.md index 12e8e430..5c2de08a 100644 --- a/docs/usage/commands.md +++ b/docs/usage/commands.md @@ -14,7 +14,7 @@ k3d --network # specify a network you want to connect to --no-image-volume # disable the creation of a volume for storing images (used for the 'k3d load image' command) -p, --port # add some more port mappings - --secret # specify a cluster secret (default: auto-generated) + --token # specify a cluster token (default: auto-generated) --timeout # specify a timeout, after which the cluster creation will be interrupted and changes rolled back --update-kubeconfig # enable the automated update of the default kubeconfig with the details of the newly created cluster (also sets '--wait=true') --switch # (implies --update-kubeconfig) automatically sets the current-context of your default kubeconfig to the new cluster's context diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 9eb74736..3b6634c7 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -25,6 +25,7 @@ import ( "bytes" "context" "fmt" + "sort" "strconv" "strings" "time" @@ -79,19 +80,19 @@ func CreateCluster(ctx context.Context, runtime k3drt.Runtime, cluster *k3d.Clus } cluster.Network.Name = networkID extraLabels := map[string]string{ - "k3d.cluster.network": networkID, - "k3d.cluster.network.external": strconv.FormatBool(cluster.Network.External), + k3d.LabelNetwork: networkID, + k3d.LabelNetworkExternal: strconv.FormatBool(cluster.Network.External), } if networkExists { - extraLabels["k3d.cluster.network.external"] = "true" // if the network wasn't created, we say that it's managed externally (important for cluster deletion) + extraLabels[k3d.LabelNetworkExternal] = "true" // if the network wasn't created, we say that it's managed externally (important for cluster deletion) } /* - * Cluster Secret + * Cluster Token */ - if cluster.Secret == "" { - cluster.Secret = GenerateClusterSecret() + if cluster.Token == "" { + cluster.Token = GenerateClusterToken() } /* @@ -105,7 +106,7 @@ func CreateCluster(ctx context.Context, runtime k3drt.Runtime, cluster *k3d.Clus return err } - extraLabels["k3d.cluster.imageVolume"] = imageVolumeName + extraLabels[k3d.LabelImageVolume] = imageVolumeName // attach volume to nodes for _, node := range cluster.Nodes { @@ -127,8 +128,8 @@ func CreateCluster(ctx context.Context, runtime k3drt.Runtime, cluster *k3d.Clus node.Labels = make(map[string]string) // TODO: maybe create an init function? } node.Labels["k3d.cluster"] = cluster.Name - node.Env = append(node.Env, fmt.Sprintf("K3S_TOKEN=%s", cluster.Secret)) - node.Labels["k3d.cluster.secret"] = cluster.Secret + node.Env = append(node.Env, fmt.Sprintf("K3S_TOKEN=%s", cluster.Token)) + node.Labels[k3d.LabelToken] = cluster.Token node.Labels["k3d.cluster.url"] = connectionURL // append extra labels @@ -419,7 +420,7 @@ func populateClusterFieldsFromLabels(cluster *k3d.Cluster) error { // get the name of the cluster network if cluster.Network.Name == "" { - if networkName, ok := node.Labels["k3d.cluster.network"]; ok { + if networkName, ok := node.Labels[k3d.LabelNetwork]; ok { cluster.Network.Name = networkName } } @@ -427,7 +428,7 @@ func populateClusterFieldsFromLabels(cluster *k3d.Cluster) error { // check if the network is external // since the struct value is a bool, initialized as false, we cannot check if it's unset if !cluster.Network.External && !networkExternalSet { - if networkExternalString, ok := node.Labels["k3d.cluster.network.external"]; ok { + if networkExternalString, ok := node.Labels[k3d.LabelNetworkExternal]; ok { if networkExternal, err := strconv.ParseBool(networkExternalString); err == nil { cluster.Network.External = networkExternal networkExternalSet = true @@ -437,11 +438,17 @@ func populateClusterFieldsFromLabels(cluster *k3d.Cluster) error { // get image volume // TODO: enable external image volumes the same way we do it with networks if cluster.ImageVolume == "" { - if imageVolumeName, ok := node.Labels["k3d.cluster.imageVolume"]; ok { + if imageVolumeName, ok := node.Labels[k3d.LabelImageVolume]; ok { cluster.ImageVolume = imageVolumeName } } + // get k3s cluster's token + if cluster.Token == "" { + if token, ok := node.Labels[k3d.LabelToken]; ok { + cluster.Token = token + } + } } return nil @@ -487,8 +494,8 @@ func GetCluster(ctx context.Context, runtime k3drt.Runtime, cluster *k3d.Cluster return cluster, nil } -// GenerateClusterSecret generates a random 20 character string -func GenerateClusterSecret() string { +// GenerateClusterToken generates a random 20 character string +func GenerateClusterToken() string { return util.GenerateRandomString(20) } @@ -585,3 +592,11 @@ func StopCluster(ctx context.Context, runtime k3drt.Runtime, cluster *k3d.Cluste } return nil } + +// SortClusters : in place sort cluster list by cluster name alphabetical order +func SortClusters(clusters []*k3d.Cluster) []*k3d.Cluster { + sort.Slice(clusters, func(i, j int) bool { + return clusters[i].Name < clusters[j].Name + }) + return clusters +} \ No newline at end of file diff --git a/pkg/tools/tools.go b/pkg/tools/tools.go index ddb8fdc4..21d87bbe 100644 --- a/pkg/tools/tools.go +++ b/pkg/tools/tools.go @@ -50,7 +50,7 @@ func LoadImagesIntoCluster(ctx context.Context, runtime runtimes.Runtime, images var ok bool for _, node := range cluster.Nodes { if node.Role == k3d.MasterRole || node.Role == k3d.WorkerRole { - if imageVolume, ok = node.Labels["k3d.cluster.imageVolume"]; ok { + if imageVolume, ok = node.Labels[k3d.LabelImageVolume]; ok { break } } diff --git a/pkg/types/types.go b/pkg/types/types.go index dd67d33e..b1950259 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -74,6 +74,14 @@ var DefaultObjectLabels = map[string]string{ "app": "k3d", } +// List of k3d technical label name +const ( + LabelToken string = "k3d.cluster.token" + LabelImageVolume string = "k3d.cluster.imageVolume" + LabelNetworkExternal string = "k3d.cluster.network.external" + LabelNetwork string = "k3d.cluster.network" +) + // DefaultRoleCmds maps the node roles to their respective default commands var DefaultRoleCmds = map[Role][]string{ MasterRole: {"server"}, @@ -152,7 +160,7 @@ type ClusterNetwork struct { type Cluster struct { Name string `yaml:"name" json:"name,omitempty"` Network ClusterNetwork `yaml:"network" json:"network,omitempty"` - Secret string `yaml:"cluster_secret" json:"clusterSecret,omitempty"` + Token string `yaml:"cluster_token" json:"clusterToken,omitempty"` Nodes []*Node `yaml:"nodes" json:"nodes,omitempty"` InitNode *Node // init master node ExternalDatastore ExternalDatastore `yaml:"external_datastore" json:"externalDatastore,omitempty"` @@ -162,6 +170,27 @@ type Cluster struct { ImageVolume string `yaml:"image_volume" json:"imageVolume,omitempty"` } +// MasterCount return number of master node into cluster +func (c *Cluster) MasterCount() int { + masterCount := 0 + for _, node := range c.Nodes { + if node.Role == MasterRole { + masterCount++ + } + } + return masterCount +} +// WorkerCount return number of worker node into cluster +func (c *Cluster) WorkerCount() int { + workerCount := 0 + for _, node := range c.Nodes { + if node.Role == WorkerRole { + workerCount++ + } + } + return workerCount +} + // Node describes a k3d node type Node struct { Name string `yaml:"name" json:"name,omitempty"` diff --git a/pkg/util/files.go b/pkg/util/files.go index 2eb48c2d..2276a3a8 100644 --- a/pkg/util/files.go +++ b/pkg/util/files.go @@ -43,7 +43,7 @@ func GetConfigDirOrCreate() (string, error) { // create directories if necessary if err := createDirIfNotExists(configDir); err != nil { - log.Errorln("Failed to create config path '%s'", configDir) + log.Errorf("Failed to create config path '%s'", configDir) return "", err } diff --git a/pkg/util/randomString.go b/pkg/util/randomString.go index 03ff2ee4..def2d959 100644 --- a/pkg/util/randomString.go +++ b/pkg/util/randomString.go @@ -37,7 +37,7 @@ const ( var src = rand.NewSource(time.Now().UnixNano()) // GenerateRandomString thanks to https://stackoverflow.com/a/31832326/6450189 -// GenerateRandomString is used to generate a random string that is used as a cluster secret +// GenerateRandomString is used to generate a random string that is used as a cluster token func GenerateRandomString(n int) string { sb := strings.Builder{} diff --git a/tests/common.sh b/tests/common.sh index f6464e4c..42b180f4 100755 --- a/tests/common.sh +++ b/tests/common.sh @@ -107,3 +107,8 @@ check_registry() { check_volume_exists() { docker volume inspect "$1" >/dev/null 2>&1 } + +check_cluster_token_exist() { + [ -n "$EXE" ] || abort "EXE is not defined" + $EXE get cluster "$1" --token | grep "TOKEN" >/dev/null 2>&1 +} diff --git a/tests/test_basic.sh b/tests/test_basic.sh index 1ace7ba0..bdf9b930 100755 --- a/tests/test_basic.sh +++ b/tests/test_basic.sh @@ -16,6 +16,10 @@ check_cluster_count 2 info "Checking we have access to both clusters..." check_clusters "c1" "c2" || failed "error checking cluster" +info "Check k3s token retrieval" +check_cluster_token_exist "c1" || failed "could not find cluster token c1" +check_cluster_token_exist "c2" || failed "could not find cluster token c2" + info "Deleting clusters..." $EXE delete cluster c1 || failed "could not delete the cluster c1" $EXE delete cluster c2 || failed "could not delete the cluster c2" diff --git a/tests/test_full_lifecycle.sh b/tests/test_full_lifecycle.sh index f3c5cf1a..fe0a48c8 100755 --- a/tests/test_full_lifecycle.sh +++ b/tests/test_full_lifecycle.sh @@ -40,7 +40,7 @@ check_multi_node "$clustername" 2 || failed "failed to verify number of nodes" # 4. adding another worker node info "Adding one worker node..." -LOG_LEVEL=debug $EXE create node "extra-worker" --cluster "$clustername" --role "worker" --wait --timeout 360s || failed "failed to add worker node" +$EXE create node "extra-worker" --cluster "$clustername" --role "worker" --wait --timeout 360s || failed "failed to add worker node" info "Checking that we have 3 nodes available now..." check_multi_node "$clustername" 3 || failed "failed to verify number of nodes"