-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds version output parsing #7
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package tfexec | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/hashicorp/go-version" | ||
) | ||
|
||
// Version returns structured output from the terraform version command including both the Terraform CLI version | ||
// and any initialized provider versions. This will read cached values when present unless the skipCache parameter | ||
// is set to true. | ||
func (tf *Terraform) Version(ctx context.Context, skipCache bool) (tfVersion *version.Version, providerVersions map[string]*version.Version, err error) { | ||
tf.versionLock.Lock() | ||
defer tf.versionLock.Unlock() | ||
|
||
if tf.execVersion == nil || skipCache { | ||
tf.execVersion, tf.provVersions, err = tf.version(ctx) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
} | ||
|
||
return tf.execVersion, tf.provVersions, nil | ||
} | ||
|
||
// version does not use the locking on the Terraform instance and should probably not be used directly, prefer Version. | ||
func (tf *Terraform) version(ctx context.Context) (*version.Version, map[string]*version.Version, error) { | ||
// TODO: 0.13.0-beta2? and above supports a `-json` on the version command, should add support | ||
// for that here and fallback to string parsing | ||
|
||
versionCmd := tf.buildTerraformCmd(ctx, "version") | ||
var errBuf strings.Builder | ||
var outBuf bytes.Buffer | ||
versionCmd.Stderr = &errBuf | ||
versionCmd.Stdout = &outBuf | ||
|
||
err := versionCmd.Run() | ||
if err != nil { | ||
fmt.Println(errBuf.String()) | ||
return nil, nil, fmt.Errorf("unable to run version command: %w, %s", err, errBuf.String()) | ||
} | ||
|
||
tfVersion, providerVersions, err := parseVersionOutput(outBuf.String()) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("unable to parse version: %w", err) | ||
} | ||
|
||
return tfVersion, providerVersions, nil | ||
} | ||
|
||
var ( | ||
simpleVersionRe = `v?(?P<version>[0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9\.]+)?)` | ||
|
||
versionOutputRe = regexp.MustCompile(`^Terraform ` + simpleVersionRe) | ||
providerVersionOutputRe = regexp.MustCompile(`(\n\+ provider[\. ](?P<name>\S+) ` + simpleVersionRe + `)`) | ||
) | ||
|
||
func parseVersionOutput(stdout string) (*version.Version, map[string]*version.Version, error) { | ||
stdout = strings.TrimSpace(stdout) | ||
|
||
submatches := versionOutputRe.FindStringSubmatch(stdout) | ||
if len(submatches) != 2 { | ||
return nil, nil, fmt.Errorf("unexpected number of version matches %d for %s", len(submatches), stdout) | ||
} | ||
v, err := version.NewVersion(submatches[1]) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("unable to parse version %q: %w", submatches[1], err) | ||
} | ||
|
||
allSubmatches := providerVersionOutputRe.FindAllStringSubmatch(stdout, -1) | ||
provV := map[string]*version.Version{} | ||
|
||
for _, submatches := range allSubmatches { | ||
if len(submatches) != 4 { | ||
return nil, nil, fmt.Errorf("unexpected number of providerion version matches %d for %s", len(submatches), stdout) | ||
} | ||
|
||
v, err := version.NewVersion(submatches[3]) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("unable to parse provider version %q: %w", submatches[3], err) | ||
} | ||
|
||
provV[submatches[2]] = v | ||
} | ||
|
||
return v, provV, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package tfexec | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/hashicorp/go-version" | ||
) | ||
|
||
func TestVersion(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
for _, tfv := range []string{ | ||
"0.11.14", | ||
"0.12.28", | ||
"0.13.0-beta3", | ||
} { | ||
t.Run(tfv, func(t *testing.T) { | ||
td := testTempDir(t) | ||
defer os.RemoveAll(td) | ||
|
||
err := copyFile(filepath.Join(testFixtureDir, "basic/main.tf"), td) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
tf, err := NewTerraform(td, tfVersion(t, tfv)) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
err = tf.Init(ctx, Lock(false)) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
v, _, err := tf.Version(ctx, false) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if v.String() != tfv { | ||
t.Fatalf("expected version %q, got %q", tfv, v) | ||
} | ||
|
||
// TODO: test/assert provider info | ||
|
||
// force execution / skip cache as well | ||
v, _, err = tf.Version(ctx, true) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if v.String() != tfv { | ||
t.Fatalf("expected version %q, got %q", tfv, v) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestParseVersionOutput(t *testing.T) { | ||
var mustVer = func(s string) *version.Version { | ||
v, err := version.NewVersion(s) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
return v | ||
} | ||
|
||
for i, c := range []struct { | ||
expectedV *version.Version | ||
expectedProviders map[string]*version.Version | ||
|
||
stdout string | ||
}{ | ||
// 0.13 tests | ||
{ | ||
mustVer("0.13.0-dev"), nil, ` | ||
Terraform v0.13.0-dev`, | ||
}, | ||
{ | ||
mustVer("0.13.0-dev"), map[string]*version.Version{ | ||
"registry.terraform.io/hashicorp/null": mustVer("2.1.2"), | ||
"registry.terraform.io/paultyng/null": mustVer("0.1.0"), | ||
}, ` | ||
Terraform v0.13.0-dev | ||
+ provider registry.terraform.io/hashicorp/null v2.1.2 | ||
+ provider registry.terraform.io/paultyng/null v0.1.0`, | ||
}, | ||
{ | ||
mustVer("0.13.0-dev"), nil, ` | ||
Terraform v0.13.0-dev | ||
|
||
Your version of Terraform is out of date! The latest version | ||
is 0.13.1. You can update by downloading from https://www.terraform.io/downloads.html`, | ||
}, | ||
{ | ||
mustVer("0.13.0-dev"), map[string]*version.Version{ | ||
"registry.terraform.io/hashicorp/null": mustVer("2.1.2"), | ||
"registry.terraform.io/paultyng/null": mustVer("0.1.0"), | ||
}, ` | ||
Terraform v0.13.0-dev | ||
+ provider registry.terraform.io/hashicorp/null v2.1.2 | ||
+ provider registry.terraform.io/paultyng/null v0.1.0 | ||
|
||
Your version of Terraform is out of date! The latest version | ||
is 0.13.1. You can update by downloading from https://www.terraform.io/downloads.html`, | ||
}, | ||
|
||
// 0.12 tests | ||
{ | ||
mustVer("0.12.26"), nil, ` | ||
Terraform v0.12.26 | ||
`, | ||
}, | ||
{ | ||
mustVer("0.12.26"), map[string]*version.Version{ | ||
"null": mustVer("2.1.2"), | ||
}, ` | ||
Terraform v0.12.26 | ||
+ provider.null v2.1.2 | ||
`, | ||
}, | ||
{ | ||
mustVer("0.12.18"), nil, ` | ||
Terraform v0.12.18 | ||
|
||
Your version of Terraform is out of date! The latest version | ||
is 0.12.26. You can update by downloading from https://www.terraform.io/downloads.html | ||
`, | ||
}, | ||
{ | ||
mustVer("0.12.18"), map[string]*version.Version{ | ||
"null": mustVer("2.1.2"), | ||
}, ` | ||
Terraform v0.12.18 | ||
+ provider.null v2.1.2 | ||
|
||
Your version of Terraform is out of date! The latest version | ||
is 0.12.26. You can update by downloading from https://www.terraform.io/downloads.html | ||
`, | ||
}, | ||
} { | ||
t.Run(fmt.Sprintf("%d %s", i, c.expectedV), func(t *testing.T) { | ||
actualV, actualProv, err := parseVersionOutput(c.stdout) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if !c.expectedV.Equal(actualV) { | ||
t.Fatalf("expected %s, got %s", c.expectedV, actualV) | ||
} | ||
|
||
for k, v := range c.expectedProviders { | ||
if actual := actualProv[k]; actual == nil || !v.Equal(actual) { | ||
t.Fatalf("expected %s for %s, got %s", v, k, actual) | ||
} | ||
} | ||
|
||
if len(c.expectedProviders) != len(actualProv) { | ||
t.Fatalf("expected %d providers, got %d", len(c.expectedProviders), len(actualProv)) | ||
} | ||
}) | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the note could be suppressed by disabling checkpoint when running under test, which would make the tests less fragile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And perhaps we should even disable checkpoint for
version
commands by default as the advice is ignored and it's not shown to the end-user anyway?