use docker sdk wherever possible

pull/12/head
iwilltry42 5 years ago
parent e35716bbfd
commit 7eec5b061b
  1. 207
      cli/commands.go
  2. 35
      cli/config.go
  3. 76
      cli/container.go
  4. 2
      go.mod
  5. 14
      main.go
  6. 16
      vendor/modules.txt

@ -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)
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
}
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)
}
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(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
}
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)
args = args[:len(args)-1] // pop last element from list (name of last cluster)
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
}
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)
args = args[:len(args)-1] // pop last element from list (name of last cluster)
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
}

@ -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
}

@ -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

@ -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 {

16
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/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/docker/api/types/mount
github.com/docker/docker/api/types/blkiodev
github.com/docker/docker/api/types/strslice
# 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…
Cancel
Save