Change recommended production deployment path from /Shared to /Users (#1091)

## Changes

This PR changes the default and `mode: production` recommendation to
target `/Users` for deployment. Previously, we used `/Shared`, but
because of a lack of POSIX-like permissions in WorkspaceFS this meant
that files inside would be readable and writable by other users in the
workspace.

Detailed change:
* `default-python` no longer uses a path that starts with `/Shared`
* `mode: production` no longer requires a path that starts with
`/Shared`
 
## Related PRs

Docs: https://github.com/databricks/docs/pull/14585
Examples: https://github.com/databricks/bundle-examples/pull/17

## Tests

* Manual tests
* Template unit tests (with an extra check to avoid /Shared)
This commit is contained in:
Lennart Kats (databricks) 2024-01-02 20:58:24 +01:00 committed by GitHub
parent e80882b5af
commit 167deec8c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 24 additions and 31 deletions

View File

@ -101,29 +101,25 @@ func transformDevelopmentMode(b *bundle.Bundle) error {
} }
func validateDevelopmentMode(b *bundle.Bundle) error { func validateDevelopmentMode(b *bundle.Bundle) error {
if path := findIncorrectPath(b, config.Development); path != "" { if path := findNonUserPath(b); path != "" {
return fmt.Errorf("%s must start with '~/' or contain the current username when using 'mode: development'", path) return fmt.Errorf("%s must start with '~/' or contain the current username when using 'mode: development'", path)
} }
return nil return nil
} }
func findIncorrectPath(b *bundle.Bundle, mode config.Mode) string { func findNonUserPath(b *bundle.Bundle) string {
username := b.Config.Workspace.CurrentUser.UserName username := b.Config.Workspace.CurrentUser.UserName
containsExpected := true
if mode == config.Production {
containsExpected = false
}
if strings.Contains(b.Config.Workspace.RootPath, username) != containsExpected && b.Config.Workspace.RootPath != "" { if b.Config.Workspace.RootPath != "" && !strings.Contains(b.Config.Workspace.RootPath, username) {
return "root_path" return "root_path"
} }
if strings.Contains(b.Config.Workspace.StatePath, username) != containsExpected { if b.Config.Workspace.StatePath != "" && !strings.Contains(b.Config.Workspace.StatePath, username) {
return "state_path" return "state_path"
} }
if strings.Contains(b.Config.Workspace.FilePath, username) != containsExpected { if b.Config.Workspace.FilePath != "" && !strings.Contains(b.Config.Workspace.FilePath, username) {
return "file_path" return "file_path"
} }
if strings.Contains(b.Config.Workspace.ArtifactPath, username) != containsExpected { if b.Config.Workspace.ArtifactPath != "" && !strings.Contains(b.Config.Workspace.ArtifactPath, username) {
return "artifact_path" return "artifact_path"
} }
return "" return ""
@ -138,23 +134,12 @@ func validateProductionMode(ctx context.Context, b *bundle.Bundle, isPrincipalUs
r := b.Config.Resources r := b.Config.Resources
for i := range r.Pipelines { for i := range r.Pipelines {
if r.Pipelines[i].Development { if r.Pipelines[i].Development {
return fmt.Errorf("target with 'mode: production' cannot specify a pipeline with 'development: true'") return fmt.Errorf("target with 'mode: production' cannot include a pipeline with 'development: true'")
} }
} }
if !isPrincipalUsed { if !isPrincipalUsed && !isRunAsSet(r) {
if path := findIncorrectPath(b, config.Production); path != "" { return fmt.Errorf("'run_as' must be set for all jobs when using 'mode: production'")
message := "%s must not contain the current username when using 'mode: production'"
if path == "root_path" {
return fmt.Errorf(message+"\n tip: set workspace.root_path to a shared path such as /Shared/.bundle/${bundle.name}/${bundle.target}", path)
} else {
return fmt.Errorf(message, path)
}
}
if !isRunAsSet(r) {
return fmt.Errorf("'run_as' must be set for all jobs when using 'mode: production'")
}
} }
return nil return nil
} }

View File

@ -205,7 +205,7 @@ func TestProcessTargetModeProduction(t *testing.T) {
b := mockBundle(config.Production) b := mockBundle(config.Production)
err := validateProductionMode(context.Background(), b, false) err := validateProductionMode(context.Background(), b, false)
require.ErrorContains(t, err, "state_path") require.ErrorContains(t, err, "run_as")
b.Config.Workspace.StatePath = "/Shared/.bundle/x/y/state" b.Config.Workspace.StatePath = "/Shared/.bundle/x/y/state"
b.Config.Workspace.ArtifactPath = "/Shared/.bundle/x/y/artifacts" b.Config.Workspace.ArtifactPath = "/Shared/.bundle/x/y/artifacts"

View File

@ -32,11 +32,19 @@ targets:
mode: production mode: production
workspace: workspace:
host: {{workspace_host}} host: {{workspace_host}}
# We only have a single deployment copy for production, so we use a shared path. # We always use /Users/{{user_name}} for all resources to make sure we only have a single copy.
root_path: /Shared/.bundle/prod/${bundle.name} {{- /*
{{- if not is_service_principal}} Internal note 2023-12: CLI versions v0.211.0 and before would show an error when using `mode: production`
with a path that doesn't say "/Shared". For now, we'll include an extra comment in the template
to explain that customers should update if they see this.
*/}}
# If this path results in an error, please make sure you have a recent version of the CLI installed.
root_path: /Users/{{user_name}}/.bundle/${bundle.name}/${bundle.target}
run_as: run_as:
# This runs as {{user_name}} in production. We could also use a service principal here {{- if is_service_principal}}
# using service_principal_name (see https://docs.databricks.com/dev-tools/bundles/permissions.html). service_principal_name: {{user_name}}
{{- else}}
# This runs as {{user_name}} in production. We could also use a service principal here,
# see https://docs.databricks.com/dev-tools/bundles/permissions.html.
user_name: {{user_name}} user_name: {{user_name}}
{{end -}} {{- end}}