mirror of https://github.com/databricks/cli.git
Allow specifying CLI version constraints required to run the bundle (#1320)
## Changes Allow specifying CLI version constraints required to run the bundle Example of configuration: #### only allow specific version ``` bundle: name: my-bundle databricks_cli_version: "0.210.0" ``` #### allow all patch releases ``` bundle: name: my-bundle databricks_cli_version: "0.210.*" ``` #### constrain minimum version ``` bundle: name: my-bundle databricks_cli_version: ">= 0.210.0" ``` #### constrain range ``` bundle: name: my-bundle databricks_cli_version: ">= 0.210.0, <= 1.0.0" ``` For other examples see: https://github.com/Masterminds/semver?tab=readme-ov-file#checking-version-constraints Example error ``` sh-3.2$ databricks bundle validate Error: Databricks CLI version constraint not satisfied. Required: >= 1.0.0, current: 0.216.0 ``` ## Tests Added unit test cover all possible configuration permutations --------- Co-authored-by: Lennart Kats (databricks) <lennart.kats@databricks.com>
This commit is contained in:
parent
dca81a40f4
commit
56e393c743
4
NOTICE
4
NOTICE
|
@ -73,6 +73,10 @@ ghodss/yaml - https://github.com/ghodss/yaml
|
||||||
Copyright (c) 2014 Sam Ghods
|
Copyright (c) 2014 Sam Ghods
|
||||||
License - https://github.com/ghodss/yaml/blob/master/LICENSE
|
License - https://github.com/ghodss/yaml/blob/master/LICENSE
|
||||||
|
|
||||||
|
Masterminds/semver - https://github.com/Masterminds/semver
|
||||||
|
Copyright (C) 2014-2019, Matt Butcher and Matt Farina
|
||||||
|
License - https://github.com/Masterminds/semver/blob/master/LICENSE.txt
|
||||||
|
|
||||||
mattn/go-isatty - https://github.com/mattn/go-isatty
|
mattn/go-isatty - https://github.com/mattn/go-isatty
|
||||||
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
|
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
|
||||||
https://github.com/mattn/go-isatty/blob/master/LICENSE
|
https://github.com/mattn/go-isatty/blob/master/LICENSE
|
||||||
|
|
|
@ -43,4 +43,7 @@ type Bundle struct {
|
||||||
|
|
||||||
// Deployment section specifies deployment related configuration for bundle
|
// Deployment section specifies deployment related configuration for bundle
|
||||||
Deployment Deployment `json:"deployment,omitempty"`
|
Deployment Deployment `json:"deployment,omitempty"`
|
||||||
|
|
||||||
|
// Databricks CLI version constraints required to run the bundle.
|
||||||
|
DatabricksCliVersion string `json:"databricks_cli_version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,9 @@ func DefaultMutators() []bundle.Mutator {
|
||||||
loader.EntryPoint(),
|
loader.EntryPoint(),
|
||||||
loader.ProcessRootIncludes(),
|
loader.ProcessRootIncludes(),
|
||||||
|
|
||||||
|
// Verify that the CLI version is within the specified range.
|
||||||
|
VerifyCliVersion(),
|
||||||
|
|
||||||
// Execute preinit script after loading all configuration files.
|
// Execute preinit script after loading all configuration files.
|
||||||
scripts.Execute(config.ScriptPreInit),
|
scripts.Execute(config.ScriptPreInit),
|
||||||
EnvironmentsToTargets(),
|
EnvironmentsToTargets(),
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package mutator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
semver "github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/databricks/cli/bundle"
|
||||||
|
"github.com/databricks/cli/internal/build"
|
||||||
|
"github.com/databricks/cli/libs/diag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func VerifyCliVersion() bundle.Mutator {
|
||||||
|
return &verifyCliVersion{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type verifyCliVersion struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifyCliVersion) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
|
// No constraints specified, skip the check.
|
||||||
|
if b.Config.Bundle.DatabricksCliVersion == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint := b.Config.Bundle.DatabricksCliVersion
|
||||||
|
if err := validateConstraintSyntax(constraint); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
currentVersion := build.GetInfo().Version
|
||||||
|
c, err := semver.NewConstraint(constraint)
|
||||||
|
if err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := semver.NewVersion(currentVersion)
|
||||||
|
if err != nil {
|
||||||
|
return diag.Errorf("parsing CLI version %q failed", currentVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.Check(version) {
|
||||||
|
return diag.Errorf("Databricks CLI version constraint not satisfied. Required: %s, current: %s", constraint, currentVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifyCliVersion) Name() string {
|
||||||
|
return "VerifyCliVersion"
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateConstraintSyntax validates the syntax of the version constraint.
|
||||||
|
func validateConstraintSyntax(constraint string) error {
|
||||||
|
r := generateConstraintSyntaxRegexp()
|
||||||
|
if !r.MatchString(constraint) {
|
||||||
|
return fmt.Errorf("invalid version constraint %q specified. Please specify the version constraint in the format (>=) 0.0.0(, <= 1.0.0)", constraint)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate regexp which matches the supported version constraint syntax.
|
||||||
|
func generateConstraintSyntaxRegexp() *regexp.Regexp {
|
||||||
|
// We intentionally only support the format supported by requirements.txt:
|
||||||
|
// 1. 0.0.0
|
||||||
|
// 2. >= 0.0.0
|
||||||
|
// 3. <= 0.0.0
|
||||||
|
// 4. > 0.0.0
|
||||||
|
// 5. < 0.0.0
|
||||||
|
// 6. != 0.0.0
|
||||||
|
// 7. 0.0.*
|
||||||
|
// 8. 0.*
|
||||||
|
// 9. >= 0.0.0, <= 1.0.0
|
||||||
|
// 10. 0.0.0-0
|
||||||
|
// 11. 0.0.0-beta
|
||||||
|
// 12. >= 0.0.0-0, <= 1.0.0-0
|
||||||
|
|
||||||
|
matchVersion := `(\d+\.\d+\.\d+(\-\w+)?|\d+\.\d+.\*|\d+\.\*)`
|
||||||
|
matchOperators := `(>=|<=|>|<|!=)?`
|
||||||
|
return regexp.MustCompile(fmt.Sprintf(`^%s ?%s(, %s %s)?$`, matchOperators, matchVersion, matchOperators, matchVersion))
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
package mutator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/databricks/cli/bundle"
|
||||||
|
"github.com/databricks/cli/bundle/config"
|
||||||
|
"github.com/databricks/cli/internal/build"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
currentVersion string
|
||||||
|
constraint string
|
||||||
|
expectedError string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyCliVersion(t *testing.T) {
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
currentVersion: "0.0.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.0.1",
|
||||||
|
constraint: "0.100.0",
|
||||||
|
expectedError: "Databricks CLI version constraint not satisfied. Required: 0.100.0, current: 0.0.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.0.1",
|
||||||
|
constraint: ">= 0.100.0",
|
||||||
|
expectedError: "Databricks CLI version constraint not satisfied. Required: >= 0.100.0, current: 0.0.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.0",
|
||||||
|
constraint: "0.100.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.1",
|
||||||
|
constraint: "0.100.0",
|
||||||
|
expectedError: "Databricks CLI version constraint not satisfied. Required: 0.100.0, current: 0.100.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.1",
|
||||||
|
constraint: ">= 0.100.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.0",
|
||||||
|
constraint: "<= 1.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "1.0.0",
|
||||||
|
constraint: "<= 1.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "1.0.0",
|
||||||
|
constraint: "<= 0.100.0",
|
||||||
|
expectedError: "Databricks CLI version constraint not satisfied. Required: <= 0.100.0, current: 1.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.99.0",
|
||||||
|
constraint: ">= 0.100.0, <= 0.100.2",
|
||||||
|
expectedError: "Databricks CLI version constraint not satisfied. Required: >= 0.100.0, <= 0.100.2, current: 0.99.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.0",
|
||||||
|
constraint: ">= 0.100.0, <= 0.100.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.1",
|
||||||
|
constraint: ">= 0.100.0, <= 0.100.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.2",
|
||||||
|
constraint: ">= 0.100.0, <= 0.100.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.101.0",
|
||||||
|
constraint: ">= 0.100.0, <= 0.100.2",
|
||||||
|
expectedError: "Databricks CLI version constraint not satisfied. Required: >= 0.100.0, <= 0.100.2, current: 0.101.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.0-beta",
|
||||||
|
constraint: ">= 0.100.0, <= 0.100.2",
|
||||||
|
expectedError: "Databricks CLI version constraint not satisfied. Required: >= 0.100.0, <= 0.100.2, current: 0.100.0-beta",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.0-beta",
|
||||||
|
constraint: ">= 0.100.0-0, <= 0.100.2-0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.1-beta",
|
||||||
|
constraint: ">= 0.100.0-0, <= 0.100.2-0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.3-beta",
|
||||||
|
constraint: ">= 0.100.0, <= 0.100.2",
|
||||||
|
expectedError: "Databricks CLI version constraint not satisfied. Required: >= 0.100.0, <= 0.100.2, current: 0.100.3-beta",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.123",
|
||||||
|
constraint: "0.100.*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentVersion: "0.100.123",
|
||||||
|
constraint: "^0.100",
|
||||||
|
expectedError: "invalid version constraint \"^0.100\" specified. Please specify the version constraint in the format (>=) 0.0.0(, <= 1.0.0)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
// Reset the build version to the default version
|
||||||
|
// so that it doesn't affect other tests
|
||||||
|
// It doesn't really matter what we configure this to when testing
|
||||||
|
// as long as it is a valid semver version.
|
||||||
|
build.SetBuildVersion(build.DefaultSemver)
|
||||||
|
})
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("testcase #%d", i), func(t *testing.T) {
|
||||||
|
build.SetBuildVersion(tc.currentVersion)
|
||||||
|
b := &bundle.Bundle{
|
||||||
|
Config: config.Root{
|
||||||
|
Bundle: config.Bundle{
|
||||||
|
DatabricksCliVersion: tc.constraint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
diags := bundle.Apply(context.Background(), b, VerifyCliVersion())
|
||||||
|
if tc.expectedError != "" {
|
||||||
|
require.NotEmpty(t, diags)
|
||||||
|
require.Equal(t, tc.expectedError, diags.Error().Error())
|
||||||
|
} else {
|
||||||
|
require.Empty(t, diags)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateConstraint(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
constraint string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"0.0.0", true},
|
||||||
|
{">= 0.0.0", true},
|
||||||
|
{"<= 0.0.0", true},
|
||||||
|
{"> 0.0.0", true},
|
||||||
|
{"< 0.0.0", true},
|
||||||
|
{"!= 0.0.0", true},
|
||||||
|
{"0.0.*", true},
|
||||||
|
{"0.*", true},
|
||||||
|
{">= 0.0.0, <= 1.0.0", true},
|
||||||
|
{">= 0.0.0-0, <= 1.0.0-0", true},
|
||||||
|
{"0.0.0-0", true},
|
||||||
|
{"0.0.0-beta", true},
|
||||||
|
{"^0.0.0", false},
|
||||||
|
{"~0.0.0", false},
|
||||||
|
{"0.0.0 1.0.0", false},
|
||||||
|
{"> 0.0.0 < 1.0.0", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.constraint, func(t *testing.T) {
|
||||||
|
err := validateConstraintSyntax(tc.constraint)
|
||||||
|
if tc.expected {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
4
go.mod
4
go.mod
|
@ -3,6 +3,7 @@ module github.com/databricks/cli
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.1 // MIT
|
||||||
github.com/briandowns/spinner v1.23.0 // Apache 2.0
|
github.com/briandowns/spinner v1.23.0 // Apache 2.0
|
||||||
github.com/databricks/databricks-sdk-go v0.36.0 // Apache 2.0
|
github.com/databricks/databricks-sdk-go v0.36.0 // Apache 2.0
|
||||||
github.com/fatih/color v1.16.0 // MIT
|
github.com/fatih/color v1.16.0 // MIT
|
||||||
|
@ -27,10 +28,9 @@ require (
|
||||||
golang.org/x/term v0.18.0
|
golang.org/x/term v0.18.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
gopkg.in/ini.v1 v1.67.0 // Apache 2.0
|
gopkg.in/ini.v1 v1.67.0 // Apache 2.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require gopkg.in/yaml.v3 v3.0.1
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.23.4 // indirect
|
cloud.google.com/go/compute v1.23.4 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
|
|
|
@ -6,6 +6,8 @@ cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2Aawl
|
||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE9N5vPhgY2I+j0=
|
github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE9N5vPhgY2I+j0=
|
||||||
|
|
|
@ -16,3 +16,9 @@ var buildPatch string = "0"
|
||||||
var buildPrerelease string = ""
|
var buildPrerelease string = ""
|
||||||
var buildIsSnapshot string = "false"
|
var buildIsSnapshot string = "false"
|
||||||
var buildTimestamp string = "0"
|
var buildTimestamp string = "0"
|
||||||
|
|
||||||
|
// This function is used to set the build version for testing purposes.
|
||||||
|
func SetBuildVersion(version string) {
|
||||||
|
buildVersion = version
|
||||||
|
info.Version = version
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue