use docker sdk wherever possible
This commit is contained in:
parent
e35716bbfd
commit
7eec5b061b
209
cli/commands.go
209
cli/commands.go
@ -1,80 +1,70 @@
|
||||
package run
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"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 {
|
||||
ctx := context.Background()
|
||||
docker, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ping, err := docker.Ping(ctx)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR: checking docker failed\n%+v", err)
|
||||
}
|
||||
log.Println("SUCCESS: Checking docker succeeded")
|
||||
log.Printf("SUCCESS: Checking docker succeeded (API: v%s)\n", ping.APIVersion)
|
||||
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("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"))
|
||||
cmd := "docker"
|
||||
|
||||
// default docker arguments
|
||||
args := []string{
|
||||
"run",
|
||||
"--name", c.String("name"),
|
||||
"--publish", port,
|
||||
"--privileged",
|
||||
"--detach",
|
||||
"--env", "K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml",
|
||||
}
|
||||
|
||||
// additional docker arguments
|
||||
extraArgs := []string{}
|
||||
if c.IsSet("env") || c.IsSet("e") {
|
||||
for _, env := range c.StringSlice("env") {
|
||||
extraArgs = append(extraArgs, "--env", env)
|
||||
}
|
||||
}
|
||||
if c.IsSet("volume") {
|
||||
extraArgs = append(extraArgs, "--volume", c.String("volume"))
|
||||
}
|
||||
if len(extraArgs) > 0 {
|
||||
args = append(args, extraArgs...)
|
||||
}
|
||||
|
||||
// k3s version and options
|
||||
args = append(args,
|
||||
image,
|
||||
"server", // cmd
|
||||
"--https-listen-port", c.String("port"), //args
|
||||
)
|
||||
|
||||
// additional k3s server arguments
|
||||
// k3s server arguments
|
||||
k3sServerArgs := []string{"--https-listen-port", c.String("port")}
|
||||
if c.IsSet("server-arg") || c.IsSet("x") {
|
||||
args = append(args, c.StringSlice("server-arg")...)
|
||||
k3sServerArgs = append(k3sServerArgs, c.StringSlice("server-arg")...)
|
||||
}
|
||||
|
||||
// let's go
|
||||
log.Printf("Creating cluster [%s]", c.String("name"))
|
||||
if err := runCommand(true, cmd, args...); err != nil {
|
||||
return fmt.Errorf("ERROR: couldn't create cluster [%s]\n%+v", c.String("name"), err)
|
||||
dockerID, err := createServer(
|
||||
c.Bool("verbose"),
|
||||
fmt.Sprintf("docker.io/rancher/k3s:%s", c.String("version")),
|
||||
c.String("port"),
|
||||
k3sServerArgs,
|
||||
[]string{"K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml"},
|
||||
c.String("name"),
|
||||
strings.Split(c.String("volume"), ","),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: failed to create cluster\n%+v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
docker, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for k3s to be up and running if we want it
|
||||
@ -88,17 +78,17 @@ func CreateCluster(c *cli.Context) error {
|
||||
}
|
||||
return errors.New("Cluster creation exceeded specified timeout")
|
||||
}
|
||||
cmd := "docker"
|
||||
args = []string{
|
||||
"logs",
|
||||
c.String("name"),
|
||||
}
|
||||
prog := exec.Command(cmd, args...)
|
||||
output, err := prog.CombinedOutput()
|
||||
|
||||
out, err := docker.ContainerLogs(ctx, dockerID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true})
|
||||
if err != nil {
|
||||
return err
|
||||
out.Close()
|
||||
return fmt.Errorf("ERROR: couldn't get docker logs for %s\n%+v", c.String("name"), err)
|
||||
}
|
||||
if strings.Contains(string(output), "Running kubelet") {
|
||||
buf := new(bytes.Buffer)
|
||||
nRead, _ := buf.ReadFrom(out)
|
||||
out.Close()
|
||||
output := buf.String()
|
||||
if nRead > 0 && strings.Contains(string(output), "Running kubelet") {
|
||||
break
|
||||
}
|
||||
|
||||
@ -116,38 +106,42 @@ 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"}
|
||||
clusters := []string{}
|
||||
ctx := context.Background()
|
||||
docker, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clusterNames := []string{}
|
||||
|
||||
// operate on one or all clusters
|
||||
if !c.Bool("all") {
|
||||
clusters = append(clusters, c.String("name"))
|
||||
clusterNames = append(clusterNames, c.String("name"))
|
||||
} else {
|
||||
clusterList, err := getClusterNames()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR: `--all` specified, but no clusters were found\n%+v", err)
|
||||
}
|
||||
clusters = append(clusters, clusterList...)
|
||||
clusterNames = append(clusterNames, 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.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)
|
||||
for _, name := range clusterNames {
|
||||
log.Printf("Removing cluster [%s]", name)
|
||||
cluster, err := getCluster(name)
|
||||
if err != nil {
|
||||
log.Printf("WARNING: couldn't get docker info for %s", name)
|
||||
continue
|
||||
}
|
||||
deleteClusterDir(cluster)
|
||||
log.Printf("SUCCESS: removed cluster [%s]", cluster)
|
||||
args = args[:len(args)-1] // pop last element from list (name of last cluster)
|
||||
if err := docker.ContainerRemove(ctx, cluster.id, types.ContainerRemoveOptions{}); err != nil {
|
||||
log.Printf("WARNING: couldn't delete cluster [%s], trying a force remove now.", cluster.name)
|
||||
if err := docker.ContainerRemove(ctx, cluster.id, types.ContainerRemoveOptions{Force: true}); err != nil {
|
||||
log.Printf("FAILURE: couldn't delete cluster container for [%s] -> %+v", cluster.name, err)
|
||||
}
|
||||
}
|
||||
deleteClusterDir(cluster.name)
|
||||
log.Printf("SUCCESS: removed cluster [%s]", cluster.name)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -155,31 +149,40 @@ func DeleteCluster(c *cli.Context) error {
|
||||
|
||||
// StopCluster stops a running cluster container (restartable)
|
||||
func StopCluster(c *cli.Context) error {
|
||||
cmd := "docker"
|
||||
args := []string{"stop"}
|
||||
clusters := []string{}
|
||||
|
||||
ctx := context.Background()
|
||||
docker, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clusterNames := []string{}
|
||||
|
||||
// operate on one or all clusters
|
||||
if !c.Bool("all") {
|
||||
clusters = append(clusters, c.String("name"))
|
||||
clusterNames = append(clusterNames, c.String("name"))
|
||||
} else {
|
||||
clusterList, err := getClusterNames()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR: `--all` specified, but no clusters were found\n%+v", err)
|
||||
}
|
||||
clusters = append(clusters, clusterList...)
|
||||
clusterNames = append(clusterNames, 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("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)
|
||||
for _, name := range clusterNames {
|
||||
log.Printf("Stopping cluster [%s]", name)
|
||||
cluster, err := getCluster(name)
|
||||
if err != nil {
|
||||
log.Printf("WARNING: couldn't get docker info for %s", name)
|
||||
continue
|
||||
}
|
||||
log.Printf("SUCCESS: stopped cluster [%s]", cluster)
|
||||
args = args[:len(args)-1] // pop last element from list (name of last cluster)
|
||||
if err := docker.ContainerStop(ctx, cluster.id, nil); err != nil {
|
||||
fmt.Printf("WARNING: couldn't stop cluster %s\n%+v", cluster.name, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("SUCCESS: stopped cluster [%s]", cluster.name)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -187,31 +190,39 @@ func StopCluster(c *cli.Context) error {
|
||||
|
||||
// StartCluster starts a stopped cluster container
|
||||
func StartCluster(c *cli.Context) error {
|
||||
cmd := "docker"
|
||||
args := []string{"start"}
|
||||
clusters := []string{}
|
||||
ctx := context.Background()
|
||||
docker, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clusterNames := []string{}
|
||||
|
||||
// operate on one or all clusters
|
||||
if !c.Bool("all") {
|
||||
clusters = append(clusters, c.String("name"))
|
||||
clusterNames = append(clusterNames, c.String("name"))
|
||||
} else {
|
||||
clusterList, err := getClusterNames()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR: `--all` specified, but no clusters were found\n%+v", err)
|
||||
}
|
||||
clusters = append(clusters, clusterList...)
|
||||
clusterNames = append(clusterNames, clusterList...)
|
||||
}
|
||||
|
||||
// start clusters one by one instead of appending all names to the docker command
|
||||
// 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 start cluster [%s] -> %+v", cluster, err)
|
||||
for _, name := range clusterNames {
|
||||
log.Printf("Starting cluster [%s]", name)
|
||||
cluster, err := getCluster(name)
|
||||
if err != nil {
|
||||
log.Printf("WARNING: couldn't get docker info for %s", name)
|
||||
continue
|
||||
}
|
||||
log.Printf("SUCCESS: started cluster [%s]", cluster)
|
||||
args = args[:len(args)-1] // pop last element from list (name of last cluster)
|
||||
if err := docker.ContainerStart(ctx, cluster.id, types.ContainerStartOptions{}); err != nil {
|
||||
fmt.Printf("WARNING: couldn't start cluster %s\n%+v", cluster.name, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("SUCCESS: started cluster [%s]", cluster.name)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -229,8 +240,8 @@ func GetKubeConfig(c *cli.Context) error {
|
||||
destPath, _ := getClusterDir(c.String("name"))
|
||||
cmd := "docker"
|
||||
args := []string{"cp", sourcePath, destPath}
|
||||
if err := runCommand(false, cmd, args...); err != nil {
|
||||
return fmt.Errorf("ERROR: Couldn't get kubeconfig for cluster [%s]\n%+v", c.String("name"), err)
|
||||
if err := runCommand(c.GlobalBool("verbose"), cmd, args...); err != nil {
|
||||
return fmt.Errorf("ERROR: Couldn't get kubeconfig for cluster [%s]\n%+v", fmt.Sprintf("%s-server", c.String("name")), err)
|
||||
}
|
||||
fmt.Printf("%s\n", path.Join(destPath, "kubeconfig.yaml"))
|
||||
return nil
|
||||
|
@ -2,10 +2,15 @@ package run
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
@ -16,6 +21,8 @@ type cluster struct {
|
||||
name string
|
||||
image string
|
||||
status string
|
||||
ports []string
|
||||
id string
|
||||
}
|
||||
|
||||
// createDirIfNotExists checks for the existence of a directory and creates it along with all required parents if not.
|
||||
@ -106,19 +113,35 @@ func getCluster(name string) (cluster, error) {
|
||||
name: name,
|
||||
image: "UNKNOWN",
|
||||
status: "UNKNOWN",
|
||||
ports: []string{"UNKNOWN"},
|
||||
id: "UNKNOWN",
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
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)
|
||||
|
||||
filters := filters.NewArgs()
|
||||
filters.Add("label", "app=k3d")
|
||||
filters.Add("label", fmt.Sprintf("cluster=%s", cluster.name))
|
||||
filters.Add("label", "component=server")
|
||||
|
||||
containerList, err := docker.ContainerList(ctx, types.ContainerListOptions{
|
||||
All: true,
|
||||
Filters: filters,
|
||||
})
|
||||
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, fmt.Errorf("WARNING: couldn't get docker info for [%s] -> %+v", cluster.name, err)
|
||||
}
|
||||
container := containerList[0]
|
||||
cluster.image = container.Image
|
||||
cluster.status = container.State
|
||||
for _, port := range container.Ports {
|
||||
cluster.ports = append(cluster.ports, strconv.Itoa(int(port.PublicPort)))
|
||||
}
|
||||
cluster.id = container.ID
|
||||
|
||||
return cluster, nil
|
||||
}
|
||||
|
76
cli/container.go
Normal file
76
cli/container.go
Normal file
@ -0,0 +1,76 @@
|
||||
package run
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
func createServer(verbose bool, image string, port string, args []string, env []string, name string, volumes []string) (string, error) {
|
||||
ctx := context.Background()
|
||||
docker, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ERROR: couldn't create docker client\n%+v", err)
|
||||
}
|
||||
|
||||
reader, err := docker.ImagePull(ctx, image, types.ImagePullOptions{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ERROR: couldn't pull image %s\n%+v", image, err)
|
||||
}
|
||||
if verbose {
|
||||
_, err := io.Copy(os.Stdout, reader) // TODO: only if verbose mode
|
||||
if err != nil {
|
||||
log.Printf("WARNING: couldn't get docker output\n%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
containerLabels := make(map[string]string)
|
||||
containerLabels["app"] = "k3d"
|
||||
containerLabels["component"] = "server"
|
||||
containerLabels["created"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
containerLabels["cluster"] = name
|
||||
|
||||
containerName := fmt.Sprintf("%s-server", name)
|
||||
|
||||
containerPort := nat.Port(fmt.Sprintf("%s/tcp", port))
|
||||
|
||||
resp, err := docker.ContainerCreate(ctx, &container.Config{
|
||||
Image: image,
|
||||
Cmd: append([]string{"server"}, args...),
|
||||
ExposedPorts: nat.PortSet{
|
||||
containerPort: struct{}{},
|
||||
},
|
||||
Env: env,
|
||||
Labels: containerLabels,
|
||||
}, &container.HostConfig{
|
||||
Binds: volumes,
|
||||
PortBindings: nat.PortMap{
|
||||
containerPort: []nat.PortBinding{
|
||||
{
|
||||
HostIP: "0.0.0.0",
|
||||
HostPort: port,
|
||||
},
|
||||
},
|
||||
},
|
||||
Privileged: true,
|
||||
}, nil, containerName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ERROR: couldn't create container %s\n%+v", containerName, err)
|
||||
}
|
||||
|
||||
if err := docker.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
|
||||
return "", fmt.Errorf("ERROR: couldn't start container %s\n%+v", containerName, err)
|
||||
}
|
||||
|
||||
return resp.ID, nil
|
||||
|
||||
}
|
2
go.mod
2
go.mod
@ -6,7 +6,7 @@ require (
|
||||
github.com/Microsoft/go-winio v0.4.12 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v1.13.1
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.3.3 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
|
14
main.go
14
main.go
@ -53,7 +53,7 @@ func main() {
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "volume, v",
|
||||
Usage: "Mount a volume into the cluster node (Docker notation: `source:destination`)",
|
||||
Usage: "Mount one or more volumes into the cluster node (Docker notation: `source:destination[,source:destination]`)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "version",
|
||||
@ -82,6 +82,11 @@ func main() {
|
||||
Name: "env, e",
|
||||
Usage: "Pass an additional environment variable (new flag per variable)",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "workers",
|
||||
Value: 0,
|
||||
Usage: "Specify how many worker nodes you want to spawn",
|
||||
},
|
||||
},
|
||||
Action: run.CreateCluster,
|
||||
},
|
||||
@ -169,6 +174,13 @@ func main() {
|
||||
},
|
||||
}
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Usage: "Enable verbose output",
|
||||
},
|
||||
}
|
||||
|
||||
// run the whole thing
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
|
18
vendor/modules.txt
vendored
18
vendor/modules.txt
vendored
@ -4,26 +4,26 @@ github.com/Microsoft/go-winio
|
||||
github.com/docker/distribution/reference
|
||||
github.com/docker/distribution/digestset
|
||||
# github.com/docker/docker v1.13.1
|
||||
github.com/docker/docker/client
|
||||
github.com/docker/docker/api/types
|
||||
github.com/docker/docker/api/types/container
|
||||
github.com/docker/docker/api/types/events
|
||||
github.com/docker/docker/api/types/filters
|
||||
github.com/docker/docker/client
|
||||
github.com/docker/docker/api/types/mount
|
||||
github.com/docker/docker/api/types/network
|
||||
github.com/docker/docker/api/types/reference
|
||||
github.com/docker/docker/api/types/registry
|
||||
github.com/docker/docker/api/types/swarm
|
||||
github.com/docker/docker/api/types/time
|
||||
github.com/docker/docker/api/types/versions
|
||||
github.com/docker/docker/api/types/volume
|
||||
github.com/docker/docker/pkg/tlsconfig
|
||||
github.com/docker/docker/api/types/mount
|
||||
github.com/docker/docker/api/types/blkiodev
|
||||
github.com/docker/docker/api/types/strslice
|
||||
github.com/docker/docker/api/types/versions
|
||||
github.com/docker/docker/api/types/events
|
||||
github.com/docker/docker/api/types/reference
|
||||
github.com/docker/docker/api/types/time
|
||||
github.com/docker/docker/api/types/volume
|
||||
github.com/docker/docker/pkg/tlsconfig
|
||||
# github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-connections/nat
|
||||
github.com/docker/go-connections/sockets
|
||||
github.com/docker/go-connections/tlsconfig
|
||||
github.com/docker/go-connections/nat
|
||||
# github.com/docker/go-units v0.3.3
|
||||
github.com/docker/go-units
|
||||
# github.com/mattn/go-runewidth v0.0.4
|
||||
|
Loading…
Reference in New Issue
Block a user