diff --git a/Makefile b/Makefile index fb3936184..0c3860e29 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ test: cover: rm -fr ./acceptance/build/cover/ - CLI_GOCOVERDIR=build/cover ${GOTESTSUM_CMD} -- -coverprofile=coverage.txt ${PACKAGES} + VERBOSE_TEST=1 CLI_GOCOVERDIR=build/cover ${GOTESTSUM_CMD} -- -coverprofile=coverage.txt ${PACKAGES} rm -fr ./acceptance/build/cover-merged/ mkdir -p acceptance/build/cover-merged/ go tool covdata merge -i $$(printf '%s,' acceptance/build/cover/* | sed 's/,$$//') -o acceptance/build/cover-merged/ @@ -61,6 +61,6 @@ integration: vendor $(INTEGRATION) integration-short: vendor - $(INTEGRATION) -short + VERBOSE_TEST=1 $(INTEGRATION) -short .PHONY: lint tidy lintcheck fmt test cover showcover build snapshot vendor schema integration integration-short acc-cover acc-showcover docs diff --git a/NOTICE b/NOTICE index 4331a2a32..0b1d2da04 100644 --- a/NOTICE +++ b/NOTICE @@ -114,3 +114,7 @@ dario.cat/mergo Copyright (c) 2013 Dario Castañé. All rights reserved. Copyright (c) 2012 The Go Authors. All rights reserved. https://github.com/darccio/mergo/blob/master/LICENSE + +https://github.com/gorilla/mux +Copyright (c) 2023 The Gorilla Authors. All rights reserved. +https://github.com/gorilla/mux/blob/main/LICENSE diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index b05b10f47..117172f60 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "runtime" "slices" "sort" @@ -26,12 +27,14 @@ import ( "github.com/databricks/cli/libs/testdiff" "github.com/databricks/cli/libs/testserver" "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/stretchr/testify/require" ) var ( - KeepTmp bool - NoRepl bool + KeepTmp bool + NoRepl bool + VerboseTest bool = os.Getenv("VERBOSE_TEST") != "" ) // In order to debug CLI running under acceptance test, set this to full subtest name, e.g. "bundle/variables/empty" @@ -71,7 +74,8 @@ func TestInprocessMode(t *testing.T) { if InprocessMode { t.Skip("Already tested by TestAccept") } - require.Equal(t, 1, testAccept(t, true, "selftest")) + require.Equal(t, 1, testAccept(t, true, "selftest/basic")) + require.Equal(t, 1, testAccept(t, true, "selftest/server")) } func testAccept(t *testing.T, InprocessMode bool, singleTest string) int { @@ -117,14 +121,12 @@ func testAccept(t *testing.T, InprocessMode bool, singleTest string) int { uvCache := getUVDefaultCacheDir(t) t.Setenv("UV_CACHE_DIR", uvCache) - ctx := context.Background() cloudEnv := os.Getenv("CLOUD_ENV") if cloudEnv == "" { defaultServer := testserver.New(t) AddHandlers(defaultServer) - // Redirect API access to local server: - t.Setenv("DATABRICKS_HOST", defaultServer.URL) + t.Setenv("DATABRICKS_DEFAULT_HOST", defaultServer.URL) homeDir := t.TempDir() // Do not read user's ~/.databrickscfg @@ -147,27 +149,12 @@ func testAccept(t *testing.T, InprocessMode bool, singleTest string) int { // do it last so that full paths match first: repls.SetPath(buildDir, "[BUILD_DIR]") - var config databricks.Config - if cloudEnv == "" { - // use fake token for local tests - config = databricks.Config{Token: "dbapi1234"} - } else { - // non-local tests rely on environment variables - config = databricks.Config{} - } - workspaceClient, err := databricks.NewWorkspaceClient(&config) - require.NoError(t, err) - - user, err := workspaceClient.CurrentUser.Me(ctx) - require.NoError(t, err) - require.NotNil(t, user) - testdiff.PrepareReplacementsUser(t, &repls, *user) - testdiff.PrepareReplacementsWorkspaceClient(t, &repls, workspaceClient) - testdiff.PrepareReplacementsUUID(t, &repls) testdiff.PrepareReplacementsDevVersion(t, &repls) testdiff.PrepareReplacementSdkVersion(t, &repls) testdiff.PrepareReplacementsGoVersion(t, &repls) + repls.Repls = append(repls.Repls, testdiff.Replacement{Old: regexp.MustCompile("dbapi[0-9a-f]+"), New: "[DATABRICKS_TOKEN]"}) + testDirs := getTests(t) require.NotEmpty(t, testDirs) @@ -179,8 +166,7 @@ func testAccept(t *testing.T, InprocessMode bool, singleTest string) int { } for _, dir := range testDirs { - testName := strings.ReplaceAll(dir, "\\", "/") - t.Run(testName, func(t *testing.T) { + t.Run(dir, func(t *testing.T) { if !InprocessMode { t.Parallel() } @@ -202,7 +188,8 @@ func getTests(t *testing.T) []string { name := filepath.Base(path) if name == EntryPointScript { // Presence of 'script' marks a test case in this directory - testDirs = append(testDirs, filepath.Dir(path)) + testName := filepath.ToSlash(filepath.Dir(path)) + testDirs = append(testDirs, testName) } return nil }) @@ -238,7 +225,6 @@ func runTest(t *testing.T, dir, coverDir string, repls testdiff.ReplacementsCont } repls.SetPathWithParents(tmpDir, "[TMPDIR]") - repls.Repls = append(repls.Repls, config.Repls...) scriptContents := readMergedScriptContents(t, dir) testutil.WriteFile(t, filepath.Join(tmpDir, EntryPointScript), scriptContents) @@ -252,38 +238,79 @@ func runTest(t *testing.T, dir, coverDir string, repls testdiff.ReplacementsCont cmd := exec.Command(args[0], args[1:]...) cmd.Env = os.Environ() + var workspaceClient *databricks.WorkspaceClient + var user iam.User + // Start a new server with a custom configuration if the acceptance test // specifies a custom server stubs. var server *testserver.Server - // Start a new server for this test if either: - // 1. A custom server spec is defined in the test configuration. - // 2. The test is configured to record requests and assert on them. We need - // a duplicate of the default server to record requests because the default - // server otherwise is a shared resource. - if len(config.Server) > 0 || config.RecordRequests { - server = testserver.New(t) - server.RecordRequests = config.RecordRequests - server.IncludeRequestHeaders = config.IncludeRequestHeaders + if cloudEnv == "" { + // Start a new server for this test if either: + // 1. A custom server spec is defined in the test configuration. + // 2. The test is configured to record requests and assert on them. We need + // a duplicate of the default server to record requests because the default + // server otherwise is a shared resource. - // If no custom server stubs are defined, add the default handlers. - if len(config.Server) == 0 { + databricksLocalHost := os.Getenv("DATABRICKS_DEFAULT_HOST") + + if len(config.Server) > 0 || config.RecordRequests { + server = testserver.New(t) + server.RecordRequests = config.RecordRequests + server.IncludeRequestHeaders = config.IncludeRequestHeaders + + for _, stub := range config.Server { + require.NotEmpty(t, stub.Pattern) + items := strings.Split(stub.Pattern, " ") + require.Len(t, items, 2) + server.Handle(items[0], items[1], func(fakeWorkspace *testserver.FakeWorkspace, req *http.Request) (any, int) { + statusCode := http.StatusOK + if stub.Response.StatusCode != 0 { + statusCode = stub.Response.StatusCode + } + return stub.Response.Body, statusCode + }) + } + + // The earliest handlers take precedence, add default handlers last AddHandlers(server) + databricksLocalHost = server.URL } - for _, stub := range config.Server { - require.NotEmpty(t, stub.Pattern) - server.Handle(stub.Pattern, func(fakeWorkspace *testserver.FakeWorkspace, req *http.Request) (any, int) { - statusCode := http.StatusOK - if stub.Response.StatusCode != 0 { - statusCode = stub.Response.StatusCode - } - return stub.Response.Body, statusCode - }) + // Each local test should use a new token that will result into a new fake workspace, + // so that test don't interfere with each other. + tokenSuffix := strings.ReplaceAll(uuid.NewString(), "-", "") + config := databricks.Config{ + Host: databricksLocalHost, + Token: "dbapi" + tokenSuffix, } - cmd.Env = append(cmd.Env, "DATABRICKS_HOST="+server.URL) + workspaceClient, err = databricks.NewWorkspaceClient(&config) + require.NoError(t, err) + + cmd.Env = append(cmd.Env, "DATABRICKS_HOST="+config.Host) + cmd.Env = append(cmd.Env, "DATABRICKS_TOKEN="+config.Token) + + // For the purposes of replacements, use testUser. + // Note, users might have overriden /api/2.0/preview/scim/v2/Me but that should not affect the replacement: + user = testUser + } else { + // Use whatever authentication mechanism is configured by the test runner. + workspaceClient, err = databricks.NewWorkspaceClient(&databricks.Config{}) + require.NoError(t, err) + pUser, err := workspaceClient.CurrentUser.Me(context.Background()) + require.NoError(t, err, "Failed to get current user") + user = *pUser } + testdiff.PrepareReplacementsUser(t, &repls, user) + testdiff.PrepareReplacementsWorkspaceClient(t, &repls, workspaceClient) + + // Must be added PrepareReplacementsUser, otherwise conflicts with [USERNAME] + testdiff.PrepareReplacementsUUID(t, &repls) + + // User replacements come last: + repls.Repls = append(repls.Repls, config.Repls...) + if coverDir != "" { // Creating individual coverage directory for each test, because writing to the same one // results in sporadic failures like this one (only if tests are running in parallel): @@ -294,15 +321,6 @@ func runTest(t *testing.T, dir, coverDir string, repls testdiff.ReplacementsCont cmd.Env = append(cmd.Env, "GOCOVERDIR="+coverDir) } - // Each local test should use a new token that will result into a new fake workspace, - // so that test don't interfere with each other. - if cloudEnv == "" { - tokenSuffix := strings.ReplaceAll(uuid.NewString(), "-", "") - token := "dbapi" + tokenSuffix - cmd.Env = append(cmd.Env, "DATABRICKS_TOKEN="+token) - repls.Set(token, "[DATABRICKS_TOKEN]") - } - // Write combined output to a file out, err := os.Create(filepath.Join(tmpDir, "output.txt")) require.NoError(t, err) @@ -319,7 +337,7 @@ func runTest(t *testing.T, dir, coverDir string, repls testdiff.ReplacementsCont for _, req := range server.Requests { reqJson, err := json.MarshalIndent(req, "", " ") - require.NoError(t, err) + require.NoErrorf(t, err, "Failed to indent: %#v", req) reqJsonWithRepls := repls.Replace(string(reqJson)) _, err = f.WriteString(reqJsonWithRepls + "\n") @@ -412,7 +430,7 @@ func doComparison(t *testing.T, repls testdiff.ReplacementsContext, dirRef, dirN testutil.WriteFile(t, pathRef, valueNew) } - if !equal && printedRepls != nil && !*printedRepls { + if VerboseTest && !equal && printedRepls != nil && !*printedRepls { *printedRepls = true var items []string for _, item := range repls.Repls { diff --git a/acceptance/auth/bundle_and_profile/output.txt b/acceptance/auth/bundle_and_profile/output.txt index 022b3148d..8d2584622 100644 --- a/acceptance/auth/bundle_and_profile/output.txt +++ b/acceptance/auth/bundle_and_profile/output.txt @@ -13,13 +13,13 @@ === Inside the bundle, profile flag not matching bundle host. Badness: should use profile from flag instead and not fail >>> errcode [CLI] current-user me -p profile_name -Error: cannot resolve bundle auth configuration: config host mismatch: profile uses host https://non-existing-subdomain.databricks.com, but CLI configured to use [DATABRICKS_URL] +Error: cannot resolve bundle auth configuration: config host mismatch: profile uses host https://non-existing-subdomain.databricks.com, but CLI configured to use [DATABRICKS_TARGET] Exit code: 1 === Inside the bundle, target and not matching profile >>> errcode [CLI] current-user me -t dev -p profile_name -Error: cannot resolve bundle auth configuration: config host mismatch: profile uses host https://non-existing-subdomain.databricks.com, but CLI configured to use [DATABRICKS_URL] +Error: cannot resolve bundle auth configuration: config host mismatch: profile uses host https://non-existing-subdomain.databricks.com, but CLI configured to use [DATABRICKS_TARGET] Exit code: 1 diff --git a/acceptance/auth/bundle_and_profile/test.toml b/acceptance/auth/bundle_and_profile/test.toml index b20190ca5..1a611ed95 100644 --- a/acceptance/auth/bundle_and_profile/test.toml +++ b/acceptance/auth/bundle_and_profile/test.toml @@ -5,4 +5,8 @@ Badness = "When -p flag is used inside the bundle folder for any CLI commands, C # This is a workaround to replace DATABRICKS_URL with DATABRICKS_HOST [[Repls]] Old='DATABRICKS_HOST' -New='DATABRICKS_URL' +New='DATABRICKS_TARGET' + +[[Repls]] +Old='DATABRICKS_URL' +New='DATABRICKS_TARGET' diff --git a/acceptance/auth/credentials/basic/out.requests.txt b/acceptance/auth/credentials/basic/out.requests.txt new file mode 100644 index 000000000..b549c7423 --- /dev/null +++ b/acceptance/auth/credentials/basic/out.requests.txt @@ -0,0 +1,12 @@ +{ + "headers": { + "Authorization": [ + "Basic [ENCODED_AUTH]" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/current-user_me cmd-exec-id/[UUID] auth/basic" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} diff --git a/acceptance/auth/credentials/basic/output.txt b/acceptance/auth/credentials/basic/output.txt new file mode 100644 index 000000000..c5747c9e4 --- /dev/null +++ b/acceptance/auth/credentials/basic/output.txt @@ -0,0 +1,4 @@ +{ + "id":"[USERID]", + "userName":"[USERNAME]" +} diff --git a/acceptance/auth/credentials/basic/script b/acceptance/auth/credentials/basic/script new file mode 100644 index 000000000..aae249083 --- /dev/null +++ b/acceptance/auth/credentials/basic/script @@ -0,0 +1,8 @@ +# Unset the token which is configured by default +# in acceptance tests +export DATABRICKS_TOKEN="" + +export DATABRICKS_USERNAME=username +export DATABRICKS_PASSWORD=password + +$CLI current-user me diff --git a/acceptance/auth/credentials/basic/test.toml b/acceptance/auth/credentials/basic/test.toml new file mode 100644 index 000000000..4998d81d7 --- /dev/null +++ b/acceptance/auth/credentials/basic/test.toml @@ -0,0 +1,4 @@ +# "username:password" in base64 is dXNlcm5hbWU6cGFzc3dvcmQ=, expect to see this in Authorization header +[[Repls]] +Old = "dXNlcm5hbWU6cGFzc3dvcmQ=" +New = "[ENCODED_AUTH]" diff --git a/acceptance/auth/credentials/oauth/out.requests.txt b/acceptance/auth/credentials/oauth/out.requests.txt new file mode 100644 index 000000000..525e148d8 --- /dev/null +++ b/acceptance/auth/credentials/oauth/out.requests.txt @@ -0,0 +1,34 @@ +{ + "headers": { + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]" + ] + }, + "method": "GET", + "path": "/oidc/.well-known/oauth-authorization-server" +} +{ + "headers": { + "Authorization": [ + "Basic [ENCODED_AUTH]" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]" + ] + }, + "method": "POST", + "path": "/oidc/v1/token", + "raw_body": "grant_type=client_credentials\u0026scope=all-apis" +} +{ + "headers": { + "Authorization": [ + "Bearer oauth-token" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/current-user_me cmd-exec-id/[UUID] auth/oauth-m2m" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} diff --git a/acceptance/auth/credentials/oauth/output.txt b/acceptance/auth/credentials/oauth/output.txt new file mode 100644 index 000000000..c5747c9e4 --- /dev/null +++ b/acceptance/auth/credentials/oauth/output.txt @@ -0,0 +1,4 @@ +{ + "id":"[USERID]", + "userName":"[USERNAME]" +} diff --git a/acceptance/auth/credentials/oauth/script b/acceptance/auth/credentials/oauth/script new file mode 100644 index 000000000..e4519e41b --- /dev/null +++ b/acceptance/auth/credentials/oauth/script @@ -0,0 +1,8 @@ +# Unset the token which is configured by default +# in acceptance tests +export DATABRICKS_TOKEN="" + +export DATABRICKS_CLIENT_ID=client_id +export DATABRICKS_CLIENT_SECRET=client_secret + +$CLI current-user me diff --git a/acceptance/auth/credentials/oauth/test.toml b/acceptance/auth/credentials/oauth/test.toml new file mode 100644 index 000000000..2adade96a --- /dev/null +++ b/acceptance/auth/credentials/oauth/test.toml @@ -0,0 +1,5 @@ +# "client_id:client_secret" in base64 is Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=, expect to +# see this in Authorization header +[[Repls]] +Old = "Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=" +New = "[ENCODED_AUTH]" diff --git a/acceptance/auth/credentials/pat/out.requests.txt b/acceptance/auth/credentials/pat/out.requests.txt new file mode 100644 index 000000000..73c448c2f --- /dev/null +++ b/acceptance/auth/credentials/pat/out.requests.txt @@ -0,0 +1,12 @@ +{ + "headers": { + "Authorization": [ + "Bearer dapi1234" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/current-user_me cmd-exec-id/[UUID] auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} diff --git a/acceptance/auth/credentials/pat/output.txt b/acceptance/auth/credentials/pat/output.txt new file mode 100644 index 000000000..c5747c9e4 --- /dev/null +++ b/acceptance/auth/credentials/pat/output.txt @@ -0,0 +1,4 @@ +{ + "id":"[USERID]", + "userName":"[USERNAME]" +} diff --git a/acceptance/auth/credentials/pat/script b/acceptance/auth/credentials/pat/script new file mode 100644 index 000000000..ccf1098e7 --- /dev/null +++ b/acceptance/auth/credentials/pat/script @@ -0,0 +1,3 @@ +export DATABRICKS_TOKEN=dapi1234 + +$CLI current-user me diff --git a/acceptance/auth/credentials/test.toml b/acceptance/auth/credentials/test.toml new file mode 100644 index 000000000..89438f43a --- /dev/null +++ b/acceptance/auth/credentials/test.toml @@ -0,0 +1,20 @@ +LocalOnly = true + +RecordRequests = true +IncludeRequestHeaders = ["Authorization", "User-Agent"] + +[[Repls]] +Old = '(linux|darwin|windows)' +New = '[OS]' + +[[Repls]] +Old = " upstream/[A-Za-z0-9.-]+" +New = "" + +[[Repls]] +Old = " upstream-version/[A-Za-z0-9.-]+" +New = "" + +[[Repls]] +Old = " cicd/[A-Za-z0-9.-]+" +New = "" diff --git a/acceptance/cmd_server_test.go b/acceptance/cmd_server_test.go index c8a52f4cd..d3db06003 100644 --- a/acceptance/cmd_server_test.go +++ b/acceptance/cmd_server_test.go @@ -14,11 +14,7 @@ import ( func StartCmdServer(t *testing.T) *testserver.Server { server := testserver.New(t) - - // {$} is a wildcard that only matches the end of the URL. We explicitly use - // /{$} to disambiguate it from the generic handler for '/' which is used to - // identify unhandled API endpoints in the test server. - server.Handle("/{$}", func(w *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("GET", "/", func(_ *testserver.FakeWorkspace, r *http.Request) (any, int) { q := r.URL.Query() args := strings.Split(q.Get("args"), " ") diff --git a/acceptance/selftest/out.hello.txt b/acceptance/selftest/basic/out.hello.txt similarity index 100% rename from acceptance/selftest/out.hello.txt rename to acceptance/selftest/basic/out.hello.txt diff --git a/acceptance/selftest/output.txt b/acceptance/selftest/basic/output.txt similarity index 100% rename from acceptance/selftest/output.txt rename to acceptance/selftest/basic/output.txt diff --git a/acceptance/selftest/script b/acceptance/selftest/basic/script similarity index 100% rename from acceptance/selftest/script rename to acceptance/selftest/basic/script diff --git a/acceptance/selftest/test.toml b/acceptance/selftest/basic/test.toml similarity index 100% rename from acceptance/selftest/test.toml rename to acceptance/selftest/basic/test.toml diff --git a/acceptance/selftest/server/out.requests.txt b/acceptance/selftest/server/out.requests.txt new file mode 100644 index 000000000..2cb8708ac --- /dev/null +++ b/acceptance/selftest/server/out.requests.txt @@ -0,0 +1,8 @@ +{ + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} +{ + "method": "GET", + "path": "/custom/endpoint" +} diff --git a/acceptance/selftest/server/output.txt b/acceptance/selftest/server/output.txt new file mode 100644 index 000000000..f9e51caa9 --- /dev/null +++ b/acceptance/selftest/server/output.txt @@ -0,0 +1,15 @@ + +>>> curl -s [DATABRICKS_URL]/api/2.0/preview/scim/v2/Me +{ + "id": "[USERID]", + "userName": "[USERNAME]" +} +>>> curl -sD - [DATABRICKS_URL]/custom/endpoint?query=param +HTTP/1.1 201 Created +Content-Type: application/json +Date: (redacted) +Content-Length: (redacted) + +custom +--- +response diff --git a/acceptance/selftest/server/script b/acceptance/selftest/server/script new file mode 100644 index 000000000..53e2c4b8a --- /dev/null +++ b/acceptance/selftest/server/script @@ -0,0 +1,2 @@ +trace curl -s $DATABRICKS_HOST/api/2.0/preview/scim/v2/Me +trace curl -sD - $DATABRICKS_HOST/custom/endpoint?query=param diff --git a/acceptance/selftest/server/test.toml b/acceptance/selftest/server/test.toml new file mode 100644 index 000000000..2531fb910 --- /dev/null +++ b/acceptance/selftest/server/test.toml @@ -0,0 +1,18 @@ +LocalOnly = true +RecordRequests = true + +[[Server]] +Pattern = "GET /custom/endpoint" +Response.Body = '''custom +--- +response +''' +Response.StatusCode = 201 + +[[Repls]] +Old = 'Date: .*' +New = 'Date: (redacted)' + +[[Repls]] +Old = 'Content-Length: [0-9]*' +New = 'Content-Length: (redacted)' diff --git a/acceptance/server_test.go b/acceptance/server_test.go index d21ab66e8..fd8006b8f 100644 --- a/acceptance/server_test.go +++ b/acceptance/server_test.go @@ -8,6 +8,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/catalog" "github.com/databricks/databricks-sdk-go/service/iam" + "github.com/gorilla/mux" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/databricks/databricks-sdk-go/service/jobs" @@ -16,8 +17,13 @@ import ( "github.com/databricks/databricks-sdk-go/service/workspace" ) +var testUser = iam.User{ + Id: "1000012345", + UserName: "tester@databricks.com", +} + func AddHandlers(server *testserver.Server) { - server.Handle("GET /api/2.0/policies/clusters/list", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("GET", "/api/2.0/policies/clusters/list", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { return compute.ListPoliciesResponse{ Policies: []compute.Policy{ { @@ -32,7 +38,7 @@ func AddHandlers(server *testserver.Server) { }, http.StatusOK }) - server.Handle("GET /api/2.0/instance-pools/list", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("GET", "/api/2.0/instance-pools/list", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { return compute.ListInstancePools{ InstancePools: []compute.InstancePoolAndStats{ { @@ -43,7 +49,7 @@ func AddHandlers(server *testserver.Server) { }, http.StatusOK }) - server.Handle("GET /api/2.1/clusters/list", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("GET", "/api/2.1/clusters/list", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { return compute.ListClustersResponse{ Clusters: []compute.ClusterDetails{ { @@ -58,20 +64,17 @@ func AddHandlers(server *testserver.Server) { }, http.StatusOK }) - server.Handle("GET /api/2.0/preview/scim/v2/Me", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { - return iam.User{ - Id: "1000012345", - UserName: "tester@databricks.com", - }, http.StatusOK + server.Handle("GET", "/api/2.0/preview/scim/v2/Me", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + return testUser, http.StatusOK }) - server.Handle("GET /api/2.0/workspace/get-status", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("GET", "/api/2.0/workspace/get-status", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { path := r.URL.Query().Get("path") return fakeWorkspace.WorkspaceGetStatus(path) }) - server.Handle("POST /api/2.0/workspace/mkdirs", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("POST", "/api/2.0/workspace/mkdirs", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { request := workspace.Mkdirs{} decoder := json.NewDecoder(r.Body) @@ -83,13 +86,13 @@ func AddHandlers(server *testserver.Server) { return fakeWorkspace.WorkspaceMkdirs(request) }) - server.Handle("GET /api/2.0/workspace/export", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("GET", "/api/2.0/workspace/export", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { path := r.URL.Query().Get("path") return fakeWorkspace.WorkspaceExport(path) }) - server.Handle("POST /api/2.0/workspace/delete", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("POST", "/api/2.0/workspace/delete", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { path := r.URL.Query().Get("path") recursiveStr := r.URL.Query().Get("recursive") var recursive bool @@ -103,8 +106,9 @@ func AddHandlers(server *testserver.Server) { return fakeWorkspace.WorkspaceDelete(path, recursive) }) - server.Handle("POST /api/2.0/workspace-files/import-file/{path}", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { - path := r.PathValue("path") + server.Handle("POST", "/api/2.0/workspace-files/import-file/{path:.*}", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + vars := mux.Vars(r) + path := vars["path"] body := new(bytes.Buffer) _, err := body.ReadFrom(r.Body) @@ -115,14 +119,15 @@ func AddHandlers(server *testserver.Server) { return fakeWorkspace.WorkspaceFilesImportFile(path, body.Bytes()) }) - server.Handle("GET /api/2.1/unity-catalog/current-metastore-assignment", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("GET", "/api/2.1/unity-catalog/current-metastore-assignment", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { return catalog.MetastoreAssignment{ DefaultCatalogName: "main", }, http.StatusOK }) - server.Handle("GET /api/2.0/permissions/directories/{objectId}", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { - objectId := r.PathValue("objectId") + server.Handle("GET", "/api/2.0/permissions/directories/{objectId}", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + vars := mux.Vars(r) + objectId := vars["objectId"] return workspace.WorkspaceObjectPermissions{ ObjectId: objectId, @@ -140,7 +145,7 @@ func AddHandlers(server *testserver.Server) { }, http.StatusOK }) - server.Handle("POST /api/2.1/jobs/create", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("POST", "/api/2.1/jobs/create", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { request := jobs.CreateJob{} decoder := json.NewDecoder(r.Body) @@ -152,15 +157,31 @@ func AddHandlers(server *testserver.Server) { return fakeWorkspace.JobsCreate(request) }) - server.Handle("GET /api/2.1/jobs/get", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("GET", "/api/2.1/jobs/get", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { jobId := r.URL.Query().Get("job_id") return fakeWorkspace.JobsGet(jobId) }) - server.Handle("GET /api/2.1/jobs/list", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + server.Handle("GET", "/api/2.1/jobs/list", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { return fakeWorkspace.JobsList() }) + + server.Handle("GET", "/oidc/.well-known/oauth-authorization-server", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + return map[string]string{ + "authorization_endpoint": server.URL + "oidc/v1/authorize", + "token_endpoint": server.URL + "/oidc/v1/token", + }, http.StatusOK + }) + + server.Handle("POST", "/oidc/v1/token", func(fakeWorkspace *testserver.FakeWorkspace, r *http.Request) (any, int) { + return map[string]string{ + "access_token": "oauth-token", + "expires_in": "3600", + "scope": "all-apis", + "token_type": "Bearer", + }, http.StatusOK + }) } func internalError(err error) (any, int) { diff --git a/acceptance/terraform/main.tf b/acceptance/terraform/main.tf index 93f665ff4..674b41a3c 100644 --- a/acceptance/terraform/main.tf +++ b/acceptance/terraform/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { databricks = { source = "databricks/databricks" - version = "1.64.1" + version = "1.65.1" } } diff --git a/acceptance/terraform/output.txt b/acceptance/terraform/output.txt index 6bdc809f6..851785827 100644 --- a/acceptance/terraform/output.txt +++ b/acceptance/terraform/output.txt @@ -4,9 +4,9 @@ Initializing the backend... Initializing provider plugins... -- Finding databricks/databricks versions matching "1.64.1"... -- Installing databricks/databricks v1.64.1... -- Installed databricks/databricks v1.64.1 (unauthenticated) +- Finding databricks/databricks versions matching "1.65.1"... +- Installing databricks/databricks v1.65.1... +- Installed databricks/databricks v1.65.1 (unauthenticated) Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository diff --git a/bundle/deploy/terraform/tfdyn/convert_pipeline_test.go b/bundle/deploy/terraform/tfdyn/convert_pipeline_test.go index d8de55bf0..63d023c43 100644 --- a/bundle/deploy/terraform/tfdyn/convert_pipeline_test.go +++ b/bundle/deploy/terraform/tfdyn/convert_pipeline_test.go @@ -122,6 +122,9 @@ func TestConvertPipeline(t *testing.T) { "num_workers": int64(1), }, }, + "run_as": map[string]any{ + "user_name": "foo@bar.com", + }, }, out.Pipeline["my_pipeline"]) // Assert equality on the permissions diff --git a/bundle/internal/tf/codegen/README.md b/bundle/internal/tf/codegen/README.md index b1f8a33a8..968bf29ed 100644 --- a/bundle/internal/tf/codegen/README.md +++ b/bundle/internal/tf/codegen/README.md @@ -19,3 +19,6 @@ How to regenerate Go structs from an updated terraform provider? 2. Delete `./tmp` if it exists 3. Run `go run .` 4. Run `gofmt -s -w ../schema` +5. Go back to the root of the repo. +6. Update `/acceptance/terraform/main.tf` file to use new version of TF provider +7. Run `go test ./acceptance -v -update -run TestAccept/terraform` to update test output with a new version of TF provider diff --git a/bundle/internal/tf/codegen/schema/version.go b/bundle/internal/tf/codegen/schema/version.go index 393afd6ed..46548f3e8 100644 --- a/bundle/internal/tf/codegen/schema/version.go +++ b/bundle/internal/tf/codegen/schema/version.go @@ -1,3 +1,3 @@ package schema -const ProviderVersion = "1.64.1" +const ProviderVersion = "1.65.1" diff --git a/bundle/internal/tf/schema/data_source_catalog.go b/bundle/internal/tf/schema/data_source_catalog.go index 6f9237cfa..4b8c6df97 100644 --- a/bundle/internal/tf/schema/data_source_catalog.go +++ b/bundle/internal/tf/schema/data_source_catalog.go @@ -28,7 +28,6 @@ type DataSourceCatalogCatalogInfo struct { Owner string `json:"owner,omitempty"` Properties map[string]string `json:"properties,omitempty"` ProviderName string `json:"provider_name,omitempty"` - SecurableKind string `json:"securable_kind,omitempty"` SecurableType string `json:"securable_type,omitempty"` ShareName string `json:"share_name,omitempty"` StorageLocation string `json:"storage_location,omitempty"` diff --git a/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_access_policy_setting.go b/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_access_policy_setting.go new file mode 100644 index 000000000..d816b235d --- /dev/null +++ b/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_access_policy_setting.go @@ -0,0 +1,14 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourceAibiDashboardEmbeddingAccessPolicySettingAibiDashboardEmbeddingAccessPolicy struct { + AccessPolicyType string `json:"access_policy_type"` +} + +type ResourceAibiDashboardEmbeddingAccessPolicySetting struct { + Etag string `json:"etag,omitempty"` + Id string `json:"id,omitempty"` + SettingName string `json:"setting_name,omitempty"` + AibiDashboardEmbeddingAccessPolicy *ResourceAibiDashboardEmbeddingAccessPolicySettingAibiDashboardEmbeddingAccessPolicy `json:"aibi_dashboard_embedding_access_policy,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_approved_domains_setting.go b/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_approved_domains_setting.go new file mode 100644 index 000000000..690b334cd --- /dev/null +++ b/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_approved_domains_setting.go @@ -0,0 +1,14 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourceAibiDashboardEmbeddingApprovedDomainsSettingAibiDashboardEmbeddingApprovedDomains struct { + ApprovedDomains []string `json:"approved_domains"` +} + +type ResourceAibiDashboardEmbeddingApprovedDomainsSetting struct { + Etag string `json:"etag,omitempty"` + Id string `json:"id,omitempty"` + SettingName string `json:"setting_name,omitempty"` + AibiDashboardEmbeddingApprovedDomains *ResourceAibiDashboardEmbeddingApprovedDomainsSettingAibiDashboardEmbeddingApprovedDomains `json:"aibi_dashboard_embedding_approved_domains,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_custom_app_integration.go b/bundle/internal/tf/schema/resource_custom_app_integration.go index e89eb7fe5..0a964f6ab 100644 --- a/bundle/internal/tf/schema/resource_custom_app_integration.go +++ b/bundle/internal/tf/schema/resource_custom_app_integration.go @@ -8,16 +8,17 @@ type ResourceCustomAppIntegrationTokenAccessPolicy struct { } type ResourceCustomAppIntegration struct { - ClientId string `json:"client_id,omitempty"` - ClientSecret string `json:"client_secret,omitempty"` - Confidential bool `json:"confidential,omitempty"` - CreateTime string `json:"create_time,omitempty"` - CreatedBy int `json:"created_by,omitempty"` - CreatorUsername string `json:"creator_username,omitempty"` - Id string `json:"id,omitempty"` - IntegrationId string `json:"integration_id,omitempty"` - Name string `json:"name,omitempty"` - RedirectUrls []string `json:"redirect_urls,omitempty"` - Scopes []string `json:"scopes,omitempty"` - TokenAccessPolicy *ResourceCustomAppIntegrationTokenAccessPolicy `json:"token_access_policy,omitempty"` + ClientId string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + Confidential bool `json:"confidential,omitempty"` + CreateTime string `json:"create_time,omitempty"` + CreatedBy int `json:"created_by,omitempty"` + CreatorUsername string `json:"creator_username,omitempty"` + Id string `json:"id,omitempty"` + IntegrationId string `json:"integration_id,omitempty"` + Name string `json:"name,omitempty"` + RedirectUrls []string `json:"redirect_urls,omitempty"` + Scopes []string `json:"scopes,omitempty"` + UserAuthorizedScopes []string `json:"user_authorized_scopes,omitempty"` + TokenAccessPolicy *ResourceCustomAppIntegrationTokenAccessPolicy `json:"token_access_policy,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_job.go b/bundle/internal/tf/schema/resource_job.go index da277b5c1..2c27f0be7 100644 --- a/bundle/internal/tf/schema/resource_job.go +++ b/bundle/internal/tf/schema/resource_job.go @@ -1489,6 +1489,7 @@ type ResourceJob struct { MaxRetries int `json:"max_retries,omitempty"` MinRetryIntervalMillis int `json:"min_retry_interval_millis,omitempty"` Name string `json:"name,omitempty"` + PerformanceTarget string `json:"performance_target,omitempty"` RetryOnTimeout bool `json:"retry_on_timeout,omitempty"` Tags map[string]string `json:"tags,omitempty"` TimeoutSeconds int `json:"timeout_seconds,omitempty"` diff --git a/bundle/internal/tf/schema/resource_pipeline.go b/bundle/internal/tf/schema/resource_pipeline.go index ebdb85027..8e260e65c 100644 --- a/bundle/internal/tf/schema/resource_pipeline.go +++ b/bundle/internal/tf/schema/resource_pipeline.go @@ -249,6 +249,11 @@ type ResourcePipelineRestartWindow struct { TimeZoneId string `json:"time_zone_id,omitempty"` } +type ResourcePipelineRunAs struct { + ServicePrincipalName string `json:"service_principal_name,omitempty"` + UserName string `json:"user_name,omitempty"` +} + type ResourcePipelineTriggerCron struct { QuartzCronSchedule string `json:"quartz_cron_schedule,omitempty"` TimezoneId string `json:"timezone_id,omitempty"` @@ -296,5 +301,6 @@ type ResourcePipeline struct { Library []ResourcePipelineLibrary `json:"library,omitempty"` Notification []ResourcePipelineNotification `json:"notification,omitempty"` RestartWindow *ResourcePipelineRestartWindow `json:"restart_window,omitempty"` + RunAs *ResourcePipelineRunAs `json:"run_as,omitempty"` Trigger *ResourcePipelineTrigger `json:"trigger,omitempty"` } diff --git a/bundle/internal/tf/schema/resources.go b/bundle/internal/tf/schema/resources.go index b57c2711a..c6eaa5b21 100644 --- a/bundle/internal/tf/schema/resources.go +++ b/bundle/internal/tf/schema/resources.go @@ -3,115 +3,119 @@ package schema type Resources struct { - AccessControlRuleSet map[string]any `json:"databricks_access_control_rule_set,omitempty"` - Alert map[string]any `json:"databricks_alert,omitempty"` - App map[string]any `json:"databricks_app,omitempty"` - ArtifactAllowlist map[string]any `json:"databricks_artifact_allowlist,omitempty"` - AutomaticClusterUpdateWorkspaceSetting map[string]any `json:"databricks_automatic_cluster_update_workspace_setting,omitempty"` - AwsS3Mount map[string]any `json:"databricks_aws_s3_mount,omitempty"` - AzureAdlsGen1Mount map[string]any `json:"databricks_azure_adls_gen1_mount,omitempty"` - AzureAdlsGen2Mount map[string]any `json:"databricks_azure_adls_gen2_mount,omitempty"` - AzureBlobMount map[string]any `json:"databricks_azure_blob_mount,omitempty"` - Budget map[string]any `json:"databricks_budget,omitempty"` - Catalog map[string]any `json:"databricks_catalog,omitempty"` - CatalogWorkspaceBinding map[string]any `json:"databricks_catalog_workspace_binding,omitempty"` - Cluster map[string]any `json:"databricks_cluster,omitempty"` - ClusterPolicy map[string]any `json:"databricks_cluster_policy,omitempty"` - ComplianceSecurityProfileWorkspaceSetting map[string]any `json:"databricks_compliance_security_profile_workspace_setting,omitempty"` - Connection map[string]any `json:"databricks_connection,omitempty"` - Credential map[string]any `json:"databricks_credential,omitempty"` - CustomAppIntegration map[string]any `json:"databricks_custom_app_integration,omitempty"` - Dashboard map[string]any `json:"databricks_dashboard,omitempty"` - DbfsFile map[string]any `json:"databricks_dbfs_file,omitempty"` - DefaultNamespaceSetting map[string]any `json:"databricks_default_namespace_setting,omitempty"` - Directory map[string]any `json:"databricks_directory,omitempty"` - EnhancedSecurityMonitoringWorkspaceSetting map[string]any `json:"databricks_enhanced_security_monitoring_workspace_setting,omitempty"` - Entitlements map[string]any `json:"databricks_entitlements,omitempty"` - ExternalLocation map[string]any `json:"databricks_external_location,omitempty"` - File map[string]any `json:"databricks_file,omitempty"` - GitCredential map[string]any `json:"databricks_git_credential,omitempty"` - GlobalInitScript map[string]any `json:"databricks_global_init_script,omitempty"` - Grant map[string]any `json:"databricks_grant,omitempty"` - Grants map[string]any `json:"databricks_grants,omitempty"` - Group map[string]any `json:"databricks_group,omitempty"` - GroupInstanceProfile map[string]any `json:"databricks_group_instance_profile,omitempty"` - GroupMember map[string]any `json:"databricks_group_member,omitempty"` - GroupRole map[string]any `json:"databricks_group_role,omitempty"` - InstancePool map[string]any `json:"databricks_instance_pool,omitempty"` - InstanceProfile map[string]any `json:"databricks_instance_profile,omitempty"` - IpAccessList map[string]any `json:"databricks_ip_access_list,omitempty"` - Job map[string]any `json:"databricks_job,omitempty"` - LakehouseMonitor map[string]any `json:"databricks_lakehouse_monitor,omitempty"` - Library map[string]any `json:"databricks_library,omitempty"` - Metastore map[string]any `json:"databricks_metastore,omitempty"` - MetastoreAssignment map[string]any `json:"databricks_metastore_assignment,omitempty"` - MetastoreDataAccess map[string]any `json:"databricks_metastore_data_access,omitempty"` - MlflowExperiment map[string]any `json:"databricks_mlflow_experiment,omitempty"` - MlflowModel map[string]any `json:"databricks_mlflow_model,omitempty"` - MlflowWebhook map[string]any `json:"databricks_mlflow_webhook,omitempty"` - ModelServing map[string]any `json:"databricks_model_serving,omitempty"` - Mount map[string]any `json:"databricks_mount,omitempty"` - MwsCredentials map[string]any `json:"databricks_mws_credentials,omitempty"` - MwsCustomerManagedKeys map[string]any `json:"databricks_mws_customer_managed_keys,omitempty"` - MwsLogDelivery map[string]any `json:"databricks_mws_log_delivery,omitempty"` - MwsNccBinding map[string]any `json:"databricks_mws_ncc_binding,omitempty"` - MwsNccPrivateEndpointRule map[string]any `json:"databricks_mws_ncc_private_endpoint_rule,omitempty"` - MwsNetworkConnectivityConfig map[string]any `json:"databricks_mws_network_connectivity_config,omitempty"` - MwsNetworks map[string]any `json:"databricks_mws_networks,omitempty"` - MwsPermissionAssignment map[string]any `json:"databricks_mws_permission_assignment,omitempty"` - MwsPrivateAccessSettings map[string]any `json:"databricks_mws_private_access_settings,omitempty"` - MwsStorageConfigurations map[string]any `json:"databricks_mws_storage_configurations,omitempty"` - MwsVpcEndpoint map[string]any `json:"databricks_mws_vpc_endpoint,omitempty"` - MwsWorkspaces map[string]any `json:"databricks_mws_workspaces,omitempty"` - Notebook map[string]any `json:"databricks_notebook,omitempty"` - NotificationDestination map[string]any `json:"databricks_notification_destination,omitempty"` - OboToken map[string]any `json:"databricks_obo_token,omitempty"` - OnlineTable map[string]any `json:"databricks_online_table,omitempty"` - PermissionAssignment map[string]any `json:"databricks_permission_assignment,omitempty"` - Permissions map[string]any `json:"databricks_permissions,omitempty"` - Pipeline map[string]any `json:"databricks_pipeline,omitempty"` - Provider map[string]any `json:"databricks_provider,omitempty"` - QualityMonitor map[string]any `json:"databricks_quality_monitor,omitempty"` - Query map[string]any `json:"databricks_query,omitempty"` - Recipient map[string]any `json:"databricks_recipient,omitempty"` - RegisteredModel map[string]any `json:"databricks_registered_model,omitempty"` - Repo map[string]any `json:"databricks_repo,omitempty"` - RestrictWorkspaceAdminsSetting map[string]any `json:"databricks_restrict_workspace_admins_setting,omitempty"` - Schema map[string]any `json:"databricks_schema,omitempty"` - Secret map[string]any `json:"databricks_secret,omitempty"` - SecretAcl map[string]any `json:"databricks_secret_acl,omitempty"` - SecretScope map[string]any `json:"databricks_secret_scope,omitempty"` - ServicePrincipal map[string]any `json:"databricks_service_principal,omitempty"` - ServicePrincipalRole map[string]any `json:"databricks_service_principal_role,omitempty"` - ServicePrincipalSecret map[string]any `json:"databricks_service_principal_secret,omitempty"` - Share map[string]any `json:"databricks_share,omitempty"` - SqlAlert map[string]any `json:"databricks_sql_alert,omitempty"` - SqlDashboard map[string]any `json:"databricks_sql_dashboard,omitempty"` - SqlEndpoint map[string]any `json:"databricks_sql_endpoint,omitempty"` - SqlGlobalConfig map[string]any `json:"databricks_sql_global_config,omitempty"` - SqlPermissions map[string]any `json:"databricks_sql_permissions,omitempty"` - SqlQuery map[string]any `json:"databricks_sql_query,omitempty"` - SqlTable map[string]any `json:"databricks_sql_table,omitempty"` - SqlVisualization map[string]any `json:"databricks_sql_visualization,omitempty"` - SqlWidget map[string]any `json:"databricks_sql_widget,omitempty"` - StorageCredential map[string]any `json:"databricks_storage_credential,omitempty"` - SystemSchema map[string]any `json:"databricks_system_schema,omitempty"` - Table map[string]any `json:"databricks_table,omitempty"` - Token map[string]any `json:"databricks_token,omitempty"` - User map[string]any `json:"databricks_user,omitempty"` - UserInstanceProfile map[string]any `json:"databricks_user_instance_profile,omitempty"` - UserRole map[string]any `json:"databricks_user_role,omitempty"` - VectorSearchEndpoint map[string]any `json:"databricks_vector_search_endpoint,omitempty"` - VectorSearchIndex map[string]any `json:"databricks_vector_search_index,omitempty"` - Volume map[string]any `json:"databricks_volume,omitempty"` - WorkspaceBinding map[string]any `json:"databricks_workspace_binding,omitempty"` - WorkspaceConf map[string]any `json:"databricks_workspace_conf,omitempty"` - WorkspaceFile map[string]any `json:"databricks_workspace_file,omitempty"` + AccessControlRuleSet map[string]any `json:"databricks_access_control_rule_set,omitempty"` + AibiDashboardEmbeddingAccessPolicySetting map[string]any `json:"databricks_aibi_dashboard_embedding_access_policy_setting,omitempty"` + AibiDashboardEmbeddingApprovedDomainsSetting map[string]any `json:"databricks_aibi_dashboard_embedding_approved_domains_setting,omitempty"` + Alert map[string]any `json:"databricks_alert,omitempty"` + App map[string]any `json:"databricks_app,omitempty"` + ArtifactAllowlist map[string]any `json:"databricks_artifact_allowlist,omitempty"` + AutomaticClusterUpdateWorkspaceSetting map[string]any `json:"databricks_automatic_cluster_update_workspace_setting,omitempty"` + AwsS3Mount map[string]any `json:"databricks_aws_s3_mount,omitempty"` + AzureAdlsGen1Mount map[string]any `json:"databricks_azure_adls_gen1_mount,omitempty"` + AzureAdlsGen2Mount map[string]any `json:"databricks_azure_adls_gen2_mount,omitempty"` + AzureBlobMount map[string]any `json:"databricks_azure_blob_mount,omitempty"` + Budget map[string]any `json:"databricks_budget,omitempty"` + Catalog map[string]any `json:"databricks_catalog,omitempty"` + CatalogWorkspaceBinding map[string]any `json:"databricks_catalog_workspace_binding,omitempty"` + Cluster map[string]any `json:"databricks_cluster,omitempty"` + ClusterPolicy map[string]any `json:"databricks_cluster_policy,omitempty"` + ComplianceSecurityProfileWorkspaceSetting map[string]any `json:"databricks_compliance_security_profile_workspace_setting,omitempty"` + Connection map[string]any `json:"databricks_connection,omitempty"` + Credential map[string]any `json:"databricks_credential,omitempty"` + CustomAppIntegration map[string]any `json:"databricks_custom_app_integration,omitempty"` + Dashboard map[string]any `json:"databricks_dashboard,omitempty"` + DbfsFile map[string]any `json:"databricks_dbfs_file,omitempty"` + DefaultNamespaceSetting map[string]any `json:"databricks_default_namespace_setting,omitempty"` + Directory map[string]any `json:"databricks_directory,omitempty"` + EnhancedSecurityMonitoringWorkspaceSetting map[string]any `json:"databricks_enhanced_security_monitoring_workspace_setting,omitempty"` + Entitlements map[string]any `json:"databricks_entitlements,omitempty"` + ExternalLocation map[string]any `json:"databricks_external_location,omitempty"` + File map[string]any `json:"databricks_file,omitempty"` + GitCredential map[string]any `json:"databricks_git_credential,omitempty"` + GlobalInitScript map[string]any `json:"databricks_global_init_script,omitempty"` + Grant map[string]any `json:"databricks_grant,omitempty"` + Grants map[string]any `json:"databricks_grants,omitempty"` + Group map[string]any `json:"databricks_group,omitempty"` + GroupInstanceProfile map[string]any `json:"databricks_group_instance_profile,omitempty"` + GroupMember map[string]any `json:"databricks_group_member,omitempty"` + GroupRole map[string]any `json:"databricks_group_role,omitempty"` + InstancePool map[string]any `json:"databricks_instance_pool,omitempty"` + InstanceProfile map[string]any `json:"databricks_instance_profile,omitempty"` + IpAccessList map[string]any `json:"databricks_ip_access_list,omitempty"` + Job map[string]any `json:"databricks_job,omitempty"` + LakehouseMonitor map[string]any `json:"databricks_lakehouse_monitor,omitempty"` + Library map[string]any `json:"databricks_library,omitempty"` + Metastore map[string]any `json:"databricks_metastore,omitempty"` + MetastoreAssignment map[string]any `json:"databricks_metastore_assignment,omitempty"` + MetastoreDataAccess map[string]any `json:"databricks_metastore_data_access,omitempty"` + MlflowExperiment map[string]any `json:"databricks_mlflow_experiment,omitempty"` + MlflowModel map[string]any `json:"databricks_mlflow_model,omitempty"` + MlflowWebhook map[string]any `json:"databricks_mlflow_webhook,omitempty"` + ModelServing map[string]any `json:"databricks_model_serving,omitempty"` + Mount map[string]any `json:"databricks_mount,omitempty"` + MwsCredentials map[string]any `json:"databricks_mws_credentials,omitempty"` + MwsCustomerManagedKeys map[string]any `json:"databricks_mws_customer_managed_keys,omitempty"` + MwsLogDelivery map[string]any `json:"databricks_mws_log_delivery,omitempty"` + MwsNccBinding map[string]any `json:"databricks_mws_ncc_binding,omitempty"` + MwsNccPrivateEndpointRule map[string]any `json:"databricks_mws_ncc_private_endpoint_rule,omitempty"` + MwsNetworkConnectivityConfig map[string]any `json:"databricks_mws_network_connectivity_config,omitempty"` + MwsNetworks map[string]any `json:"databricks_mws_networks,omitempty"` + MwsPermissionAssignment map[string]any `json:"databricks_mws_permission_assignment,omitempty"` + MwsPrivateAccessSettings map[string]any `json:"databricks_mws_private_access_settings,omitempty"` + MwsStorageConfigurations map[string]any `json:"databricks_mws_storage_configurations,omitempty"` + MwsVpcEndpoint map[string]any `json:"databricks_mws_vpc_endpoint,omitempty"` + MwsWorkspaces map[string]any `json:"databricks_mws_workspaces,omitempty"` + Notebook map[string]any `json:"databricks_notebook,omitempty"` + NotificationDestination map[string]any `json:"databricks_notification_destination,omitempty"` + OboToken map[string]any `json:"databricks_obo_token,omitempty"` + OnlineTable map[string]any `json:"databricks_online_table,omitempty"` + PermissionAssignment map[string]any `json:"databricks_permission_assignment,omitempty"` + Permissions map[string]any `json:"databricks_permissions,omitempty"` + Pipeline map[string]any `json:"databricks_pipeline,omitempty"` + Provider map[string]any `json:"databricks_provider,omitempty"` + QualityMonitor map[string]any `json:"databricks_quality_monitor,omitempty"` + Query map[string]any `json:"databricks_query,omitempty"` + Recipient map[string]any `json:"databricks_recipient,omitempty"` + RegisteredModel map[string]any `json:"databricks_registered_model,omitempty"` + Repo map[string]any `json:"databricks_repo,omitempty"` + RestrictWorkspaceAdminsSetting map[string]any `json:"databricks_restrict_workspace_admins_setting,omitempty"` + Schema map[string]any `json:"databricks_schema,omitempty"` + Secret map[string]any `json:"databricks_secret,omitempty"` + SecretAcl map[string]any `json:"databricks_secret_acl,omitempty"` + SecretScope map[string]any `json:"databricks_secret_scope,omitempty"` + ServicePrincipal map[string]any `json:"databricks_service_principal,omitempty"` + ServicePrincipalRole map[string]any `json:"databricks_service_principal_role,omitempty"` + ServicePrincipalSecret map[string]any `json:"databricks_service_principal_secret,omitempty"` + Share map[string]any `json:"databricks_share,omitempty"` + SqlAlert map[string]any `json:"databricks_sql_alert,omitempty"` + SqlDashboard map[string]any `json:"databricks_sql_dashboard,omitempty"` + SqlEndpoint map[string]any `json:"databricks_sql_endpoint,omitempty"` + SqlGlobalConfig map[string]any `json:"databricks_sql_global_config,omitempty"` + SqlPermissions map[string]any `json:"databricks_sql_permissions,omitempty"` + SqlQuery map[string]any `json:"databricks_sql_query,omitempty"` + SqlTable map[string]any `json:"databricks_sql_table,omitempty"` + SqlVisualization map[string]any `json:"databricks_sql_visualization,omitempty"` + SqlWidget map[string]any `json:"databricks_sql_widget,omitempty"` + StorageCredential map[string]any `json:"databricks_storage_credential,omitempty"` + SystemSchema map[string]any `json:"databricks_system_schema,omitempty"` + Table map[string]any `json:"databricks_table,omitempty"` + Token map[string]any `json:"databricks_token,omitempty"` + User map[string]any `json:"databricks_user,omitempty"` + UserInstanceProfile map[string]any `json:"databricks_user_instance_profile,omitempty"` + UserRole map[string]any `json:"databricks_user_role,omitempty"` + VectorSearchEndpoint map[string]any `json:"databricks_vector_search_endpoint,omitempty"` + VectorSearchIndex map[string]any `json:"databricks_vector_search_index,omitempty"` + Volume map[string]any `json:"databricks_volume,omitempty"` + WorkspaceBinding map[string]any `json:"databricks_workspace_binding,omitempty"` + WorkspaceConf map[string]any `json:"databricks_workspace_conf,omitempty"` + WorkspaceFile map[string]any `json:"databricks_workspace_file,omitempty"` } func NewResources() *Resources { return &Resources{ - AccessControlRuleSet: make(map[string]any), + AccessControlRuleSet: make(map[string]any), + AibiDashboardEmbeddingAccessPolicySetting: make(map[string]any), + AibiDashboardEmbeddingApprovedDomainsSetting: make(map[string]any), Alert: make(map[string]any), App: make(map[string]any), ArtifactAllowlist: make(map[string]any), diff --git a/bundle/internal/tf/schema/root.go b/bundle/internal/tf/schema/root.go index 2ac852355..816e8e6aa 100644 --- a/bundle/internal/tf/schema/root.go +++ b/bundle/internal/tf/schema/root.go @@ -21,7 +21,7 @@ type Root struct { const ProviderHost = "registry.terraform.io" const ProviderSource = "databricks/databricks" -const ProviderVersion = "1.64.1" +const ProviderVersion = "1.65.1" func NewRoot() *Root { return &Root{ diff --git a/go.mod b/go.mod index db3261beb..2e2505361 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/databricks/databricks-sdk-go v0.57.0 // Apache 2.0 github.com/fatih/color v1.18.0 // MIT github.com/google/uuid v1.6.0 // BSD-3-Clause + github.com/gorilla/mux v1.8.1 // BSD 3-Clause github.com/hashicorp/go-version v1.7.0 // MPL 2.0 github.com/hashicorp/hc-install v0.9.1 // MPL 2.0 github.com/hashicorp/terraform-exec v0.22.0 // MPL 2.0 @@ -27,11 +28,11 @@ require ( github.com/stretchr/testify v1.10.0 // MIT github.com/wI2L/jsondiff v0.6.1 // MIT golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 - golang.org/x/mod v0.22.0 - golang.org/x/oauth2 v0.25.0 - golang.org/x/sync v0.10.0 - golang.org/x/term v0.28.0 - golang.org/x/text v0.21.0 + golang.org/x/mod v0.23.0 + golang.org/x/oauth2 v0.26.0 + golang.org/x/sync v0.11.0 + golang.org/x/term v0.29.0 + golang.org/x/text v0.22.0 gopkg.in/ini.v1 v1.67.0 // Apache 2.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -71,7 +72,7 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.182.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect diff --git a/go.sum b/go.sum index d6102a0d1..fbf942148 100644 --- a/go.sum +++ b/go.sum @@ -97,6 +97,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= @@ -199,8 +201,8 @@ golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -210,13 +212,13 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= +golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -227,14 +229,14 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/libs/testserver/server.go b/libs/testserver/server.go index d5877e90e..cf4d5aca2 100644 --- a/libs/testserver/server.go +++ b/libs/testserver/server.go @@ -9,6 +9,8 @@ import ( "strings" "sync" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" "github.com/databricks/cli/internal/testutil" @@ -17,7 +19,7 @@ import ( type Server struct { *httptest.Server - Mux *http.ServeMux + Router *mux.Router t testutil.TestingT @@ -34,26 +36,25 @@ type Request struct { Headers http.Header `json:"headers,omitempty"` Method string `json:"method"` Path string `json:"path"` - Body any `json:"body"` + Body any `json:"body,omitempty"` + RawBody string `json:"raw_body,omitempty"` } func New(t testutil.TestingT) *Server { - mux := http.NewServeMux() - server := httptest.NewServer(mux) + router := mux.NewRouter() + server := httptest.NewServer(router) t.Cleanup(server.Close) s := &Server{ Server: server, - Mux: mux, + Router: router, t: t, mu: &sync.Mutex{}, fakeWorkspaces: map[string]*FakeWorkspace{}, } - // The server resolves conflicting handlers by using the one with higher - // specificity. This handler is the least specific, so it will be used as a - // fallback when no other handlers match. - s.Handle("/", func(fakeWorkspace *FakeWorkspace, r *http.Request) (any, int) { + // Set up the not found handler as fallback + router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pattern := r.Method + " " + r.URL.Path t.Errorf(` @@ -74,9 +75,22 @@ Response.StatusCode = `, pattern, pattern) - return apierr.APIError{ + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotImplemented) + + resp := apierr.APIError{ Message: "No stub found for pattern: " + pattern, - }, http.StatusNotImplemented + } + + respBytes, err := json.Marshal(resp) + if err != nil { + t.Errorf("JSON encoding error: %s", err) + respBytes = []byte("{\"message\": \"JSON encoding error\"}") + } + + if _, err := w.Write(respBytes); err != nil { + t.Errorf("Response write error: %s", err) + } }) return s @@ -84,8 +98,8 @@ Response.StatusCode = type HandlerFunc func(fakeWorkspace *FakeWorkspace, req *http.Request) (resp any, statusCode int) -func (s *Server) Handle(pattern string, handler HandlerFunc) { - s.Mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { +func (s *Server) Handle(method, path string, handler HandlerFunc) { + s.Router.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { // For simplicity we process requests sequentially. It's fast enough because // we don't do any IO except reading and writing request/response bodies. s.mu.Lock() @@ -119,13 +133,19 @@ func (s *Server) Handle(pattern string, handler HandlerFunc) { } } - s.Requests = append(s.Requests, Request{ + req := Request{ Headers: headers, Method: r.Method, Path: r.URL.Path, - Body: json.RawMessage(body), - }) + } + if json.Valid(body) { + req.Body = json.RawMessage(body) + } else { + req.RawBody = string(body) + } + + s.Requests = append(s.Requests, req) } w.Header().Set("Content-Type", "application/json") @@ -149,7 +169,7 @@ func (s *Server) Handle(pattern string, handler HandlerFunc) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - }) + }).Methods(method) } func getToken(r *http.Request) string {