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  }