github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/crossmodel/offerurl.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package crossmodel
     5  
     6  import (
     7  	"fmt"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"gopkg.in/juju/names.v2"
    13  )
    14  
    15  // OfferURL represents the location of an offered application and its
    16  // associated exported endpoints.
    17  type OfferURL struct {
    18  	// Source represents where the offer is hosted.
    19  	// If empty, the model is another model in the same controller.
    20  	Source string // "<controller-name>" or "<jaas>" or ""
    21  
    22  	// User is the user whose namespace in which the offer is made.
    23  	// Where a model is specified, the user is the model owner.
    24  	User string
    25  
    26  	// ModelName is the name of the model providing the exported endpoints.
    27  	// It is only used for local URLs or for specifying models in the same
    28  	// controller.
    29  	ModelName string
    30  
    31  	// ApplicationName is the name of the application providing the exported endpoints.
    32  	ApplicationName string
    33  }
    34  
    35  // Path returns the path component of the URL.
    36  func (u *OfferURL) Path() string {
    37  	var parts []string
    38  	if u.User != "" {
    39  		parts = append(parts, u.User)
    40  	}
    41  	if u.ModelName != "" {
    42  		parts = append(parts, u.ModelName)
    43  	}
    44  	path := strings.Join(parts, "/")
    45  	path = fmt.Sprintf("%s.%s", path, u.ApplicationName)
    46  	if u.Source == "" {
    47  		return path
    48  	}
    49  	return fmt.Sprintf("%s:%s", u.Source, path)
    50  }
    51  
    52  func (u *OfferURL) String() string {
    53  	return u.Path()
    54  }
    55  
    56  // AsLocal returns a copy of the URL with an empty (local) source.
    57  func (u *OfferURL) AsLocal() *OfferURL {
    58  	localURL := *u
    59  	localURL.Source = ""
    60  	return &localURL
    61  }
    62  
    63  // HasEndpoint returns whether this offer URL includes an
    64  // endpoint name in the application name.
    65  func (u *OfferURL) HasEndpoint() bool {
    66  	return strings.Contains(u.ApplicationName, ":")
    67  }
    68  
    69  // modelApplicationRegexp parses urls of the form controller:user/model.application[:relname]
    70  var modelApplicationRegexp = regexp.MustCompile(`(/?((?P<user>[^/]+)/)?(?P<model>[^.]*)(\.(?P<application>[^:]*(:.*)?))?)?`)
    71  
    72  //var modelApplicationRegexp = regexp.MustCompile(`(/?((?P<user>[a-zA-Z]+)/)?(?P<model>[a-zA-Z]+)?(\.(?P<application>[^:]*(:[a-zA-Z]+)?))?)?`)
    73  
    74  // ParseOfferURL parses the specified URL string into an OfferURL.
    75  // The URL string is of one of the forms:
    76  //  <model-name>.<application-name>
    77  //  <model-name>.<application-name>:<relation-name>
    78  //  <user>/<model-name>.<application-name>
    79  //  <user>/<model-name>.<application-name>:<relation-name>
    80  //  <controller>:<user>/<model-name>.<application-name>
    81  //  <controller>:<user>/<model-name>.<application-name>:<relation-name>
    82  func ParseOfferURL(urlStr string) (*OfferURL, error) {
    83  	return parseOfferURL(urlStr)
    84  }
    85  
    86  // parseOfferURL parses the specified URL string into an OfferURL.
    87  func parseOfferURL(urlStr string) (*OfferURL, error) {
    88  	urlParts, err := parseOfferURLParts(urlStr, false)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	url := OfferURL(*urlParts)
    93  	return &url, nil
    94  }
    95  
    96  // OfferURLParts contains various attributes of a URL.
    97  type OfferURLParts OfferURL
    98  
    99  // ParseOfferURLParts parses a partial URL, filling out what parts are supplied.
   100  // This method is used to generate a filter used to query matching offer URLs.
   101  func ParseOfferURLParts(urlStr string) (*OfferURLParts, error) {
   102  	return parseOfferURLParts(urlStr, true)
   103  }
   104  
   105  var endpointRegexp = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
   106  
   107  func maybeParseSource(urlStr string) (source, rest string) {
   108  	parts := strings.Split(urlStr, ":")
   109  	switch len(parts) {
   110  	case 3:
   111  		return parts[0], parts[1] + ":" + parts[2]
   112  	case 2:
   113  		if endpointRegexp.MatchString(parts[1]) {
   114  			return "", urlStr
   115  		}
   116  		return parts[0], parts[1]
   117  	}
   118  	return "", urlStr
   119  }
   120  
   121  func parseOfferURLParts(urlStr string, allowIncomplete bool) (*OfferURLParts, error) {
   122  	var result OfferURLParts
   123  	source, urlParts := maybeParseSource(urlStr)
   124  
   125  	valid := !strings.HasPrefix(urlStr, ":")
   126  	valid = valid && modelApplicationRegexp.MatchString(urlParts)
   127  	if valid {
   128  		result.Source = source
   129  		result.User = modelApplicationRegexp.ReplaceAllString(urlParts, "$user")
   130  		result.ModelName = modelApplicationRegexp.ReplaceAllString(urlParts, "$model")
   131  		result.ApplicationName = modelApplicationRegexp.ReplaceAllString(urlParts, "$application")
   132  	}
   133  	if !valid || strings.Contains(result.ModelName, "/") || strings.Contains(result.ApplicationName, "/") {
   134  		// TODO(wallyworld) - update error message when we support multi-controller and JAAS CMR
   135  		return nil, errors.Errorf("application offer URL has invalid form, must be [<user/]<model>.<appname>: %q", urlStr)
   136  	}
   137  	if !allowIncomplete && result.ModelName == "" {
   138  		return nil, errors.Errorf("application offer URL is missing model")
   139  	}
   140  	if !allowIncomplete && result.ApplicationName == "" {
   141  		return nil, errors.Errorf("application offer URL is missing application")
   142  	}
   143  
   144  	// Application name part may contain a relation name part, so strip that bit out
   145  	// before validating the name.
   146  	appName := strings.Split(result.ApplicationName, ":")[0]
   147  	// Validate the resulting URL part values.
   148  	if result.User != "" && !names.IsValidUser(result.User) {
   149  		return nil, errors.NotValidf("user name %q", result.User)
   150  	}
   151  	if result.ModelName != "" && !names.IsValidModelName(result.ModelName) {
   152  		return nil, errors.NotValidf("model name %q", result.ModelName)
   153  	}
   154  	if appName != "" && !names.IsValidApplication(appName) {
   155  		return nil, errors.NotValidf("application name %q", appName)
   156  	}
   157  	return &result, nil
   158  }
   159  
   160  // MakeURL constructs an offer URL from the specified components.
   161  func MakeURL(user, model, application, controller string) string {
   162  	base := fmt.Sprintf("%s/%s.%s", user, model, application)
   163  	if controller == "" {
   164  		return base
   165  	}
   166  	return fmt.Sprintf("%s:%s", controller, base)
   167  }