Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nextchanges/bundles/7081.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Add support for configuring an app deploy-time `health_check` via inline `config` in bundle config. ([#7081](https://github.com/databricks/cli/pull/7081))
3 changes: 3 additions & 0 deletions acceptance/bundle/refschema/out.fields.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ resources.apps.*.config.env[*] resources.AppEnvVar ALL
resources.apps.*.config.env[*].name string ALL
resources.apps.*.config.env[*].value string ALL
resources.apps.*.config.env[*].value_from string ALL
resources.apps.*.config.health_check *resources.AppHealthCheck ALL
resources.apps.*.config.health_check.path string ALL
resources.apps.*.config.health_check.timeout_seconds int ALL
resources.apps.*.create_time string ALL
resources.apps.*.creator string ALL
resources.apps.*.default_git_source *apps.GitSource ALL
Expand Down
5 changes: 5 additions & 0 deletions acceptance/bundle/resources/apps/inline_config/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@
@app.route("/")
def home():
return "Hello from inline config app!"


@app.route("/health")
def health():
return "OK"
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ resources:
value: test_value
- name: ANOTHER_VAR
value: another_value
health_check:
path: /health
timeout_seconds: 120
6 changes: 5 additions & 1 deletion acceptance/bundle/resources/apps/inline_config/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ Resources: 1 created, 0 changed, 0 deleted, 0 unchanged
{
"name": "ANOTHER_VAR"
}
]
],
"health_check": {
"path": "/health",
"timeout": "120s"
}
}

>>> [CLI] bundle destroy --auto-approve
Expand Down
2 changes: 1 addition & 1 deletion acceptance/bundle/resources/apps/inline_config/script
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ $CLI bundle run myapp > /dev/null 2>&1
DEPLOYMENT_ID=$($CLI apps get "$APP_NAME" | jq -r '.active_deployment.deployment_id')
add_repl "$DEPLOYMENT_ID" DEPLOYMENT_ID

trace $CLI apps get-deployment "$APP_NAME" "$DEPLOYMENT_ID" | jq '{command, env_vars}'
trace $CLI apps get-deployment "$APP_NAME" "$DEPLOYMENT_ID" | jq '{command, env_vars, health_check}'
10 changes: 10 additions & 0 deletions bundle/appdeploy/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/databricks/cli/bundle/config/resources"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/common/types/duration"
sdkapps "github.com/databricks/databricks-sdk-go/service/apps"
)

Expand Down Expand Up @@ -45,6 +46,15 @@ func BuildDeployment(sourcePath string, config *resources.AppConfig, gitSource *
}
}
}

if config.HealthCheck != nil {
deployment.HealthCheck = &sdkapps.AppHealthCheck{
Path: config.HealthCheck.Path,
}
if config.HealthCheck.TimeoutSeconds > 0 {
deployment.HealthCheck.Timeout = duration.New(time.Duration(config.HealthCheck.TimeoutSeconds) * time.Second)
}
}
}

return deployment
Expand Down
12 changes: 12 additions & 0 deletions bundle/config/resources/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ type AppConfig struct {

// Env contains environment variables to set for the app
Env []AppEnvVar `json:"env,omitempty" yaml:"env,omitempty"`

// HealthCheck configures the HTTP check that must pass before a deployment is considered successful
HealthCheck *AppHealthCheck `json:"health_check,omitempty" yaml:"health_check,omitempty"`
}

// AppHealthCheck represents a deploy-time health check configuration for an app
type AppHealthCheck struct {
// Path is the HTTP path to probe (e.g., "/health")
Path string `json:"path" yaml:"path"`

// TimeoutSeconds is how long to wait for the health check to pass before failing the deployment
TimeoutSeconds int `json:"timeout_seconds,omitempty" yaml:"timeout_seconds,omitempty"`
}

// AppEnvVar represents an environment variable configuration for an app
Expand Down
19 changes: 15 additions & 4 deletions bundle/direct/dresources/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,15 @@ func remoteIsStarted(entry *PlanEntry) bool {
}

