diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index a7151ca74a..97396c8d13 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -9,5 +9,6 @@ ### CLI ### Bundles +* Added support for model serving endpoints in deployment bind/unbind commands ([#2634](https://github.com/databricks/cli/pull/2634)) ### API Changes diff --git a/acceptance/bundle/deployment/bind/model-serving-endpoint/databricks.yml.tmpl b/acceptance/bundle/deployment/bind/model-serving-endpoint/databricks.yml.tmpl new file mode 100644 index 0000000000..29c2691ed8 --- /dev/null +++ b/acceptance/bundle/deployment/bind/model-serving-endpoint/databricks.yml.tmpl @@ -0,0 +1,7 @@ +bundle: + name: bind-model-serving-endpoint-test-$UNIQUE_NAME + +resources: + model_serving_endpoints: + endpoint1: + name: $ENDPOINT_NAME diff --git a/acceptance/bundle/deployment/bind/model-serving-endpoint/output.txt b/acceptance/bundle/deployment/bind/model-serving-endpoint/output.txt new file mode 100644 index 0000000000..f6fdef914b --- /dev/null +++ b/acceptance/bundle/deployment/bind/model-serving-endpoint/output.txt @@ -0,0 +1,72 @@ +bundle: + name: bind-model-serving-endpoint-test-[UNIQUE_NAME] + +resources: + model_serving_endpoints: + endpoint1: + name: test-endpoint-[UUID] + +>>> [CLI] serving-endpoints create test-endpoint-[UUID] +{ + "name": "test-endpoint-[UUID]", + "permission_level": "CAN_MANAGE", + "route_optimized": false, + "state": { + "config_update": "NOT_UPDATING", + "ready": "NOT_READY" + } +} + +>>> [CLI] serving-endpoints get test-endpoint-[UUID] +{ + "name": "test-endpoint-[UUID]", + "permission_level": "CAN_MANAGE", + "route_optimized": false, + "state": { + "config_update": "NOT_UPDATING", + "ready": "NOT_READY" + } +} + +>>> [CLI] bundle deployment bind endpoint1 test-endpoint-[UUID] +Updating deployment state... +Successfully bound model_serving_endpoint with an id 'test-endpoint-[UUID]'. Run 'bundle deploy' to deploy changes to your workspace + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/bind-model-serving-endpoint-test-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] serving-endpoints get test-endpoint-[UUID] +{ + "name": "test-endpoint-[UUID]", + "permission_level": "CAN_MANAGE", + "route_optimized": false, + "state": { + "config_update": "NOT_UPDATING", + "ready": "NOT_READY" + } +} + +>>> [CLI] bundle deployment unbind endpoint1 +Updating deployment state... + +>>> [CLI] bundle destroy --auto-approve +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/bind-model-serving-endpoint-test-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! + +>>> [CLI] serving-endpoints get test-endpoint-[UUID] +{ + "name": "test-endpoint-[UUID]", + "permission_level": "CAN_MANAGE", + "route_optimized": false, + "state": { + "config_update": "NOT_UPDATING", + "ready": "NOT_READY" + } +} + +>>> [CLI] serving-endpoints delete test-endpoint-[UUID] diff --git a/acceptance/bundle/deployment/bind/model-serving-endpoint/script b/acceptance/bundle/deployment/bind/model-serving-endpoint/script new file mode 100644 index 0000000000..2fb220940a --- /dev/null +++ b/acceptance/bundle/deployment/bind/model-serving-endpoint/script @@ -0,0 +1,30 @@ +ENDPOINT_NAME="test-endpoint-$(uuid)" +if [ -z "$CLOUD_ENV" ]; then + ENDPOINT_NAME="test-endpoint-6260d50f-e8ff-4905-8f28-812345678903" +fi +export ENDPOINT_NAME +envsubst < databricks.yml.tmpl > databricks.yml +cat databricks.yml + +# Create a pre-defined serving endpoint: +trace $CLI serving-endpoints create "${ENDPOINT_NAME}" | jq '{name, permission_level, route_optimized, state}' + +cleanup() { + trace $CLI serving-endpoints delete "${ENDPOINT_NAME}" +} +trap cleanup EXIT + +trace $CLI serving-endpoints get "${ENDPOINT_NAME}" | jq '{name, permission_level, route_optimized, state}' + +trace $CLI bundle deployment bind endpoint1 "${ENDPOINT_NAME}" + +trace $CLI bundle deploy + +trace $CLI serving-endpoints get "${ENDPOINT_NAME}" | jq '{name, permission_level, route_optimized, state}' + +trace $CLI bundle deployment unbind endpoint1 + +trace $CLI bundle destroy --auto-approve + +# Read the pre-defined serving-endpoint again (expecting it still exists and is not deleted): +trace $CLI serving-endpoints get "${ENDPOINT_NAME}" | jq '{name, permission_level, route_optimized, state}' diff --git a/acceptance/bundle/deployment/bind/model-serving-endpoint/test.toml b/acceptance/bundle/deployment/bind/model-serving-endpoint/test.toml new file mode 100644 index 0000000000..3134784e8f --- /dev/null +++ b/acceptance/bundle/deployment/bind/model-serving-endpoint/test.toml @@ -0,0 +1,34 @@ +Local = true +Cloud = true + +Ignore = [ + "databricks.yml", +] + +[[Server]] +Pattern = "POST /api/2.0/serving-endpoints" +Response.Body = ''' +{ + "name": "test-endpoint-6260d50f-e8ff-4905-8f28-812345678903" +} +''' + +[[Server]] +Pattern = "GET /api/2.0/serving-endpoints/" + +[[Server]] +Pattern = "GET /api/2.0/serving-endpoints/{endpoint_name}" +Response.Body = ''' +{ + "name": "test-endpoint-6260d50f-e8ff-4905-8f28-812345678903", + "permission_level": "CAN_MANAGE", + "route_optimized": false, + "state": { + "config_update": "NOT_UPDATING", + "ready": "NOT_READY" + } +} +''' + +[[Server]] +Pattern = "DELETE /api/2.0/serving-endpoints/{endpoint_name}" diff --git a/bundle/config/resources.go b/bundle/config/resources.go index b87248438d..cffd5ada87 100644 --- a/bundle/config/resources.go +++ b/bundle/config/resources.go @@ -157,6 +157,12 @@ func (r *Resources) FindResourceByConfigKey(key string) (ConfigResource, error) } } + for k := range r.ModelServingEndpoints { + if k == key { + found = append(found, r.ModelServingEndpoints[k]) + } + } + if len(found) == 0 { return nil, fmt.Errorf("no such resource: %s", key) } diff --git a/bundle/config/resources/model_serving_endpoint.go b/bundle/config/resources/model_serving_endpoint.go index 1b48eb9bb3..e489999bb3 100644 --- a/bundle/config/resources/model_serving_endpoint.go +++ b/bundle/config/resources/model_serving_endpoint.go @@ -35,12 +35,12 @@ func (s ModelServingEndpoint) MarshalJSON() ([]byte, error) { return marshal.Marshal(s) } -func (s *ModelServingEndpoint) Exists(ctx context.Context, w *databricks.WorkspaceClient, id string) (bool, error) { +func (s *ModelServingEndpoint) Exists(ctx context.Context, w *databricks.WorkspaceClient, name string) (bool, error) { _, err := w.ServingEndpoints.Get(ctx, serving.GetServingEndpointRequest{ - Name: id, + Name: name, }) if err != nil { - log.Debugf(ctx, "serving endpoint %s does not exist", id) + log.Debugf(ctx, "serving endpoint %s does not exist", name) return false, err } return true, nil diff --git a/bundle/config/resources_test.go b/bundle/config/resources_test.go index c69f950112..7424966fe7 100644 --- a/bundle/config/resources_test.go +++ b/bundle/config/resources_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "github.com/databricks/databricks-sdk-go/service/serving" + "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/databricks-sdk-go/experimental/mocks" "github.com/databricks/databricks-sdk-go/service/apps" @@ -164,8 +166,13 @@ func TestResourcesBindSupport(t *testing.T) { CreateMonitor: &catalog.CreateMonitor{}, }, }, + ModelServingEndpoints: map[string]*resources.ModelServingEndpoint{ + "my_model_serving_endpoint": { + CreateServingEndpoint: &serving.CreateServingEndpoint{}, + }, + }, } - unbindableResources := map[string]bool{"model": true, "model_serving_endpoint": true} + unbindableResources := map[string]bool{"model": true} ctx := context.Background() m := mocks.NewMockWorkspaceClient(t) @@ -179,6 +186,7 @@ func TestResourcesBindSupport(t *testing.T) { m.GetMockVolumesAPI().EXPECT().Read(mock.Anything, mock.Anything).Return(nil, nil) m.GetMockAppsAPI().EXPECT().GetByName(mock.Anything, mock.Anything).Return(nil, nil) m.GetMockQualityMonitorsAPI().EXPECT().Get(mock.Anything, mock.Anything).Return(nil, nil) + m.GetMockServingEndpointsAPI().EXPECT().Get(mock.Anything, mock.Anything).Return(nil, nil) allResources := supportedResources.AllResources() for _, group := range allResources {