Skip to content

Commit ccf6e94

Browse files
authored
Infer version from git and URL based module sources (#370)
1 parent 366132a commit ccf6e94

File tree

5 files changed

+92
-1
lines changed

5 files changed

+92
-1
lines changed

pkg/modprovider/server.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,20 @@ func parseParameterizeRequest(
245245
}
246246

247247
if !isValidVersion(args[1]) {
248+
// if the second arg is not a version then it must be package name
249+
source := TFModuleSource(args[0])
250+
if referencedVersion, ok := source.ReferencedVersionInURL(); ok && isValidVersion(referencedVersion) {
251+
// here the source is remote but the version is specified in the URL
252+
// usually this is a git URL with a ref query parameter
253+
// we make sure that the version is valid because sometimes
254+
// the ref query parameter refers to a branch
255+
return applyConfigWhenAvailable(args[1], ParameterizeArgs{
256+
TFModuleSource: source,
257+
TFModuleVersion: TFModuleVersion(referencedVersion),
258+
PackageName: packageName(args[1]),
259+
})
260+
}
261+
248262
// if the second arg is not a version then it must be package name
249263
// but the source is remote so we need to resolve the version ourselves
250264
latest, err := latestModuleVersion(ctx, args[0])

pkg/modprovider/server_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,21 @@ func TestParseParameterizeRequest(t *testing.T) {
105105
assert.Equal(t, packageName("demoWebsite"), args.PackageName)
106106
})
107107

108+
t.Run("parses github-based remote module source with version", func(t *testing.T) {
109+
testRequest := &pulumirpc.ParameterizeRequest{
110+
Parameters: &pulumirpc.ParameterizeRequest_Args{
111+
Args: &pulumirpc.ParameterizeRequest_ParametersArgs{
112+
Args: []string{"github.com/terraform-aws-modules/terraform-aws-vpc?ref=v5.21.0", "vpc"},
113+
},
114+
},
115+
}
116+
args, err := parseParameterizeRequest(ctx, testRequest)
117+
assert.NoError(t, err)
118+
assert.Equal(t, TFModuleSource("github.com/terraform-aws-modules/terraform-aws-vpc?ref=v5.21.0"), args.TFModuleSource)
119+
assert.Equal(t, TFModuleVersion("5.21.0"), args.TFModuleVersion)
120+
assert.Equal(t, packageName("vpc"), args.PackageName)
121+
})
122+
108123
t.Run("fails on invalid module source", func(t *testing.T) {
109124
testRequest := &pulumirpc.ParameterizeRequest{
110125
Parameters: &pulumirpc.ParameterizeRequest_Args{

pkg/modprovider/tfmodules_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,51 @@ func TestInferModuleSchemaFromGitHubSourceWithSubModule(t *testing.T) {
398398
}
399399
}
400400

401+
func TestInferModuleSchemaFromGitHubSourceWithSubModuleAndVersion(t *testing.T) {
402+
ctx := context.Background()
403+
packageName := packageName("consulCluster")
404+
executors := getExecutorsFromEnv()
405+
for _, executor := range executors {
406+
t.Run("executor="+executor, func(t *testing.T) {
407+
tf := newTestRuntime(t, executor)
408+
source := TFModuleSource("github.com/hashicorp/terraform-aws-consul//modules/consul-cluster?ref=v0.11.0")
409+
referencedVersion, ok := source.ReferencedVersionInURL()
410+
assert.True(t, ok, "referenced version should be found in the source URL")
411+
assert.Equal(t, "0.11.0", referencedVersion, "referenced version should be 0.11.0")
412+
consulClusterSchema, err := InferModuleSchema(ctx,
413+
tf,
414+
packageName,
415+
"github.com/hashicorp/terraform-aws-consul//modules/consul-cluster?ref=v0.11.0",
416+
TFModuleVersion(referencedVersion))
417+
418+
assert.NoError(t, err, "failed to infer module schema for github submodule")
419+
assert.NotNil(t, consulClusterSchema, "inferred module schema for aws consul cluster submodule is nil")
420+
// verify a sample of the inputs with different inferred types
421+
expectedSampleInputs := map[string]*schema.PropertySpec{
422+
"ami_id": {
423+
Description: "The ID of the AMI to run in this cluster. " +
424+
"Should be an AMI that had Consul installed and configured by the install-consul module.",
425+
Secret: false,
426+
TypeSpec: stringType,
427+
},
428+
"spot_price": {
429+
Description: "The maximum hourly price to pay for EC2 Spot Instances.",
430+
Secret: false,
431+
TypeSpec: numberType,
432+
},
433+
}
434+
435+
for name, expected := range expectedSampleInputs {
436+
actual, ok := consulClusterSchema.Inputs[resource.PropertyKey(name)]
437+
assert.True(t, ok, "input %s is missing from the schema", name)
438+
assert.Equal(t, expected.Description, actual.Description, "input %s description is incorrect", name)
439+
assert.Equal(t, expected.Secret, actual.Secret, "input %s secret is incorrect", name)
440+
assert.Equal(t, expected.TypeSpec, actual.TypeSpec, "input %s type is incorrect", name)
441+
}
442+
})
443+
}
444+
}
445+
401446
func TestResolveModuleSources(t *testing.T) {
402447
executors := getExecutorsFromEnv()
403448
for _, executor := range executors {

pkg/tfsandbox/tf_file.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,11 @@ func CreateTFFile(
141141
moduleProps := map[string]interface{}{
142142
"source": absoluteSource,
143143
}
144+
145+
_, hasRef := source.ReferencedVersionInURL()
144146
// local modules and github-based modules don't have a version
145-
if version != "" {
147+
// because setting a version is only valid for registry modules
148+
if version != "" && !hasRef {
146149
moduleProps["version"] = version
147150
}
148151

pkg/tfsandbox/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tfsandbox
22

33
import (
4+
"net/url"
45
"strings"
56
)
67

@@ -22,6 +23,19 @@ func (s TFModuleSource) IsLocalPath() bool {
2223
return false
2324
}
2425

26+
// ReferencedVersionInURL returns the version reference in the module source URL, if any.
27+
// for example git::https://example.com/vpc.git?ref=v1.2.0 would return "1.2.0", true.
28+
func (s TFModuleSource) ReferencedVersionInURL() (string, bool) {
29+
source := string(s)
30+
parsedURL, err := url.Parse(source)
31+
if err != nil {
32+
return "", false
33+
}
34+
35+
ref := strings.TrimPrefix(parsedURL.Query().Get("ref"), "v")
36+
return ref, ref != ""
37+
}
38+
2539
// Version specification for a Terraform module, for example "5.16.0".
2640
//
2741
// May indicate version constraints, or be empty.

0 commit comments

Comments
 (0)