From 29fced4ef78bfecc7e1168fccfb88588a0145caa Mon Sep 17 00:00:00 2001 From: Rishabh Gupta Date: Tue, 9 Apr 2019 13:47:47 +0530 Subject: [PATCH] Added version tags, separated command implementaitons, added wait and timeout Signed-off-by: Rishabh Gupta --- Makefile | 12 ++- cli/commands.go | 161 +++++++++++++++++++++++++++ config.go => cli/config.go | 2 +- cli/run.go | 18 ++++ main.go | 215 ++++--------------------------------- version/version.go | 12 +++ 6 files changed, 221 insertions(+), 199 deletions(-) create mode 100644 cli/commands.go rename config.go => cli/config.go (99%) create mode 100644 cli/run.go create mode 100644 version/version.go diff --git a/Makefile b/Makefile index 79a7a02a1..b8fdd7360 100644 --- a/Makefile +++ b/Makefile @@ -4,21 +4,27 @@ SHELL := /bin/bash TARGETS ?= darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 windows/amd64 TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-386.tar.gz linux-386.tar.gz.sha256 linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm64.tar.gz linux-arm64.tar.gz.sha256 windows-amd64.zip windows-amd64.zip.sha256 +# get git tag +GIT_TAG := $(shell git describe --tags) +ifeq ($(GIT_TAG),) +GIT_TAG := $(shell git describe --always) +endif + # Go options GO ?= go PKG := $(shell go mod vendor) TAGS := TESTS := . TESTFLAGS := -LDFLAGS := -w -s +LDFLAGS := -w -s -X github.com/iwilltry42/k3d-go/version.Version=${GIT_TAG} GOFLAGS := BINDIR := $(CURDIR)/bin -BINARIES := k3d +BINARIES := k3d export GO111MODULE=on # go source files, ignore vendor directory -SRC = $(shell find . -type f -name '*.go' -not -path "./vendor/*") +SRC = $(shell find . -type f -name '*.go' -not -path "./*/*") .PHONY: all build build-cross clean fmt simplify check diff --git a/cli/commands.go b/cli/commands.go new file mode 100644 index 000000000..323520901 --- /dev/null +++ b/cli/commands.go @@ -0,0 +1,161 @@ +package run + +import ( + "errors" + "fmt" + "log" + "os" + "os/exec" + "path" + "strings" + "time" + + "github.com/urfave/cli" +) + +// CheckTools checks if the installed tools work correctly +func CheckTools(c *cli.Context) error { + log.Print("Checking docker...") + cmd := "docker" + args := []string{"version"} + if err := runCommand(true, cmd, args...); err != nil { + log.Fatalf("Checking docker: FAILED") + return err + } + log.Println("Checking docker: SUCCESS") + return nil +} + +// CreateCluster creates a new single-node cluster container and initializes the cluster directory +func CreateCluster(c *cli.Context) error { + if c.IsSet("timeout") && !c.IsSet("wait") { + return errors.New("--wait flag is not specified") + } + port := fmt.Sprintf("%s:%s", c.String("port"), c.String("port")) + image := fmt.Sprintf("rancher/k3s:%s", c.String("version")) + cmd := "docker" + args := []string{ + "run", + "--name", c.String("name"), + "-e", "K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml", + "--publish", port, + "--privileged", + } + extraArgs := []string{} + if c.IsSet("volume") { + extraArgs = append(extraArgs, "--volume", c.String("volume")) + } + if len(extraArgs) > 0 { + args = append(args, extraArgs...) + } + args = append(args, + "-d", + image, + "server", // cmd + "--https-listen-port", c.String("port"), //args + ) + log.Printf("Creating cluster [%s]", c.String("name")) + if err := runCommand(true, cmd, args...); err != nil { + log.Fatalf("FAILURE: couldn't create cluster [%s] -> %+v", c.String("name"), err) + return err + } + + start := time.Now() + timeout := time.Duration(c.Int("timeout")) * time.Second + for c.IsSet("wait") { + if timeout != 0 && !time.Now().After(start.Add(timeout)) { + err := DeleteCluster(c) + if err != nil { + return err + } + return errors.New("Cluster timeout expired") + } + cmd := "docker" + args = []string{ + "logs", + c.String("name"), + } + prog := exec.Command(cmd, args...) + output, err := prog.CombinedOutput() + if err != nil { + return err + } + if strings.Contains(string(output), "Running kubelet") { + break + } + + time.Sleep(1 * time.Second) + } + + createClusterDir(c.String("name")) + log.Printf("SUCCESS: created cluster [%s]", c.String("name")) + log.Printf(`You can now use the cluster with: + +export KUBECONFIG="$(%s get-kubeconfig --name='%s')" +kubectl cluster-info`, os.Args[0], c.String("name")) + return nil +} + +// DeleteCluster removes the cluster container and its cluster directory +func DeleteCluster(c *cli.Context) error { + cmd := "docker" + args := []string{"rm", c.String("name")} + log.Printf("Deleting cluster [%s]", c.String("name")) + if err := runCommand(true, cmd, args...); err != nil { + log.Printf("WARNING: couldn't delete cluster [%s], trying a force remove now.", c.String("name")) + args = append(args, "-f") + if err := runCommand(true, cmd, args...); err != nil { + log.Fatalf("FAILURE: couldn't delete cluster [%s] -> %+v", c.String("name"), err) + return err + } + } + deleteClusterDir(c.String("name")) + log.Printf("SUCCESS: deleted cluster [%s]", c.String("name")) + return nil +} + +// StopCluster stops a running cluster container (restartable) +func StopCluster(c *cli.Context) error { + cmd := "docker" + args := []string{"stop", c.String("name")} + log.Printf("Stopping cluster [%s]", c.String("name")) + if err := runCommand(true, cmd, args...); err != nil { + log.Fatalf("FAILURE: couldn't stop cluster [%s] -> %+v", c.String("name"), err) + return err + } + log.Printf("SUCCESS: stopped cluster [%s]", c.String("name")) + return nil +} + +// StartCluster starts a stopped cluster container +func StartCluster(c *cli.Context) error { + cmd := "docker" + args := []string{"start", c.String("name")} + log.Printf("Starting cluster [%s]", c.String("name")) + if err := runCommand(true, cmd, args...); err != nil { + log.Fatalf("FAILURE: couldn't start cluster [%s] -> %+v", c.String("name"), err) + return err + } + log.Printf("SUCCESS: started cluster [%s]", c.String("name")) + return nil +} + +// ListClusters prints a list of created clusters +func ListClusters(c *cli.Context) error { + printClusters(c.Bool("all")) + return nil +} + +// GetKubeConfig grabs the kubeconfig from the running cluster and prints the path to stdout +func GetKubeConfig(c *cli.Context) error { + sourcePath := fmt.Sprintf("%s:/output/kubeconfig.yaml", c.String("name")) + destPath, _ := getClusterDir(c.String("name")) + cmd := "docker" + args := []string{"cp", sourcePath, destPath} + if err := runCommand(false, cmd, args...); err != nil { + log.Fatalf("FAILURE: couldn't get kubeconfig for cluster [%s] -> %+v", c.String("name"), err) + return err + } + fmt.Printf("%s\n", path.Join(destPath, "kubeconfig.yaml")) + return nil +} diff --git a/config.go b/cli/config.go similarity index 99% rename from config.go rename to cli/config.go index 4ea3ca736..3b0c949e6 100644 --- a/config.go +++ b/cli/config.go @@ -1,4 +1,4 @@ -package main +package run import ( "context" diff --git a/cli/run.go b/cli/run.go new file mode 100644 index 000000000..770770f49 --- /dev/null +++ b/cli/run.go @@ -0,0 +1,18 @@ +package run + +import ( + "log" + "os" + "os/exec" +) + +// runCommand accepts the name and args and runs the specified command +func runCommand(verbose bool, name string, args ...string) error { + if verbose { + log.Printf("Running command: %+v", append([]string{name}, args...)) + } + cmd := exec.Command(name, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/main.go b/main.go index a1f5a55b7..007b0b9fd 100644 --- a/main.go +++ b/main.go @@ -1,178 +1,14 @@ package main import ( - "fmt" "log" "os" - "os/exec" - "path" + "github.com/iwilltry42/k3d-go/cli" + "github.com/iwilltry42/k3d-go/version" "github.com/urfave/cli" ) -// createCluster creates a new single-node cluster container and initializes the cluster directory -func createCluster(c *cli.Context) error { - createClusterDir(c.String("name")) - port := fmt.Sprintf("%s:%s", c.String("port"), c.String("port")) - image := fmt.Sprintf("rancher/k3s:%s", c.String("version")) - cmd := "docker" - args := []string{ - "run", - "--name", c.String("name"), - "-e", "K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml", - "--publish", port, - "--privileged", - } - extraArgs := []string{} - if c.IsSet("volume") { - extraArgs = append(extraArgs, "--volume", c.String("volume")) - } - if len(extraArgs) > 0 { - args = append(args, extraArgs...) - } - args = append(args, - "-d", - image, - "server", // cmd - "--https-listen-port", c.String("port"), //args - ) - log.Printf("Creating cluster [%s]", c.String("name")) - if err := run(true, cmd, args...); err != nil { - log.Fatalf("FAILURE: couldn't create cluster [%s] -> %+v", c.String("name"), err) - return err - } - log.Printf("SUCCESS: created cluster [%s]", c.String("name")) - log.Printf(`You can now use the cluster with: - -export KUBECONFIG="$(%s get-kubeconfig --name='%s')" -kubectl cluster-info`, os.Args[0], c.String("name")) - return nil -} - -// deleteCluster removes the cluster container and its cluster directory -func deleteCluster(c *cli.Context) error { - cmd := "docker" - args := []string{"rm"} - clusters := []string{} - - // operate on one or all clusters - if !c.Bool("all") { - clusters = append(clusters, c.String("name")) - } else { - clusterList, err := getClusterNames() - if err != nil { - log.Fatalf("ERROR: `--all` specified, but no clusters were found.") - } - clusters = append(clusters, clusterList...) - } - - // remove clusters one by one instead of appending all names to the docker command - // this allows for more granular error handling and logging - for _, cluster := range clusters { - log.Printf("Removing cluster [%s]", cluster) - args = append(args, cluster) - if err := run(true, cmd, args...); err != nil { - log.Printf("WARNING: couldn't delete cluster [%s], trying a force remove now.", cluster) - args = args[:len(args)-1] // pop last element from list (name of cluster) - args = append(args, "-f", cluster) - if err := run(true, cmd, args...); err != nil { - log.Printf("FAILURE: couldn't delete cluster [%s] -> %+v", cluster, err) - } - args = args[:len(args)-1] // pop last element from list (-f flag) - } - deleteClusterDir(cluster) - log.Printf("SUCCESS: removed cluster [%s]", cluster) - args = args[:len(args)-1] // pop last element from list (name of last cluster) - } - - return nil - -} - -// stopCluster stops a running cluster container (restartable) -func stopCluster(c *cli.Context) error { - cmd := "docker" - args := []string{"stop"} - clusters := []string{} - - // operate on one or all clusters - if !c.Bool("all") { - clusters = append(clusters, c.String("name")) - } else { - clusterList, err := getClusterNames() - if err != nil { - log.Fatalf("ERROR: `--all` specified, but no clusters were found.") - } - clusters = append(clusters, clusterList...) - } - - // stop clusters one by one instead of appending all names to the docker command - // this allows for more granular error handling and logging - for _, cluster := range clusters { - log.Printf("Starting cluster [%s]", cluster) - args = append(args, cluster) - if err := run(true, cmd, args...); err != nil { - log.Printf("FAILURE: couldn't stop cluster [%s] -> %+v", cluster, err) - } - log.Printf("SUCCESS: stopped cluster [%s]", cluster) - args = args[:len(args)-1] // pop last element from list (name of last cluster) - } - - return nil -} - -// startCluster starts a stopped cluster container -func startCluster(c *cli.Context) error { - cmd := "docker" - args := []string{"start"} - clusters := []string{} - - // operate on one or all clusters - if !c.Bool("all") { - clusters = append(clusters, c.String("name")) - } else { - clusterList, err := getClusterNames() - if err != nil { - log.Fatalf("ERROR: `--all` specified, but no clusters were found.") - } - clusters = append(clusters, clusterList...) - } - - // start clusters one by one instead of appending all names to the docker command - // this allows for more granular error handling and logging - for _, cluster := range clusters { - log.Printf("Starting cluster [%s]", cluster) - args = append(args, cluster) - if err := run(true, cmd, args...); err != nil { - log.Printf("FAILURE: couldn't start cluster [%s] -> %+v", cluster, err) - } - log.Printf("SUCCESS: started cluster [%s]", cluster) - args = args[:len(args)-1] // pop last element from list (name of last cluster) - } - - return nil -} - -// listClusters prints a list of created clusters -func listClusters(c *cli.Context) error { - printClusters(c.Bool("all")) - return nil -} - -// getKubeConfig grabs the kubeconfig from the running cluster and prints the path to stdout -func getKubeConfig(c *cli.Context) error { - sourcePath := fmt.Sprintf("%s:/output/kubeconfig.yaml", c.String("name")) - destPath, _ := getClusterDir(c.String("name")) - cmd := "docker" - args := []string{"cp", sourcePath, destPath} - if err := run(false, cmd, args...); err != nil { - log.Fatalf("FAILURE: couldn't get kubeconfig for cluster [%s] -> %+v", c.String("name"), err) - return err - } - fmt.Printf("%s\n", path.Join(destPath, "kubeconfig.yaml")) - return nil -} - // main represents the CLI application func main() { @@ -180,7 +16,7 @@ func main() { app := cli.NewApp() app.Name = "k3d" app.Usage = "Run k3s in Docker!" - app.Version = "v0.1.1" + app.Version = version.GetVersion() app.Authors = []cli.Author{ cli.Author{ Name: "iwilltry42", @@ -195,17 +31,7 @@ func main() { Name: "check-tools", Aliases: []string{"ct"}, Usage: "Check if docker is running", - Action: func(c *cli.Context) error { - log.Print("Checking docker...") - cmd := "docker" - args := []string{"version"} - if err := run(true, cmd, args...); err != nil { - log.Fatalf("Checking docker: FAILED") - return err - } - log.Println("Checking docker: SUCCESS") - return nil - }, + Action: run.CheckTools, }, { // create creates a new k3s cluster in a container @@ -220,7 +46,7 @@ func main() { }, cli.StringFlag{ Name: "volume, v", - Usage: "Mount a volume into the cluster node (Docker notation: `source:destination`", + Usage: "Mount a volume into the cluster node (Docker notation: `source:destination`)", }, cli.StringFlag{ Name: "version", @@ -232,8 +58,17 @@ func main() { Value: 6443, Usage: "Set a port on which the ApiServer will listen", }, + cli.IntFlag{ + Name: "timeout, t", + Value: 0, + Usage: "Set the timeout value when --wait flag is set", + }, + cli.BoolFlag{ + Name: "wait, w", + Usage: "Wait for the cluster to come up", + }, }, - Action: createCluster, + Action: run.CreateCluster, }, { // delete deletes an existing k3s cluster (remove container and cluster directory) @@ -251,7 +86,7 @@ func main() { Usage: "delete all existing clusters (this ignores the --name/-n flag)", }, }, - Action: deleteCluster, + Action: run.DeleteCluster, }, { // stop stopy a running cluster (its container) so it's restartable @@ -268,7 +103,7 @@ func main() { Usage: "stop all running clusters (this ignores the --name/-n flag)", }, }, - Action: stopCluster, + Action: run.StopCluster, }, { // start restarts a stopped cluster container @@ -285,7 +120,7 @@ func main() { Usage: "start all stopped clusters (this ignores the --name/-n flag)", }, }, - Action: startCluster, + Action: run.StartCluster, }, { // list prints a list of created clusters @@ -298,7 +133,7 @@ func main() { Usage: "also show non-running clusters", }, }, - Action: listClusters, + Action: run.ListClusters, }, { // get-kubeconfig grabs the kubeconfig from the cluster and prints the path to it @@ -315,7 +150,7 @@ func main() { Usage: "get kubeconfig for all clusters (this ignores the --name/-n flag)", }, }, - Action: getKubeConfig, + Action: run.GetKubeConfig, }, } @@ -325,13 +160,3 @@ func main() { log.Fatal(err) } } - -func run(verbose bool, name string, args ...string) error { - if verbose { - log.Printf("Running command: %+v", append([]string{name}, args...)) - } - cmd := exec.Command(name, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} diff --git a/version/version.go b/version/version.go new file mode 100644 index 000000000..8fc22c077 --- /dev/null +++ b/version/version.go @@ -0,0 +1,12 @@ +package version + +// Version is the string that contains version +var Version string + +// GetVersion returns the version for cli, it gets it from "git describe --tags" or returns "dev" when doing simple go build +func GetVersion() string { + if len(Version) == 0 { + return "dev" + } + return Version +}