Skip to content
This repository was archived by the owner on Jun 11, 2021. It is now read-only.

Commit 667b8f3

Browse files
committed
Add initial registry module related code
1 parent 92f929c commit 667b8f3

File tree

4 files changed

+173
-17
lines changed

4 files changed

+173
-17
lines changed

cmd/install.go

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func init() {
6969
func getModule(moduleName string, moduleMeta module, wg *sync.WaitGroup) {
7070
defer wg.Done()
7171

72-
moduleSource, modulePath := splitAddrSubdir(moduleMeta.Source)
72+
moduleSource, modulePath := getter.SourceDirSubdir(moduleMeta.Source)
7373

7474
moduleVersion := ""
7575
if len(moduleMeta.Version) > 0 {
@@ -85,7 +85,7 @@ func getModule(moduleName string, moduleMeta module, wg *sync.WaitGroup) {
8585
switch {
8686
case xt.IsLocalSourceAddr(moduleSource):
8787
xt.CopyFile(moduleName, moduleSource, directory)
88-
case isRegistrySourceAddr(moduleSource):
88+
case xt.IsRegistrySourceAddr(moduleSource):
8989
source, version := getRegistrySource(moduleName, moduleSource, moduleVersion)
9090
getWithGoGetter(moduleName, source, version, directory)
9191
default:
@@ -112,19 +112,12 @@ func getModule(moduleName string, moduleMeta module, wg *sync.WaitGroup) {
112112
var registryBaseURL = "https://registry.terraform.io/v1/modules"
113113
var githubDownloadURLRe = regexp.MustCompile(`https://[^/]+/repos/([^/]+)/([^/]+)/tarball/([^/]+)/.*`)
114114

115-
func isRegistrySourceAddr(addr string) bool {
116-
nameRegex := "[0-9A-Za-z](?:[0-9A-Za-z-_]{0,62}[0-9A-Za-z])?"
117-
providerRegex := "[0-9a-z]{1,64}"
118-
registryRegex := regexp.MustCompile(
119-
fmt.Sprintf("^(%s)\\/(%s)\\/(%s)(?:\\/\\/(.*))?$", nameRegex, nameRegex, providerRegex))
120-
return registryRegex.MatchString(addr)
121-
}
122-
123115
func getRegistrySource(name string, source string, version string) (string, string) {
124116
logVersion := "latest"
125117
if len(version) > 0 {
126118
logVersion = version
127119
}
120+
128121
jww.INFO.Printf("[%s] Looking up %s version %s in Terraform registry", name, source, logVersion)
129122

130123
src := strings.Split(source, "/")
@@ -150,6 +143,7 @@ func getRegistrySource(name string, source string, version string) (string, stri
150143
if len(resp.Header["X-Terraform-Get"]) > 0 {
151144
githubDownloadURL = resp.Header["X-Terraform-Get"][0]
152145
}
146+
jww.INFO.Printf("[%s] %v", name, registryDownloadURL)
153147

154148
if githubDownloadURLRe.MatchString(githubDownloadURL) {
155149
matches := githubDownloadURLRe.FindStringSubmatch(githubDownloadURL)
@@ -159,7 +153,7 @@ func getRegistrySource(name string, source string, version string) (string, stri
159153
}
160154
err = errors.New("Unable to find module or version download url")
161155
xt.CheckIfError(name, err)
162-
return "", "" // Never reacbhes here
156+
return "", "" // Never reaches here
163157
}
164158

165159
// Handle modules from other sources to reflect:
@@ -248,9 +242,3 @@ func getWithGoGetter(name string, source string, version string, directory strin
248242
err = client.Get()
249243
xt.CheckIfError(name, err)
250244
}
251-
252-
// The subDir portion will be returned as empty if no subdir separator
253-
// ("//") is present in the address.
254-
func splitAddrSubdir(addr string) (packageAddr, subDir string) {
255-
return getter.SourceDirSubdir(addr)
256-
}

pkg/xterrafile/registry.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright © 2019 Tim Birkett <tim.birkett@devopsmakers.com>
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package xterrafile
22+
23+
import (
24+
"fmt"
25+
"io/ioutil"
26+
"log"
27+
28+
"github.com/blang/semver"
29+
"github.com/hashicorp/terraform/registry"
30+
"github.com/hashicorp/terraform/registry/regsrc"
31+
"github.com/hashicorp/terraform/svchost/disco"
32+
33+
jww "github.com/spf13/jwalterweatherman"
34+
)
35+
36+
// IsRegistrySourceAddr check an address is a valid registry address
37+
func IsRegistrySourceAddr(addr string) bool {
38+
jww.DEBUG.Printf("Testing if %s is a registry source", addr)
39+
_, err := regsrc.ParseModuleSource(addr)
40+
return err == nil
41+
}
42+
43+
// GetRegistrySource retrieves a modules download source from a Terraform registry
44+
func GetRegistrySource(name string, source string, version string) bool {
45+
modSrc, err := getModSrc(source)
46+
CheckIfError(name, err)
47+
48+
version, err = getRegistryVersion(modSrc, version, nil)
49+
CheckIfError(name, err)
50+
51+
jww.ERROR.Printf("[%s] Found module version %s at %s", name, version, modSrc.Host())
52+
53+
return true
54+
}
55+
56+
// Helper function to return a valid version
57+
func getRegistryVersion(modSrc *regsrc.Module, version string, services *disco.Disco) (string, error) {
58+
// Don't log from Terraform's HTTP client
59+
log.SetFlags(0)
60+
log.SetOutput(ioutil.Discard)
61+
62+
regClient := registry.NewClient(services, nil)
63+
regClientResp, err := regClient.ModuleVersions(modSrc)
64+
if err != nil {
65+
return "", err
66+
}
67+
68+
validModuleVersionRange, err := semver.ParseRange(version)
69+
if err != nil {
70+
return "", err
71+
}
72+
73+
regModule := regClientResp.Modules[0]
74+
for _, moduleVersion := range regModule.Versions {
75+
v, _ := semver.ParseTolerant(moduleVersion.Version)
76+
77+
if validModuleVersionRange(v) {
78+
return v.String(), nil
79+
}
80+
}
81+
err = fmt.Errorf(
82+
"Unable to find a valid version at %s newest version is %s",
83+
modSrc.Host(),
84+
regModule.Versions[0].Version)
85+
return "", err
86+
}
87+
88+
// Helper function to parse and return a module source
89+
func getModSrc(source string) (*regsrc.Module, error) {
90+
modSrc, err := regsrc.ParseModuleSource(source)
91+
if err != nil {
92+
return nil, err
93+
}
94+
return modSrc, nil
95+
}

pkg/xterrafile/registry_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package xterrafile
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform/registry/test"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestIsRegistrySourceAddr(t *testing.T) {
11+
assert.False(t, IsRegistrySourceAddr("./some/path"), "non-registry path should be false")
12+
assert.True(t, IsRegistrySourceAddr("terraform-digitalocean-modules/droplet/digitalocean"),
13+
"registry path should be true")
14+
assert.True(t, IsRegistrySourceAddr(
15+
"terraform-digitalocean-modules/droplet/digitalocean//examples/test"),
16+
"registry path with sub-path should be true")
17+
assert.True(t, IsRegistrySourceAddr("app.terraform.io/terraform-digitalocean-modules/droplet/digitalocean"),
18+
"private registry path should be true")
19+
assert.True(t, IsRegistrySourceAddr(
20+
"app.terraform.io/terraform-digitalocean-modules/droplet/digitalocean//examples/test"),
21+
"private registry path with sub-path should be true")
22+
}
23+
24+
func TestGetRegistrySource(t *testing.T) {
25+
assert.True(t, GetRegistrySource(
26+
"droplet",
27+
"terraform-digitalocean-modules/droplet/digitalocean",
28+
">=0.1.2"),
29+
"private registry path with sub-path should be true")
30+
// assert.True(t, GetRegistrySource(
31+
// "droplet2",
32+
// "app.terraform.io/terraform-digitalocean-modules/droplet/digitalocean",
33+
// ">= 1.0.0"),
34+
// "private registry path with sub-path should be true")
35+
}
36+
37+
func TestGetModSrc(t *testing.T) {
38+
module1String := "terraform-digitalocean-modules/droplet/digitalocean"
39+
module1Src, err := getModSrc(module1String)
40+
assert.Equal(t, nil, err, "error should be nil")
41+
assert.Equal(t, "registry.terraform.io", module1Src.Host().Normalized(), "host should be public repo")
42+
43+
module2String := "app.terraform.io/terraform-digitalocean-modules/droplet/digitalocean"
44+
module2Src, err := getModSrc(module2String)
45+
assert.Equal(t, nil, err, "error should be nil")
46+
assert.Equal(t, "app.terraform.io", module2Src.Host().Normalized(), "host should be private repo")
47+
48+
module3String := "---.io/terraform-digitalocean-modules/droplet/digitalocean"
49+
module3Src, err := getModSrc(module3String)
50+
assert.NotEqual(t, nil, err, "error should be present")
51+
assert.Panics(t, func() { module3Src.Host().Normalized() }, "accessing host should panic")
52+
}
53+
54+
func TestGetRegistryVersion(t *testing.T) {
55+
server := test.Registry()
56+
defer server.Close()
57+
58+
modSrc, _ := getModSrc("example.com/test-versions/name/provider")
59+
version, _ := getRegistryVersion(modSrc, ">= 2.0.0 < 2.2.0", test.Disco(server))
60+
assert.Equal(t, "2.1.1", version, "version should be >= 2.0.0 < 2.2.0")
61+
62+
_, err := getRegistryVersion(modSrc, ">= 3.0.0", test.Disco(server))
63+
assert.Error(t, err, "should have returned an error")
64+
65+
_, err = getRegistryVersion(modSrc, "not.a.version", test.Disco(server))
66+
assert.Error(t, err, "should return an error")
67+
68+
modSrc, _ = getModSrc("invalid.com/test-versions/name/provider")
69+
_, err = getRegistryVersion(modSrc, ">= 3.0.0", test.Disco(server))
70+
assert.Error(t, err, "should return an error")
71+
}

pkg/xterrafile/util_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package xterrafile
33
import (
44
"bytes"
55
"errors"
6+
"os"
67
"testing"
78

89
"github.com/stretchr/testify/assert"
@@ -15,6 +16,7 @@ func TestCheckIfError(t *testing.T) {
1516
// Capture logging
1617
var outputBuf bytes.Buffer
1718
jww.SetStdoutOutput(&outputBuf)
19+
defer jww.SetStdoutOutput(os.Stdout)
1820

1921
// override osExit to test for usage
2022
oldOsExit := osExit

0 commit comments

Comments
 (0)