From 58a32b3003636fa388026a392fc4192047209ed4 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Thu, 4 Apr 2019 09:46:03 +0200 Subject: [PATCH 01/12] add --all flag to every command where it makes sense --- main.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/main.go b/main.go index 4a5811d00..5d808cdb6 100644 --- a/main.go +++ b/main.go @@ -187,6 +187,10 @@ func main() { Value: "k3s_default", Usage: "name of the cluster", }, + cli.BoolFlag{ + Name: "all, a", + Usage: "delete all existing clusters (this ignores the --name/-n flag)", + }, }, Action: deleteCluster, }, @@ -200,6 +204,10 @@ func main() { Value: "k3s_default", Usage: "name of the cluster", }, + cli.BoolFlag{ + Name: "all, a", + Usage: "stop all running clusters (this ignores the --name/-n flag)", + }, }, Action: stopCluster, }, @@ -213,6 +221,10 @@ func main() { Value: "k3s_default", Usage: "name of the cluster", }, + cli.BoolFlag{ + Name: "all, a", + Usage: "start all stopped clusters (this ignores the --name/-n flag)", + }, }, Action: startCluster, }, @@ -238,6 +250,10 @@ func main() { Value: "k3s_default", Usage: "name of the cluster", }, + cli.BoolFlag{ + Name: "all, a", + Usage: "get kubeconfig for all clusters (this ignores the --name/-n flag)", + }, }, Action: getKubeConfig, }, From 9d0a4e9832e3d584d054195496fb62056827c56f Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Thu, 4 Apr 2019 10:40:48 +0200 Subject: [PATCH 02/12] only test against 1.12.x --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6bb1125d5..add2a73ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ env: - GO111MODULE=on go: - 1.12.x -- master git: depth: 1 install: true From 50370c25c7e33ea40e9ecc02ed015c5c3a4a97f3 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Thu, 4 Apr 2019 11:15:28 +0200 Subject: [PATCH 03/12] add go report badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f3be50fe..9462ef0b8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # k3d-go -![TravisCI](https://travis-ci.com/iwilltry42/k3d-go.svg?branch=master) +[![Build Status](https://travis-ci.com/iwilltry42/k3d-go.svg?branch=master)](https://travis-ci.com/iwilltry42/k3d-go) +[![Go Report Card](https://goreportcard.com/badge/github.com/iwilltry42/k3d-go)](https://goreportcard.com/report/github.com/iwilltry42/k3d-go) ## k3s in docker From fae59d652a39f997eaf3ee7733bdc71c269d85f1 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Thu, 4 Apr 2019 11:24:43 +0200 Subject: [PATCH 04/12] update readme and bump version --- README.md | 8 ++++++-- main.go | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9462ef0b8..0ab858ee6 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,15 @@ Thanks to @zeerorg for the original work! Grab a release from the [release tab](https://github.com/iwilltry42/k3d-go/releases). +or `go install github.com/iwilltry42/k3d-go` + or... ## Build 1. Clone this repo, e.g. via `go get -u github.com/iwilltry42/k3d-go/releases` 2. Inside the repo run - - `make bootstrap` to install build tools and then `make build` to build for your current system + - `make` to build for your current system - `go install` to install it to your `GOPATH` - `make build-cross` to build for all systems @@ -44,4 +46,6 @@ Example Workflow: Create a new cluster and use it with `kubectl` - [ ] Use the docker client library instead of commands - [ ] Test the docker version -- [ ] Improve cluster state management \ No newline at end of file +- [ ] Improve cluster state management +- [ ] Use [hsirupsen/logrus](https://github.com/sirupsen/logrus) for prettier logs +- [ ] Add install script \ No newline at end of file diff --git a/main.go b/main.go index 5d808cdb6..d968230c1 100644 --- a/main.go +++ b/main.go @@ -120,7 +120,7 @@ func main() { app := cli.NewApp() app.Name = "k3d" app.Usage = "Run k3s in Docker!" - app.Version = "0.0.2" + app.Version = "0.1.0" app.Authors = []cli.Author{ cli.Author{ Name: "iwilltry42", @@ -136,7 +136,6 @@ func main() { Aliases: []string{"ct"}, Usage: "Check if docker is running", Action: func(c *cli.Context) error { - //TODO: own function with version check log.Print("Checking docker...") cmd := "docker" args := []string{"version"} From 68c38a104fdaa2acfce6a38499777af421a9cc21 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Thu, 4 Apr 2019 09:06:56 -0700 Subject: [PATCH 05/12] Add ls and l alias to list I find myself typing ls often by accident. --- main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index d968230c1..13ea178db 100644 --- a/main.go +++ b/main.go @@ -229,8 +229,9 @@ func main() { }, { // list prints a list of created clusters - Name: "list", - Usage: "List all clusters", + Name: "list", + Aliases: []string{"ls", "l"}, + Usage: "List all clusters", Flags: []cli.Flag{ cli.BoolFlag{ Name: "all, a", From 845339e9da3c5fa3466160b977d26095669c723f Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Fri, 5 Apr 2019 14:44:07 +0200 Subject: [PATCH 06/12] add install script --- README.md | 2 +- install.sh | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 2 +- 3 files changed, 189 insertions(+), 2 deletions(-) create mode 100755 install.sh diff --git a/README.md b/README.md index 0ab858ee6..20c80e1d8 100644 --- a/README.md +++ b/README.md @@ -47,5 +47,5 @@ Example Workflow: Create a new cluster and use it with `kubectl` - [ ] Use the docker client library instead of commands - [ ] Test the docker version - [ ] Improve cluster state management -- [ ] Use [hsirupsen/logrus](https://github.com/sirupsen/logrus) for prettier logs +- [ ] Use [sirupsen/logrus](https://github.com/sirupsen/logrus) for prettier logs - [ ] Add install script \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 000000000..7cd0cc9c6 --- /dev/null +++ b/install.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash + +APP_NAME="k3d" +REPO_URL="https://github.com/iwilltry42/k3d-go" + +: ${USE_SUDO:="true"} +: ${K3D_INSTALL_DIR:="/usr/local/bin"} + +# initArch discovers the architecture for this system. +initArch() { + ARCH=$(uname -m) + case $ARCH in + armv5*) ARCH="armv5";; + armv6*) ARCH="armv6";; + armv7*) ARCH="arm";; + aarch64) ARCH="arm64";; + x86) ARCH="386";; + x86_64) ARCH="amd64";; + i686) ARCH="386";; + i386) ARCH="386";; + esac +} + +# initOS discovers the operating system for this system. +initOS() { + OS=$(echo `uname`|tr '[:upper:]' '[:lower:]') + + case "$OS" in + # Minimalist GNU for Windows + mingw*) OS='windows';; + esac +} + +# runs the given command as root (detects if we are root already) +runAsRoot() { + local CMD="$*" + + if [ $EUID -ne 0 -a $USE_SUDO = "true" ]; then + CMD="sudo $CMD" + fi + + $CMD +} + +# verifySupported checks that the os/arch combination is supported for +# binary builds. +verifySupported() { + local supported="darwin-386\ndarwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nwindows-386\nwindows-amd64" + if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then + echo "No prebuilt binary for ${OS}-${ARCH}." + echo "To build from source, go to $REPO_URL" + exit 1 + fi + + if ! type "curl" > /dev/null && ! type "wget" > /dev/null; then + echo "Either curl or wget is required" + exit 1 + fi +} + +# checkK3dInstalledVersion checks which version of k3d is installed and +# if it needs to be changed. +checkK3dInstalledVersion() { + if [[ -f "${K3D_INSTALL_DIR}/${APP_NAME}" ]]; then + local version=$(k3d --version | cut -d " " -f3) + if [[ "$version" == "$TAG" ]]; then + echo "k3d ${version} is already ${DESIRED_VERSION:-latest}" + return 0 + else + echo "k3d ${TAG} is available. Changing from version ${version}." + return 1 + fi + else + return 1 + fi +} + +# checkLatestVersion grabs the latest version string from the releases +checkLatestVersion() { + local latest_release_url="$REPO_URL/releases/latest" + if type "curl" > /dev/null; then + TAG=$(curl -Ls -o /dev/null -w %{url_effective} $latest_release_url | grep -oE "[^/]+$" ) + elif type "wget" > /dev/null; then + TAG=$(wget $latest_release_url --server-response -O /dev/null 2>&1 | awk '/^ Location: /{DEST=$2} END{ print DEST}' | grep -oE "[^/]+$") + fi +} + +# downloadFile downloads the latest binary package and also the checksum +# for that binary. +downloadFile() { + K3D_DIST="k3d-$OS-$ARCH" + DOWNLOAD_URL="$REPO_URL/releases/download/$TAG/$K3D_DIST" + K3D_TMP_ROOT="$(mktemp -dt k3d-binary-XXXXXX)" + K3D_TMP_FILE="$K3D_TMP_ROOT/$K3D_DIST" + if type "curl" > /dev/null; then + curl -SsL "$DOWNLOAD_URL" -o "$K3D_TMP_FILE" + elif type "wget" > /dev/null; then + wget -q -O "$K3D_TMP_FILE" "$DOWNLOAD_URL" + fi +} + +# installFile verifies the SHA256 for the file, then unpacks and +# installs it. +installFile() { + echo "Preparing to install $APP_NAME into ${K3D_INSTALL_DIR}" + runAsRoot chmod +x "$K3D_TMP_FILE" + runAsRoot cp "$K3D_TMP_FILE" "$K3D_INSTALL_DIR/$APP_NAME" + echo "$APP_NAME installed into $K3D_INSTALL_DIR/$APP_NAME" +} + +# fail_trap is executed if an error occurs. +fail_trap() { + result=$? + if [ "$result" != "0" ]; then + if [[ -n "$INPUT_ARGUMENTS" ]]; then + echo "Failed to install $APP_NAME with the arguments provided: $INPUT_ARGUMENTS" + help + else + echo "Failed to install $APP_NAME" + fi + echo -e "\tFor support, go to $REPO_URL." + fi + cleanup + exit $result +} + +# testVersion tests the installed client to make sure it is working. +testVersion() { + set +e + K3D="$(which $APP_NAME)" + if [ "$?" = "1" ]; then + echo "$APP_NAME not found. Is $K3D_INSTALL_DIR on your "'$PATH?' + exit 1 + fi + set -e + echo "Run '$APP_NAME --help' to see what you can do with it." +} + +# help provides possible cli installation arguments +help () { + echo "Accepted cli arguments are:" + echo -e "\t[--help|-h ] ->> prints this help" + echo -e "\t[--no-sudo] ->> install without sudo" +} + +# cleanup temporary files +cleanup() { + if [[ -d "${K3D_TMP_ROOT:-}" ]]; then + rm -rf "$K3D_TMP_ROOT" + fi +} + +# Execution + +#Stop execution on any error +trap "fail_trap" EXIT +set -e + +# Parsing input arguments (if any) +export INPUT_ARGUMENTS="${@}" +set -u +while [[ $# -gt 0 ]]; do + case $1 in + '--no-sudo') + USE_SUDO="false" + ;; + '--help'|-h) + help + exit 0 + ;; + *) exit 1 + ;; + esac + shift +done +set +u + +initArch +initOS +verifySupported +checkLatestVersion +if ! checkK3dInstalledVersion; then + downloadFile + installFile +fi +testVersion +cleanup diff --git a/main.go b/main.go index 13ea178db..ce91b8145 100644 --- a/main.go +++ b/main.go @@ -120,7 +120,7 @@ func main() { app := cli.NewApp() app.Name = "k3d" app.Usage = "Run k3s in Docker!" - app.Version = "0.1.0" + app.Version = "v0.1.1" app.Authors = []cli.Author{ cli.Author{ Name: "iwilltry42", From 18dfa5dda922a2c76d31a391cf9843def44a5c0e Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Fri, 5 Apr 2019 14:53:17 +0200 Subject: [PATCH 07/12] install script in readme --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 20c80e1d8..a146a761b 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,13 @@ Thanks to @zeerorg for the original work! ## Install -Grab a release from the [release tab](https://github.com/iwilltry42/k3d-go/releases). +You have several options there: -or `go install github.com/iwilltry42/k3d-go` +- use the install script to grab the latest release: + - wget: `wget -q -O - https://raw.githubusercontent.com/iwilltry42/k3d-go/master/install.sh | bash` + - curl: `curl -s https://raw.githubusercontent.com/iwilltry42/k3d-go/master/install.sh | bash` +- Grab a release from the [release tab](https://github.com/iwilltry42/k3d-go/releases) and install it yourself. +- Via go: `go install github.com/iwilltry42/k3d-go` or... From 476e3de6252d963e66f1f1b00394426ae1660972 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Tue, 9 Apr 2019 09:16:09 +0200 Subject: [PATCH 08/12] improve cluster handling via struct and add --all flag to commands --- config.go | 49 ++++++++++++++++++++------ main.go | 102 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 120 insertions(+), 31 deletions(-) diff --git a/config.go b/config.go index f63846104..4ea3ca736 100644 --- a/config.go +++ b/config.go @@ -12,6 +12,12 @@ import ( "github.com/olekukonko/tablewriter" ) +type cluster struct { + name string + image string + status string +} + // createDirIfNotExists checks for the existence of a directory and creates it along with all required parents if not. // It returns an error if the directory (or parents) couldn't be created and nil if it worked fine or if the path already exists. func createDirIfNotExists(path string) error { @@ -50,30 +56,30 @@ func getClusterDir(name string) (string, error) { // printClusters prints the names of existing clusters func printClusters(all bool) { - clusters, err := getClusters() + clusterNames, err := getClusterNames() if err != nil { log.Fatalf("ERROR: Couldn't list clusters -> %+v", err) } - docker, err := dockerClient.NewEnvClient() - if err != nil { - log.Printf("WARNING: couldn't get docker info -> %+v", err) + if len(clusterNames) == 0 { + log.Printf("No clusters found!") + return } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"NAME", "IMAGE", "STATUS"}) - for _, cluster := range clusters { - containerInfo, _ := docker.ContainerInspect(context.Background(), cluster) - clusterData := []string{cluster, containerInfo.Config.Image, containerInfo.ContainerJSONBase.State.Status} - if containerInfo.ContainerJSONBase.State.Status == "running" || all { + for _, clusterName := range clusterNames { + cluster, _ := getCluster(clusterName) + clusterData := []string{cluster.name, cluster.image, cluster.status} + if cluster.status == "running" || all { table.Append(clusterData) } } table.Render() } -// getClusters returns a list of cluster names which are folder names in the config directory -func getClusters() ([]string, error) { +// getClusterNames returns a list of cluster names which are folder names in the config directory +func getClusterNames() ([]string, error) { homeDir, err := homedir.Dir() if err != nil { log.Printf("ERROR: Couldn't get user's home directory") @@ -93,3 +99,26 @@ func getClusters() ([]string, error) { } return clusters, nil } + +// getCluster creates a cluster struct with populated information fields +func getCluster(name string) (cluster, error) { + cluster := cluster{ + name: name, + image: "UNKNOWN", + status: "UNKNOWN", + } + + docker, err := dockerClient.NewEnvClient() + if err != nil { + log.Printf("ERROR: couldn't create docker client -> %+v", err) + return cluster, err + } + containerInfo, err := docker.ContainerInspect(context.Background(), cluster.name) + if err != nil { + log.Printf("WARNING: couldn't get docker info for [%s] -> %+v", cluster.name, err) + } else { + cluster.image = containerInfo.Config.Image + cluster.status = containerInfo.ContainerJSONBase.State.Status + } + return cluster, nil +} diff --git a/main.go b/main.go index ce91b8145..a1f5a55b7 100644 --- a/main.go +++ b/main.go @@ -52,44 +52,104 @@ kubectl cluster-info`, os.Args[0], c.String("name")) // 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 := run(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") + 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.Fatalf("FAILURE: couldn't delete cluster [%s] -> %+v", c.String("name"), err) - return err + 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) } - 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 := run(true, cmd, args...); err != nil { - log.Fatalf("FAILURE: couldn't stop cluster [%s] -> %+v", c.String("name"), err) - return err + 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...) } - log.Printf("SUCCESS: stopped cluster [%s]", c.String("name")) + + // 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", c.String("name")} - log.Printf("Starting cluster [%s]", c.String("name")) - if err := run(true, cmd, args...); err != nil { - log.Fatalf("FAILURE: couldn't start cluster [%s] -> %+v", c.String("name"), err) - return err + 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) } - log.Printf("SUCCESS: started cluster [%s]", c.String("name")) + return nil } From 29fced4ef78bfecc7e1168fccfb88588a0145caa Mon Sep 17 00:00:00 2001 From: Rishabh Gupta Date: Tue, 9 Apr 2019 13:47:47 +0530 Subject: [PATCH 09/12] 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 +} From 93c8d8ddf05146c5b5c5ac035cf1799fee2dea66 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Tue, 9 Apr 2019 10:20:14 +0200 Subject: [PATCH 10/12] bump version to 0.1.2 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index a1f5a55b7..bf0499bf3 100644 --- a/main.go +++ b/main.go @@ -180,7 +180,7 @@ func main() { app := cli.NewApp() app.Name = "k3d" app.Usage = "Run k3s in Docker!" - app.Version = "v0.1.1" + app.Version = "v0.1.2" app.Authors = []cli.Author{ cli.Author{ Name: "iwilltry42", From 0b35d7c138ecb8dca4b6af7e9607a51f40b35110 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Tue, 9 Apr 2019 15:12:38 +0200 Subject: [PATCH 11/12] get back --all function for all commands --- cli/commands.go | 101 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/cli/commands.go b/cli/commands.go index 323520901..467f37d13 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -99,44 +99,103 @@ kubectl cluster-info`, os.Args[0], c.String("name")) // 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") + 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 := runCommand(true, cmd, args...); err != nil { - log.Fatalf("FAILURE: couldn't delete cluster [%s] -> %+v", c.String("name"), err) - return err + 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 := runCommand(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) } - 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 + 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...) } - log.Printf("SUCCESS: stopped cluster [%s]", c.String("name")) + + // 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 := runCommand(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", 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 + 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...) } - log.Printf("SUCCESS: started cluster [%s]", c.String("name")) + + // 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 := runCommand(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 } From 65c0bf9e5c6625f288dc5e8ddf2cd96785b0bd6a Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Wed, 10 Apr 2019 08:38:54 +0200 Subject: [PATCH 12/12] better errors --- cli/commands.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/cli/commands.go b/cli/commands.go index 467f37d13..13a80dd35 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -19,17 +19,16 @@ func CheckTools(c *cli.Context) error { cmd := "docker" args := []string{"version"} if err := runCommand(true, cmd, args...); err != nil { - log.Fatalf("Checking docker: FAILED") - return err + return fmt.Errorf("ERROR: checking docker failed\n%+v", err) } - log.Println("Checking docker: SUCCESS") + log.Println("SUCCESS: Checking docker succeeded") 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") + return errors.New("Cannot use --timeout flag without --wait flag") } port := fmt.Sprintf("%s:%s", c.String("port"), c.String("port")) image := fmt.Sprintf("rancher/k3s:%s", c.String("version")) @@ -56,8 +55,7 @@ func CreateCluster(c *cli.Context) error { ) 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 + return fmt.Errorf("ERROR: couldn't create cluster [%s]\n%+v", c.String("name"), err) } start := time.Now() @@ -68,7 +66,7 @@ func CreateCluster(c *cli.Context) error { if err != nil { return err } - return errors.New("Cluster timeout expired") + return errors.New("Cluster creation exceeded specified timeout") } cmd := "docker" args = []string{ @@ -108,7 +106,7 @@ func DeleteCluster(c *cli.Context) error { } else { clusterList, err := getClusterNames() if err != nil { - log.Fatalf("ERROR: `--all` specified, but no clusters were found.") + return fmt.Errorf("ERROR: `--all` specified, but no clusters were found\n%+v", err) } clusters = append(clusters, clusterList...) } @@ -147,7 +145,7 @@ func StopCluster(c *cli.Context) error { } else { clusterList, err := getClusterNames() if err != nil { - log.Fatalf("ERROR: `--all` specified, but no clusters were found.") + return fmt.Errorf("ERROR: `--all` specified, but no clusters were found\n%+v", err) } clusters = append(clusters, clusterList...) } @@ -155,7 +153,7 @@ func StopCluster(c *cli.Context) error { // 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) + log.Printf("Stopping cluster [%s]", cluster) args = append(args, cluster) if err := runCommand(true, cmd, args...); err != nil { log.Printf("FAILURE: couldn't stop cluster [%s] -> %+v", cluster, err) @@ -179,7 +177,7 @@ func StartCluster(c *cli.Context) error { } else { clusterList, err := getClusterNames() if err != nil { - log.Fatalf("ERROR: `--all` specified, but no clusters were found.") + return fmt.Errorf("ERROR: `--all` specified, but no clusters were found\n%+v", err) } clusters = append(clusters, clusterList...) } @@ -212,8 +210,7 @@ func GetKubeConfig(c *cli.Context) error { 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 + return fmt.Errorf("ERROR: Couldn't get kubeconfig for cluster [%s]\n%+v", c.String("name"), err) } fmt.Printf("%s\n", path.Join(destPath, "kubeconfig.yaml")) return nil