Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fs.Version
to support version constrained binary discovery (#52)
* `fs.AnyVersion` supports optional version constraint Allows users to optionally specify one or more (comma separated) version constraints for `fs.AnyVersion` source. * Refine readme * Modify `Constraint` type and name to align with `releases.Versions` * revert everything * New source - `fs.Version`: allows specifying version constraints * Modify e2e test * enable test * modify and pass test
- Loading branch information
Showing
4 changed files
with
206 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package fs | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/hashicorp/go-version" | ||
"github.com/hashicorp/hc-install/errors" | ||
"github.com/hashicorp/hc-install/internal/src" | ||
"github.com/hashicorp/hc-install/internal/validators" | ||
"github.com/hashicorp/hc-install/product" | ||
) | ||
|
||
// Version finds the first executable binary of the product name | ||
// which matches the version constraint within system $PATH and any declared ExtraPaths | ||
// (which are *appended* to any directories in $PATH) | ||
type Version struct { | ||
Product product.Product | ||
Constraints version.Constraints | ||
ExtraPaths []string | ||
Timeout time.Duration | ||
|
||
logger *log.Logger | ||
} | ||
|
||
func (*Version) IsSourceImpl() src.InstallSrcSigil { | ||
return src.InstallSrcSigil{} | ||
} | ||
|
||
func (v *Version) SetLogger(logger *log.Logger) { | ||
v.logger = logger | ||
} | ||
|
||
func (v *Version) log() *log.Logger { | ||
if v.logger == nil { | ||
return discardLogger | ||
} | ||
return v.logger | ||
} | ||
|
||
func (v *Version) Validate() error { | ||
if !validators.IsBinaryNameValid(v.Product.BinaryName()) { | ||
return fmt.Errorf("invalid binary name: %q", v.Product.BinaryName()) | ||
} | ||
if len(v.Constraints) == 0 { | ||
return fmt.Errorf("undeclared version constraints") | ||
} | ||
if v.Product.GetVersion == nil { | ||
return fmt.Errorf("undeclared version getter") | ||
} | ||
return nil | ||
} | ||
|
||
func (v *Version) Find(ctx context.Context) (string, error) { | ||
timeout := defaultTimeout | ||
if v.Timeout > 0 { | ||
timeout = v.Timeout | ||
} | ||
ctx, cancelFunc := context.WithTimeout(ctx, timeout) | ||
defer cancelFunc() | ||
|
||
execPath, err := findFile(lookupDirs(v.ExtraPaths), v.Product.BinaryName(), func(file string) error { | ||
err := checkExecutable(file) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
ver, err := v.Product.GetVersion(ctx, file) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, vc := range v.Constraints { | ||
if !vc.Check(ver) { | ||
return fmt.Errorf("version (%s) doesn't meet constraints %s", ver, vc.String()) | ||
} | ||
} | ||
|
||
return nil | ||
}) | ||
if err != nil { | ||
return "", errors.SkippableErr(err) | ||
} | ||
|
||
if !filepath.IsAbs(execPath) { | ||
var err error | ||
execPath, err = filepath.Abs(execPath) | ||
if err != nil { | ||
return "", errors.SkippableErr(err) | ||
} | ||
} | ||
|
||
return execPath, nil | ||
} |
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,70 @@ | ||
package fs | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/go-version" | ||
"github.com/hashicorp/hc-install/product" | ||
) | ||
|
||
func TestVersionValidate(t *testing.T) { | ||
t.Parallel() | ||
|
||
testCases := map[string]struct { | ||
v Version | ||
expectedErr error | ||
}{ | ||
"Product-incorrect-binary-name": { | ||
v: Version{ | ||
Product: product.Product{ | ||
BinaryName: func() string { return "invalid!" }, | ||
}, | ||
}, | ||
expectedErr: fmt.Errorf("invalid binary name: \"invalid!\""), | ||
}, | ||
"Product-missing-get-version": { | ||
v: Version{ | ||
Product: product.Product{ | ||
BinaryName: product.Terraform.BinaryName, | ||
}, | ||
Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), | ||
}, | ||
expectedErr: fmt.Errorf("undeclared version getter"), | ||
}, | ||
"Product-missing-version-constraint": { | ||
v: Version{ | ||
Product: product.Terraform, | ||
}, | ||
expectedErr: fmt.Errorf("undeclared version constraints"), | ||
}, | ||
"Product-and-version-constraint": { | ||
v: Version{ | ||
Product: product.Terraform, | ||
Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), | ||
}, | ||
}, | ||
} | ||
|
||
for name, testCase := range testCases { | ||
name, testCase := name, testCase | ||
|
||
t.Run(name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
err := testCase.v.Validate() | ||
|
||
if err == nil && testCase.expectedErr != nil { | ||
t.Fatalf("expected error: %s, got no error", testCase.expectedErr) | ||
} | ||
|
||
if err != nil && testCase.expectedErr == nil { | ||
t.Fatalf("expected no error, got error: %s", err) | ||
} | ||
|
||
if err != nil && testCase.expectedErr != nil && err.Error() != testCase.expectedErr.Error() { | ||
t.Fatalf("expected error: %s, got error: %s", testCase.expectedErr, err) | ||
} | ||
}) | ||
} | ||
} |