github.com/kevinklinger/open_terraform@v1.3.6/noninternal/registry/regsrc/module.go (about)

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