github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/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/hashicorp/terraform/internal/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 }