github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/container/lxd/connection.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd 5 6 import ( 7 "fmt" 8 "net/http" 9 "net/url" 10 "os" 11 "path/filepath" 12 "strings" 13 14 "github.com/juju/errors" 15 "github.com/lxc/lxd/client" 16 "github.com/lxc/lxd/shared" 17 ) 18 19 type Protocol string 20 21 const ( 22 LXDProtocol Protocol = "lxd" 23 SimpleStreamsProtocol Protocol = "simplestreams" 24 ) 25 26 // ServerSpec describes the location and connection details for a 27 // server utilized in LXD workflows. 28 type ServerSpec struct { 29 Name string 30 Host string 31 Protocol Protocol 32 connectionArgs *lxd.ConnectionArgs 33 } 34 35 // ProxyFunc defines a function that can act as a proxy for requests 36 type ProxyFunc func(*http.Request) (*url.URL, error) 37 38 // NewServerSpec creates a ServerSpec with default values where needed. 39 // It also ensures the HTTPS for the host implicitly 40 func NewServerSpec(host, serverCert string, clientCert *Certificate) ServerSpec { 41 return ServerSpec{ 42 Host: EnsureHTTPS(host), 43 connectionArgs: &lxd.ConnectionArgs{ 44 TLSServerCert: serverCert, 45 TLSClientCert: string(clientCert.CertPEM), 46 TLSClientKey: string(clientCert.KeyPEM), 47 }, 48 } 49 } 50 51 // WithProxy adds the optional proxy to the server spec. 52 // Returns the ServerSpec to enable chaining of optional values 53 func (s ServerSpec) WithProxy(proxy ProxyFunc) ServerSpec { 54 s.connectionArgs.Proxy = proxy 55 return s 56 } 57 58 // WithClientCertificate adds the optional client Certificate to the server 59 // spec. 60 // Returns the ServerSpec to enable chaining of optional values 61 func (s ServerSpec) WithClientCertificate(clientCert *Certificate) ServerSpec { 62 s.connectionArgs.TLSClientCert = string(clientCert.CertPEM) 63 s.connectionArgs.TLSClientKey = string(clientCert.KeyPEM) 64 return s 65 } 66 67 // WithSkipGetServer adds the option skipping of the get server verification to 68 // the server spec. 69 func (s ServerSpec) WithSkipGetServer(b bool) ServerSpec { 70 s.connectionArgs.SkipGetServer = b 71 return s 72 } 73 74 // NewInsecureServerSpec creates a ServerSpec without certificate requirements, 75 // which also bypasses the TLS verification. 76 // It also ensures the HTTPS for the host implicitly 77 func NewInsecureServerSpec(host string) ServerSpec { 78 return ServerSpec{ 79 Host: EnsureHTTPS(host), 80 connectionArgs: &lxd.ConnectionArgs{ 81 InsecureSkipVerify: true, 82 }, 83 } 84 } 85 86 // MakeSimpleStreamsServerSpec creates a ServerSpec for the SimpleStreams 87 // protocol, ensuring that the host is HTTPS 88 func MakeSimpleStreamsServerSpec(name, host string) ServerSpec { 89 return ServerSpec{ 90 Name: name, 91 Host: EnsureHTTPS(host), 92 Protocol: SimpleStreamsProtocol, 93 } 94 } 95 96 // Validate ensures that the ServerSpec is valid. 97 func (s *ServerSpec) Validate() error { 98 return nil 99 } 100 101 // CloudImagesRemote hosts releases blessed by the Canonical team. 102 var CloudImagesRemote = ServerSpec{ 103 Name: "cloud-images.ubuntu.com", 104 Host: "https://cloud-images.ubuntu.com/releases", 105 Protocol: SimpleStreamsProtocol, 106 } 107 108 // CloudImagesDailyRemote hosts images from daily package builds. 109 // These images have not been independently tested, but should be sound for 110 // use, being build from packages in the released archive. 111 var CloudImagesDailyRemote = ServerSpec{ 112 Name: "cloud-images.ubuntu.com", 113 Host: "https://cloud-images.ubuntu.com/daily", 114 Protocol: SimpleStreamsProtocol, 115 } 116 117 // ConnectImageRemote connects to a remote ImageServer using specified protocol. 118 var ConnectImageRemote = connectImageRemote 119 120 func connectImageRemote(remote ServerSpec) (lxd.ImageServer, error) { 121 switch remote.Protocol { 122 case LXDProtocol: 123 return lxd.ConnectPublicLXD(remote.Host, remote.connectionArgs) 124 case SimpleStreamsProtocol: 125 return lxd.ConnectSimpleStreams(remote.Host, remote.connectionArgs) 126 } 127 return nil, fmt.Errorf("bad protocol supplied for connection: %v", remote.Protocol) 128 } 129 130 // ConnectLocal connects to LXD on a local socket. 131 var ConnectLocal = connectLocal 132 133 func connectLocal() (lxd.ContainerServer, error) { 134 client, err := lxd.ConnectLXDUnix(SocketPath(nil), nil) 135 return client, errors.Trace(err) 136 } 137 138 // ConnectRemote connects to LXD on a remote socket. 139 func ConnectRemote(spec ServerSpec) (lxd.ContainerServer, error) { 140 // Ensure the Port on the Host, if we get an error it is reasonable to 141 // assume that the address in the spec is invalid. 142 uri, err := EnsureHostPort(spec.Host) 143 if err != nil { 144 return nil, errors.Trace(err) 145 } 146 client, err := lxd.ConnectLXD(uri, spec.connectionArgs) 147 return client, errors.Trace(err) 148 } 149 150 // SocketPath returns the path to the local LXD socket. 151 // The following are tried in order of preference: 152 // - LXD_DIR environment variable. 153 // - Snap socket. 154 // - Debian socket. 155 // We give preference to LXD installed via Snap. 156 // isSocket defaults to socket detection from the LXD shared package. 157 // TODO (manadart 2018-04-30) This looks like it can be achieved by using a 158 // combination of VarPath and HostPath from lxd.shared, in which case this 159 // can be deprecated in their favour. 160 func SocketPath(isSocket func(path string) bool) string { 161 path := os.Getenv("LXD_DIR") 162 if path != "" { 163 logger.Debugf("using environment LXD_DIR as socket path: %q", path) 164 } else { 165 path = filepath.FromSlash("/var/snap/lxd/common/lxd") 166 if isSocket == nil { 167 isSocket = shared.IsUnixSocket 168 } 169 if isSocket(filepath.Join(path, "unix.socket")) { 170 logger.Debugf("using LXD snap socket: %q", path) 171 } else { 172 path = filepath.FromSlash("/var/lib/lxd") 173 logger.Debugf("LXD snap socket not found, falling back to debian socket: %q", path) 174 } 175 } 176 return filepath.Join(path, "unix.socket") 177 } 178 179 // EnsureHTTPS takes a URI and ensures that it is a HTTPS URL. 180 // LXD Requires HTTPS. 181 func EnsureHTTPS(address string) string { 182 if strings.HasPrefix(address, "https://") { 183 return address 184 } 185 if strings.HasPrefix(address, "http://") { 186 addr := strings.Replace(address, "http://", "https://", 1) 187 logger.Debugf("LXD requires https://, using: %s", addr) 188 return addr 189 } 190 return "https://" + address 191 } 192 193 const defaultPort = 8443 194 195 // EnsureHostPort takes a URI and ensures that it has a port set, if it doesn't 196 // then it will ensure that port if added. 197 // The address supplied for the Host will be validated when parsed and if the 198 // address is not valid, then it will return an error. 199 func EnsureHostPort(address string) (string, error) { 200 // make sure we ensure a schema, otherwise somewhere:8443 can return a 201 // the following //:8443/somewhere 202 uri, err := url.Parse(EnsureHTTPS(address)) 203 if err != nil { 204 return "", errors.Trace(err) 205 } 206 if uri.Port() == "" { 207 uri.Host = fmt.Sprintf("%s:%d", uri.Host, defaultPort) 208 } 209 return uri.String(), nil 210 }