diff --git a/acceptance/bundle/refschema/out.fields.txt b/acceptance/bundle/refschema/out.fields.txt index db4fbe39c1..d950ba064a 100644 --- a/acceptance/bundle/refschema/out.fields.txt +++ b/acceptance/bundle/refschema/out.fields.txt @@ -2252,6 +2252,475 @@ resources.jobs.*.permissions.permissions[*].group_name string ALL resources.jobs.*.permissions.permissions[*].permission_level iam.PermissionLevel ALL resources.jobs.*.permissions.permissions[*].service_principal_name string ALL resources.jobs.*.permissions.permissions[*].user_name string ALL +resources.model_serving_endpoints.*.ai_gateway *serving.AiGatewayConfig INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.fallback_config *serving.FallbackConfig INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.fallback_config.enabled bool INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails *serving.AiGatewayGuardrails INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.input *serving.AiGatewayGuardrailParameters INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.input.invalid_keywords []string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.input.invalid_keywords[*] string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.input.pii *serving.AiGatewayGuardrailPiiBehavior INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.input.pii.behavior serving.AiGatewayGuardrailPiiBehaviorBehavior INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.input.safety bool INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.input.valid_topics []string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.input.valid_topics[*] string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.output *serving.AiGatewayGuardrailParameters INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.output.invalid_keywords []string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.output.invalid_keywords[*] string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.output.pii *serving.AiGatewayGuardrailPiiBehavior INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.output.pii.behavior serving.AiGatewayGuardrailPiiBehaviorBehavior INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.output.safety bool INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.output.valid_topics []string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.guardrails.output.valid_topics[*] string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.inference_table_config *serving.AiGatewayInferenceTableConfig INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.inference_table_config.catalog_name string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.inference_table_config.enabled bool INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.inference_table_config.schema_name string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.inference_table_config.table_name_prefix string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.rate_limits []serving.AiGatewayRateLimit INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.rate_limits[*] serving.AiGatewayRateLimit INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.rate_limits[*].calls int64 INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.rate_limits[*].key serving.AiGatewayRateLimitKey INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.rate_limits[*].principal string INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.rate_limits[*].renewal_period serving.AiGatewayRateLimitRenewalPeriod INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.rate_limits[*].tokens int64 INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.usage_tracking_config *serving.AiGatewayUsageTrackingConfig INPUT STATE +resources.model_serving_endpoints.*.ai_gateway.usage_tracking_config.enabled bool INPUT STATE +resources.model_serving_endpoints.*.budget_policy_id string INPUT STATE +resources.model_serving_endpoints.*.config *serving.EndpointCoreConfigInput INPUT STATE +resources.model_serving_endpoints.*.config.auto_capture_config *serving.AutoCaptureConfigInput INPUT STATE +resources.model_serving_endpoints.*.config.auto_capture_config.catalog_name string INPUT STATE +resources.model_serving_endpoints.*.config.auto_capture_config.enabled bool INPUT STATE +resources.model_serving_endpoints.*.config.auto_capture_config.schema_name string INPUT STATE +resources.model_serving_endpoints.*.config.auto_capture_config.table_name_prefix string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities []serving.ServedEntityInput INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*] serving.ServedEntityInput INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].entity_name string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].entity_version string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].environment_vars map[string]string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].environment_vars.* string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model *serving.ExternalModel INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.ai21labs_config *serving.Ai21LabsConfig INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.amazon_bedrock_config *serving.AmazonBedrockConfig INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.amazon_bedrock_config.aws_region string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.amazon_bedrock_config.bedrock_provider serving.AmazonBedrockConfigBedrockProvider INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.amazon_bedrock_config.instance_profile_arn string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.anthropic_config *serving.AnthropicConfig INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.anthropic_config.anthropic_api_key string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.anthropic_config.anthropic_api_key_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.cohere_config *serving.CohereConfig INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.cohere_config.cohere_api_base string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.cohere_config.cohere_api_key string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.cohere_config.cohere_api_key_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.custom_provider_config *serving.CustomProviderConfig INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.custom_provider_config.api_key_auth *serving.ApiKeyAuth INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.custom_provider_config.api_key_auth.key string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.custom_provider_config.api_key_auth.value string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.custom_provider_config.api_key_auth.value_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.custom_provider_config.bearer_token_auth *serving.BearerTokenAuth INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.custom_provider_config.custom_provider_url string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.databricks_model_serving_config *serving.DatabricksModelServingConfig INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.databricks_model_serving_config.databricks_workspace_url string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.google_cloud_vertex_ai_config *serving.GoogleCloudVertexAiConfig INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.google_cloud_vertex_ai_config.project_id string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.google_cloud_vertex_ai_config.region string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.name string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config *serving.OpenAiConfig INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.microsoft_entra_client_id string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.microsoft_entra_tenant_id string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.openai_api_base string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.openai_api_key string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.openai_api_key_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.openai_api_type string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.openai_api_version string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.openai_deployment_name string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.openai_config.openai_organization string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.palm_config *serving.PaLmConfig INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.palm_config.palm_api_key string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.palm_config.palm_api_key_plaintext string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.provider serving.ExternalModelProvider INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].external_model.task string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].instance_profile_arn string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].max_provisioned_concurrency int INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].max_provisioned_throughput int INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].min_provisioned_concurrency int INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].min_provisioned_throughput int INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].name string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].provisioned_model_units int64 INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].scale_to_zero_enabled bool INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].workload_size string INPUT STATE +resources.model_serving_endpoints.*.config.served_entities[*].workload_type serving.ServingModelWorkloadType INPUT STATE +resources.model_serving_endpoints.*.config.served_models []serving.ServedModelInput INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*] serving.ServedModelInput INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].environment_vars map[string]string INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].environment_vars.* string INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].instance_profile_arn string INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].max_provisioned_concurrency int INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].max_provisioned_throughput int INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].min_provisioned_concurrency int INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].min_provisioned_throughput int INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].model_name string INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].model_version string INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].name string INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].provisioned_model_units int64 INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].scale_to_zero_enabled bool INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].workload_size string INPUT STATE +resources.model_serving_endpoints.*.config.served_models[*].workload_type serving.ServedModelInputWorkloadType INPUT STATE +resources.model_serving_endpoints.*.config.traffic_config *serving.TrafficConfig INPUT STATE +resources.model_serving_endpoints.*.config.traffic_config.routes []serving.Route INPUT STATE +resources.model_serving_endpoints.*.config.traffic_config.routes[*] serving.Route INPUT STATE +resources.model_serving_endpoints.*.config.traffic_config.routes[*].served_entity_name string INPUT STATE +resources.model_serving_endpoints.*.config.traffic_config.routes[*].served_model_name string INPUT STATE +resources.model_serving_endpoints.*.config.traffic_config.routes[*].traffic_percentage int INPUT STATE +resources.model_serving_endpoints.*.description string INPUT STATE +resources.model_serving_endpoints.*.email_notifications *serving.EmailNotifications INPUT STATE +resources.model_serving_endpoints.*.email_notifications.on_update_failure []string INPUT STATE +resources.model_serving_endpoints.*.email_notifications.on_update_failure[*] string INPUT STATE +resources.model_serving_endpoints.*.email_notifications.on_update_success []string INPUT STATE +resources.model_serving_endpoints.*.email_notifications.on_update_success[*] string INPUT STATE +resources.model_serving_endpoints.*.endpoint_details *serving.ServingEndpointDetailed REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway *serving.AiGatewayConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.fallback_config *serving.FallbackConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.fallback_config.enabled bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails *serving.AiGatewayGuardrails REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.input *serving.AiGatewayGuardrailParameters REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.input.invalid_keywords []string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.input.invalid_keywords[*] string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.input.pii *serving.AiGatewayGuardrailPiiBehavior REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.input.pii.behavior serving.AiGatewayGuardrailPiiBehaviorBehavior REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.input.safety bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.input.valid_topics []string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.input.valid_topics[*] string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.output *serving.AiGatewayGuardrailParameters REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.output.invalid_keywords []string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.output.invalid_keywords[*] string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.output.pii *serving.AiGatewayGuardrailPiiBehavior REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.output.pii.behavior serving.AiGatewayGuardrailPiiBehaviorBehavior REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.output.safety bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.output.valid_topics []string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.guardrails.output.valid_topics[*] string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.inference_table_config *serving.AiGatewayInferenceTableConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.inference_table_config.catalog_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.inference_table_config.enabled bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.inference_table_config.schema_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.inference_table_config.table_name_prefix string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.rate_limits []serving.AiGatewayRateLimit REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.rate_limits[*] serving.AiGatewayRateLimit REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.rate_limits[*].calls int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.rate_limits[*].key serving.AiGatewayRateLimitKey REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.rate_limits[*].principal string REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.rate_limits[*].renewal_period serving.AiGatewayRateLimitRenewalPeriod REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.rate_limits[*].tokens int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.usage_tracking_config *serving.AiGatewayUsageTrackingConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.ai_gateway.usage_tracking_config.enabled bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.budget_policy_id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config *serving.EndpointCoreConfigOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config *serving.AutoCaptureConfigOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config.catalog_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config.enabled bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config.schema_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config.state *serving.AutoCaptureState REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config.state.payload_table *serving.PayloadTable REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config.state.payload_table.name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config.state.payload_table.status string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config.state.payload_table.status_message string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.auto_capture_config.table_name_prefix string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.config_version int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities []serving.ServedEntityOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*] serving.ServedEntityOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].creation_timestamp int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].creator string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].entity_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].entity_version string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].environment_vars map[string]string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].environment_vars.* string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model *serving.ExternalModel REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.ai21labs_config *serving.Ai21LabsConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.amazon_bedrock_config *serving.AmazonBedrockConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.amazon_bedrock_config.aws_region string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.amazon_bedrock_config.bedrock_provider serving.AmazonBedrockConfigBedrockProvider REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.amazon_bedrock_config.instance_profile_arn string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.anthropic_config *serving.AnthropicConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.anthropic_config.anthropic_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.anthropic_config.anthropic_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.cohere_config *serving.CohereConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.cohere_config.cohere_api_base string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.cohere_config.cohere_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.cohere_config.cohere_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.custom_provider_config *serving.CustomProviderConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.custom_provider_config.api_key_auth *serving.ApiKeyAuth REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.custom_provider_config.api_key_auth.key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.custom_provider_config.api_key_auth.value string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.custom_provider_config.api_key_auth.value_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.custom_provider_config.bearer_token_auth *serving.BearerTokenAuth REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.custom_provider_config.custom_provider_url string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.databricks_model_serving_config *serving.DatabricksModelServingConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.databricks_model_serving_config.databricks_workspace_url string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.google_cloud_vertex_ai_config *serving.GoogleCloudVertexAiConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.google_cloud_vertex_ai_config.project_id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.google_cloud_vertex_ai_config.region string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config *serving.OpenAiConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.microsoft_entra_client_id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.microsoft_entra_tenant_id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.openai_api_base string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.openai_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.openai_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.openai_api_type string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.openai_api_version string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.openai_deployment_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.openai_config.openai_organization string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.palm_config *serving.PaLmConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.palm_config.palm_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.palm_config.palm_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.provider serving.ExternalModelProvider REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].external_model.task string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].foundation_model *serving.FoundationModel REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].foundation_model.description string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].foundation_model.display_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].foundation_model.docs string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].foundation_model.name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].instance_profile_arn string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].max_provisioned_concurrency int REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].max_provisioned_throughput int REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].min_provisioned_concurrency int REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].min_provisioned_throughput int REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].provisioned_model_units int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].scale_to_zero_enabled bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].state *serving.ServedModelState REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].state.deployment serving.ServedModelStateDeployment REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].state.deployment_state_message string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].workload_size string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_entities[*].workload_type serving.ServingModelWorkloadType REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models []serving.ServedModelOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*] serving.ServedModelOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].creation_timestamp int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].creator string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].environment_vars map[string]string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].environment_vars.* string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].instance_profile_arn string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].max_provisioned_concurrency int REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].min_provisioned_concurrency int REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].model_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].model_version string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].provisioned_model_units int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].scale_to_zero_enabled bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].state *serving.ServedModelState REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].state.deployment serving.ServedModelStateDeployment REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].state.deployment_state_message string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].workload_size string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.served_models[*].workload_type serving.ServingModelWorkloadType REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.traffic_config *serving.TrafficConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.traffic_config.routes []serving.Route REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.traffic_config.routes[*] serving.Route REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.traffic_config.routes[*].served_entity_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.traffic_config.routes[*].served_model_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.config.traffic_config.routes[*].traffic_percentage int REMOTE +resources.model_serving_endpoints.*.endpoint_details.creation_timestamp int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.creator string REMOTE +resources.model_serving_endpoints.*.endpoint_details.data_plane_info *serving.ModelDataPlaneInfo REMOTE +resources.model_serving_endpoints.*.endpoint_details.data_plane_info.query_info *serving.DataPlaneInfo REMOTE +resources.model_serving_endpoints.*.endpoint_details.data_plane_info.query_info.authorization_details string REMOTE +resources.model_serving_endpoints.*.endpoint_details.data_plane_info.query_info.endpoint_url string REMOTE +resources.model_serving_endpoints.*.endpoint_details.description string REMOTE +resources.model_serving_endpoints.*.endpoint_details.email_notifications *serving.EmailNotifications REMOTE +resources.model_serving_endpoints.*.endpoint_details.email_notifications.on_update_failure []string REMOTE +resources.model_serving_endpoints.*.endpoint_details.email_notifications.on_update_failure[*] string REMOTE +resources.model_serving_endpoints.*.endpoint_details.email_notifications.on_update_success []string REMOTE +resources.model_serving_endpoints.*.endpoint_details.email_notifications.on_update_success[*] string REMOTE +resources.model_serving_endpoints.*.endpoint_details.endpoint_url string REMOTE +resources.model_serving_endpoints.*.endpoint_details.id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.last_updated_timestamp int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config *serving.EndpointPendingConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config *serving.AutoCaptureConfigOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config.catalog_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config.enabled bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config.schema_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config.state *serving.AutoCaptureState REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config.state.payload_table *serving.PayloadTable REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config.state.payload_table.name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config.state.payload_table.status string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config.state.payload_table.status_message string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.auto_capture_config.table_name_prefix string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.config_version int REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities []serving.ServedEntityOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*] serving.ServedEntityOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].creation_timestamp int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].creator string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].entity_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].entity_version string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].environment_vars map[string]string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].environment_vars.* string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model *serving.ExternalModel REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.ai21labs_config *serving.Ai21LabsConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.amazon_bedrock_config *serving.AmazonBedrockConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.amazon_bedrock_config.aws_region string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.amazon_bedrock_config.bedrock_provider serving.AmazonBedrockConfigBedrockProvider REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.amazon_bedrock_config.instance_profile_arn string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.anthropic_config *serving.AnthropicConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.anthropic_config.anthropic_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.anthropic_config.anthropic_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.cohere_config *serving.CohereConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.cohere_config.cohere_api_base string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.cohere_config.cohere_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.cohere_config.cohere_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.custom_provider_config *serving.CustomProviderConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.custom_provider_config.api_key_auth *serving.ApiKeyAuth REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.custom_provider_config.api_key_auth.key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.custom_provider_config.api_key_auth.value string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.custom_provider_config.api_key_auth.value_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.custom_provider_config.bearer_token_auth *serving.BearerTokenAuth REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.custom_provider_config.custom_provider_url string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.databricks_model_serving_config *serving.DatabricksModelServingConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.databricks_model_serving_config.databricks_workspace_url string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.google_cloud_vertex_ai_config *serving.GoogleCloudVertexAiConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.google_cloud_vertex_ai_config.project_id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.google_cloud_vertex_ai_config.region string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config *serving.OpenAiConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.microsoft_entra_client_id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.microsoft_entra_tenant_id string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.openai_api_base string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.openai_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.openai_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.openai_api_type string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.openai_api_version string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.openai_deployment_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.openai_config.openai_organization string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.palm_config *serving.PaLmConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.palm_config.palm_api_key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.palm_config.palm_api_key_plaintext string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.provider serving.ExternalModelProvider REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].external_model.task string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].foundation_model *serving.FoundationModel REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].foundation_model.description string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].foundation_model.display_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].foundation_model.docs string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].foundation_model.name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].instance_profile_arn string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].max_provisioned_concurrency int REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].max_provisioned_throughput int REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].min_provisioned_concurrency int REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].min_provisioned_throughput int REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].provisioned_model_units int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].scale_to_zero_enabled bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].state *serving.ServedModelState REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].state.deployment serving.ServedModelStateDeployment REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].state.deployment_state_message string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].workload_size string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_entities[*].workload_type serving.ServingModelWorkloadType REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models []serving.ServedModelOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*] serving.ServedModelOutput REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].creation_timestamp int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].creator string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].environment_vars map[string]string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].environment_vars.* string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].instance_profile_arn string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].max_provisioned_concurrency int REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].min_provisioned_concurrency int REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].model_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].model_version string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].provisioned_model_units int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].scale_to_zero_enabled bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].state *serving.ServedModelState REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].state.deployment serving.ServedModelStateDeployment REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].state.deployment_state_message string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].workload_size string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.served_models[*].workload_type serving.ServingModelWorkloadType REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.start_time int64 REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.traffic_config *serving.TrafficConfig REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.traffic_config.routes []serving.Route REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.traffic_config.routes[*] serving.Route REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.traffic_config.routes[*].served_entity_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.traffic_config.routes[*].served_model_name string REMOTE +resources.model_serving_endpoints.*.endpoint_details.pending_config.traffic_config.routes[*].traffic_percentage int REMOTE +resources.model_serving_endpoints.*.endpoint_details.permission_level serving.ServingEndpointDetailedPermissionLevel REMOTE +resources.model_serving_endpoints.*.endpoint_details.route_optimized bool REMOTE +resources.model_serving_endpoints.*.endpoint_details.state *serving.EndpointState REMOTE +resources.model_serving_endpoints.*.endpoint_details.state.config_update serving.EndpointStateConfigUpdate REMOTE +resources.model_serving_endpoints.*.endpoint_details.state.ready serving.EndpointStateReady REMOTE +resources.model_serving_endpoints.*.endpoint_details.tags []serving.EndpointTag REMOTE +resources.model_serving_endpoints.*.endpoint_details.tags[*] serving.EndpointTag REMOTE +resources.model_serving_endpoints.*.endpoint_details.tags[*].key string REMOTE +resources.model_serving_endpoints.*.endpoint_details.tags[*].value string REMOTE +resources.model_serving_endpoints.*.endpoint_details.task string REMOTE +resources.model_serving_endpoints.*.endpoint_id string REMOTE +resources.model_serving_endpoints.*.id string INPUT +resources.model_serving_endpoints.*.lifecycle resources.Lifecycle INPUT +resources.model_serving_endpoints.*.lifecycle.prevent_destroy bool INPUT +resources.model_serving_endpoints.*.modified_status string INPUT +resources.model_serving_endpoints.*.name string INPUT STATE +resources.model_serving_endpoints.*.permissions []resources.ModelServingEndpointPermission INPUT +resources.model_serving_endpoints.*.permissions[*] resources.ModelServingEndpointPermission INPUT +resources.model_serving_endpoints.*.permissions[*].group_name string INPUT +resources.model_serving_endpoints.*.permissions[*].level resources.ModelServingEndpointPermissionLevel INPUT +resources.model_serving_endpoints.*.permissions[*].service_principal_name string INPUT +resources.model_serving_endpoints.*.permissions[*].user_name string INPUT +resources.model_serving_endpoints.*.rate_limits []serving.RateLimit INPUT STATE +resources.model_serving_endpoints.*.rate_limits[*] serving.RateLimit INPUT STATE +resources.model_serving_endpoints.*.rate_limits[*].calls int64 INPUT STATE +resources.model_serving_endpoints.*.rate_limits[*].key serving.RateLimitKey INPUT STATE +resources.model_serving_endpoints.*.rate_limits[*].renewal_period serving.RateLimitRenewalPeriod INPUT STATE +resources.model_serving_endpoints.*.route_optimized bool INPUT STATE +resources.model_serving_endpoints.*.tags []serving.EndpointTag INPUT STATE +resources.model_serving_endpoints.*.tags[*] serving.EndpointTag INPUT STATE +resources.model_serving_endpoints.*.tags[*].key string INPUT STATE +resources.model_serving_endpoints.*.tags[*].value string INPUT STATE +resources.model_serving_endpoints.*.url string INPUT +resources.model_serving_endpoints.*.permissions.object_id string ALL +resources.model_serving_endpoints.*.permissions.permissions []iam.AccessControlRequest ALL +resources.model_serving_endpoints.*.permissions.permissions[*] iam.AccessControlRequest ALL +resources.model_serving_endpoints.*.permissions.permissions[*].group_name string ALL +resources.model_serving_endpoints.*.permissions.permissions[*].permission_level iam.PermissionLevel ALL +resources.model_serving_endpoints.*.permissions.permissions[*].service_principal_name string ALL +resources.model_serving_endpoints.*.permissions.permissions[*].user_name string ALL resources.models.*.creation_timestamp int64 REMOTE resources.models.*.description string ALL resources.models.*.id string INPUT REMOTE diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/databricks.yml.tmpl b/acceptance/bundle/resources/model_serving_endpoints/basic/databricks.yml.tmpl new file mode 100644 index 0000000000..52269097f4 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/databricks.yml.tmpl @@ -0,0 +1,11 @@ +bundle: + name: acc-$UNIQUE_NAME + +resources: + model_serving_endpoints: + my_endpoint: + name: $ENDPOINT_NAME + + permissions: + - level: CAN_VIEW + user_name: deco-test-user@databricks.com diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-plan.direct.json new file mode 100644 index 0000000000..8dbb050838 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-plan.direct.json @@ -0,0 +1,39 @@ +{ + "plan": { + "resources.model_serving_endpoints.my_endpoint": { + "action": "create", + "new_state": { + "config": { + "name": "[ENDPOINT_NAME_1]" + } + } + }, + "resources.model_serving_endpoints.my_endpoint.permissions": { + "depends_on": [ + { + "node": "resources.model_serving_endpoints.my_endpoint", + "label": "${resources.model_serving_endpoints.my_endpoint.endpoint_id}" + } + ], + "action": "create", + "new_state": { + "config": { + "object_id": "", + "permissions": [ + { + "permission_level": "CAN_VIEW", + "user_name": "deco-test-user@databricks.com" + }, + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[USERNAME]" + } + ] + }, + "vars": { + "object_id": "/serving-endpoints/${resources.model_serving_endpoints.my_endpoint.endpoint_id}" + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-plan.terraform.json new file mode 100644 index 0000000000..1d92fcbea4 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.my_endpoint": { + "action": "create" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-requests.direct.json b/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-requests.direct.json new file mode 100644 index 0000000000..7231a1d7fc --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-requests.direct.json @@ -0,0 +1,23 @@ +{ + "body": { + "name": "[ENDPOINT_NAME_1]" + }, + "method": "POST", + "path": "/api/2.0/serving-endpoints" +} +{ + "body": { + "access_control_list": [ + { + "permission_level": "CAN_VIEW", + "user_name": "deco-test-user@databricks.com" + }, + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[USERNAME]" + } + ] + }, + "method": "PUT", + "path": "/api/2.0/permissions/serving-endpoints/[ENDPOINT_ID_1]" +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-requests.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-requests.terraform.json new file mode 100644 index 0000000000..70a6d2b840 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.first-requests.terraform.json @@ -0,0 +1,23 @@ +{ + "body": { + "name": "[ENDPOINT_NAME_1]" + }, + "method": "POST", + "path": "/api/2.0/serving-endpoints" +} +{ + "body": { + "access_control_list": [ + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[USERNAME]" + }, + { + "permission_level": "CAN_VIEW", + "user_name": "deco-test-user@databricks.com" + } + ] + }, + "method": "PUT", + "path": "/api/2.0/permissions/serving-endpoints/[ENDPOINT_ID_1]" +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-plan.direct.json new file mode 100644 index 0000000000..768c8eaac2 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-plan.direct.json @@ -0,0 +1,66 @@ +{ + "plan": { + "resources.model_serving_endpoints.my_endpoint": { + "action": "recreate", + "new_state": { + "config": { + "name": "[ENDPOINT_NAME_2]" + } + }, + "changes": { + "local": { + "name": { + "action": "recreate" + } + } + } + }, + "resources.model_serving_endpoints.my_endpoint.permissions": { + "depends_on": [ + { + "node": "resources.model_serving_endpoints.my_endpoint", + "label": "${resources.model_serving_endpoints.my_endpoint.endpoint_id}" + } + ], + "action": "update", + "new_state": { + "config": { + "object_id": "", + "permissions": [ + { + "permission_level": "CAN_VIEW", + "user_name": "deco-test-user@databricks.com" + }, + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[USERNAME]" + } + ] + }, + "vars": { + "object_id": "/serving-endpoints/${resources.model_serving_endpoints.my_endpoint.endpoint_id}" + } + }, + "remote_state": { + "object_id": "/serving-endpoints/[ENDPOINT_ID_1]", + "permissions": [ + { + "permission_level": "CAN_VIEW", + "user_name": "deco-test-user@databricks.com" + }, + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[USERNAME]" + } + ] + }, + "changes": { + "local": { + "object_id": { + "action": "update" + } + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-plan.terraform.json new file mode 100644 index 0000000000..0e6bc43fd9 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.my_endpoint": { + "action": "recreate" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-requests.direct.json b/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-requests.direct.json new file mode 100644 index 0000000000..6826868502 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-requests.direct.json @@ -0,0 +1,27 @@ +{ + "method": "DELETE", + "path": "/api/2.0/serving-endpoints/[ENDPOINT_NAME_1]" +} +{ + "body": { + "name": "[ENDPOINT_NAME_2]" + }, + "method": "POST", + "path": "/api/2.0/serving-endpoints" +} +{ + "body": { + "access_control_list": [ + { + "permission_level": "CAN_VIEW", + "user_name": "deco-test-user@databricks.com" + }, + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[USERNAME]" + } + ] + }, + "method": "PUT", + "path": "/api/2.0/permissions/serving-endpoints/[ENDPOINT_ID_2]" +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-requests.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-requests.terraform.json new file mode 100644 index 0000000000..11bb519e1c --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.second-requests.terraform.json @@ -0,0 +1,39 @@ +{ + "body": { + "access_control_list": [ + { + "permission_level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } + ] + }, + "method": "PUT", + "path": "/api/2.0/permissions/serving-endpoints/[ENDPOINT_ID_1]" +} +{ + "method": "DELETE", + "path": "/api/2.0/serving-endpoints/[ENDPOINT_NAME_1]" +} +{ + "body": { + "name": "[ENDPOINT_NAME_2]" + }, + "method": "POST", + "path": "/api/2.0/serving-endpoints" +} +{ + "body": { + "access_control_list": [ + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[USERNAME]" + }, + { + "permission_level": "CAN_VIEW", + "user_name": "deco-test-user@databricks.com" + } + ] + }, + "method": "PUT", + "path": "/api/2.0/permissions/serving-endpoints/[ENDPOINT_ID_2]" +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/basic/out.test.toml new file mode 100644 index 0000000000..f474b1b917 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.test.toml @@ -0,0 +1,5 @@ +Local = false +Cloud = true + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/output.txt b/acceptance/bundle/resources/model_serving_endpoints/basic/output.txt new file mode 100644 index 0000000000..b5400e5285 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/output.txt @@ -0,0 +1,41 @@ + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints + +=== Print the GET endpoint details +{ + "name": "[ENDPOINT_NAME_1]", + "creator": "[USERNAME]" +} + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints + +=== Print the GET endpoint details +{ + "name": "[ENDPOINT_NAME_2]", + "creator": "[USERNAME]" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete model_serving_endpoint my_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/script b/acceptance/bundle/resources/model_serving_endpoints/basic/script new file mode 100755 index 0000000000..5201fbeee2 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/script @@ -0,0 +1,39 @@ +export ENDPOINT_NAME="test-endpoint-$UNIQUE_NAME-1" + +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle debug plan > out.first-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy +trace print_requests.py //serving-endpoints | jq --sort-keys > out.first-requests.$DATABRICKS_BUNDLE_ENGINE.json + +echo "$ENDPOINT_NAME:ENDPOINT_NAME_1" >> ACC_REPLS + +# Record the endpoint ID for the first endpoint +get_output=$($CLI serving-endpoints get $ENDPOINT_NAME) +endpoint_id=$(echo "$get_output" | jq -r '.id') +echo "$endpoint_id:ENDPOINT_ID_1" >> ACC_REPLS + +title "Print the GET endpoint details\n" +echo "$get_output" | jq '{name, creator}' + +export ENDPOINT_NAME="test-endpoint-$UNIQUE_NAME-2" +envsubst < databricks.yml.tmpl > databricks.yml +trace $CLI bundle debug plan > out.second-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy +trace print_requests.py //serving-endpoints | jq --sort-keys > out.second-requests.$DATABRICKS_BUNDLE_ENGINE.json + +echo "$ENDPOINT_NAME:ENDPOINT_NAME_2" >> ACC_REPLS + +# Record the endpoint ID for the second endpoint +get_output=$($CLI serving-endpoints get $ENDPOINT_NAME) +endpoint_id=$(echo "$get_output" | jq -r '.id') +echo "$endpoint_id:ENDPOINT_ID_2" >> ACC_REPLS + +title "Print the GET endpoint details\n" +echo "$get_output" | jq '{name, creator}' diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/test.toml b/acceptance/bundle/resources/model_serving_endpoints/basic/test.toml new file mode 100644 index 0000000000..c64c7dfec0 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/test.toml @@ -0,0 +1,6 @@ +Local = false +Cloud = true +RecordRequests = true + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/databricks.yml.tmpl b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/databricks.yml.tmpl new file mode 100644 index 0000000000..5cb6657fd5 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/databricks.yml.tmpl @@ -0,0 +1,23 @@ +bundle: + name: test-mse-catalog-$UNIQUE_NAME + +workspace: + root_path: ~/.bundle/$UNIQUE_NAME + +resources: + model_serving_endpoints: + test_endpoint: + name: test-endpoint-$UNIQUE_NAME + config: + served_entities: + - name: prod + external_model: + name: gpt-4o-mini + provider: openai + task: llm/v1/chat + openai_config: + openai_api_key: "{{secrets/test-scope/openai-key}}" + auto_capture_config: + catalog_name: main + schema_name: default + table_name_prefix: my_table diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.first-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.first-plan.direct.json new file mode 100644 index 0000000000..11afeef35c --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.first-plan.direct.json @@ -0,0 +1,32 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create", + "new_state": { + "config": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "default", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.first-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.first-plan.terraform.json new file mode 100644 index 0000000000..82ea9497c9 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.first-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.second-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.second-plan.direct.json new file mode 100644 index 0000000000..156261de42 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.second-plan.direct.json @@ -0,0 +1,39 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate", + "new_state": { + "config": { + "config": { + "auto_capture_config": { + "catalog_name": "other_catalog", + "schema_name": "default", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } + }, + "changes": { + "local": { + "config.auto_capture_config.catalog_name": { + "action": "recreate" + } + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.second-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.second-plan.terraform.json new file mode 100644 index 0000000000..225f2eb49a --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.second-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/output.txt b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/output.txt new file mode 100644 index 0000000000..8688f1beee --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/output.txt @@ -0,0 +1,95 @@ + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "default", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } +} + +>>> [CLI] serving-endpoints get [ORIGINAL_ENDPOINT_ID] +"main" + +>>> update_file.py databricks.yml catalog_name: main catalog_name: other_catalog + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "DELETE", + "path": "/api/2.0/serving-endpoints/[ORIGINAL_ENDPOINT_ID]" +} +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "auto_capture_config": { + "catalog_name": "other_catalog", + "schema_name": "default", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } +} + +>>> [CLI] serving-endpoints get [ORIGINAL_ENDPOINT_ID] +"other_catalog" + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete model_serving_endpoint test_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME] + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/script b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/script new file mode 100755 index 0000000000..e4a7b4276a --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/script @@ -0,0 +1,26 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle debug plan > out.first-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +echo "$ENDPOINT_ID:ORIGINAL_ENDPOINT_ID" >> ACC_REPLS +trace $CLI serving-endpoints get "${ENDPOINT_ID}" | jq '.config.auto_capture_config.catalog_name' + +trace update_file.py databricks.yml "catalog_name: main" "catalog_name: other_catalog" +trace $CLI bundle debug plan > out.second-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +NEW_ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +echo "$NEW_ENDPOINT_ID:NEW_ENDPOINT_ID" >> ACC_REPLS +trace $CLI serving-endpoints get "${NEW_ENDPOINT_ID}" | jq '.config.auto_capture_config.catalog_name' diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/databricks.yml.tmpl b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/databricks.yml.tmpl new file mode 100644 index 0000000000..fe2f5f02c9 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/databricks.yml.tmpl @@ -0,0 +1,19 @@ +bundle: + name: test-mse-name-$UNIQUE_NAME + +workspace: + root_path: ~/.bundle/$UNIQUE_NAME + +resources: + model_serving_endpoints: + test_endpoint: + name: test-endpoint-$UNIQUE_NAME + config: + served_entities: + - name: prod + external_model: + name: gpt-4o-mini + provider: openai + task: llm/v1/chat + openai_config: + openai_api_key: "{{secrets/test-scope/openai-key}}" diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.first-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.first-plan.direct.json new file mode 100644 index 0000000000..ece5a9db9a --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.first-plan.direct.json @@ -0,0 +1,27 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create", + "new_state": { + "config": { + "config": { + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.first-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.first-plan.terraform.json new file mode 100644 index 0000000000..82ea9497c9 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.first-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.second-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.second-plan.direct.json new file mode 100644 index 0000000000..fa0d636f23 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.second-plan.direct.json @@ -0,0 +1,34 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate", + "new_state": { + "config": { + "config": { + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[NEW_ENDPOINT_ID]" + } + }, + "changes": { + "local": { + "name": { + "action": "recreate" + } + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.second-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.second-plan.terraform.json new file mode 100644 index 0000000000..225f2eb49a --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.second-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/output.txt b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/output.txt new file mode 100644 index 0000000000..70320f337f --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/output.txt @@ -0,0 +1,85 @@ + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } +} + +>>> [CLI] serving-endpoints get [ORIGINAL_ENDPOINT_ID] +"[ORIGINAL_ENDPOINT_ID]" + +>>> update_file.py databricks.yml name: [ORIGINAL_ENDPOINT_ID] name: [NEW_ENDPOINT_ID] + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "DELETE", + "path": "/api/2.0/serving-endpoints/[ORIGINAL_ENDPOINT_ID]" +} +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[NEW_ENDPOINT_ID]" + } +} + +>>> [CLI] serving-endpoints get [NEW_ENDPOINT_ID] +"[NEW_ENDPOINT_ID]" + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete model_serving_endpoint test_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME] + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/script b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/script new file mode 100755 index 0000000000..fdbbeaa40d --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/script @@ -0,0 +1,26 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle debug plan > out.first-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +echo "$ENDPOINT_ID:ORIGINAL_ENDPOINT_ID" >> ACC_REPLS +trace $CLI serving-endpoints get "${ENDPOINT_ID}" | jq '.name' + +trace update_file.py databricks.yml "name: test-endpoint-$UNIQUE_NAME" "name: test-endpoint-2-$UNIQUE_NAME" +trace $CLI bundle debug plan > out.second-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +NEW_ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +echo "$NEW_ENDPOINT_ID:NEW_ENDPOINT_ID" >> ACC_REPLS +trace $CLI serving-endpoints get "${NEW_ENDPOINT_ID}" | jq '.name' diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/databricks.yml.tmpl b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/databricks.yml.tmpl new file mode 100644 index 0000000000..d20ffc62da --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/databricks.yml.tmpl @@ -0,0 +1,18 @@ +bundle: + name: test-mse-route-$UNIQUE_NAME + +workspace: + root_path: ~/.bundle/$UNIQUE_NAME + +resources: + model_serving_endpoints: + test_endpoint: + name: test-endpoint-$UNIQUE_NAME + config: + served_entities: + - name: "llama" + entity_name: system.ai.llama_v3_2_1b_instruct + entity_version: 1 + workload_size: Small + scale_to_zero_enabled: true + route_optimized: false diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-get.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-get.direct.json new file mode 100644 index 0000000000..f74ee87921 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-get.direct.json @@ -0,0 +1,4 @@ +{ + "route_optimized": false, + "id": "[UUID]" +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-get.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-get.terraform.json new file mode 100644 index 0000000000..f4d7f8991f --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-get.terraform.json @@ -0,0 +1,4 @@ +{ + "route_optimized": null, + "id": "[UUID]" +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-plan.direct.json new file mode 100644 index 0000000000..c11b2e1980 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-plan.direct.json @@ -0,0 +1,24 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create", + "new_state": { + "config": { + "config": { + "served_entities": [ + { + "entity_name": "system.ai.llama_v3_2_1b_instruct", + "entity_version": "1", + "name": "llama", + "scale_to_zero_enabled": true, + "workload_size": "Small" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]", + "route_optimized": false + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-plan.terraform.json new file mode 100644 index 0000000000..82ea9497c9 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-requests.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-requests.direct.json new file mode 100644 index 0000000000..a287e1d81e --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-requests.direct.json @@ -0,0 +1,19 @@ +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "served_entities": [ + { + "entity_name": "system.ai.llama_v3_2_1b_instruct", + "entity_version": "1", + "name": "llama", + "scale_to_zero_enabled": true, + "workload_size": "Small" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]", + "route_optimized": false + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-requests.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-requests.terraform.json new file mode 100644 index 0000000000..95e359ba4e --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.first-requests.terraform.json @@ -0,0 +1,18 @@ +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "served_entities": [ + { + "entity_name": "system.ai.llama_v3_2_1b_instruct", + "entity_version": "1", + "name": "llama", + "scale_to_zero_enabled": true, + "workload_size": "Small" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.second-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.second-plan.direct.json new file mode 100644 index 0000000000..145e2879d7 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.second-plan.direct.json @@ -0,0 +1,31 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate", + "new_state": { + "config": { + "config": { + "served_entities": [ + { + "entity_name": "system.ai.llama_v3_2_1b_instruct", + "entity_version": "1", + "name": "llama", + "scale_to_zero_enabled": true, + "workload_size": "Small" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]", + "route_optimized": true + } + }, + "changes": { + "local": { + "route_optimized": { + "action": "recreate" + } + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.second-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.second-plan.terraform.json new file mode 100644 index 0000000000..225f2eb49a --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.second-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/output.txt b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/output.txt new file mode 100644 index 0000000000..8c29071412 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/output.txt @@ -0,0 +1,76 @@ + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] serving-endpoints get [ORIGINAL_ENDPOINT_ID] + +>>> update_file.py databricks.yml route_optimized: false route_optimized: true + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "DELETE", + "path": "/api/2.0/serving-endpoints/[ORIGINAL_ENDPOINT_ID]" +} +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "served_entities": [ + { + "entity_name": "system.ai.llama_v3_2_1b_instruct", + "entity_version": "1", + "name": "llama", + "scale_to_zero_enabled": true, + "workload_size": "Small" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]", + "route_optimized": true + } +} + +>>> [CLI] serving-endpoints get [ORIGINAL_ENDPOINT_ID] +{ + "config": { + "served_entities": [ + { + "entity_name":"system.ai.llama_v3_2_1b_instruct", + "entity_version":"1", + "name":"llama", + "scale_to_zero_enabled":true, + "workload_size":"Small" + } + ] + }, + "creator":"[USERNAME]", + "id":"[UUID]", + "name":"[ORIGINAL_ENDPOINT_ID]", + "route_optimized":true, + "state": { + "config_update":"NOT_UPDATING" + } +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete model_serving_endpoint test_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME] + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/script b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/script new file mode 100755 index 0000000000..fb93230a9e --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/script @@ -0,0 +1,26 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle debug plan > out.first-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +echo "$ENDPOINT_ID:ORIGINAL_ENDPOINT_ID" >> ACC_REPLS +trace $CLI serving-endpoints get "${ENDPOINT_ID}" | jq '{route_optimized, id}' > out.first-get.$DATABRICKS_BUNDLE_ENGINE.json + +print_requests.py //serving-endpoints > out.first-requests.$DATABRICKS_BUNDLE_ENGINE.json + +trace update_file.py databricks.yml "route_optimized: false" "route_optimized: true" +trace $CLI bundle debug plan > out.second-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +# New and original endpoint ID should remain the same since they are both the name of the endpoint. +NEW_ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +trace $CLI serving-endpoints get "${NEW_ENDPOINT_ID}" diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/databricks.yml.tmpl b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/databricks.yml.tmpl new file mode 100644 index 0000000000..f57fcc4f03 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/databricks.yml.tmpl @@ -0,0 +1,23 @@ +bundle: + name: test-mse-schema-$UNIQUE_NAME + +workspace: + root_path: ~/.bundle/$UNIQUE_NAME + +resources: + model_serving_endpoints: + test_endpoint: + name: test-endpoint-$UNIQUE_NAME + config: + served_entities: + - name: prod + external_model: + name: gpt-4o-mini + provider: openai + task: llm/v1/chat + openai_config: + openai_api_key: "{{secrets/test-scope/openai-key}}" + auto_capture_config: + catalog_name: main + schema_name: default + table_name_prefix: my_table diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.first-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.first-plan.direct.json new file mode 100644 index 0000000000..11afeef35c --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.first-plan.direct.json @@ -0,0 +1,32 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create", + "new_state": { + "config": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "default", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.first-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.first-plan.terraform.json new file mode 100644 index 0000000000..82ea9497c9 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.first-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.second-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.second-plan.direct.json new file mode 100644 index 0000000000..c1f8fa4b13 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.second-plan.direct.json @@ -0,0 +1,39 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate", + "new_state": { + "config": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "other_schema", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } + }, + "changes": { + "local": { + "config.auto_capture_config.schema_name": { + "action": "recreate" + } + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.second-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.second-plan.terraform.json new file mode 100644 index 0000000000..225f2eb49a --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.second-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/output.txt b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/output.txt new file mode 100644 index 0000000000..edf577a5c4 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/output.txt @@ -0,0 +1,95 @@ + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "default", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } +} + +>>> [CLI] serving-endpoints get [ORIGINAL_ENDPOINT_ID] +"default" + +>>> update_file.py databricks.yml schema_name: default schema_name: other_schema + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "DELETE", + "path": "/api/2.0/serving-endpoints/[ORIGINAL_ENDPOINT_ID]" +} +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "other_schema", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } +} + +>>> [CLI] serving-endpoints get [ORIGINAL_ENDPOINT_ID] +"other_schema" + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete model_serving_endpoint test_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME] + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/script b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/script new file mode 100755 index 0000000000..59c22a077a --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/script @@ -0,0 +1,26 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle debug plan > out.first-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +echo "$ENDPOINT_ID:ORIGINAL_ENDPOINT_ID" >> ACC_REPLS +trace $CLI serving-endpoints get "${ENDPOINT_ID}" | jq '.config.auto_capture_config.schema_name' + +trace update_file.py databricks.yml "schema_name: default" "schema_name: other_schema" +trace $CLI bundle debug plan > out.second-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +NEW_ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +echo "$NEW_ENDPOINT_ID:NEW_ENDPOINT_ID" >> ACC_REPLS +trace $CLI serving-endpoints get "${NEW_ENDPOINT_ID}" | jq '.config.auto_capture_config.schema_name' diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/databricks.yml.tmpl b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/databricks.yml.tmpl new file mode 100644 index 0000000000..8b22b900f6 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/databricks.yml.tmpl @@ -0,0 +1,23 @@ +bundle: + name: test-mse-table-$UNIQUE_NAME + +workspace: + root_path: ~/.bundle/$UNIQUE_NAME + +resources: + model_serving_endpoints: + test_endpoint: + name: test-endpoint-$UNIQUE_NAME + config: + served_entities: + - name: prod + external_model: + name: gpt-4o-mini + provider: openai + task: llm/v1/chat + openai_config: + openai_api_key: "{{secrets/test-scope/openai-key}}" + auto_capture_config: + catalog_name: main + schema_name: default + table_name_prefix: my_table diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.first-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.first-plan.direct.json new file mode 100644 index 0000000000..11afeef35c --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.first-plan.direct.json @@ -0,0 +1,32 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create", + "new_state": { + "config": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "default", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.first-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.first-plan.terraform.json new file mode 100644 index 0000000000..82ea9497c9 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.first-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "create" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.second-plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.second-plan.direct.json new file mode 100644 index 0000000000..c546a1392b --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.second-plan.direct.json @@ -0,0 +1,39 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate", + "new_state": { + "config": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "default", + "table_name_prefix": "other_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } + }, + "changes": { + "local": { + "config.auto_capture_config.table_name_prefix": { + "action": "recreate" + } + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.second-plan.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.second-plan.terraform.json new file mode 100644 index 0000000000..225f2eb49a --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.second-plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.test_endpoint": { + "action": "recreate" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/output.txt b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/output.txt new file mode 100644 index 0000000000..b178c70fb3 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/output.txt @@ -0,0 +1,95 @@ + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "default", + "table_name_prefix": "my_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } +} + +>>> [CLI] serving-endpoints get [ORIGINAL_ENDPOINT_ID] +"my_table" + +>>> update_file.py databricks.yml table_name_prefix: my_table table_name_prefix: other_table + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "DELETE", + "path": "/api/2.0/serving-endpoints/[ORIGINAL_ENDPOINT_ID]" +} +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "auto_capture_config": { + "catalog_name": "main", + "schema_name": "default", + "table_name_prefix": "other_table" + }, + "served_entities": [ + { + "external_model": { + "name": "gpt-4o-mini", + "openai_config": { + "openai_api_key": "{{secrets/test-scope/openai-key}}" + }, + "provider": "openai", + "task": "llm/v1/chat" + }, + "name": "prod" + } + ] + }, + "name": "[ORIGINAL_ENDPOINT_ID]" + } +} + +>>> [CLI] serving-endpoints get [ORIGINAL_ENDPOINT_ID] +"other_table" + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete model_serving_endpoint test_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME] + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/script b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/script new file mode 100755 index 0000000000..1d3f7b35a0 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/script @@ -0,0 +1,26 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle debug plan > out.first-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +echo "$ENDPOINT_ID:ORIGINAL_ENDPOINT_ID" >> ACC_REPLS +trace $CLI serving-endpoints get "${ENDPOINT_ID}" | jq '.config.auto_capture_config.table_name_prefix' + +trace update_file.py databricks.yml "table_name_prefix: my_table" "table_name_prefix: other_table" +trace $CLI bundle debug plan > out.second-plan.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +NEW_ENDPOINT_ID=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.test_endpoint.id') +echo "$NEW_ENDPOINT_ID:NEW_ENDPOINT_ID" >> ACC_REPLS +trace $CLI serving-endpoints get "${NEW_ENDPOINT_ID}" | jq '.config.auto_capture_config.table_name_prefix' diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/test.toml new file mode 100644 index 0000000000..3aa4a19050 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/test.toml @@ -0,0 +1,7 @@ +Local = true +Cloud = false +RecordRequests = true + +Ignore = [ + "databricks.yml", +] diff --git a/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/databricks.yml.tmpl b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/databricks.yml.tmpl new file mode 100644 index 0000000000..73e0943152 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/databricks.yml.tmpl @@ -0,0 +1,14 @@ +bundle: + name: acc-$UNIQUE_NAME + +resources: + model_serving_endpoints: + my_endpoint: + name: test-endpoint-$UNIQUE_NAME + config: + served_entities: + - name: "llama" + entity_name: system.ai.llama_v3_2_1b_instruct + entity_version: $VERSION + workload_size: Small + scale_to_zero_enabled: true diff --git a/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.plan.direct.txt b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.plan.direct.txt new file mode 100644 index 0000000000..3539d2da56 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.plan.direct.txt @@ -0,0 +1,23 @@ +{ + "plan": { + "resources.model_serving_endpoints.my_endpoint": { + "action": "create", + "new_state": { + "config": { + "config": { + "served_entities": [ + { + "entity_name": "system.ai.llama_v3_2_1b_instruct", + "entity_version": "[VERSION]", + "name": "llama", + "scale_to_zero_enabled": true, + "workload_size": "Small" + } + ] + }, + "name": "test-endpoint-[UNIQUE_NAME]" + } + } + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.plan.terraform.txt b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.plan.terraform.txt new file mode 100644 index 0000000000..1d92fcbea4 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.plan.terraform.txt @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.model_serving_endpoints.my_endpoint": { + "action": "create" + } + } +} diff --git a/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.test.toml new file mode 100644 index 0000000000..5366fbb1a4 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = true +CloudSlow = true + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/output.txt b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/output.txt new file mode 100644 index 0000000000..1fd0ab1517 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/output.txt @@ -0,0 +1,43 @@ + +>>> [CLI] bundle debug plan + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //serving-endpoints +{ + "method": "POST", + "path": "/api/2.0/serving-endpoints", + "body": { + "config": { + "served_entities": [ + { + "entity_name": "system.ai.llama_v3_2_1b_instruct", + "entity_version": "[VERSION]", + "name": "llama", + "scale_to_zero_enabled": true, + "workload_size": "Small" + } + ] + }, + "name": "test-endpoint-[UNIQUE_NAME]" + } +} + +>>> [CLI] serving-endpoints get test-endpoint-[UNIQUE_NAME] +{ + "name": "test-endpoint-[UNIQUE_NAME]", + "creator": "[USERNAME]" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete model_serving_endpoint my_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/script b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/script new file mode 100644 index 0000000000..fab464d557 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/script @@ -0,0 +1,17 @@ +export VERSION=$($CLI model-versions list system.ai.llama_v3_2_1b_instruct | jq '.[0].version') + +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle debug plan > out.plan.$DATABRICKS_BUNDLE_ENGINE.txt +trace $CLI bundle deploy + +trace print_requests.py //serving-endpoints + +endpoint_name=$($CLI bundle summary -o json | jq -r '.resources.model_serving_endpoints.my_endpoint.name') +trace $CLI serving-endpoints get $endpoint_name | jq '{name, creator}' diff --git a/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/test.toml b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/test.toml new file mode 100644 index 0000000000..b3c25f76e7 --- /dev/null +++ b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/test.toml @@ -0,0 +1,20 @@ +Timeout = '30m' +Cloud = true +Local = true +CloudSlow = true + +[[Server]] +Pattern = "GET /api/2.1/unity-catalog/models/system.ai.llama_v3_2_1b_instruct/versions" +Response.Body = ''' +{ + "model_versions": [ + { + "version": 1 + } + ] +} +''' + +[[Repls]] +Old = '"entity_version": "1",' +New = '"entity_version": "[VERSION]",' diff --git a/bundle/direct/dresources/all.go b/bundle/direct/dresources/all.go index 5a66fe9132..5fb8e95a58 100644 --- a/bundle/direct/dresources/all.go +++ b/bundle/direct/dresources/all.go @@ -7,31 +7,33 @@ import ( ) var SupportedResources = map[string]any{ - "jobs": (*ResourceJob)(nil), - "pipelines": (*ResourcePipeline)(nil), - "experiments": (*ResourceExperiment)(nil), - "schemas": (*ResourceSchema)(nil), - "volumes": (*ResourceVolume)(nil), - "models": (*ResourceMlflowModel)(nil), - "apps": (*ResourceApp)(nil), - "sql_warehouses": (*ResourceSqlWarehouse)(nil), - "database_instances": (*ResourceDatabaseInstance)(nil), - "database_catalogs": (*ResourceDatabaseCatalog)(nil), - "synced_database_tables": (*ResourceSyncedDatabaseTable)(nil), - "alerts": (*ResourceAlert)(nil), - "clusters": (*ResourceCluster)(nil), - "registered_models": (*ResourceRegisteredModel)(nil), - "dashboards": (*ResourceDashboard)(nil), + "jobs": (*ResourceJob)(nil), + "pipelines": (*ResourcePipeline)(nil), + "experiments": (*ResourceExperiment)(nil), + "schemas": (*ResourceSchema)(nil), + "volumes": (*ResourceVolume)(nil), + "models": (*ResourceMlflowModel)(nil), + "apps": (*ResourceApp)(nil), + "sql_warehouses": (*ResourceSqlWarehouse)(nil), + "database_instances": (*ResourceDatabaseInstance)(nil), + "database_catalogs": (*ResourceDatabaseCatalog)(nil), + "synced_database_tables": (*ResourceSyncedDatabaseTable)(nil), + "alerts": (*ResourceAlert)(nil), + "clusters": (*ResourceCluster)(nil), + "registered_models": (*ResourceRegisteredModel)(nil), + "dashboards": (*ResourceDashboard)(nil), + "model_serving_endpoints": (*ResourceModelServingEndpoint)(nil), // Permissions - "jobs.permissions": (*ResourcePermissions)(nil), - "pipelines.permissions": (*ResourcePermissions)(nil), - "apps.permissions": (*ResourcePermissions)(nil), - "clusters.permissions": (*ResourcePermissions)(nil), - "database_instances.permissions": (*ResourcePermissions)(nil), - "experiments.permissions": (*ResourcePermissions)(nil), - "models.permissions": (*ResourcePermissions)(nil), - "sql_warehouses.permissions": (*ResourcePermissions)(nil), + "jobs.permissions": (*ResourcePermissions)(nil), + "pipelines.permissions": (*ResourcePermissions)(nil), + "apps.permissions": (*ResourcePermissions)(nil), + "clusters.permissions": (*ResourcePermissions)(nil), + "database_instances.permissions": (*ResourcePermissions)(nil), + "experiments.permissions": (*ResourcePermissions)(nil), + "models.permissions": (*ResourcePermissions)(nil), + "sql_warehouses.permissions": (*ResourcePermissions)(nil), + "model_serving_endpoints.permissions": (*ResourcePermissions)(nil), // Grants "schemas.grants": (*ResourceGrants)(nil), diff --git a/bundle/direct/dresources/all_test.go b/bundle/direct/dresources/all_test.go index 08ac7b7e51..506be36c47 100644 --- a/bundle/direct/dresources/all_test.go +++ b/bundle/direct/dresources/all_test.go @@ -24,6 +24,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/ml" "github.com/databricks/databricks-sdk-go/service/pipelines" + "github.com/databricks/databricks-sdk-go/service/serving" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -97,6 +98,50 @@ var testConfig map[string]any = map[string]any{ }, }, }, + + "model_serving_endpoints": &resources.ModelServingEndpoint{ + CreateServingEndpoint: serving.CreateServingEndpoint{ + Name: "my-endpoint", + Config: &serving.EndpointCoreConfigInput{ + Name: "my-endpoint", + AutoCaptureConfig: &serving.AutoCaptureConfigInput{ + CatalogName: "main", + SchemaName: "myschema", + TableNamePrefix: "my_table", + Enabled: true, + ForceSendFields: nil, + }, + ServedModels: nil, + ServedEntities: []serving.ServedEntityInput{ + { + EntityName: "entity-name", + EntityVersion: "1", + WorkloadSize: "Small", + ScaleToZeroEnabled: true, + WorkloadType: serving.ServingModelWorkloadTypeCpu, + EnvironmentVars: map[string]string{"key": "value"}, + InstanceProfileArn: "arn:aws:iam::123456789012:instance-profile/my-instance-profile", + MaxProvisionedConcurrency: 10, + MaxProvisionedThroughput: 100, + MinProvisionedConcurrency: 1, + MinProvisionedThroughput: 10, + Name: "entity-name", + ProvisionedModelUnits: 100, + ExternalModel: nil, + ForceSendFields: nil, + }, + }, + TrafficConfig: &serving.TrafficConfig{ + Routes: []serving.Route{ + { + ServedModelName: "model-name-1", + TrafficPercentage: 100, + }, + }, + }, + }, + }, + }, } type prepareWorkspace func(client *databricks.WorkspaceClient) (any, error) @@ -253,6 +298,33 @@ var testDeps = map[string]prepareWorkspace{ }, nil }, + "model_serving_endpoints.permissions": func(client *databricks.WorkspaceClient) (any, error) { + waiter, err := client.ServingEndpoints.Create(context.Background(), serving.CreateServingEndpoint{ + Name: "endpoint-permissions", + Config: &serving.EndpointCoreConfigInput{ + ServedModels: []serving.ServedModelInput{ + { + ModelName: "model-name", + ModelVersion: "1", + WorkloadSize: "Small", + ScaleToZeroEnabled: true, + }, + }, + }, + }) + if err != nil { + return nil, err + } + + return &PermissionsState{ + ObjectID: "/serving-endpoints/" + waiter.Response.Name, + Permissions: []iam.AccessControlRequest{{ + PermissionLevel: "CAN_MANAGE", + UserName: "user@example.com", + }}, + }, nil + }, + "schemas.grants": func(client *databricks.WorkspaceClient) (any, error) { return &GrantsState{ SecurableType: "schema", diff --git a/bundle/direct/dresources/model_serving_endpoint.go b/bundle/direct/dresources/model_serving_endpoint.go new file mode 100644 index 0000000000..a50c635dd2 --- /dev/null +++ b/bundle/direct/dresources/model_serving_endpoint.go @@ -0,0 +1,311 @@ +package dresources + +import ( + "context" + "fmt" + "time" + + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/deployplan" + "github.com/databricks/cli/libs/utils" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/serving" + "golang.org/x/sync/errgroup" +) + +type ResourceModelServingEndpoint struct { + client *databricks.WorkspaceClient +} + +func (*ResourceModelServingEndpoint) New(client *databricks.WorkspaceClient) *ResourceModelServingEndpoint { + return &ResourceModelServingEndpoint{ + client: client, + } +} + +func (*ResourceModelServingEndpoint) PrepareState(input *resources.ModelServingEndpoint) *serving.CreateServingEndpoint { + return &input.CreateServingEndpoint +} + +func autoCaptureConfigOutputToInput(output *serving.AutoCaptureConfigOutput) *serving.AutoCaptureConfigInput { + if output == nil { + return nil + } + return &serving.AutoCaptureConfigInput{ + CatalogName: output.CatalogName, + SchemaName: output.SchemaName, + TableNamePrefix: output.TableNamePrefix, + Enabled: output.Enabled, + ForceSendFields: utils.FilterFields[serving.AutoCaptureConfigInput](output.ForceSendFields), + } +} + +func servedEntitiesOutputToInput(output []serving.ServedEntityOutput) []serving.ServedEntityInput { + entities := make([]serving.ServedEntityInput, len(output)) + for i, entity := range output { + entities[i] = serving.ServedEntityInput{ + EntityName: entity.EntityName, + EntityVersion: entity.EntityVersion, + EnvironmentVars: entity.EnvironmentVars, + ExternalModel: entity.ExternalModel, + InstanceProfileArn: entity.InstanceProfileArn, + MaxProvisionedConcurrency: entity.MaxProvisionedConcurrency, + MaxProvisionedThroughput: entity.MaxProvisionedThroughput, + MinProvisionedConcurrency: entity.MinProvisionedConcurrency, + MinProvisionedThroughput: entity.MinProvisionedThroughput, + Name: entity.Name, + ProvisionedModelUnits: entity.ProvisionedModelUnits, + ScaleToZeroEnabled: entity.ScaleToZeroEnabled, + WorkloadSize: entity.WorkloadSize, + WorkloadType: entity.WorkloadType, + ForceSendFields: utils.FilterFields[serving.ServedEntityInput](entity.ForceSendFields), + } + } + + return entities +} + +func configOutputToInput(output *serving.EndpointCoreConfigOutput) *serving.EndpointCoreConfigInput { + if output == nil { + return nil + } + return &serving.EndpointCoreConfigInput{ + AutoCaptureConfig: autoCaptureConfigOutputToInput(output.AutoCaptureConfig), + ServedEntities: servedEntitiesOutputToInput(output.ServedEntities), + ServedModels: nil, + TrafficConfig: output.TrafficConfig, + Name: "", + } +} + +func (*ResourceModelServingEndpoint) RemapState(state *RefreshOutput) *serving.CreateServingEndpoint { + details := state.EndpointDetails + // Map the remote state (ServingEndpointDetailed) to the local state (CreateServingEndpoint) + // for proper comparison during diff calculation + return &serving.CreateServingEndpoint{ + AiGateway: details.AiGateway, + BudgetPolicyId: details.BudgetPolicyId, + Config: configOutputToInput(details.Config), + Description: details.Description, + EmailNotifications: details.EmailNotifications, + Name: details.Name, + RouteOptimized: details.RouteOptimized, + Tags: details.Tags, + ForceSendFields: utils.FilterFields[serving.CreateServingEndpoint](details.ForceSendFields), + + // Rate limits are a deprecated field that are not returned by the API on GET calls. Thus we map them to nil. + // TODO(shreyas): Add a warning when users try setting top level rate limits. + RateLimits: nil, + } +} + +type RefreshOutput struct { + EndpointDetails *serving.ServingEndpointDetailed `json:"endpoint_details"` + EndpointId string `json:"endpoint_id"` +} + +func (r *ResourceModelServingEndpoint) DoRefresh(ctx context.Context, id string) (*RefreshOutput, error) { + endpoint, err := r.client.ServingEndpoints.GetByName(ctx, id) + if err != nil { + return nil, err + } + return &RefreshOutput{ + EndpointDetails: endpoint, + EndpointId: endpoint.Id, + }, nil +} + +func (r *ResourceModelServingEndpoint) DoCreate(ctx context.Context, config *serving.CreateServingEndpoint) (string, error) { + waiter, err := r.client.ServingEndpoints.Create(ctx, *config) + if err != nil { + return "", err + } + + return waiter.Response.Name, nil +} + +// waitForEndpointReady waits for the serving endpoint to be ready (not updating) +func (r *ResourceModelServingEndpoint) waitForEndpointReady(ctx context.Context, name string) (*RefreshOutput, error) { + details, err := r.client.ServingEndpoints.WaitGetServingEndpointNotUpdating(ctx, name, 35*time.Minute, nil) + if err != nil { + return nil, err + } + + return &RefreshOutput{ + EndpointDetails: details, + EndpointId: details.Id, + }, nil +} + +func (r *ResourceModelServingEndpoint) WaitAfterCreate(ctx context.Context, config *serving.CreateServingEndpoint) (*RefreshOutput, error) { + return r.waitForEndpointReady(ctx, config.Name) +} + +func (r *ResourceModelServingEndpoint) updateAiGateway(ctx context.Context, id string, aiGateway *serving.AiGatewayConfig) error { + if aiGateway == nil { + req := serving.PutAiGatewayRequest{ + Name: id, + FallbackConfig: nil, + Guardrails: nil, + InferenceTableConfig: nil, + RateLimits: nil, + UsageTrackingConfig: nil, + } + _, err := r.client.ServingEndpoints.PutAiGateway(ctx, req) + return err + } + + req := serving.PutAiGatewayRequest{ + Name: id, + FallbackConfig: aiGateway.FallbackConfig, + Guardrails: aiGateway.Guardrails, + InferenceTableConfig: aiGateway.InferenceTableConfig, + RateLimits: aiGateway.RateLimits, + UsageTrackingConfig: aiGateway.UsageTrackingConfig, + } + _, err := r.client.ServingEndpoints.PutAiGateway(ctx, req) + if err != nil { + return fmt.Errorf("failed to update AI gateway: %w", err) + } + return nil +} + +// TODO(shreyas): Add acceptance tests for update and for unsetting. Will be done once we add selective updates for these fields. +func (r *ResourceModelServingEndpoint) updateConfig(ctx context.Context, id string, config *serving.EndpointCoreConfigInput) error { + if config == nil { + // Unset config in resource. + req := serving.EndpointCoreConfigInput{ + Name: id, + AutoCaptureConfig: nil, + ServedEntities: nil, + TrafficConfig: nil, + ServedModels: nil, + } + _, err := r.client.ServingEndpoints.UpdateConfig(ctx, req) + return err + } + req := serving.EndpointCoreConfigInput{ + Name: id, + AutoCaptureConfig: config.AutoCaptureConfig, + ServedEntities: config.ServedEntities, + TrafficConfig: config.TrafficConfig, + ServedModels: config.ServedModels, + } + _, err := r.client.ServingEndpoints.UpdateConfig(ctx, req) + if err != nil { + return fmt.Errorf("failed to update config: %w", err) + } + return nil +} + +func (r *ResourceModelServingEndpoint) updateNotifications(ctx context.Context, id string, notifications *serving.EmailNotifications) error { + req := serving.UpdateInferenceEndpointNotifications{ + Name: id, + EmailNotifications: notifications, + } + _, err := r.client.ServingEndpoints.UpdateNotifications(ctx, req) + if err != nil { + return fmt.Errorf("failed to update notifications: %w", err) + } + return nil +} + +func diffTags(currentTags, desiredTags []serving.EndpointTag) (addTags []serving.EndpointTag, deleteTags []string) { + addTags = make([]serving.EndpointTag, 0) + + // build maps for easy lookup. + currentTagsMap := make(map[string]string) + desiredTagsMap := make(map[string]string) + for _, tag := range currentTags { + currentTagsMap[tag.Key] = tag.Value + } + for _, tag := range desiredTags { + desiredTagsMap[tag.Key] = tag.Value + } + + // Compute keys to be added. + for key, desiredValue := range desiredTagsMap { + v, ok := currentTagsMap[key] + if !ok { + addTags = append(addTags, serving.EndpointTag{ + Key: key, + Value: desiredValue, + ForceSendFields: nil, + }) + continue + } + if v != desiredValue { + addTags = append(addTags, serving.EndpointTag{ + Key: key, + Value: desiredValue, + ForceSendFields: nil, + }) + } + } + + // Compute keys to be deleted. + for key := range currentTagsMap { + if _, ok := desiredTagsMap[key]; !ok { + deleteTags = append(deleteTags, key) + } + } + + return addTags, deleteTags +} + +func (r *ResourceModelServingEndpoint) updateTags(ctx context.Context, id string, tags []serving.EndpointTag) error { + endpoint, err := r.client.ServingEndpoints.GetByName(ctx, id) + if err != nil { + return err + } + + addTags, deleteTags := diffTags(endpoint.Tags, tags) + + req := serving.PatchServingEndpointTags{ + Name: id, + AddTags: addTags, + DeleteTags: deleteTags, + } + _, err = r.client.ServingEndpoints.Patch(ctx, req) + if err != nil { + return fmt.Errorf("failed to update tags: %w", err) + } + return nil +} + +func (r *ResourceModelServingEndpoint) DoUpdate(ctx context.Context, id string, config *serving.CreateServingEndpoint) error { + errGroup := errgroup.Group{} + errGroup.Go(func() error { + return r.updateAiGateway(ctx, id, config.AiGateway) + }) + errGroup.Go(func() error { + return r.updateConfig(ctx, id, config.Config) + }) + errGroup.Go(func() error { + return r.updateNotifications(ctx, id, config.EmailNotifications) + }) + errGroup.Go(func() error { + return r.updateTags(ctx, id, config.Tags) + }) + return errGroup.Wait() +} + +func (r *ResourceModelServingEndpoint) WaitAfterUpdate(ctx context.Context, config *serving.CreateServingEndpoint) (*RefreshOutput, error) { + return r.waitForEndpointReady(ctx, config.Name) +} + +func (r *ResourceModelServingEndpoint) DoDelete(ctx context.Context, id string) error { + return r.client.ServingEndpoints.DeleteByName(ctx, id) +} + +func (*ResourceModelServingEndpoint) FieldTriggers(_ bool) map[string]deployplan.ActionType { + // TF implementation: https://github.com/databricks/terraform-provider-databricks/blob/6c106e8e7052bb2726148d66309fd460ed444236/mlflow/resource_mlflow_experiment.go#L22 + return map[string]deployplan.ActionType{ + "name": deployplan.ActionTypeRecreate, + "description": deployplan.ActionTypeRecreate, // description is immutable, can't be updated via API + "config.auto_capture_config.catalog_name": deployplan.ActionTypeRecreate, + "config.auto_capture_config.schema_name": deployplan.ActionTypeRecreate, + "config.auto_capture_config.table_name_prefix": deployplan.ActionTypeRecreate, + "route_optimized": deployplan.ActionTypeRecreate, + } +} diff --git a/bundle/direct/dresources/permissions.go b/bundle/direct/dresources/permissions.go index ce06a853a2..b6dbf4c6ad 100644 --- a/bundle/direct/dresources/permissions.go +++ b/bundle/direct/dresources/permissions.go @@ -57,13 +57,22 @@ func PreparePermissionsInputConfig(inputConfig any, node string) (*structvar.Str }) } + objectIdRef := prefix + "${" + baseNode + ".id}" + // For permissions, model serving endpoint uses it's internal ID, which is different + // from its CRUD APIs which use the name. + // We have a wrapper struct [RefreshOutput] from which we read the internal ID + // in order to set the appropriate permissions. + if strings.HasPrefix(baseNode, "resources.model_serving_endpoints.") { + objectIdRef = prefix + "${" + baseNode + ".endpoint_id}" + } + return &structvar.StructVar{ Config: &PermissionsState{ ObjectID: "", // Always a reference, defined in Refs below Permissions: permissions, }, Refs: map[string]string{ - "object_id": prefix + "${" + baseNode + ".id}", + "object_id": objectIdRef, }, }, nil } diff --git a/libs/testserver/fake_workspace.go b/libs/testserver/fake_workspace.go index b1fbf90fd1..89af48778b 100644 --- a/libs/testserver/fake_workspace.go +++ b/libs/testserver/fake_workspace.go @@ -22,6 +22,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/ml" "github.com/databricks/databricks-sdk-go/service/pipelines" + "github.com/databricks/databricks-sdk-go/service/serving" "github.com/databricks/databricks-sdk-go/service/sql" "github.com/databricks/databricks-sdk-go/service/workspace" ) @@ -134,6 +135,7 @@ type FakeWorkspace struct { Clusters map[string]compute.ClusterDetails Catalogs map[string]catalog.CatalogInfo RegisteredModels map[string]catalog.RegisteredModelInfo + ServingEndpoints map[string]serving.ServingEndpointDetailed Acls map[string][]workspace.AclItem @@ -232,6 +234,7 @@ func NewFakeWorkspace(url, token string) *FakeWorkspace { Dashboards: map[string]fakeDashboard{}, PublishedDashboards: map[string]dashboards.PublishedDashboard{}, SqlWarehouses: map[string]sql.GetWarehouseResponse{}, + ServingEndpoints: map[string]serving.ServingEndpointDetailed{}, Repos: map[string]workspace.RepoInfo{}, Acls: map[string][]workspace.AclItem{}, Permissions: map[string]iam.ObjectPermissions{}, diff --git a/libs/testserver/handlers.go b/libs/testserver/handlers.go index 5c1b6c784a..56ff7616d4 100644 --- a/libs/testserver/handlers.go +++ b/libs/testserver/handlers.go @@ -603,6 +603,35 @@ func AddDefaultHandlers(server *Server) { return MapDelete(req.Workspace, req.Workspace.ModelRegistryModels, req.URL.Query().Get("name")) }) + // Serving Endpoints: + server.Handle("GET", "/api/2.0/serving-endpoints/{name}", func(req Request) any { + return MapGet(req.Workspace, req.Workspace.ServingEndpoints, req.Vars["name"]) + }) + + server.Handle("POST", "/api/2.0/serving-endpoints", func(req Request) any { + return req.Workspace.ServingEndpointCreate(req) + }) + + server.Handle("PUT", "/api/2.0/serving-endpoints/{name}/config", func(req Request) any { + return req.Workspace.ServingEndpointUpdate(req, req.Vars["name"]) + }) + + server.Handle("DELETE", "/api/2.0/serving-endpoints/{name}", func(req Request) any { + return MapDelete(req.Workspace, req.Workspace.ServingEndpoints, req.Vars["name"]) + }) + + server.Handle("PUT", "/api/2.0/serving-endpoints/{name}/ai-gateway", func(req Request) any { + return req.Workspace.ServingEndpointPutAiGateway(req, req.Vars["name"]) + }) + + server.Handle("PATCH", "/api/2.0/serving-endpoints/{name}/notifications", func(req Request) any { + return req.Workspace.ServingEndpointUpdateNotifications(req, req.Vars["name"]) + }) + + server.Handle("PATCH", "/api/2.0/serving-endpoints/{name}/tags", func(req Request) any { + return req.Workspace.ServingEndpointPatchTags(req, req.Vars["name"]) + }) + // Generic permissions endpoints server.Handle("GET", "/api/2.0/permissions/{object_type}/{object_id}", func(req Request) any { return req.Workspace.GetPermissions(req) diff --git a/libs/testserver/serving_endpoints.go b/libs/testserver/serving_endpoints.go new file mode 100644 index 0000000000..86dfc4babe --- /dev/null +++ b/libs/testserver/serving_endpoints.go @@ -0,0 +1,312 @@ +package testserver + +import ( + "encoding/json" + "fmt" + "sort" + + "github.com/databricks/databricks-sdk-go/service/serving" +) + +func servedEntitiesInputToOutput(input []serving.ServedEntityInput) []serving.ServedEntityOutput { + entities := make([]serving.ServedEntityOutput, len(input)) + for i, entity := range input { + entities[i] = serving.ServedEntityOutput{ + EntityName: entity.EntityName, + EntityVersion: entity.EntityVersion, + EnvironmentVars: entity.EnvironmentVars, + ExternalModel: entity.ExternalModel, + InstanceProfileArn: entity.InstanceProfileArn, + MaxProvisionedConcurrency: entity.MaxProvisionedConcurrency, + MaxProvisionedThroughput: entity.MaxProvisionedThroughput, + MinProvisionedConcurrency: entity.MinProvisionedConcurrency, + MinProvisionedThroughput: entity.MinProvisionedThroughput, + Name: entity.Name, + ProvisionedModelUnits: entity.ProvisionedModelUnits, + ScaleToZeroEnabled: entity.ScaleToZeroEnabled, + WorkloadSize: entity.WorkloadSize, + WorkloadType: entity.WorkloadType, + ForceSendFields: entity.ForceSendFields, + } + } + return entities +} + +func servedModelsInputToOutput(input []serving.ServedModelInput) []serving.ServedModelOutput { + models := make([]serving.ServedModelOutput, len(input)) + for i, model := range input { + models[i] = serving.ServedModelOutput{ + ModelName: model.ModelName, + ModelVersion: model.ModelVersion, + EnvironmentVars: model.EnvironmentVars, + InstanceProfileArn: model.InstanceProfileArn, + MaxProvisionedConcurrency: model.MaxProvisionedConcurrency, + MinProvisionedConcurrency: model.MinProvisionedConcurrency, + ProvisionedModelUnits: model.ProvisionedModelUnits, + ScaleToZeroEnabled: model.ScaleToZeroEnabled, + WorkloadSize: model.WorkloadSize, + WorkloadType: serving.ServingModelWorkloadType(model.WorkloadType), + ForceSendFields: model.ForceSendFields, + } + } + return models +} + +func autoCaptureConfigInputToOutput(input *serving.AutoCaptureConfigInput) *serving.AutoCaptureConfigOutput { + return &serving.AutoCaptureConfigOutput{ + CatalogName: input.CatalogName, + SchemaName: input.SchemaName, + TableNamePrefix: input.TableNamePrefix, + Enabled: input.Enabled, + ForceSendFields: input.ForceSendFields, + } +} + +func (s *FakeWorkspace) ServingEndpointCreate(req Request) Response { + defer s.LockUnlock()() + + var createReq serving.CreateServingEndpoint + err := json.Unmarshal(req.Body, &createReq) + if err != nil { + return Response{ + Body: fmt.Sprintf("cannot unmarshal request body: %s", err), + StatusCode: 400, + } + } + + // Check if endpoint with this name already exists + if _, exists := s.ServingEndpoints[createReq.Name]; exists { + return Response{ + StatusCode: 409, + Body: map[string]string{"error_code": "RESOURCE_ALREADY_EXISTS", "message": fmt.Sprintf("Serving endpoint with name %s already exists", createReq.Name)}, + } + } + + // Convert config to output format + var config *serving.EndpointCoreConfigOutput + if createReq.Config != nil { + config = &serving.EndpointCoreConfigOutput{ + TrafficConfig: createReq.Config.TrafficConfig, + } + + // Convert ServedEntityInput to ServedEntityOutput + if len(createReq.Config.ServedEntities) > 0 { + config.ServedEntities = servedEntitiesInputToOutput(createReq.Config.ServedEntities) + } + + // Convert ServedModelInput to ServedModelOutput + if len(createReq.Config.ServedModels) > 0 { + config.ServedModels = servedModelsInputToOutput(createReq.Config.ServedModels) + } + + // Convert AutoCaptureConfig if present + if createReq.Config.AutoCaptureConfig != nil { + config.AutoCaptureConfig = autoCaptureConfigInputToOutput(createReq.Config.AutoCaptureConfig) + } + } + + endpoint := serving.ServingEndpointDetailed{ + AiGateway: createReq.AiGateway, + BudgetPolicyId: createReq.BudgetPolicyId, + Config: config, + Creator: s.CurrentUser().UserName, + Description: createReq.Description, + EmailNotifications: createReq.EmailNotifications, + Id: nextUUID(), + Name: createReq.Name, + RouteOptimized: createReq.RouteOptimized, + Tags: createReq.Tags, + State: &serving.EndpointState{ + ConfigUpdate: serving.EndpointStateConfigUpdateNotUpdating, + }, + ForceSendFields: createReq.ForceSendFields, + } + + s.ServingEndpoints[createReq.Name] = endpoint + + return Response{ + Body: endpoint, + } +} + +func (s *FakeWorkspace) ServingEndpointUpdate(req Request, name string) Response { + defer s.LockUnlock()() + + var updateReq serving.EndpointCoreConfigInput + err := json.Unmarshal(req.Body, &updateReq) + if err != nil { + return Response{ + Body: fmt.Sprintf("cannot unmarshal request body: %s", err), + StatusCode: 400, + } + } + + endpoint, exists := s.ServingEndpoints[name] + if !exists { + return Response{ + StatusCode: 404, + Body: map[string]string{"error_code": "RESOURCE_DOES_NOT_EXIST", "message": fmt.Sprintf("Serving endpoint with name %s not found", name)}, + } + } + + // Convert config to output format + var config *serving.EndpointCoreConfigOutput + if updateReq.ServedEntities != nil || updateReq.ServedModels != nil || updateReq.TrafficConfig != nil { + config = &serving.EndpointCoreConfigOutput{ + TrafficConfig: updateReq.TrafficConfig, + } + + // Convert ServedEntityInput to ServedEntityOutput + if len(updateReq.ServedEntities) > 0 { + config.ServedEntities = servedEntitiesInputToOutput(updateReq.ServedEntities) + } + + // Convert ServedModelInput to ServedModelOutput + if len(updateReq.ServedModels) > 0 { + config.ServedModels = servedModelsInputToOutput(updateReq.ServedModels) + } + + // Convert AutoCaptureConfig if present + if updateReq.AutoCaptureConfig != nil { + config.AutoCaptureConfig = autoCaptureConfigInputToOutput(updateReq.AutoCaptureConfig) + } + } + + endpoint.Config = config + endpoint.State = &serving.EndpointState{ + ConfigUpdate: serving.EndpointStateConfigUpdateNotUpdating, + } + + s.ServingEndpoints[name] = endpoint + + return Response{ + Body: endpoint, + } +} + +func (s *FakeWorkspace) ServingEndpointPutAiGateway(req Request, name string) Response { + defer s.LockUnlock()() + + var putReq serving.PutAiGatewayRequest + err := json.Unmarshal(req.Body, &putReq) + if err != nil { + return Response{ + Body: fmt.Sprintf("cannot unmarshal request body: %s", err), + StatusCode: 400, + } + } + + endpoint, exists := s.ServingEndpoints[name] + if !exists { + return Response{ + StatusCode: 404, + Body: map[string]string{"error_code": "RESOURCE_DOES_NOT_EXIST", "message": fmt.Sprintf("Serving endpoint with name %s not found", name)}, + } + } + + // Update AI gateway config + if putReq.FallbackConfig != nil || putReq.Guardrails != nil || putReq.InferenceTableConfig != nil || putReq.RateLimits != nil || putReq.UsageTrackingConfig != nil { + endpoint.AiGateway = &serving.AiGatewayConfig{ + FallbackConfig: putReq.FallbackConfig, + Guardrails: putReq.Guardrails, + InferenceTableConfig: putReq.InferenceTableConfig, + RateLimits: putReq.RateLimits, + UsageTrackingConfig: putReq.UsageTrackingConfig, + } + } else { + // Unset AI gateway if no fields provided + endpoint.AiGateway = nil + } + + s.ServingEndpoints[name] = endpoint + + return Response{ + Body: endpoint.AiGateway, + } +} + +func (s *FakeWorkspace) ServingEndpointUpdateNotifications(req Request, name string) Response { + defer s.LockUnlock()() + + var updateReq serving.UpdateInferenceEndpointNotifications + err := json.Unmarshal(req.Body, &updateReq) + if err != nil { + return Response{ + Body: fmt.Sprintf("cannot unmarshal request body: %s", err), + StatusCode: 400, + } + } + + endpoint, exists := s.ServingEndpoints[name] + if !exists { + return Response{ + StatusCode: 404, + Body: map[string]string{"error_code": "RESOURCE_DOES_NOT_EXIST", "message": fmt.Sprintf("Serving endpoint with name %s not found", name)}, + } + } + + endpoint.EmailNotifications = updateReq.EmailNotifications + s.ServingEndpoints[name] = endpoint + + return Response{ + Body: endpoint, + } +} + +func (s *FakeWorkspace) ServingEndpointPatchTags(req Request, name string) Response { + defer s.LockUnlock()() + + var patchReq serving.PatchServingEndpointTags + err := json.Unmarshal(req.Body, &patchReq) + if err != nil { + return Response{ + Body: fmt.Sprintf("cannot unmarshal request body: %s", err), + StatusCode: 400, + } + } + + endpoint, exists := s.ServingEndpoints[name] + if !exists { + return Response{ + StatusCode: 404, + Body: map[string]string{"error_code": "RESOURCE_DOES_NOT_EXIST", "message": fmt.Sprintf("Serving endpoint with name %s not found", name)}, + } + } + + // Build map of current tags + tagMap := make(map[string]string) + for _, tag := range endpoint.Tags { + tagMap[tag.Key] = tag.Value + } + + // Add or update tags + for _, tag := range patchReq.AddTags { + tagMap[tag.Key] = tag.Value + } + + // Delete tags + for _, key := range patchReq.DeleteTags { + delete(tagMap, key) + } + + // Convert back to slice sorted by key for stable output + tags := make([]serving.EndpointTag, 0, len(tagMap)) + keys := make([]string, 0, len(tagMap)) + for key := range tagMap { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + tags = append(tags, serving.EndpointTag{Key: key, Value: tagMap[key]}) + } + + endpoint.Tags = tags + s.ServingEndpoints[name] = endpoint + + // Return the tags as EndpointTags struct, not as array + return Response{ + Body: serving.EndpointTags{ + Tags: tags, + }, + } +} diff --git a/out.first-get..json b/out.first-get..json new file mode 100644 index 0000000000..e69de29bb2