Skip to content

Commit

Permalink
Implement a helper funciton to check minimum binary versions. (#1122)
Browse files Browse the repository at this point in the history
* Implement a helper funciton to check minimum binary versions

* Address comments

* Address comments

* address comments

* address comments

* goimports
  • Loading branch information
james03160927 authored May 18, 2022
1 parent 4859f7c commit 3f80236
Show file tree
Hide file tree
Showing 3 changed files with 402 additions and 0 deletions.
10 changes: 10 additions & 0 deletions modules/version-checker/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package version_checker

// VersionMismatchErr is an error to indicate version mismatch.
type VersionMismatchErr struct {
errorMessage string
}

func (r *VersionMismatchErr) Error() string {
return r.errorMessage
}
173 changes: 173 additions & 0 deletions modules/version-checker/version_checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package version_checker

import (
"fmt"
"regexp"

"github.com/gruntwork-io/terratest/modules/shell"
"github.com/gruntwork-io/terratest/modules/testing"
"github.com/hashicorp/go-version"
"github.com/stretchr/testify/require"
)

// VersionCheckerBinary is an enum for supported version checking.
type VersionCheckerBinary int

// List of binaries supported for version checking.
const (
Docker VersionCheckerBinary = iota
Terraform
Packer
)

const (
// versionRegexMatcher is a regex used to extract version string from shell command output.
versionRegexMatcher = `\d+(\.\d+)+`
// defaultVersionArg is a default arg to pass in to get version output from shell command.
defaultVersionArg = "--version"
)

type CheckVersionParams struct {
// BinaryPath is a path to the binary you want to check the version for.
BinaryPath string
// Binary is the name of the binary you want to check the version for.
Binary VersionCheckerBinary
// VersionConstraint is a string literal containing one or more conditions, which are separated by commas.
// More information here:https://www.terraform.io/language/expressions/version-constraints
VersionConstraint string
// WorkingDir is a directory you want to run the shell command.
WorkingDir string
}

// CheckVersionE checks whether the given Binary version is greater than or equal
// to the given expected version.
func CheckVersionE(
t testing.TestingT,
params CheckVersionParams) error {

if err := validateParams(params); err != nil {
return err
}

binaryVersion, err := getVersionWithShellCommand(t, params)
if err != nil {
return err
}

return checkVersionConstraint(binaryVersion, params.VersionConstraint)
}

// CheckVersion checks whether the given Binary version is greater than or equal to the
// given expected version and fails if it's not.
func CheckVersion(
t testing.TestingT,
params CheckVersionParams) {
require.NoError(t, CheckVersionE(t, params))
}

// Validate whether the given params contains valid data to check version.
func validateParams(params CheckVersionParams) error {
// Check for empty parameters
if params.WorkingDir == "" {
return fmt.Errorf("set WorkingDir in params")
} else if params.VersionConstraint == "" {
return fmt.Errorf("set VersionConstraint in params")
}

// Check the format of the version constraint if present.
if _, err := version.NewConstraint(params.VersionConstraint); params.VersionConstraint != "" && err != nil {
return fmt.Errorf(
"invalid version constraint format found {%s}", params.VersionConstraint)
}

return nil
}

// getVersionWithShellCommand get version by running a shell command.
func getVersionWithShellCommand(t testing.TestingT, params CheckVersionParams) (string, error) {
var versionArg = defaultVersionArg
binary, err := getBinary(params)
if err != nil {
return "", err
}

// Run a shell command to get the version string.
output, err := shell.RunCommandAndGetOutputE(t, shell.Command{
Command: binary,
Args: []string{versionArg},
WorkingDir: params.WorkingDir,
Env: map[string]string{},
})
if err != nil {
return "", fmt.Errorf("failed to run shell command for Binary {%s} "+
"w/ version args {%s}: %w", binary, versionArg, err)
}

versionStr, err := extractVersionFromShellCommandOutput(output)
if err != nil {
return "", fmt.Errorf("failed to extract version from shell "+
"command output {%s}: %w", output, err)
}

return versionStr, nil
}

// getBinary retrieves the binary to use from the given params.
func getBinary(params CheckVersionParams) (string, error) {
// Use BinaryPath if it is set, otherwise use the binary enum.
if params.BinaryPath != "" {
return params.BinaryPath, nil
}

switch params.Binary {
case Docker:
return "docker", nil
case Packer:
return "packer", nil
case Terraform:
return "terraform", nil
default:
return "", fmt.Errorf("unsupported Binary for checking versions {%d}", params.Binary)
}
}

// extractVersionFromShellCommandOutput extracts version with regex string matching
// from the given shell command output string.
func extractVersionFromShellCommandOutput(output string) (string, error) {
regexMatcher := regexp.MustCompile(versionRegexMatcher)
versionStr := regexMatcher.FindString(output)
if versionStr == "" {
return "", fmt.Errorf("failed to find version using regex matcher")
}

return versionStr, nil
}

// checkVersionConstraint checks whether the given version pass the version constraint.
//
// It returns Error for ill-formatted version string and VersionMismatchErr for
// minimum version check failure.
//
// checkVersionConstraint(t, "1.2.31", ">= 1.2.0, < 2.0.0") - no error
// checkVersionConstraint(t, "1.0.31", ">= 1.2.0, < 2.0.0") - error
func checkVersionConstraint(actualVersionStr string, versionConstraintStr string) error {
actualVersion, err := version.NewVersion(actualVersionStr)
if err != nil {
return fmt.Errorf("invalid version format found for actualVersionStr: %s", actualVersionStr)
}

versionConstraint, err := version.NewConstraint(versionConstraintStr)
if err != nil {
return fmt.Errorf("invalid version format found for versionConstraint: %s", versionConstraintStr)
}

if !versionConstraint.Check(actualVersion) {
return &VersionMismatchErr{
errorMessage: fmt.Sprintf("actual version {%s} failed "+
"the version constraint {%s}", actualVersionStr, versionConstraint),
}

}

return nil
}
Loading

0 comments on commit 3f80236

Please sign in to comment.