Compare commits

...
4 Commits
8 changed files with 180 additions and 11 deletions
+6 -1
View File
@@ -33,7 +33,7 @@ type initOptions struct {
func NewInitCommand() *cobra.Command {
opts := initOptions{}
cmd := &cobra.Command{
Use: "init [USER@HOST:PORT]",
Use: "init [schema://]USER@HOST[:PORT]",
Short: "Initialise a new cluster with a remote machine as the first member.",
Long: `Initialise a new cluster by setting up a remote machine as the first member.
This command creates a new context in your Uncloud config to manage the cluster.
@@ -128,6 +128,11 @@ Connection methods:
}
func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine, opts initOptions) error {
if uncli.Config == nil {
// Config is nil when connecting directly to a remote machine (--connect) without using Uncloud config.
return fmt.Errorf("do not specify --connect when initialising a new cluster")
}
netPrefix, err := netip.ParsePrefix(opts.network)
if err != nil {
return fmt.Errorf("parse network CIDR: %w", err)
+1 -1
View File
@@ -40,8 +40,8 @@ func main() {
SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
cli.BindEnvToFlag(cmd, "connect", "UNCLOUD_CONNECT")
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
cli.BindEnvToFlag(cmd, "context", "UNCLOUD_CONTEXT")
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
var conn *config.MachineConnection
if opts.connect != "" {
+1 -1
View File
@@ -46,7 +46,7 @@ require (
github.com/muesli/termenv v0.16.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/psviderski/unregistry v0.3.1
github.com/psviderski/unregistry v0.4.1
github.com/siderolabs/discovery-api v0.1.4
github.com/siderolabs/discovery-client v0.1.9
github.com/siderolabs/grpc-proxy v0.5.1
+2 -2
View File
@@ -1040,8 +1040,8 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/psviderski/unregistry v0.3.1 h1:2xQ3uMNMyXAASqq9+kGUgNMIFOvPSMIFGdz47uDfjhQ=
github.com/psviderski/unregistry v0.3.1/go.mod h1:DXLnzrskO5NuRlzZhpbyo1E6KpirnctUvq6WtCGkpUQ=
github.com/psviderski/unregistry v0.4.1 h1:KedQTjyj8hHbzygK9uw5YS10amMZ5cjSYHqSjUUBhoE=
github.com/psviderski/unregistry v0.4.1/go.mod h1:DXLnzrskO5NuRlzZhpbyo1E6KpirnctUvq6WtCGkpUQ=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
+21 -5
View File
@@ -171,7 +171,7 @@ func (cli *CLI) InitCluster(ctx context.Context, opts InitClusterOptions) (*clie
return cli.initRemoteMachine(ctx, opts)
}
// TODO: implement local machine initialisation
return nil, fmt.Errorf("local machine initialisation is not implemented yet")
return nil, fmt.Errorf("local machine initialisation is not implemented yet. Please specify a remote machine")
}
func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions) (*client.Client, error) {
@@ -246,9 +246,17 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions)
MachineID: resp.Machine.Id,
}
if opts.RemoteMachine.UseSSHCLI {
connCfg.SSHCLI = config.NewSSHDestination(opts.RemoteMachine.User, opts.RemoteMachine.Host, opts.RemoteMachine.Port)
connCfg.SSHCLI = config.NewSSHDestination(
opts.RemoteMachine.User,
opts.RemoteMachine.Host,
opts.RemoteMachine.Port,
)
} else {
connCfg.SSH = config.NewSSHDestination(opts.RemoteMachine.User, opts.RemoteMachine.Host, opts.RemoteMachine.Port)
connCfg.SSH = config.NewSSHDestination(
opts.RemoteMachine.User,
opts.RemoteMachine.Host,
opts.RemoteMachine.Port,
)
}
cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg)
if err = cli.Config.Save(); err != nil {
@@ -415,9 +423,17 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
MachineID: addResp.Machine.Id,
}
if opts.RemoteMachine.UseSSHCLI {
connCfg.SSHCLI = config.NewSSHDestination(opts.RemoteMachine.User, opts.RemoteMachine.Host, opts.RemoteMachine.Port)
connCfg.SSHCLI = config.NewSSHDestination(
opts.RemoteMachine.User,
opts.RemoteMachine.Host,
opts.RemoteMachine.Port,
)
} else {
connCfg.SSH = config.NewSSHDestination(opts.RemoteMachine.User, opts.RemoteMachine.Host, opts.RemoteMachine.Port)
connCfg.SSH = config.NewSSHDestination(
opts.RemoteMachine.User,
opts.RemoteMachine.Host,
opts.RemoteMachine.Port,
)
}
cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg)
if err = cli.Config.Save(); err != nil {
+3
View File
@@ -83,6 +83,9 @@ func (v *VolumeSpec) SetDefaults() VolumeSpec {
if spec.VolumeOptions.Name == "" {
spec.VolumeOptions.Name = spec.Name
}
if spec.VolumeOptions.Driver != nil && spec.VolumeOptions.Driver.Name == "" {
spec.VolumeOptions.Driver.Name = VolumeDriverLocal
}
}
// TODO: set explicit default values for Propagation and Recursive for bind mounts?
+142
View File
@@ -0,0 +1,142 @@
package api
import (
"testing"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/volume"
"github.com/stretchr/testify/assert"
)
func TestVolumeSpec_MatchesDockerVolume(t *testing.T) {
t.Parallel()
tests := []struct {
name string
spec VolumeSpec
vol volume.Volume
expected bool
}{
{
name: "match with explicit local driver",
spec: VolumeSpec{
Type: VolumeTypeVolume,
VolumeOptions: &VolumeOptions{
Driver: &mount.Driver{
Name: "local",
Options: map[string]string{
"foo": "bar",
},
},
},
},
vol: volume.Volume{
Driver: "local",
Options: map[string]string{
"foo": "bar",
},
},
expected: true,
},
{
name: "match with empty driver name in spec (implicit local)",
spec: VolumeSpec{
Type: VolumeTypeVolume,
VolumeOptions: &VolumeOptions{
Driver: &mount.Driver{
Name: "", // Implicitly local
Options: map[string]string{
"foo": "bar",
},
},
},
},
vol: volume.Volume{
Driver: "local",
Options: map[string]string{
"foo": "bar",
},
},
expected: true,
},
{
name: "non-match with different driver options",
spec: VolumeSpec{
Type: VolumeTypeVolume,
VolumeOptions: &VolumeOptions{
Driver: &mount.Driver{
Name: "local",
Options: map[string]string{
"foo": "baz",
},
},
},
},
vol: volume.Volume{
Driver: "local",
Options: map[string]string{
"foo": "bar",
},
},
expected: false,
},
{
name: "non-match with different driver name",
spec: VolumeSpec{
Type: VolumeTypeVolume,
VolumeOptions: &VolumeOptions{
Driver: &mount.Driver{
Name: "custom",
Options: map[string]string{
"foo": "bar",
},
},
},
},
vol: volume.Volume{
Driver: "local",
Options: map[string]string{
"foo": "bar",
},
},
expected: false,
},
{
name: "match external volume without driver by name only",
spec: VolumeSpec{
Name: "external",
Type: VolumeTypeVolume,
},
vol: volume.Volume{
Name: "external",
Driver: "local",
Options: map[string]string{
"foo": "bar",
},
},
expected: true,
},
{
name: "non-match external volume by name",
spec: VolumeSpec{
Name: "unknown",
Type: VolumeTypeVolume,
},
vol: volume.Volume{
Name: "external",
Driver: "local",
Options: map[string]string{
"foo": "bar",
},
},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matches := tt.spec.MatchesDockerVolume(tt.vol)
assert.Equal(t, tt.expected, matches)
})
}
}
+4 -1
View File
@@ -83,7 +83,10 @@ func NewVolumeScheduler(state *ClusterState, specs []api.ServiceSpec) (*VolumeSc
if spec, ok := volumeSpecs[vol.Name]; ok {
if !spec.MatchesDockerVolume(vol) {
return nil, fmt.Errorf("volume '%s' specification does not match the existing volume "+
"on machine '%s'", vol.Name, machine.Info.Name)
"on machine '%s'. Use a different volume name or adjust the volume options to match "+
"the existing volume. You can also remove the existing volume from the machine(s) with "+
"'uc volume rm' (WARNING: the data will be lost) and run the deployment again to create "+
"a new volume with the correct specification", vol.Name, machine.Info.Name)
}
if _, setInitialised := existingVolumeMachines[vol.Name]; !setInitialised {