diff --git a/libmachine/filestore.go b/libmachine/filestore.go index eba66100ee..7cdebb6394 100644 --- a/libmachine/filestore.go +++ b/libmachine/filestore.go @@ -35,7 +35,8 @@ func (s Filestore) loadHost(name string) (*Host, error) { return nil, err } - return host, nil + h := FillNestedHost(host) + return h, nil } func (s Filestore) GetPath() string { diff --git a/libmachine/host.go b/libmachine/host.go index 59a62845fe..83a1b46a61 100644 --- a/libmachine/host.go +++ b/libmachine/host.go @@ -33,6 +33,17 @@ type Host struct { Driver drivers.Driver StorePath string HostOptions *HostOptions + + // deprecated options; these are left to assist in config migrations + SwarmHost string + SwarmMaster bool + SwarmDiscovery string + CaCertPath string + PrivateKeyPath string + ServerCertPath string + ServerKeyPath string + ClientCertPath string + ClientKeyPath string } type HostOptions struct { @@ -282,7 +293,9 @@ func (h *Host) LoadConfig() error { return err } - authOptions := hostMetadata.HostOptions.AuthOptions + meta := FillNestedHostMetadata(&hostMetadata) + + authOptions := meta.HostOptions.AuthOptions driver, err := drivers.NewDriver(hostMetadata.DriverName, h.Name, h.StorePath, authOptions.CaCertPath, authOptions.PrivateKeyPath) if err != nil { diff --git a/libmachine/migrate.go b/libmachine/migrate.go new file mode 100644 index 0000000000..69f062e016 --- /dev/null +++ b/libmachine/migrate.go @@ -0,0 +1,122 @@ +package libmachine + +import ( + "path/filepath" + + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/swarm" + "github.com/docker/machine/utils" +) + +// In the 0.0.1 => 0.0.2 transition, the JSON representation of +// machines changed from a "flat" to a more "nested" structure +// for various options and configuration settings. To preserve +// compatibility with existing machines, these migration functions +// have been introduced. They preserve backwards compat at the expense +// of some duplicated information. + +// validates host config and modifies if needed +// this is used for configuration updates +func FillNestedHost(host *Host) *Host { + certInfo := getCertInfoFromHost(host) + + if host.HostOptions == nil { + host.HostOptions = &HostOptions{} + } + if host.HostOptions.EngineOptions == nil { + host.HostOptions.EngineOptions = &engine.EngineOptions{} + } + + if host.HostOptions.SwarmOptions == nil { + host.HostOptions.SwarmOptions = &swarm.SwarmOptions{ + Address: "", + Discovery: host.SwarmDiscovery, + Host: host.SwarmHost, + Master: host.SwarmMaster, + } + } + + host.HostOptions.AuthOptions = &auth.AuthOptions{ + StorePath: host.StorePath, + CaCertPath: certInfo.CaCertPath, + CaCertRemotePath: "", + ServerCertPath: certInfo.ServerCertPath, + ServerKeyPath: certInfo.ServerKeyPath, + ClientKeyPath: certInfo.ClientKeyPath, + ServerCertRemotePath: "", + ServerKeyRemotePath: "", + PrivateKeyPath: certInfo.CaKeyPath, + ClientCertPath: certInfo.ClientCertPath, + } + + return host +} + +// fills nested host metadata and modifies if needed +// this is used for configuration updates +func FillNestedHostMetadata(m *HostMetadata) *HostMetadata { + if m.HostOptions.EngineOptions == nil { + m.HostOptions.EngineOptions = &engine.EngineOptions{} + } + + if m.HostOptions.AuthOptions == nil { + m.HostOptions.AuthOptions = &auth.AuthOptions{ + StorePath: m.StorePath, + CaCertPath: m.CaCertPath, + CaCertRemotePath: "", + ServerCertPath: m.ServerCertPath, + ServerKeyPath: m.ServerKeyPath, + ClientKeyPath: "", + ServerCertRemotePath: "", + ServerKeyRemotePath: "", + PrivateKeyPath: m.PrivateKeyPath, + ClientCertPath: m.ClientCertPath, + } + } + + return m +} + +func getCertInfoFromHost(h *Host) CertPathInfo { + // setup cert paths + caCertPath := h.CaCertPath + caKeyPath := h.PrivateKeyPath + clientCertPath := h.ClientCertPath + clientKeyPath := h.ClientKeyPath + serverCertPath := h.ServerCertPath + serverKeyPath := h.ServerKeyPath + + if caCertPath == "" { + caCertPath = filepath.Join(utils.GetMachineCertDir(), "ca.pem") + } + + if caKeyPath == "" { + caKeyPath = filepath.Join(utils.GetMachineCertDir(), "ca-key.pem") + } + + if clientCertPath == "" { + clientCertPath = filepath.Join(utils.GetMachineCertDir(), "cert.pem") + } + + if clientKeyPath == "" { + clientKeyPath = filepath.Join(utils.GetMachineCertDir(), "key.pem") + } + + if serverCertPath == "" { + serverCertPath = filepath.Join(utils.GetMachineCertDir(), "server.pem") + } + + if serverKeyPath == "" { + serverKeyPath = filepath.Join(utils.GetMachineCertDir(), "server-key.pem") + } + + return CertPathInfo{ + CaCertPath: caCertPath, + CaKeyPath: caKeyPath, + ClientCertPath: clientCertPath, + ClientKeyPath: clientKeyPath, + ServerCertPath: serverCertPath, + ServerKeyPath: serverKeyPath, + } +} diff --git a/libmachine/migrate_test.go b/libmachine/migrate_test.go new file mode 100644 index 0000000000..57c726cf1f --- /dev/null +++ b/libmachine/migrate_test.go @@ -0,0 +1,119 @@ +package libmachine + +import ( + "os" + "reflect" + "testing" + + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/swarm" +) + +func TestFillNestedHost(t *testing.T) { + os.Setenv("MACHINE_STORAGE_PATH", "/tmp/migration") + originalHost := &Host{ + HostOptions: nil, + SwarmDiscovery: "token://foobar", + SwarmHost: "1.2.3.4:2376", + SwarmMaster: true, + CaCertPath: "", + PrivateKeyPath: "", + ClientCertPath: "", + ClientKeyPath: "", + ServerCertPath: "", + ServerKeyPath: "", + } + hostOptions := &HostOptions{ + SwarmOptions: &swarm.SwarmOptions{ + Master: true, + Discovery: "token://foobar", + Host: "1.2.3.4:2376", + }, + AuthOptions: &auth.AuthOptions{ + CaCertPath: "/tmp/migration/certs/ca.pem", + PrivateKeyPath: "/tmp/migration/certs/ca-key.pem", + ClientCertPath: "/tmp/migration/certs/cert.pem", + ClientKeyPath: "/tmp/migration/certs/key.pem", + ServerCertPath: "/tmp/migration/certs/server.pem", + ServerKeyPath: "/tmp/migration/certs/server-key.pem", + }, + EngineOptions: &engine.EngineOptions{}, + } + + expectedHost := &Host{ + SwarmHost: "1.2.3.4:2376", + SwarmDiscovery: "token://foobar", + SwarmMaster: true, + HostOptions: hostOptions, + } + + host := FillNestedHost(originalHost) + + if !reflect.DeepEqual(host, expectedHost) { + t.Logf("\n%+v\n%+v", host, expectedHost) + t.Logf("\n%+v\n%+v", host.HostOptions, expectedHost.HostOptions) + t.Fatal("Expected these structs to be equal, they were different") + } +} + +func TestFillNestedHostMetadata(t *testing.T) { + metadata := &HostMetadata{ + HostOptions: HostOptions{ + EngineOptions: nil, + AuthOptions: nil, + }, + StorePath: "/tmp/store", + CaCertPath: "/tmp/store/certs/ca.pem", + ServerCertPath: "/tmp/store/certs/server.pem", + } + expectedAuthOptions := &auth.AuthOptions{ + StorePath: "/tmp/store", + CaCertPath: "/tmp/store/certs/ca.pem", + ServerCertPath: "/tmp/store/certs/server.pem", + } + + expectedMetadata := &HostMetadata{ + HostOptions: HostOptions{ + EngineOptions: &engine.EngineOptions{}, + AuthOptions: expectedAuthOptions, + }, + StorePath: "/tmp/store", + CaCertPath: "/tmp/store/certs/ca.pem", + ServerCertPath: "/tmp/store/certs/server.pem", + } + + m := FillNestedHostMetadata(metadata) + + if !reflect.DeepEqual(m, expectedMetadata) { + t.Logf("\n%+v\n%+v", m, expectedMetadata) + t.Fatal("Expected these structs to be equal, they were different") + } +} + +// Tests a function which "prefills" certificate information for a host +// due to a schema migration from "flat" to a "nested" structure. +func TestGetCertInfoFromHost(t *testing.T) { + os.Setenv("MACHINE_STORAGE_PATH", "/tmp/migration") + host := &Host{ + CaCertPath: "", + PrivateKeyPath: "", + ClientCertPath: "", + ClientKeyPath: "", + ServerCertPath: "", + ServerKeyPath: "", + } + expectedCertInfo := CertPathInfo{ + CaCertPath: "/tmp/migration/certs/ca.pem", + CaKeyPath: "/tmp/migration/certs/ca-key.pem", + ClientCertPath: "/tmp/migration/certs/cert.pem", + ClientKeyPath: "/tmp/migration/certs/key.pem", + ServerCertPath: "/tmp/migration/certs/server.pem", + ServerKeyPath: "/tmp/migration/certs/server-key.pem", + } + certInfo := getCertInfoFromHost(host) + if !reflect.DeepEqual(expectedCertInfo, certInfo) { + t.Log("\n\n\n", expectedCertInfo, "\n\n\n", certInfo) + t.Fatal("Expected these structs to be equal, they were different") + } +}