// deploymentToAppConfig extracts an AppConfig from an active deployment.
// Returns nil if the deployment has no command or env vars.
// Returns nil if the deployment has no command, env vars or health check.
func deploymentToAppConfig(d *apps.AppDeployment) *resources.AppConfig {
if len(d.Command) == 0 && len(d.EnvVars) == 0 {
if len(d.Command) == 0 && len(d.EnvVars) == 0 && d.HealthCheck == nil {
return nil
}
config := &resources.AppConfig{
Command: d.Command,
Env: nil,
Command: d.Command,
Env: nil,
HealthCheck: nil,
}
if len(d.EnvVars) > 0 {
config.Env = make([]resources.AppEnvVar, len(d.EnvVars))
Expand All @@ -307,6 +308,16 @@ func deploymentToAppConfig(d *apps.AppDeployment) *resources.AppConfig {
}
}
}
if d.HealthCheck != nil {
timeoutSeconds := 0
if d.HealthCheck.Timeout != nil {
timeoutSeconds = int(d.HealthCheck.Timeout.AsDuration().Seconds())
}
config.HealthCheck = &resources.AppHealthCheck{
Path: d.HealthCheck.Path,
TimeoutSeconds: timeoutSeconds,
}
}
return config
}

Expand Down
10 changes: 10 additions & 0 deletions bundle/internal/schema/annotations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,16 @@ resources:
"value_from":
"description": |-
PLACEHOLDER
"health_check":
"description": |-
PLACEHOLDER
"$fields":
"path":
"description": |-
PLACEHOLDER
"timeout_seconds":
"description": |-
PLACEHOLDER
"effective_budget_policy_id":
"description": |-
PLACEHOLDER
Expand Down
1 change: 1 addition & 0 deletions bundle/internal/validation/generated/required_fields.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions bundle/run/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ func TestBuildAppDeploymentWithValueFrom(t *testing.T) {
ValueFrom: "secrets/override",
},
},
HealthCheck: &resources.AppHealthCheck{
Path: "/health",
TimeoutSeconds: 120,
},
},
}

Expand Down Expand Up @@ -319,4 +323,7 @@ func TestBuildAppDeploymentWithValueFrom(t *testing.T) {
require.Equal(t, "COMBINED_VAR", deployment.EnvVars[2].Name)
require.Equal(t, "default_value", deployment.EnvVars[2].Value)
require.Equal(t, "secrets/override", deployment.EnvVars[2].ValueFrom)

require.Equal(t, "/health", deployment.HealthCheck.Path)
require.Equal(t, 120*time.Second, deployment.HealthCheck.Timeout.AsDuration())
}
26 changes: 26 additions & 0 deletions bundle/schema/jsonschema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions cmd/apps/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,15 @@ func inlineAppConfigFile(appValue *dyn.Value) (string, error) {
configMap["env"] = envValue
}

// Add health_check if present
if healthCheck, ok := appConfig["health_check"]; ok {
healthCheckValue, err := convert.FromTyped(healthCheck, dyn.NilValue)
if err != nil {
return "", fmt.Errorf("failed to convert health_check: %w", err)
}
configMap["health_check"] = healthCheckValue
}

// Add the config section if we have any items
if len(configMap) > 0 {
newPairs = append(newPairs, dyn.Pair{
Expand Down
4 changes: 4 additions & 0 deletions cmd/apps/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ func TestInlineAppConfigFileFieldPriority(t *testing.T) {
env:
- name: FOO
value: bar
health_check:
path: /health
timeout_seconds: 120
resources:
- name: ENDPOINT
serving_endpoint:
Expand Down Expand Up @@ -245,6 +248,7 @@ resources:
assert.Equal(t, "existing description", result["description"])
assert.NotNil(t, result["config"])
assert.NotNil(t, result["resources"])
assert.Equal(t, map[string]any{"path": "/health", "timeout_seconds": int64(120)}, result["config"].(map[string]any)["health_check"])
})
}

Expand Down