mirror of https://github.com/databricks/cli.git
Compare commits
3 Commits
edc08149d3
...
7dcc791b05
Author | SHA1 | Date |
---|---|---|
|
7dcc791b05 | |
|
85459c1963 | |
|
70363836d5 |
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -1,5 +1,28 @@
|
||||||
# Version changelog
|
# Version changelog
|
||||||
|
|
||||||
|
## [Release] Release v0.227.1
|
||||||
|
|
||||||
|
CLI:
|
||||||
|
* Disable prompt for storage-credentials get command ([#1723](https://github.com/databricks/cli/pull/1723)).
|
||||||
|
|
||||||
|
Bundles:
|
||||||
|
* Do not treat empty path as a local path ([#1717](https://github.com/databricks/cli/pull/1717)).
|
||||||
|
* Correctly mark PyPI package name specs with multiple specifiers as remote libraries ([#1725](https://github.com/databricks/cli/pull/1725)).
|
||||||
|
* Improve error handling for /Volumes paths in mode: development ([#1716](https://github.com/databricks/cli/pull/1716)).
|
||||||
|
|
||||||
|
Internal:
|
||||||
|
* Ignore CLI version check on development builds of the CLI ([#1714](https://github.com/databricks/cli/pull/1714)).
|
||||||
|
|
||||||
|
API Changes:
|
||||||
|
* Added `databricks resource-quotas` command group.
|
||||||
|
* Added `databricks policy-compliance-for-clusters` command group.
|
||||||
|
* Added `databricks policy-compliance-for-jobs` command group.
|
||||||
|
|
||||||
|
OpenAPI commit 3eae49b444cac5a0118a3503e5b7ecef7f96527a (2024-08-21)
|
||||||
|
Dependency updates:
|
||||||
|
* Bump github.com/databricks/databricks-sdk-go from 0.44.0 to 0.45.0 ([#1719](https://github.com/databricks/cli/pull/1719)).
|
||||||
|
* Revert hc-install version to 0.7.0 ([#1711](https://github.com/databricks/cli/pull/1711)).
|
||||||
|
|
||||||
## [Release] Release v0.227.0
|
## [Release] Release v0.227.0
|
||||||
|
|
||||||
CLI:
|
CLI:
|
||||||
|
|
|
@ -64,6 +64,7 @@ func transformDevelopmentMode(ctx context.Context, b *bundle.Bundle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateDevelopmentMode(b *bundle.Bundle) diag.Diagnostics {
|
func validateDevelopmentMode(b *bundle.Bundle) diag.Diagnostics {
|
||||||
|
var diags diag.Diagnostics
|
||||||
p := b.Config.Presets
|
p := b.Config.Presets
|
||||||
u := b.Config.Workspace.CurrentUser
|
u := b.Config.Workspace.CurrentUser
|
||||||
|
|
||||||
|
@ -74,44 +75,56 @@ func validateDevelopmentMode(b *bundle.Bundle) diag.Diagnostics {
|
||||||
// status to UNPAUSED at the level of an individual object, whic hwas
|
// status to UNPAUSED at the level of an individual object, whic hwas
|
||||||
// historically allowed.)
|
// historically allowed.)
|
||||||
if p.TriggerPauseStatus == config.Unpaused {
|
if p.TriggerPauseStatus == config.Unpaused {
|
||||||
return diag.Diagnostics{{
|
diags = diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Error,
|
Severity: diag.Error,
|
||||||
Summary: "target with 'mode: development' cannot set trigger pause status to UNPAUSED by default",
|
Summary: "target with 'mode: development' cannot set trigger pause status to UNPAUSED by default",
|
||||||
Locations: []dyn.Location{b.Config.GetLocation("presets.trigger_pause_status")},
|
Locations: []dyn.Location{b.Config.GetLocation("presets.trigger_pause_status")},
|
||||||
}}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure this development copy has unique names and paths to avoid conflicts
|
// Make sure this development copy has unique names and paths to avoid conflicts
|
||||||
if path := findNonUserPath(b); path != "" {
|
if path := findNonUserPath(b); path != "" {
|
||||||
return diag.Errorf("%s must start with '~/' or contain the current username when using 'mode: development'", path)
|
if path == "artifact_path" && strings.HasPrefix(b.Config.Workspace.ArtifactPath, "/Volumes") {
|
||||||
|
// For Volumes paths we recommend including the current username as a substring
|
||||||
|
diags = diags.Extend(diag.Errorf("%s should contain the current username or ${workspace.current_user.short_name} to ensure uniqueness when using 'mode: development'", path))
|
||||||
|
} else {
|
||||||
|
// For non-Volumes paths recommend simply putting things in the home folder
|
||||||
|
diags = diags.Extend(diag.Errorf("%s must start with '~/' or contain the current username to ensure uniqueness when using 'mode: development'", path))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if p.NamePrefix != "" && !strings.Contains(p.NamePrefix, u.ShortName) && !strings.Contains(p.NamePrefix, u.UserName) {
|
if p.NamePrefix != "" && !strings.Contains(p.NamePrefix, u.ShortName) && !strings.Contains(p.NamePrefix, u.UserName) {
|
||||||
// Resources such as pipelines require a unique name, e.g. '[dev steve] my_pipeline'.
|
// Resources such as pipelines require a unique name, e.g. '[dev steve] my_pipeline'.
|
||||||
// For this reason we require the name prefix to contain the current username;
|
// For this reason we require the name prefix to contain the current username;
|
||||||
// it's a pitfall for users if they don't include it and later find out that
|
// it's a pitfall for users if they don't include it and later find out that
|
||||||
// only a single user can do development deployments.
|
// only a single user can do development deployments.
|
||||||
return diag.Diagnostics{{
|
diags = diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Error,
|
Severity: diag.Error,
|
||||||
Summary: "prefix should contain the current username or ${workspace.current_user.short_name} to ensure uniqueness when using 'mode: development'",
|
Summary: "prefix should contain the current username or ${workspace.current_user.short_name} to ensure uniqueness when using 'mode: development'",
|
||||||
Locations: []dyn.Location{b.Config.GetLocation("presets.name_prefix")},
|
Locations: []dyn.Location{b.Config.GetLocation("presets.name_prefix")},
|
||||||
}}
|
})
|
||||||
}
|
}
|
||||||
return nil
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findNonUserPath finds the first workspace path such as root_path that doesn't
|
||||||
|
// contain the current username or current user's shortname.
|
||||||
func findNonUserPath(b *bundle.Bundle) string {
|
func findNonUserPath(b *bundle.Bundle) string {
|
||||||
|
containsName := func(path string) bool {
|
||||||
username := b.Config.Workspace.CurrentUser.UserName
|
username := b.Config.Workspace.CurrentUser.UserName
|
||||||
|
shortname := b.Config.Workspace.CurrentUser.ShortName
|
||||||
|
return strings.Contains(path, username) || strings.Contains(path, shortname)
|
||||||
|
}
|
||||||
|
|
||||||
if b.Config.Workspace.RootPath != "" && !strings.Contains(b.Config.Workspace.RootPath, username) {
|
if b.Config.Workspace.RootPath != "" && !containsName(b.Config.Workspace.RootPath) {
|
||||||
return "root_path"
|
return "root_path"
|
||||||
}
|
}
|
||||||
if b.Config.Workspace.StatePath != "" && !strings.Contains(b.Config.Workspace.StatePath, username) {
|
if b.Config.Workspace.StatePath != "" && !containsName(b.Config.Workspace.StatePath) {
|
||||||
return "state_path"
|
return "state_path"
|
||||||
}
|
}
|
||||||
if b.Config.Workspace.FilePath != "" && !strings.Contains(b.Config.Workspace.FilePath, username) {
|
if b.Config.Workspace.FilePath != "" && !containsName(b.Config.Workspace.FilePath) {
|
||||||
return "file_path"
|
return "file_path"
|
||||||
}
|
}
|
||||||
if b.Config.Workspace.ArtifactPath != "" && !strings.Contains(b.Config.Workspace.ArtifactPath, username) {
|
if b.Config.Workspace.ArtifactPath != "" && !containsName(b.Config.Workspace.ArtifactPath) {
|
||||||
return "artifact_path"
|
return "artifact_path"
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -230,10 +230,20 @@ func TestValidateDevelopmentMode(t *testing.T) {
|
||||||
diags := validateDevelopmentMode(b)
|
diags := validateDevelopmentMode(b)
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
|
|
||||||
|
// Test with /Volumes path
|
||||||
|
b = mockBundle(config.Development)
|
||||||
|
b.Config.Workspace.ArtifactPath = "/Volumes/catalog/schema/lennart/libs"
|
||||||
|
diags = validateDevelopmentMode(b)
|
||||||
|
require.NoError(t, diags.Error())
|
||||||
|
b.Config.Workspace.ArtifactPath = "/Volumes/catalog/schema/libs"
|
||||||
|
diags = validateDevelopmentMode(b)
|
||||||
|
require.ErrorContains(t, diags.Error(), "artifact_path should contain the current username or ${workspace.current_user.short_name} to ensure uniqueness when using 'mode: development'")
|
||||||
|
|
||||||
// Test with a bundle that has a non-user path
|
// Test with a bundle that has a non-user path
|
||||||
|
b = mockBundle(config.Development)
|
||||||
b.Config.Workspace.RootPath = "/Shared/.bundle/x/y/state"
|
b.Config.Workspace.RootPath = "/Shared/.bundle/x/y/state"
|
||||||
diags = validateDevelopmentMode(b)
|
diags = validateDevelopmentMode(b)
|
||||||
require.ErrorContains(t, diags.Error(), "root_path")
|
require.ErrorContains(t, diags.Error(), "root_path must start with '~/' or contain the current username to ensure uniqueness when using 'mode: development'")
|
||||||
|
|
||||||
// Test with a bundle that has an unpaused trigger pause status
|
// Test with a bundle that has an unpaused trigger pause status
|
||||||
b = mockBundle(config.Development)
|
b = mockBundle(config.Development)
|
||||||
|
|
|
@ -72,9 +72,11 @@ func IsLibraryLocal(dep string) bool {
|
||||||
|
|
||||||
// ^[a-zA-Z0-9\-_]+: Matches the package name, allowing alphanumeric characters, dashes (-), and underscores (_).
|
// ^[a-zA-Z0-9\-_]+: Matches the package name, allowing alphanumeric characters, dashes (-), and underscores (_).
|
||||||
// \[.*\])?: Optionally matches any extras specified in square brackets, e.g., [security].
|
// \[.*\])?: Optionally matches any extras specified in square brackets, e.g., [security].
|
||||||
// ((==|!=|<=|>=|~=|>|<)\d+(\.\d+){0,2}(\.\*)?)?: Optionally matches version specifiers, supporting various operators (==, !=, etc.) followed by a version number (e.g., 2.25.1).
|
// ((==|!=|<=|>=|~=|>|<)\d+(\.\d+){0,2}(\.\*)?): Optionally matches version specifiers, supporting various operators (==, !=, etc.) followed by a version number (e.g., 2.25.1).
|
||||||
|
// ,?: Optionally matches a comma (,) at the end of the specifier which is used to separate multiple specifiers.
|
||||||
|
// There can be multiple version specifiers separated by commas or no specifiers.
|
||||||
// Spec for package name and version specifier: https://pip.pypa.io/en/stable/reference/requirement-specifiers/
|
// Spec for package name and version specifier: https://pip.pypa.io/en/stable/reference/requirement-specifiers/
|
||||||
var packageRegex = regexp.MustCompile(`^[a-zA-Z0-9\-_]+\s?(\[.*\])?\s?((==|!=|<=|>=|~=|==|>|<)\s?\d+(\.\d+){0,2}(\.\*)?)?$`)
|
var packageRegex = regexp.MustCompile(`^[a-zA-Z0-9\-_]+\s?(\[.*\])?\s?((==|!=|<=|>=|~=|==|>|<)\s?\d+(\.\d+){0,2}(\.\*)?,?)*$`)
|
||||||
|
|
||||||
func isPackage(name string) bool {
|
func isPackage(name string) bool {
|
||||||
if packageRegex.MatchString(name) {
|
if packageRegex.MatchString(name) {
|
||||||
|
|
|
@ -62,6 +62,8 @@ func TestIsLibraryLocal(t *testing.T) {
|
||||||
{path: "beautifulsoup4 ~= 4.12.3", expected: false},
|
{path: "beautifulsoup4 ~= 4.12.3", expected: false},
|
||||||
{path: "beautifulsoup4[security, tests]", expected: false},
|
{path: "beautifulsoup4[security, tests]", expected: false},
|
||||||
{path: "beautifulsoup4[security, tests] ~= 4.12.3", expected: false},
|
{path: "beautifulsoup4[security, tests] ~= 4.12.3", expected: false},
|
||||||
|
{path: "beautifulsoup4>=1.0.0,<2.0.0", expected: false},
|
||||||
|
{path: "beautifulsoup4>=1.0.0,~=1.2.0,<2.0.0", expected: false},
|
||||||
{path: "https://github.com/pypa/pip/archive/22.0.2.zip", expected: false},
|
{path: "https://github.com/pypa/pip/archive/22.0.2.zip", expected: false},
|
||||||
{path: "pip @ https://github.com/pypa/pip/archive/22.0.2.zip", expected: false},
|
{path: "pip @ https://github.com/pypa/pip/archive/22.0.2.zip", expected: false},
|
||||||
{path: "requests [security] @ https://github.com/psf/requests/archive/refs/heads/main.zip", expected: false},
|
{path: "requests [security] @ https://github.com/psf/requests/archive/refs/heads/main.zip", expected: false},
|
||||||
|
|
Loading…
Reference in New Issue