diff --git a/docs/resources/vector_search_endpoint.md b/docs/resources/vector_search_endpoint.md new file mode 100644 index 0000000000..3553c8c7c2 --- /dev/null +++ b/docs/resources/vector_search_endpoint.md @@ -0,0 +1,48 @@ +--- +subcategory: "Vector Search" +--- +# databricks_vector_search_endpoint Resource + +-> **Note** This resource could be only used on Unity Catalog-enabled workspace! + +This resource allows you to create [Vector Search Endpoint](https://docs.databricks.com/en/generative-ai/vector-search.html) in Databricks. Vector Search is a serverless similarity search engine that allows you to store a vector representation of your data, including metadata, in a vector database. The Vector Search Endpoint is used to create and access vector search indexes. + +## Example Usage + + +```hcl +resource "databricks_vector_search_endpoint" "this" { + name = "vector-search-test" + endpoint_type = "STANDARD" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Name of the Vector Search Endpoint to create. If name is changed, Vector Search Endpoint is recreated. +* `endpoint_type` (Required) type of Vector Search Endpoint. Currently only accepting single value: `STANDARD` (See [documentation](https://docs.databricks.com/api/workspace/vectorsearchendpoints/createendpoint) for the list of currently supported values). If it's changed, Vector Search Endpoint is recreated. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The same as the name of the endpoint. +* `creator` - Creator of the endpoint. +* `creation_timestamp` - Timestamp of endpoint creation (milliseconds). +* `last_updated_user` - User who last updated the endpoint. +* `last_updated_timestamp` - Timestamp of last update to the endpoint (milliseconds). +* `endpoint_id` - Unique internal identifier of the endpoint (UUID). +* `num_indexes` - Number of indexes on the endpoint. +* `endpoint_status` - Object describing the current status of the endpoint consisting of following fields: + * `state` - Current state of the endpoint. Currently following values are supported: `PROVISIONING`, `ONLINE`, `OFFLINE`. + * `message` - Additional status message. + +## Import + +The resource can be imported using the name of the Vector Search Endpoint + +```bash +terraform import databricks_vector_search_endpoint.this +``` diff --git a/exporter/exporter_test.go b/exporter/exporter_test.go index 4ba68ae64b..303d791ec7 100644 --- a/exporter/exporter_test.go +++ b/exporter/exporter_test.go @@ -2168,6 +2168,7 @@ func TestImportingNotebooksWorkspaceFiles(t *testing.T) { Response: workspace.ObjectList{ Objects: []workspace.ObjectStatus{notebookStatus, fileStatus}, }, + ReuseRequest: true, }, { Method: "GET", diff --git a/internal/acceptance/vector_search_test.go b/internal/acceptance/vector_search_test.go new file mode 100644 index 0000000000..a6033aa802 --- /dev/null +++ b/internal/acceptance/vector_search_test.go @@ -0,0 +1,30 @@ +package acceptance + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" +) + +func TestUcAccVectorSearchEndpoint(t *testing.T) { + cloudEnv := os.Getenv("CLOUD_ENV") + switch cloudEnv { + case "ucws", "azure": + default: + t.Skipf("not available on %s", cloudEnv) + } + + name := fmt.Sprintf("terraform-test-vector-search-%[1]s", + acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)) + unityWorkspaceLevel(t, step{ + Template: fmt.Sprintf(` + resource "databricks_vector_search_endpoint" "this" { + name = "%s" + endpoint_type = "STANDARD" + } + `, name), + }, + ) +} diff --git a/provider/provider.go b/provider/provider.go index 3b0e1dcf66..fbd8f324a6 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -39,6 +39,7 @@ import ( "github.com/databricks/terraform-provider-databricks/sql" "github.com/databricks/terraform-provider-databricks/storage" "github.com/databricks/terraform-provider-databricks/tokens" + "github.com/databricks/terraform-provider-databricks/vectorsearch" "github.com/databricks/terraform-provider-databricks/workspace" ) @@ -173,6 +174,7 @@ func DatabricksProvider() *schema.Provider { "databricks_user": scim.ResourceUser().ToResource(), "databricks_user_instance_profile": aws.ResourceUserInstanceProfile().ToResource(), "databricks_user_role": aws.ResourceUserRole().ToResource(), + "databricks_vector_search_endpoint": vectorsearch.ResourceVectorSearchEndpoint().ToResource(), "databricks_volume": catalog.ResourceVolume().ToResource(), "databricks_workspace_conf": workspace.ResourceWorkspaceConf().ToResource(), "databricks_workspace_file": workspace.ResourceWorkspaceFile().ToResource(), diff --git a/vectorsearch/resource_vector_search_endpoint.go b/vectorsearch/resource_vector_search_endpoint.go new file mode 100644 index 0000000000..c9647fa316 --- /dev/null +++ b/vectorsearch/resource_vector_search_endpoint.go @@ -0,0 +1,85 @@ +package vectorsearch + +import ( + "context" + "time" + + "github.com/databricks/terraform-provider-databricks/common" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/databricks/databricks-sdk-go/service/vectorsearch" +) + +const DefaultProvisionTimeout = 45 * time.Minute + +func ResourceVectorSearchEndpoint() common.Resource { + s := common.StructToSchema( + vectorsearch.EndpointInfo{}, + func(s map[string]*schema.Schema) map[string]*schema.Schema { + common.CustomizeSchemaPath(s, "name").SetRequired().SetForceNew() + common.CustomizeSchemaPath(s, "endpoint_type").SetRequired().SetForceNew() + delete(s, "id") + common.CustomizeSchemaPath(s, "creator").SetReadOnly() + common.CustomizeSchemaPath(s, "creation_timestamp").SetReadOnly() + common.CustomizeSchemaPath(s, "last_updated_timestamp").SetReadOnly() + common.CustomizeSchemaPath(s, "last_updated_user").SetReadOnly() + common.CustomizeSchemaPath(s, "endpoint_status").SetReadOnly() + common.CustomizeSchemaPath(s, "num_indexes").SetReadOnly() + common.CustomizeSchemaPath(s).AddNewField("endpoint_id", &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }) + + return s + }) + + return common.Resource{ + Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error { + w, err := c.WorkspaceClient() + if err != nil { + return err + } + var req vectorsearch.CreateEndpoint + common.DataToStructPointer(d, s, &req) + wait, err := w.VectorSearchEndpoints.CreateEndpoint(ctx, req) + if err != nil { + return err + } + endpoint, err := wait.GetWithTimeout(d.Timeout(schema.TimeoutCreate)) + if err != nil { + return err + } + d.SetId(endpoint.Name) + return nil + }, + Read: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error { + w, err := c.WorkspaceClient() + if err != nil { + return err + } + endpoint, err := w.VectorSearchEndpoints.GetEndpointByEndpointName(ctx, d.Id()) + if err != nil { + return err + } + err = common.StructToData(*endpoint, s, d) + if err != nil { + return err + } + d.Set("endpoint_id", endpoint.Id) + return nil + }, + Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error { + w, err := c.WorkspaceClient() + if err != nil { + return err + } + return w.VectorSearchEndpoints.DeleteEndpointByEndpointName(ctx, d.Id()) + }, + StateUpgraders: []schema.StateUpgrader{}, + Schema: s, + SchemaVersion: 0, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(DefaultProvisionTimeout), + }, + } +} diff --git a/vectorsearch/resource_vector_search_endpoint_test.go b/vectorsearch/resource_vector_search_endpoint_test.go new file mode 100644 index 0000000000..ec5e193a57 --- /dev/null +++ b/vectorsearch/resource_vector_search_endpoint_test.go @@ -0,0 +1,83 @@ +package vectorsearch + +import ( + "testing" + + "github.com/databricks/databricks-sdk-go/experimental/mocks" + "github.com/databricks/databricks-sdk-go/qa/poll" + "github.com/databricks/terraform-provider-databricks/qa" + + "github.com/databricks/databricks-sdk-go/service/vectorsearch" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestVectorSearchEndpointCornerCases(t *testing.T) { + qa.ResourceCornerCases(t, ResourceVectorSearchEndpoint()) +} + +func TestVectorSearchEndpointCreate(t *testing.T) { + ei := &vectorsearch.EndpointInfo{ + Name: "abc", + EndpointStatus: &vectorsearch.EndpointStatus{State: "ONLINE"}, + Id: "1234-5678", + } + d, err := qa.ResourceFixture{ + MockWorkspaceClientFunc: func(w *mocks.MockWorkspaceClient) { + e := w.GetMockVectorSearchEndpointsAPI().EXPECT() + e.CreateEndpoint(mock.Anything, vectorsearch.CreateEndpoint{ + Name: "abc", + EndpointType: "STANDARD", + }).Return(&vectorsearch.WaitGetEndpointVectorSearchEndpointOnline[vectorsearch.EndpointInfo]{Poll: poll.Simple(*ei)}, nil) + + e.GetEndpointByEndpointName(mock.Anything, "abc").Return(ei, nil) + }, + Resource: ResourceVectorSearchEndpoint(), + HCL: ` + name = "abc" + endpoint_type = "STANDARD" + `, + Create: true, + }.Apply(t) + assert.NoError(t, err) + assert.Equal(t, "abc", d.Id()) + assert.Equal(t, "1234-5678", d.Get("endpoint_id")) +} + +func TestVectorSearchEndpointRead(t *testing.T) { + ei := &vectorsearch.EndpointInfo{ + Name: "abc", + EndpointStatus: &vectorsearch.EndpointStatus{State: "ONLINE"}, + Id: "1234-5678", + } + d, err := qa.ResourceFixture{ + MockWorkspaceClientFunc: func(w *mocks.MockWorkspaceClient) { + e := w.GetMockVectorSearchEndpointsAPI().EXPECT() + e.GetEndpointByEndpointName(mock.Anything, "abc").Return(ei, nil) + }, + Resource: ResourceVectorSearchEndpoint(), + ID: "abc", + HCL: ` + name = "abc" + endpoint_type = "STANDARD" + `, + Read: true, + }.Apply(t) + assert.NoError(t, err) + assert.Equal(t, "abc", d.Id()) + assert.Equal(t, "1234-5678", d.Get("endpoint_id")) +} + +func TestResourcePASDelete(t *testing.T) { + d, err := qa.ResourceFixture{ + MockWorkspaceClientFunc: func(a *mocks.MockWorkspaceClient) { + a.GetMockVectorSearchEndpointsAPI().EXPECT().DeleteEndpointByEndpointName(mock.Anything, "abc").Return(nil) + }, + Resource: ResourceVectorSearchEndpoint(), + Delete: true, + ID: "abc", + }.Apply(t) + assert.NoError(t, err) + assert.Equal(t, "abc", d.Id()) +}