terraform-provider-gitea/vendor/github.com/hashicorp/terraform-exec/tfexec/version.go
dependabot[bot] 84c9110a24
Bump github.com/hashicorp/terraform-plugin-sdk/v2 from 2.24.1 to 2.26.0
Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.24.1 to 2.26.0.
- [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.24.1...v2.26.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 20:25:53 +00:00

211 lines
6.4 KiB
Go

package tfexec
import (
"bytes"
"context"
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/hashicorp/go-version"
tfjson "github.com/hashicorp/terraform-json"
)
var (
tf0_4_1 = version.Must(version.NewVersion("0.4.1"))
tf0_5_0 = version.Must(version.NewVersion("0.5.0"))
tf0_6_13 = version.Must(version.NewVersion("0.6.13"))
tf0_7_7 = version.Must(version.NewVersion("0.7.7"))
tf0_8_0 = version.Must(version.NewVersion("0.8.0"))
tf0_10_0 = version.Must(version.NewVersion("0.10.0"))
tf0_12_0 = version.Must(version.NewVersion("0.12.0"))
tf0_13_0 = version.Must(version.NewVersion("0.13.0"))
tf0_14_0 = version.Must(version.NewVersion("0.14.0"))
tf0_15_0 = version.Must(version.NewVersion("0.15.0"))
tf0_15_2 = version.Must(version.NewVersion("0.15.2"))
tf0_15_3 = version.Must(version.NewVersion("0.15.3"))
tf1_1_0 = version.Must(version.NewVersion("1.1.0"))
tf1_4_0 = version.Must(version.NewVersion("1.4.0"))
)
// 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) {
versionCmd := tf.buildTerraformCmd(ctx, nil, "version", "-json")
var outBuf bytes.Buffer
versionCmd.Stdout = &outBuf
err := tf.runTerraformCmd(ctx, versionCmd)
if err != nil {
return nil, nil, err
}
tfVersion, providerVersions, err := parseJsonVersionOutput(outBuf.Bytes())
if err != nil {
if _, ok := err.(*json.SyntaxError); ok {
return tf.versionFromPlaintext(ctx)
}
}
return tfVersion, providerVersions, err
}
func parseJsonVersionOutput(stdout []byte) (*version.Version, map[string]*version.Version, error) {
var out tfjson.VersionOutput
err := json.Unmarshal(stdout, &out)
if err != nil {
return nil, nil, err
}
tfVersion, err := version.NewVersion(out.Version)
if err != nil {
return nil, nil, fmt.Errorf("unable to parse version %q: %w", out.Version, err)
}
providerVersions := make(map[string]*version.Version, 0)
for provider, versionStr := range out.ProviderSelections {
v, err := version.NewVersion(versionStr)
if err != nil {
return nil, nil, fmt.Errorf("unable to parse %q version %q: %w",
provider, versionStr, err)
}
providerVersions[provider] = v
}
return tfVersion, providerVersions, nil
}
func (tf *Terraform) versionFromPlaintext(ctx context.Context) (*version.Version, map[string]*version.Version, error) {
versionCmd := tf.buildTerraformCmd(ctx, nil, "version")
var outBuf strings.Builder
versionCmd.Stdout = &outBuf
err := tf.runTerraformCmd(ctx, versionCmd)
if err != nil {
return nil, nil, err
}
tfVersion, providerVersions, err := parsePlaintextVersionOutput(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 parsePlaintextVersionOutput(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 provider 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
}
func errorVersionString(v *version.Version) string {
if v == nil {
return "-"
}
return v.String()
}
// compatible asserts compatibility of the cached terraform version with the executable, and returns a well known error if not.
func (tf *Terraform) compatible(ctx context.Context, minInclusive *version.Version, maxExclusive *version.Version) error {
tfv, _, err := tf.Version(ctx, false)
if err != nil {
return err
}
if ok := versionInRange(tfv, minInclusive, maxExclusive); !ok {
return &ErrVersionMismatch{
MinInclusive: errorVersionString(minInclusive),
MaxExclusive: errorVersionString(maxExclusive),
Actual: errorVersionString(tfv),
}
}
return nil
}
func stripPrereleaseAndMeta(v *version.Version) *version.Version {
if v == nil {
return nil
}
segs := []string{}
for _, s := range v.Segments() {
segs = append(segs, strconv.Itoa(s))
}
vs := strings.Join(segs, ".")
clean, _ := version.NewVersion(vs)
return clean
}
// versionInRange checks compatibility of the Terraform version. The minimum is inclusive and the max
// is exclusive, equivalent to min <= expected version < max.
//
// Pre-release information is ignored for comparison.
func versionInRange(tfv *version.Version, minInclusive *version.Version, maxExclusive *version.Version) bool {
if minInclusive == nil && maxExclusive == nil {
return true
}
tfv = stripPrereleaseAndMeta(tfv)
minInclusive = stripPrereleaseAndMeta(minInclusive)
maxExclusive = stripPrereleaseAndMeta(maxExclusive)
if minInclusive != nil && !tfv.GreaterThanOrEqual(minInclusive) {
return false
}
if maxExclusive != nil && !tfv.LessThan(maxExclusive) {
return false
}
return true
}