From 4a8c0757e49dccfc0b27c086261c5f4f00175daf Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Thu, 20 Jun 2024 15:57:10 +0300 Subject: [PATCH] Add a --volume/-v config create option Signed-off-by: Kimmo Lehto --- cmd/bootloose/config_create.go | 44 ++++++++++++++++++++++++++++++++++ pkg/cluster/cluster.go | 8 +++---- pkg/cluster/cluster_test.go | 9 ++++--- pkg/config/cluster.go | 6 ++--- pkg/config/get_test.go | 12 ++++++---- 5 files changed, 63 insertions(+), 16 deletions(-) diff --git a/cmd/bootloose/config_create.go b/cmd/bootloose/config_create.go index 335d6897..c9a0d331 100644 --- a/cmd/bootloose/config_create.go +++ b/cmd/bootloose/config_create.go @@ -7,6 +7,8 @@ package bootloose import ( "fmt" "os" + "path/filepath" + "strings" "github.com/k0sproject/bootloose/pkg/cluster" "github.com/k0sproject/bootloose/pkg/config" @@ -17,6 +19,7 @@ import ( type configCreateOptions struct { override bool config config.Config + volumes []string } func NewConfigCreateCommand() *cobra.Command { @@ -50,6 +53,8 @@ func NewConfigCreateCommand() *cobra.Command { containerCmd := &opts.config.Machines[0].Spec.Cmd cmd.Flags().StringVarP(containerCmd, "cmd", "d", *containerCmd, "The command to execute on the container") + cmd.Flags().StringSliceVarP(&opts.volumes, "volume", "v", nil, "Volumes to mount in the container") + return cmd } @@ -72,6 +77,45 @@ func (opts *configCreateOptions) create(cmd *cobra.Command, args []string) error if configExists(cfgFile) && !opts.override { return fmt.Errorf("configuration file at %s already exists", cfgFile) } + for _, v := range opts.volumes { + volume, err := parseVolume(v) + if err != nil { + return err + } + for _, machine := range opts.config.Machines { + machine.Spec.Volumes = append(machine.Spec.Volumes, volume) + } + } return cluster.Save(cfgFile) } +// volume flags can be in the form of: +// -v /host/path:/container/path (bind mount) +// -v volume:/container/path (volume mount) +// or contain the permissions field: +// -v /host/path:/container/path:ro (bind mount (read only)) +// -v volume:/container/path:rw (volume mount (read write)) +func parseVolume(v string) (config.Volume, error) { + if v == "" { + return config.Volume{}, fmt.Errorf("empty volume value") + } + parts := strings.Split(v, ":") + if len(parts) < 2 || len(parts) > 3 { + return config.Volume{}, fmt.Errorf("invalid volume value: %v", v) + } + + vol := config.Volume{} + if filepath.IsAbs(parts[0]) { + vol.Type = "bind" + } else { + vol.Type = "volume" + } + + if len(parts) == 3 { + vol.ReadOnly = parts[2] == "ro" + } + + vol.Source = parts[0] + vol.Destination = parts[1] + return vol, nil +} diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 2c5238c6..5c4b5872 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -124,7 +124,7 @@ func (c *Cluster) forEachMachine(do func(*Machine, int) error) error { for _, template := range c.spec.Machines { for i := 0; i < template.Count; i++ { // machine name indexed with i - machine := c.machine(&template.Spec, i) + machine := c.machine(template.Spec, i) // but to prevent port collision, we use machineIndex for the real machine creation if err := do(machine, machineIndex); err != nil { return err @@ -143,7 +143,7 @@ func (c *Cluster) forSpecificMachines(do func(*Machine, int) error, machineNames } for _, template := range c.spec.Machines { for i := 0; i < template.Count; i++ { - machine := c.machine(&template.Spec, i) + machine := c.machine(template.Spec, i) _, ok := machineToStart[machine.name] if ok { if err := do(machine, i); err != nil { @@ -499,7 +499,7 @@ func (c *Cluster) gatherMachinesByCluster() (machines []*Machine) { for _, template := range c.spec.Machines { for i := 0; i < template.Count; i++ { s := template.Spec - machine := c.machine(&s, i) + machine := c.machine(s, i) machines = append(machines, machine) } } @@ -626,7 +626,7 @@ func (c *Cluster) machineFromHostname(hostname string) (*Machine, error) { for _, template := range c.spec.Machines { for i := 0; i < template.Count; i++ { if hostname == f(template.Spec.Name, i) { - return c.machine(&template.Spec, i), nil + return c.machine(template.Spec, i), nil } } } diff --git a/pkg/cluster/cluster_test.go b/pkg/cluster/cluster_test.go index f0d8a218..dd8b2a32 100644 --- a/pkg/cluster/cluster_test.go +++ b/pkg/cluster/cluster_test.go @@ -55,13 +55,13 @@ machines: assert.Equal(t, uint16(22), portMapping.ContainerPort) assert.Equal(t, uint16(2222), portMapping.HostPort) - machine0 := cluster.machine(&template.Spec, 0) + machine0 := cluster.machine(template.Spec, 0) args0 := cluster.createMachineRunArgs(machine0, machine0.ContainerName(), 0) i := indexOf("-p", args0) assert.NotEqual(t, -1, i) assert.Equal(t, "2222:22", args0[i+1]) - machine1 := cluster.machine(&template.Spec, 1) + machine1 := cluster.machine(template.Spec, 1) args1 := cluster.createMachineRunArgs(machine1, machine1.ContainerName(), 1) i = indexOf("-p", args1) assert.NotEqual(t, -1, i) @@ -96,13 +96,12 @@ func TestCluster_EnsureSSHKeys(t *testing.T) { privStat, err = os.Stat(keyPath) if assert.NoError(t, err, "failed to stat private key file") { - assert.Equal(t, privStat.Mode().Perm(), os.FileMode(0600), "private key file has wrong permissions") - + assert.Equal(t, privStat.Mode().Perm(), os.FileMode(0o600), "private key file has wrong permissions") } pubStat, err = os.Stat(keyPath + ".pub") if assert.NoError(t, err, "failed to stat public key file") { - assert.Equal(t, pubStat.Mode().Perm(), os.FileMode(0644), "public key file has wrong permissions") + assert.Equal(t, pubStat.Mode().Perm(), os.FileMode(0o644), "public key file has wrong permissions") } }) diff --git a/pkg/config/cluster.go b/pkg/config/cluster.go index 7ffdb309..3d7ec049 100644 --- a/pkg/config/cluster.go +++ b/pkg/config/cluster.go @@ -30,8 +30,8 @@ func NewConfigFromFile(path string) (*Config, error) { // MachineReplicas are a number of machine following the same specification. type MachineReplicas struct { - Spec Machine `json:"spec"` - Count int `json:"count"` + Spec *Machine `json:"spec"` + Count int `json:"count"` } // Cluster is a set of Machines. @@ -85,7 +85,7 @@ func DefaultConfig() Config { Machines: []MachineReplicas{ { Count: 1, - Spec: Machine{ + Spec: &Machine{ Name: "node%d", Image: "quay.io/k0sproject/bootloose-ubuntu20.04", PortMappings: []PortMapping{ diff --git a/pkg/config/get_test.go b/pkg/config/get_test.go index 4894251f..f39f39d7 100644 --- a/pkg/config/get_test.go +++ b/pkg/config/get_test.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. +// SPDX-FileCopyrightText: 2024 bootloose authors +// SPDX-License-Identifier: Apache-2.0 + package config import ( @@ -10,9 +14,9 @@ func TestGetValueFromConfig(t *testing.T) { config := Config{ Cluster: Cluster{Name: "clustername", PrivateKey: "privatekey"}, Machines: []MachineReplicas{ - MachineReplicas{ + { Count: 3, - Spec: Machine{ + Spec: &Machine{ Image: "myImage", Name: "myName", Privileged: true, @@ -32,7 +36,7 @@ func TestGetValueFromConfig(t *testing.T) { "cluster.name", Config{ Cluster: Cluster{Name: "clustername", PrivateKey: "privatekey"}, - Machines: []MachineReplicas{MachineReplicas{Count: 3, Spec: Machine{}}}, + Machines: []MachineReplicas{{Count: 3, Spec: &Machine{}}}, }, "clustername", }, @@ -40,7 +44,7 @@ func TestGetValueFromConfig(t *testing.T) { "array path select global", "machines[0].spec", config, - Machine{ + &Machine{ Image: "myImage", Name: "myName", Privileged: true,