diff --git a/cmd/create/createCluster.go b/cmd/create/createCluster.go index 841c01cd..e7c3b98f 100644 --- a/cmd/create/createCluster.go +++ b/cmd/create/createCluster.go @@ -64,6 +64,9 @@ func NewCmdCreateCluster() *cobra.Command { cmd.Flags().StringArrayP("volume", "v", nil, "Mount volumes into the nodes (Format: `--volume [SOURCE:]DEST[@NODEFILTER[;NODEFILTER...]]`") cmd.Flags().StringArrayP("port", "p", nil, "Map ports from the node containers to the host (Format: `[HOST:][HOSTPORT:]CONTAINERPORT[/PROTOCOL][@NODEFILTER]`)") + /* Image Importing */ + cmd.Flags().Bool("no-image-volume", false, "Disable the creation of a volume for importing images") + /* Multi Master Configuration */ // TODO: to implement (whole multi master thingy) // multi-master - general cmd.Flags().Bool("no-lb", false, "Disable automatic deployment of a load balancer in Multi-Master setups") @@ -277,11 +280,22 @@ func parseCreateClusterCmd(cmd *cobra.Command, args []string) (runtimes.Runtime, } } - /* generate cluster */ + // --no-image-volume + noImageVolume, err := cmd.Flags().GetBool("no-image-volume") + if err != nil { + log.Fatalln(err) + } + + /******************* + * generate cluster * + ********************/ cluster := &k3d.Cluster{ Name: args[0], // TODO: validate name0 Network: network, Secret: secret, + ClusterCreationOpts: &k3d.ClusterCreationOpts{ + DisableImageVolume: noImageVolume, + }, } // generate list of nodes diff --git a/cmd/load/loadImage.go b/cmd/load/loadImage.go index ea20e831..1cb59d34 100644 --- a/cmd/load/loadImage.go +++ b/cmd/load/loadImage.go @@ -38,9 +38,10 @@ func NewCmdLoadImage() *cobra.Command { Use: "image", Short: "Load an image from docker into a k3d cluster.", Long: `Load an image from docker into a k3d cluster.`, + Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { runtime, images, clusters := parseLoadImageCmd(cmd, args) - log.Debugln("Load Images not yet implemented: ", runtime, images, clusters) + log.Debugf("Load images [%+v] from runtime [%s] into clusters [%+v]", runtime, images, clusters) }, } diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index f32d8bd1..4a9b17ba 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -70,6 +70,25 @@ func CreateCluster(cluster *k3d.Cluster, runtime k3drt.Runtime) error { cluster.Secret = GenerateClusterSecret() } + /* + * Cluster-Wide volumes + * - image volume (for importing images) + */ + if !cluster.ClusterCreationOpts.DisableImageVolume { + imageVolumeName := fmt.Sprintf("%s-images", cluster.Name) + if err := runtime.CreateVolume(imageVolumeName, map[string]string{"k3d.cluster": cluster.Name}); err != nil { + log.Errorln("Failed to create image volume '%s' for cluster '%s'", imageVolumeName, cluster.Name) + return err + } + + extraLabels["k3d.cluster.volumes.imagevolume"] = imageVolumeName + + // attach volume to nodes + for _, node := range cluster.Nodes { + node.Volumes = append(node.Volumes, fmt.Sprintf("%s:%s", imageVolumeName, k3d.DefaultImageVolumeMountPath)) + } + } + /* * Nodes */ @@ -209,6 +228,15 @@ func DeleteCluster(cluster *k3d.Cluster, runtime k3drt.Runtime) error { } } + // delete image volume + if imagevolume, ok := cluster.Nodes[0].Labels["k3d.cluster.volumes.imagevolume"]; ok { + log.Infof("Deleting image volume '%s'", imagevolume) + if err := runtime.DeleteVolume(imagevolume); err != nil { + log.Warningf("Failed to delete image volume '%s' of cluster '%s': Try to delete it manually", cluster.Name, imagevolume) + } + } + + // return error if we failed to delete a node if failed > 0 { return fmt.Errorf("Failed to delete %d nodes: Try to delete them manually", failed) } diff --git a/pkg/cluster/tools.go b/pkg/cluster/tools.go index 8c8b5b88..59c82317 100644 --- a/pkg/cluster/tools.go +++ b/pkg/cluster/tools.go @@ -22,12 +22,20 @@ THE SOFTWARE. package cluster import ( + "github.com/rancher/k3d/pkg/runtimes" k3d "github.com/rancher/k3d/pkg/types" log "github.com/sirupsen/logrus" ) -// StartToolsContainer will start a new k3d tools container and connect it to the network of the chosen cluster -func StartToolsContainer(cluster *k3d.Cluster) error { - log.Debugln("starttoolscontainer") - return nil +// LoadImagesIntoCluster starts up a k3d tools container for the selected clusters and uses it to export +// images from the runtime to import them into the nodes of the selected cluster +func LoadImagesIntoCluster(runtime runtimes.Runtime, images []string, clusters *k3d.Cluster) { + +} + +// startToolsContainer will start a new k3d tools container and connect it to the network of the chosen cluster +func startToolsContainer(runtime runtimes.Runtime, cluster *k3d.Cluster) (string, error) { + log.Debugln("starttoolscontainer") + + return "", nil } diff --git a/pkg/runtimes/containerd/volume.go b/pkg/runtimes/containerd/volume.go new file mode 100644 index 00000000..0922d556 --- /dev/null +++ b/pkg/runtimes/containerd/volume.go @@ -0,0 +1,32 @@ +/* +Copyright © 2019 Thorsten Klein + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package containerd + +// CreateVolume creates a new named volume +func (d Containerd) CreateVolume(name string, labels map[string]string) error { + return nil +} + +// DeleteVolume creates a new named volume +func (d Containerd) DeleteVolume(name string) error { + return nil +} diff --git a/pkg/runtimes/docker/volume.go b/pkg/runtimes/docker/volume.go new file mode 100644 index 00000000..9fa0670e --- /dev/null +++ b/pkg/runtimes/docker/volume.go @@ -0,0 +1,96 @@ +/* +Copyright © 2019 Thorsten Klein + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package docker + +import ( + "context" + "fmt" + + "github.com/docker/docker/api/types/volume" + "github.com/docker/docker/client" + k3d "github.com/rancher/k3d/pkg/types" + log "github.com/sirupsen/logrus" +) + +// CreateVolume creates a new named volume +func (d Docker) CreateVolume(name string, labels map[string]string) error { + // (0) create new docker client + ctx := context.Background() + docker, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + log.Errorln("Failed to create docker client") + return err + } + + // (1) create volume + volumeCreateOptions := volume.VolumeCreateBody{ + Name: name, + Labels: k3d.DefaultObjectLabels, + Driver: "local", // TODO: allow setting driver + opts + DriverOpts: map[string]string{}, + } + for k, v := range labels { + volumeCreateOptions.Labels[k] = v + } + + vol, err := docker.VolumeCreate(ctx, volumeCreateOptions) + if err != nil { + log.Errorf("Failed to create volume '%s'", name) + return err + } + log.Infof("Created volume '%s'", vol.Name) + return nil +} + +// DeleteVolume creates a new named volume +func (d Docker) DeleteVolume(name string) error { + // (0) create new docker client + ctx := context.Background() + docker, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + log.Errorln("Failed to create docker client") + return err + } + + // get volume and delete it + vol, err := docker.VolumeInspect(ctx, name) + if err != nil { + log.Errorf("Failed to find volume '%s'", name) + return err + } + + // check if volume is still in use + if vol.UsageData != nil { + if vol.UsageData.RefCount > 0 { + log.Errorf("Failed to delete volume '%s'") + return fmt.Errorf("Volume '%s' is still referenced by %d containers", name, vol.UsageData.RefCount) + } + } + + // remove volume + if err := docker.VolumeRemove(ctx, name, true); err != nil { + log.Errorf("Failed to delete volume '%s'", name) + return err + } + + return nil +} diff --git a/pkg/runtimes/runtime.go b/pkg/runtimes/runtime.go index 05fabe7f..05d2c43f 100644 --- a/pkg/runtimes/runtime.go +++ b/pkg/runtimes/runtime.go @@ -47,6 +47,8 @@ type Runtime interface { DeleteNetwork(ID string) error StartNode(*k3d.Node) error StopNode(*k3d.Node) error + CreateVolume(string, map[string]string) error + DeleteVolume(string) error // ExecContainer() error // DeleteContainer() error GetNodeLogs(*k3d.Node) (io.ReadCloser, error) diff --git a/pkg/types/types.go b/pkg/types/types.go index 874ae0d0..c1633643 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -72,15 +72,24 @@ var DefaultNodeEnv = []string{ "K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml", } +// DefaultImageVolumeMountPath defines the mount path inside k3d nodes where we will mount the shared image volume by default +const DefaultImageVolumeMountPath = "/k3d/images" + +// ClusterCreationOpts describe a set of options one can set when creating a cluster +type ClusterCreationOpts struct { + DisableImageVolume bool +} + // Cluster describes a k3d cluster type Cluster struct { - Name string `yaml:"name" json:"name,omitempty"` - Network string `yaml:"network" json:"network,omitempty"` - Secret string `yaml:"cluster_secret" json:"clusterSecret,omitempty"` - Nodes []*Node `yaml:"nodes" json:"nodes,omitempty"` - InitNode *Node // init master node - MasterLoadBalancer *ClusterLoadbalancer `yaml:"master_loadbalancer" json:"masterLoadBalancer,omitempty"` - ExternalDatastore ExternalDatastore `yaml:"external_datastore" json:"externalDatastore,omitempty"` + Name string `yaml:"name" json:"name,omitempty"` + Network string `yaml:"network" json:"network,omitempty"` + Secret string `yaml:"cluster_secret" json:"clusterSecret,omitempty"` + Nodes []*Node `yaml:"nodes" json:"nodes,omitempty"` + InitNode *Node // init master node + MasterLoadBalancer *ClusterLoadbalancer `yaml:"master_loadbalancer" json:"masterLoadBalancer,omitempty"` + ExternalDatastore ExternalDatastore `yaml:"external_datastore" json:"externalDatastore,omitempty"` + ClusterCreationOpts *ClusterCreationOpts `yaml:"options" json:"options,omitempty"` } // Node describes a k3d node diff --git a/thoughts.md b/thoughts.md index 6fdc84b6..6cdc0a67 100644 --- a/thoughts.md +++ b/thoughts.md @@ -163,6 +163,7 @@ Here's how k3d types should translate to a runtime type: - cluster NAME - --all - node NAME + - --all - get - cluster NAME - --no-headers @@ -178,3 +179,15 @@ Here's how k3d types should translate to a runtime type: - cluster NAME - --all - node NAME + +## tools + +- maybe rename `k3d load` to `k3d tools` and add tool cmds there? + - e.g. `k3d tools import-images` + - let's you set tools container version + - `k3d tools --image k3d-tools:v2 import-images` + +## extra commands + +- `k3d prune` to prune all dangling resources + - nodes, volumes, networks