github.com/Hashicorp/terraform@v0.11.12-beta1/registry/regsrc/module.go (about)

     1  package regsrc
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/terraform/svchost"
    10  )
    11  
    12  var (
    13  	ErrInvalidModuleSource = errors.New("not a valid registry module source")
    14  
    15  	// nameSubRe is the sub-expression that matches a valid module namespace or
    16  	// name. It's strictly a super-set of what GitHub allows for user/org and
    17  	// repo names respectively, but more restrictive than our original repo-name
    18  	// regex which allowed periods but could cause ambiguity with hostname
    19  	// prefixes. It does not anchor the start or end so it can be composed into
    20  	// more complex RegExps below. Alphanumeric with - and _ allowed in non
    21  	// leading or trailing positions. Max length 64 chars. (GitHub username is
    22  	// 38 max.)
    23  	nameSubRe = "[0-9A-Za-z](?:[0-9A-Za-z-_]{0,62}[0-9A-Za-z])?"
    24  
    25  	// providerSubRe is the sub-expression that matches a valid provider. It
    26  	// does not anchor the start or end so it can be composed into more complex
    27  	// RegExps below. Only lowercase chars and digits are supported in practice.
    28  	// Max length 64 chars.
    29  	providerSubRe = "[0-9a-z]{1,64}"
    30  
    31  	// moduleSourceRe is a regular expression that matches the basic
    32  	// namespace/name/provider[//...] format for registry sources. It assumes
    33  	// any FriendlyHost prefix has already been removed if present.
    34  	moduleSourceRe = regexp.MustCompile(
    35  		fmt.Sprintf("^(%s)\\/(%s)\\/(%s)(?:\\/\\/(.*))?$",
    36  			nameSubRe, nameSubRe, providerSubRe))
    37  
    38  	// NameRe is a regular expression defining the format allowed for namespace
    39  	// or name fields in module registry implementations.
    40  	NameRe = regexp.MustCompile("^" + nameSubRe + "$")
    41  
    42  	// ProviderRe is a regular expression defining the format allowed for
    43  	// provider fields in module registry implementations.
    44  	ProviderRe = regexp.MustCompile("^" + providerSubRe + "$")
    45  
    46  	// these hostnames are not allowed as registry sources, because they are
    47  	// already special case module sources in terraform.
    48  	disallowed = map[string]bool{
    49  		"github.com":    true,
    50  		"bitbucket.org": true,
    51  	}
    52  )
    53  
    54  // Module describes a Terraform Registry Module source.
    55  type Module struct {
    56  	// RawHost is the friendly host prefix if one was present. It might be nil
    57  	// if the original source had no host prefix which implies
    58  	// PublicRegistryHost but is distinct from having an actual pointer to
    59  	// PublicRegistryHost since it encodes the fact the original string didn't
    60  	// include a host prefix at all which is significant for recovering actual
    61  	// input not just normalized form. Most callers should access it with Host()
    62  	// which will return public registry host instance if it's nil.
    63  	RawHost      *FriendlyHost
    64  	RawNamespace string
    65  	RawName      string
    66  	RawProvider  string
    67  	RawSubmodule string
    68  }
    69  
    70  // NewModule construct a new module source from separate parts. Pass empty
    71  // string if host or submodule are not needed.
    72  func NewModule(host, namespace, name, provider, submodule string) (*Module, error) {
    73  	m := &Module{
    74  		RawNamespace: namespace,
    75  		RawName:      name,
    76  		RawProvider:  provider,
    77  		RawSubmodule: submodule,
    78  	}
    79  	if host != "" {
    80  		h := NewFriendlyHost(host)
    81  		if h != nil {
    82  			fmt.Println("HOST:", h)
    83  			if !h.Valid() || disallowed[h.Display()] {
    84  				return nil, ErrInvalidModuleSource
    85  			}
    86  		}
    87  		m.RawHost = h
    88  	}
    89  	return m, nil
    90  }
    91  
    92  // ParseModuleSource attempts to parse source as a Terraform registry module
    93  // source. If the string is not found to be in a valid format,
    94  // ErrInvalidModuleSource is returned. Note that this can only be used on
    95  // "input" strings, e.g. either ones supplied by the user or potentially
    96  // normalised but in Display form (unicode). It will fail to parse a source with
    97  // a punycoded domain since this is not permitted input from a user. If you have
    98  // an already normalized string internally, you can compare it without parsing
    99  // by comparing with the normalized version of the subject with the normal
   100  // string equality operator.
   101  func ParseModuleSource(source string) (*Module, error) {
   102  	// See if there is a friendly host prefix.
   103  	host, rest := ParseFriendlyHost(source)
   104  	if host != nil {
   105  		if !host.Valid() || disallowed[host.Display()] {
   106  			return nil, ErrInvalidModuleSource
   107  		}
   108  	}
   109  
   110  	matches := moduleSourceRe.FindStringSubmatch(rest)
   111  	if len(matches) < 4 {
   112  		return nil, ErrInvalidModuleSource
   113  	}
   114  
   115  	m := &Module{
   116  		RawHost:      host,
   117  		RawNamespace: matches[1],
   118  		RawName:      matches[2],
   119  		RawProvider:  matches[3],
   120  	}
   121  
   122  	if len(matches) == 5 {
   123  		m.RawSubmodule = matches[4]
   124  	}
   125  
   126  	return m, nil
   127  }
   128  
   129  // Display returns the source formatted for display to the user in CLI or web
   130  // output.
   131  func (m *Module) Display() string {
   132  	return m.formatWithPrefix(m.normalizedHostPrefix(m.Host().Display()), false)
   133  }
   134  
   135  // Normalized returns the source formatted for internal reference or comparison.
   136  func (m *Module) Normalized() string {
   137  	return m.formatWithPrefix(m.normalizedHostPrefix(m.Host().Normalized()), false)
   138  }
   139  
   140  // String returns the source formatted as the user originally typed it assuming
   141  // it was parsed from user input.
   142  func (m *Module) String() string {
   143  	// Don't normalize public registry hostname - leave it exactly like the user
   144  	// input it.
   145  	hostPrefix := ""
   146  	if m.RawHost != nil {
   147  		hostPrefix = m.RawHost.String() + "/"
   148  	}
   149  	return m.formatWithPrefix(hostPrefix, true)
   150  }
   151  
   152  // Equal compares the module source against another instance taking
   153  // normalization into account.
   154  func (m *Module) Equal(other *Module) bool {
   155  	return m.Normalized() == other.Normalized()
   156  }
   157  
   158  // Host returns the FriendlyHost object describing which registry this module is
   159  // in. If the original source string had not host component this will return the
   160  // PublicRegistryHost.
   161  func (m *Module) Host() *FriendlyHost {
   162  	if m.RawHost == nil {
   163  		return PublicRegistryHost
   164  	}
   165  	return m.RawHost
   166  }
   167  
   168  func (m *Module) normalizedHostPrefix(host string) string {
   169  	if m.Host().Equal(PublicRegistryHost) {
   170  		return ""
   171  	}
   172  	return host + "/"
   173  }
   174  
   175  func (m *Module) formatWithPrefix(hostPrefix string, preserveCase bool) string {
   176  	suffix := ""
   177  	if m.RawSubmodule != "" {
   178  		suffix = "//" + m.RawSubmodule
   179  	}
   180  	str := fmt.Sprintf("%s%s/%s/%s%s", hostPrefix, m.RawNamespace, m.RawName,
   181  		m.RawProvider, suffix)
   182  
   183  	// lower case by default
   184  	if !preserveCase {
   185  		return strings.ToLower(str)
   186  	}
   187  	return str
   188  }
   189  
   190  // Module returns just the registry ID of the module, without a hostname or
   191  // suffix.
   192  func (m *Module) Module() string {
   193  	return fmt.Sprintf("%s/%s/%s", m.RawNamespace, m.RawName, m.RawProvider)
   194  }
   195  
   196  // SvcHost returns the svchost.Hostname for this module. Since FriendlyHost may
   197  // contain an invalid hostname, this also returns an error indicating if it
   198  // could be converted to a svchost.Hostname. If no host is specified, the
   199  // default PublicRegistryHost is returned.
   200  func (m *Module) SvcHost() (svchost.Hostname, error) {
   201  	if m.RawHost == nil {
   202  		return svchost.ForComparison(PublicRegistryHost.Raw)
   203  	}
   204  	return svchost.ForComparison(m.RawHost.Raw)
   205  }