github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/environs/simplestreams/datasource.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package simplestreams
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils"
    14  )
    15  
    16  // A DataSource retrieves simplestreams metadata.
    17  type DataSource interface {
    18  	// Description describes the origin of this datasource.
    19  	// eg agent-metadata-url, cloud storage, keystone catalog etc.
    20  	Description() string
    21  
    22  	// Fetch loads the data at the specified relative path. It returns a reader from which
    23  	// the data can be retrieved as well as the full URL of the file. The full URL is typically
    24  	// used in log messages to help diagnose issues accessing the data.
    25  	Fetch(path string) (io.ReadCloser, string, error)
    26  
    27  	// URL returns the full URL of the path, as applicable to this datasource.
    28  	// This method is used primarily for logging purposes.
    29  	URL(path string) (string, error)
    30  
    31  	// PublicSigningKey returns the public key used to validate signed metadata.
    32  	PublicSigningKey() string
    33  
    34  	// SetAllowRetry sets the flag which determines if the datasource will retry fetching the metadata
    35  	// if it is not immediately available.
    36  	SetAllowRetry(allow bool)
    37  	// Priority is an importance factor for Data Source. Higher number means higher priority.
    38  	// This is will allow to sort data sources in order of importance.
    39  	Priority() int
    40  	// RequireSigned indicates whether this data source requires signed data.
    41  	RequireSigned() bool
    42  }
    43  
    44  const (
    45  	// These values used as priority factors for sorting data source data.
    46  
    47  	// EXISTING_CLOUD_DATA is the lowest in priority.
    48  	// It is mostly used in merge functions
    49  	// where existing data does not need to be ranked.
    50  	EXISTING_CLOUD_DATA = 0
    51  
    52  	// DEFAULT_CLOUD_DATA is used for common cloud data that
    53  	// is shared an is publically available.
    54  	DEFAULT_CLOUD_DATA = 10
    55  
    56  	// SPECIFIC_CLOUD_DATA is used to rank cloud specific data
    57  	// above commonly available.
    58  	// For e.g., openstack's "keystone catalogue".
    59  	SPECIFIC_CLOUD_DATA = 20
    60  
    61  	// CUSTOM_CLOUD_DATA is the highest available ranking and
    62  	// is given to custom data.
    63  	CUSTOM_CLOUD_DATA = 50
    64  )
    65  
    66  // A urlDataSource retrieves data from an HTTP URL.
    67  type urlDataSource struct {
    68  	description          string
    69  	baseURL              string
    70  	hostnameVerification utils.SSLHostnameVerification
    71  	publicSigningKey     string
    72  	priority             int
    73  	requireSigned        bool
    74  }
    75  
    76  // NewURLDataSource returns a new datasource reading from the specified baseURL.
    77  func NewURLDataSource(description, baseURL string, hostnameVerification utils.SSLHostnameVerification, priority int, requireSigned bool) DataSource {
    78  	return &urlDataSource{
    79  		description:          description,
    80  		baseURL:              baseURL,
    81  		hostnameVerification: hostnameVerification,
    82  		priority:             priority,
    83  		requireSigned:        requireSigned,
    84  	}
    85  }
    86  
    87  // NewURLSignedDataSource returns a new datasource for signed metadata reading from the specified baseURL.
    88  func NewURLSignedDataSource(description, baseURL, publicKey string, hostnameVerification utils.SSLHostnameVerification, priority int, requireSigned bool) DataSource {
    89  	return &urlDataSource{
    90  		description:          description,
    91  		baseURL:              baseURL,
    92  		publicSigningKey:     publicKey,
    93  		hostnameVerification: hostnameVerification,
    94  		priority:             priority,
    95  		requireSigned:        requireSigned,
    96  	}
    97  }
    98  
    99  // Description is defined in simplestreams.DataSource.
   100  func (u *urlDataSource) Description() string {
   101  	return u.description
   102  }
   103  
   104  func (u *urlDataSource) GoString() string {
   105  	return fmt.Sprintf("%v: urlDataSource(%q)", u.description, u.baseURL)
   106  }
   107  
   108  // urlJoin returns baseURL + relpath making sure to have a '/' inbetween them
   109  // This doesn't try to do anything fancy with URL query or parameter bits
   110  // It also doesn't use path.Join because that normalizes slashes, and you need
   111  // to keep both slashes in 'http://'.
   112  func urlJoin(baseURL, relpath string) string {
   113  	if strings.HasSuffix(baseURL, "/") {
   114  		return baseURL + relpath
   115  	}
   116  	return baseURL + "/" + relpath
   117  }
   118  
   119  // Fetch is defined in simplestreams.DataSource.
   120  func (h *urlDataSource) Fetch(path string) (io.ReadCloser, string, error) {
   121  	dataURL := urlJoin(h.baseURL, path)
   122  	client := utils.GetHTTPClient(h.hostnameVerification)
   123  	// dataURL can be http:// or file://
   124  	// MakeFileURL will only modify the URL if it's a file URL
   125  	dataURL = utils.MakeFileURL(dataURL)
   126  	resp, err := client.Get(dataURL)
   127  	if err != nil {
   128  		logger.Tracef("Got error requesting %q: %v", dataURL, err)
   129  		return nil, dataURL, errors.NotFoundf("invalid URL %q", dataURL)
   130  	}
   131  	if resp.StatusCode != http.StatusOK {
   132  		resp.Body.Close()
   133  		switch resp.StatusCode {
   134  		case http.StatusNotFound:
   135  			return nil, dataURL, errors.NotFoundf("cannot find URL %q", dataURL)
   136  		case http.StatusUnauthorized:
   137  			return nil, dataURL, errors.Unauthorizedf("unauthorised access to URL %q", dataURL)
   138  		}
   139  		return nil, dataURL, fmt.Errorf("cannot access URL %q, %q", dataURL, resp.Status)
   140  	}
   141  	return resp.Body, dataURL, nil
   142  }
   143  
   144  // URL is defined in simplestreams.DataSource.
   145  func (h *urlDataSource) URL(path string) (string, error) {
   146  	return utils.MakeFileURL(urlJoin(h.baseURL, path)), nil
   147  }
   148  
   149  // PublicSigningKey is defined in simplestreams.DataSource.
   150  func (u *urlDataSource) PublicSigningKey() string {
   151  	return u.publicSigningKey
   152  }
   153  
   154  // SetAllowRetry is defined in simplestreams.DataSource.
   155  func (h *urlDataSource) SetAllowRetry(allow bool) {
   156  	// This is a NOOP for url datasources.
   157  }
   158  
   159  // Priority is defined in simplestreams.DataSource.
   160  func (h *urlDataSource) Priority() int {
   161  	return h.priority
   162  }
   163  
   164  // RequireSigned is defined in simplestreams.DataSource.
   165  func (h *urlDataSource) RequireSigned() bool {
   166  	return h.requireSigned
   167  }