diff --git a/bindings/zeebe/client.go b/bindings/zeebe/client.go index 76c12d260f..9dfa321c8b 100644 --- a/bindings/zeebe/client.go +++ b/bindings/zeebe/client.go @@ -15,6 +15,8 @@ package zeebe import ( "errors" + "fmt" + "strings" "time" "github.com/camunda/zeebe/clients/go/v8/pkg/zbc" @@ -26,6 +28,8 @@ import ( var ErrMissingGatewayAddr = errors.New("gatewayAddr is a required attribute") +var ErrInvalidOAuthMetadata = errors.New("invalid OAuth metadata") + // ClientFactory enables injection for testing. type ClientFactory interface { Get(metadata bindings.Metadata) (zbc.Client, error) @@ -41,6 +45,12 @@ type ClientMetadata struct { GatewayKeepAlive time.Duration `json:"gatewayKeepAlive" mapstructure:"gatewayKeepAlive"` CaCertificatePath string `json:"caCertificatePath" mapstructure:"caCertificatePath"` UsePlaintextConnection bool `json:"usePlainTextConnection,string" mapstructure:"usePlainTextConnection"` + ClientID string `json:"clientId" mapstructure:"clientId"` + ClientSecret string `json:"clientSecret" mapstructure:"clientSecret"` + AuthorizationServerURL string `json:"authorizationServerUrl" mapstructure:"authorizationServerUrl"` + TokenAudience string `json:"tokenAudience" mapstructure:"tokenAudience"` + TokenScope string `json:"tokenScope" mapstructure:"tokenScope"` + ClientConfigPath string `json:"clientConfigPath" mapstructure:"clientConfigPath"` } // NewClientFactoryImpl returns a new ClientFactory instance. @@ -54,11 +64,17 @@ func (c *ClientFactoryImpl) Get(metadata bindings.Metadata) (zbc.Client, error) return nil, err } + credentialsProvider, err := meta.newCredentialsProvider() + if err != nil { + return nil, err + } + client, err := zbc.NewClient(&zbc.ClientConfig{ GatewayAddress: meta.GatewayAddr, UsePlaintextConnection: meta.UsePlaintextConnection, CaCertificatePath: meta.CaCertificatePath, KeepAlive: meta.GatewayKeepAlive, + CredentialsProvider: credentialsProvider, }) if err != nil { return nil, err @@ -80,3 +96,67 @@ func (c *ClientFactoryImpl) parseMetadata(meta bindings.Metadata) (*ClientMetada return &m, nil } + +func (m *ClientMetadata) oauthConfigured() bool { + return m.ClientID != "" || + m.ClientSecret != "" || + m.AuthorizationServerURL != "" || + m.TokenAudience != "" || + m.TokenScope != "" || + m.ClientConfigPath != "" +} + +func (m *ClientMetadata) validateOAuthMetadata() error { + if !m.oauthConfigured() { + return nil + } + + missing := make([]string, 0, 4) + if m.ClientID == "" { + missing = append(missing, "clientId") + } + if m.ClientSecret == "" { + missing = append(missing, "clientSecret") + } + if m.AuthorizationServerURL == "" { + missing = append(missing, "authorizationServerUrl") + } + if m.TokenAudience == "" { + missing = append(missing, "tokenAudience") + } + + if len(missing) > 0 { + return fmt.Errorf("%w: when OAuth is configured, clientId, clientSecret, authorizationServerUrl, and tokenAudience must all be provided; missing: %s", ErrInvalidOAuthMetadata, strings.Join(missing, ", ")) + } + + return nil +} + +func (m *ClientMetadata) newCredentialsProvider() (zbc.CredentialsProvider, error) { + if err := m.validateOAuthMetadata(); err != nil { + return nil, err + } + + if !m.oauthConfigured() { + return nil, nil + } + + providerConfig := &zbc.OAuthProviderConfig{ + ClientID: m.ClientID, + ClientSecret: m.ClientSecret, + Audience: m.TokenAudience, + Scope: m.TokenScope, + AuthorizationServerURL: m.AuthorizationServerURL, + } + + if m.ClientConfigPath != "" { + cache, err := zbc.NewOAuthYamlCredentialsCache(m.ClientConfigPath) + if err != nil { + return nil, err + } + + providerConfig.Cache = cache + } + + return zbc.NewOAuthCredentialsProvider(providerConfig) +} diff --git a/bindings/zeebe/client_test.go b/bindings/zeebe/client_test.go index e9cb447aa2..83b570d302 100644 --- a/bindings/zeebe/client_test.go +++ b/bindings/zeebe/client_test.go @@ -14,6 +14,7 @@ limitations under the License. package zeebe import ( + "path/filepath" "testing" "time" @@ -31,6 +32,12 @@ func TestParseMetadata(t *testing.T) { "gatewayKeepAlive": "5s", "caCertificatePath": "/cert/path", "usePlaintextConnection": "true", + "clientId": "zeebe-client", + "clientSecret": "zeebe-secret", + "authorizationServerUrl": "https://issuer.example.com/oauth/token", + "tokenAudience": "zeebe-api", + "tokenScope": "read write", + "clientConfigPath": "/tmp/zeebe-cache.yaml", }}} client := ClientFactoryImpl{logger: logger.NewLogger("test")} meta, err := client.parseMetadata(m) @@ -39,6 +46,12 @@ func TestParseMetadata(t *testing.T) { assert.Equal(t, 5*time.Second, meta.GatewayKeepAlive) assert.Equal(t, "/cert/path", meta.CaCertificatePath) assert.True(t, meta.UsePlaintextConnection) + assert.Equal(t, "zeebe-client", meta.ClientID) + assert.Equal(t, "zeebe-secret", meta.ClientSecret) + assert.Equal(t, "https://issuer.example.com/oauth/token", meta.AuthorizationServerURL) + assert.Equal(t, "zeebe-api", meta.TokenAudience) + assert.Equal(t, "read write", meta.TokenScope) + assert.Equal(t, "/tmp/zeebe-cache.yaml", meta.ClientConfigPath) } func TestGatewayAddrMetadataIsMandatory(t *testing.T) { @@ -58,4 +71,66 @@ func TestParseMetadataDefaultValues(t *testing.T) { assert.Equal(t, time.Duration(0), meta.GatewayKeepAlive) assert.Equal(t, "", meta.CaCertificatePath) assert.False(t, meta.UsePlaintextConnection) + assert.Equal(t, "", meta.ClientID) + assert.Equal(t, "", meta.ClientSecret) + assert.Equal(t, "", meta.AuthorizationServerURL) + assert.Equal(t, "", meta.TokenAudience) + assert.Equal(t, "", meta.TokenScope) + assert.Equal(t, "", meta.ClientConfigPath) +} + +func TestNewCredentialsProviderSkipsWhenOAuthNotConfigured(t *testing.T) { + meta := &ClientMetadata{} + + provider, err := meta.newCredentialsProvider() + + require.NoError(t, err) + assert.Nil(t, provider) +} + +func TestNewCredentialsProviderReturnsErrorOnInvalidOAuthMetadata(t *testing.T) { + meta := &ClientMetadata{ + AuthorizationServerURL: "https://issuer.example.com/oauth/token", + TokenAudience: "zeebe-api", + ClientID: "zeebe-client", + } + + provider, err := meta.newCredentialsProvider() + + assert.Nil(t, provider) + require.ErrorIs(t, err, ErrInvalidOAuthMetadata) + assert.Contains(t, err.Error(), "missing: clientSecret") +} + +func TestNewCredentialsProviderReturnsErrorWhenOnlyOptionalOAuthFieldsProvided(t *testing.T) { + meta := &ClientMetadata{ + TokenScope: "scopeA", + } + + provider, err := meta.newCredentialsProvider() + + assert.Nil(t, provider) + require.ErrorIs(t, err, ErrInvalidOAuthMetadata) + errMsg := err.Error() + assert.Contains(t, errMsg, "missing:") + assert.Contains(t, errMsg, "clientId") + assert.Contains(t, errMsg, "clientSecret") + assert.Contains(t, errMsg, "authorizationServerUrl") + assert.Contains(t, errMsg, "tokenAudience") +} + +func TestNewCredentialsProviderCreatesOAuthProviderWithCustomCachePath(t *testing.T) { + meta := &ClientMetadata{ + ClientID: "zeebe-client", + ClientSecret: "zeebe-secret", + AuthorizationServerURL: "https://issuer.example.com/oauth/token", + TokenAudience: "zeebe-api", + TokenScope: "scopeA", + ClientConfigPath: filepath.Join(t.TempDir(), "zeebe-credentials.yaml"), + } + + provider, err := meta.newCredentialsProvider() + + require.NoError(t, err) + assert.NotNil(t, provider) } diff --git a/bindings/zeebe/command/metadata.yaml b/bindings/zeebe/command/metadata.yaml index 6c0a3d7d56..f5ac979068 100644 --- a/bindings/zeebe/command/metadata.yaml +++ b/bindings/zeebe/command/metadata.yaml @@ -59,4 +59,35 @@ metadata: required: false description: The path to the CA cert example: "/path/to/ca-cert" + type: string + - name: clientId + required: false + description: The OAuth client ID used to request an access token. Required when OAuth is configured, and must be set together with clientSecret, authorizationServerUrl, and tokenAudience. + example: "zeebe-client" + type: string + - name: clientSecret + required: false + sensitive: true + description: The OAuth client secret used to request an access token. Required when OAuth is configured, and must be set together with clientId, authorizationServerUrl, and tokenAudience. + example: "zeebe-secret" + type: string + - name: authorizationServerUrl + required: false + description: The OAuth authorization server URL used to obtain access tokens. Required when OAuth is configured, and must be set together with clientId, clientSecret, and tokenAudience. + example: "https://issuer.example.com/oauth/token" + type: string + - name: tokenAudience + required: false + description: The token audience for Zeebe API access. Required when OAuth is configured, and must be set together with clientId, clientSecret, and authorizationServerUrl. + example: "zeebe-api" + type: string + - name: tokenScope + required: false + description: Optional OAuth scope to request in the access token when OAuth is configured. + example: "read write" + type: string + - name: clientConfigPath + required: false + description: Optional path to the OAuth credentials cache file when OAuth is configured. + example: "/tmp/zeebe-credentials.yaml" type: string \ No newline at end of file diff --git a/bindings/zeebe/jobworker/jobworker.go b/bindings/zeebe/jobworker/jobworker.go index ff3299cc73..6bb6dbb741 100644 --- a/bindings/zeebe/jobworker/jobworker.go +++ b/bindings/zeebe/jobworker/jobworker.go @@ -63,6 +63,14 @@ type jobWorkerMetadata struct { RetryBackOff kitmd.Duration `mapstructure:"retryBackOff"` } +// JobWorkerMetadata is an exported mirror type used by metadata reflection. +type JobWorkerMetadata jobWorkerMetadata + +type componentMetadata struct { + zeebe.ClientMetadata `mapstructure:",squash"` + JobWorkerMetadata `mapstructure:",squash"` +} + type jobHandler struct { callback bindings.Handler logger logger.Logger @@ -273,7 +281,7 @@ func (h *jobHandler) failJob(ctx context.Context, client worker.JobClient, job e // GetComponentMetadata returns the metadata of the component. func (z *ZeebeJobWorker) GetComponentMetadata() (metadataInfo metadata.MetadataMap) { - metadataStruct := jobWorkerMetadata{} + metadataStruct := componentMetadata{} metadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo, metadata.BindingType) return } diff --git a/bindings/zeebe/jobworker/jobworker_test.go b/bindings/zeebe/jobworker/jobworker_test.go index 2e0932e784..0f350c4735 100644 --- a/bindings/zeebe/jobworker/jobworker_test.go +++ b/bindings/zeebe/jobworker/jobworker_test.go @@ -120,3 +120,16 @@ func TestInit(t *testing.T) { require.NoError(t, jobWorker.Close()) }) } + +func TestGetComponentMetadataIncludesClientMetadata(t *testing.T) { + jobWorker := ZeebeJobWorker{} + metadataInfo := jobWorker.GetComponentMetadata() + + assert.Contains(t, metadataInfo, "clientId") + assert.Contains(t, metadataInfo, "clientSecret") + assert.Contains(t, metadataInfo, "authorizationServerUrl") + assert.Contains(t, metadataInfo, "tokenAudience") + assert.Contains(t, metadataInfo, "tokenScope") + assert.Contains(t, metadataInfo, "clientConfigPath") + assert.Contains(t, metadataInfo, "jobType") +} diff --git a/bindings/zeebe/jobworker/metadata.yaml b/bindings/zeebe/jobworker/metadata.yaml index 37957a5985..1247ad511a 100644 --- a/bindings/zeebe/jobworker/metadata.yaml +++ b/bindings/zeebe/jobworker/metadata.yaml @@ -34,6 +34,37 @@ metadata: description: The path to the CA cert example: "/path/to/ca-cert" type: string + - name: clientId + required: false + description: The OAuth client ID used to request an access token. Required when OAuth is configured, and must be set together with clientSecret, authorizationServerUrl, and tokenAudience. + example: "zeebe-client" + type: string + - name: clientSecret + required: false + sensitive: true + description: The OAuth client secret used to request an access token. Required when OAuth is configured, and must be set together with clientId, authorizationServerUrl, and tokenAudience. + example: "zeebe-secret" + type: string + - name: authorizationServerUrl + required: false + description: The OAuth authorization server URL used to obtain access tokens. Required when OAuth is configured, and must be set together with clientId, clientSecret, and tokenAudience. + example: "https://issuer.example.com/oauth/token" + type: string + - name: tokenAudience + required: false + description: The token audience for Zeebe API access. Required when OAuth is configured, and must be set together with clientId, clientSecret, and authorizationServerUrl. + example: "zeebe-api" + type: string + - name: tokenScope + required: false + description: Optional OAuth scope to request in the access token when OAuth is configured. + example: "read write" + type: string + - name: clientConfigPath + required: false + description: Optional path to the OAuth credentials cache file when OAuth is configured. + example: "/tmp/zeebe-credentials.yaml" + type: string - name: workerName required: false description: The name of the worker activating the jobs, mostly used for logging purposes diff --git a/tests/certification/bindings/zeebe/command/topology_oauth_test.go b/tests/certification/bindings/zeebe/command/topology_oauth_test.go new file mode 100644 index 0000000000..065b6040c7 --- /dev/null +++ b/tests/certification/bindings/zeebe/command/topology_oauth_test.go @@ -0,0 +1,150 @@ +/* +Copyright 2026 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package command_test + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strconv" + "sync/atomic" + "testing" + "time" + + "github.com/camunda/zeebe/clients/go/v8/pkg/pb" + bindings_zeebe_command "github.com/dapr/components-contrib/bindings/zeebe/command" + zeebe_test "github.com/dapr/components-contrib/tests/certification/bindings/zeebe" + "github.com/dapr/components-contrib/tests/certification/embedded" + "github.com/dapr/components-contrib/tests/certification/flow" + "github.com/dapr/components-contrib/tests/certification/flow/dockercompose" + "github.com/dapr/components-contrib/tests/certification/flow/retry" + "github.com/dapr/components-contrib/tests/certification/flow/sidecar" + dapr_testing "github.com/dapr/dapr/pkg/testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTopologyOperationWithOAuthMetadata(t *testing.T) { + ports, _ := dapr_testing.GetFreePorts(2) + grpcPort := ports[0] + httpPort := ports[1] + + var oauthRequests int64 + oauthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + atomic.AddInt64(&oauthRequests, 1) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"access_token":"cert-test-token","token_type":"Bearer","expires_in":3600}`)) + })) + defer oauthSrv.Close() + + resourcesPath := createOAuthTestResources(t, oauthSrv.URL) + + testInvokeTopology := func(ctx flow.Context) error { + client := zeebe_test.GetDaprClient(grpcPort) + defer client.Close() + + res, err := zeebe_test.ExecCommandOperation( + ctx, + client, + bindings_zeebe_command.TopologyOperation, + nil, + map[string]string{}, + ) + require.NoError(t, err) + + topology := &pb.TopologyResponse{} + err = json.Unmarshal(res.Data, topology) + require.NoError(t, err) + require.NotEmpty(t, topology.Brokers) + + res, err = zeebe_test.ExecCommandOperation( + ctx, + client, + bindings_zeebe_command.TopologyOperation, + nil, + map[string]string{}, + ) + require.NoError(t, err) + + topology = &pb.TopologyResponse{} + err = json.Unmarshal(res.Data, topology) + require.NoError(t, err) + require.NotEmpty(t, topology.Brokers) + + assert.Equal(t, int64(1), atomic.LoadInt64(&oauthRequests)) + return nil + } + + flow.New(t, "Test topology operation with OAuth metadata"). + Step(dockercompose.Run("zeebe", zeebe_test.DockerComposeYaml)). + Step("Waiting for Zeebe Readiness...", retry.Do(time.Second*3, 10, zeebe_test.CheckZeebeConnection)). + Step(sidecar.Run(zeebe_test.SidecarName, + append(componentRuntimeOptions(), + embedded.WithoutApp(), + embedded.WithResourcesPath(resourcesPath), + embedded.WithDaprGRPCPort(strconv.Itoa(grpcPort)), + embedded.WithDaprHTTPPort(strconv.Itoa(httpPort)), + )..., + )). + Step("Waiting for the component to start", flow.Sleep(10*time.Second)). + Step("Invoke topology operation", testInvokeTopology). + Run() +} + +func createOAuthTestResources(t *testing.T, oauthServerURL string) string { + t.Helper() + + resourcesPath := filepath.Join(t.TempDir(), "components") + err := os.MkdirAll(resourcesPath, 0o755) + require.NoError(t, err) + + cachePath := filepath.Join(t.TempDir(), "zeebe-credentials.yaml") + + componentPath := filepath.Join(resourcesPath, "zeebe-command.yaml") + componentYAML := fmt.Sprintf(`apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: zeebe-command +spec: + type: bindings.zeebe.command + version: v1 + metadata: + - name: gatewayAddr + value: localhost:26500 + - name: gatewayKeepAlive + value: 45s + - name: usePlainTextConnection + value: true + - name: clientId + value: cert-test-client + - name: clientSecret + value: cert-test-secret + - name: authorizationServerUrl + value: "%s" + - name: tokenAudience + value: localhost + - name: tokenScope + value: test.scope + - name: clientConfigPath + value: "%s" +`, oauthServerURL, cachePath) + + err = os.WriteFile(componentPath, []byte(componentYAML), 0o600) + require.NoError(t, err) + + return resourcesPath +} diff --git a/tests/certification/bindings/zeebe/jobworker/oauth_jobworker_test.go b/tests/certification/bindings/zeebe/jobworker/oauth_jobworker_test.go new file mode 100644 index 0000000000..c6ee020282 --- /dev/null +++ b/tests/certification/bindings/zeebe/jobworker/oauth_jobworker_test.go @@ -0,0 +1,178 @@ +/* +Copyright 2026 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jobworker_test + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strconv" + "sync/atomic" + "testing" + "time" + + zeebe_test "github.com/dapr/components-contrib/tests/certification/bindings/zeebe" + "github.com/dapr/components-contrib/tests/certification/embedded" + "github.com/dapr/components-contrib/tests/certification/flow" + "github.com/dapr/components-contrib/tests/certification/flow/app" + "github.com/dapr/components-contrib/tests/certification/flow/dockercompose" + "github.com/dapr/components-contrib/tests/certification/flow/retry" + "github.com/dapr/components-contrib/tests/certification/flow/sidecar" + "github.com/dapr/dapr/pkg/config/protocol" + dapr_testing "github.com/dapr/dapr/pkg/testing" + "github.com/dapr/go-sdk/service/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestJobworkerWithOAuthMetadata(t *testing.T) { + ports, _ := dapr_testing.GetFreePorts(3) + grpcPort := ports[0] + httpPort := ports[1] + appPort := ports[2] + + id := zeebe_test.TestID() + ch := make(chan bool, 2) + var count int32 + var oauthRequests int64 + + oauthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + atomic.AddInt64(&oauthRequests, 1) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"access_token":"cert-test-token","token_type":"Bearer","expires_in":3600}`)) + })) + defer oauthSrv.Close() + + resourcesPath := createOAuthJobworkerTestResources(t, oauthSrv.URL) + + workers := func(_ flow.Context, s common.Service) error { + return s.AddBindingInvocationHandler(zeebe_test.JobworkerTestName, func(_ context.Context, _ *common.BindingEvent) ([]byte, error) { + atomic.AddInt32(&count, 1) + ch <- true + return []byte("{}"), nil + }) + } + + executeProcess := func(ctx flow.Context) error { + client := zeebe_test.GetDaprClient(grpcPort) + defer client.Close() + + err := deployTestProcess(client, id, 1) + require.NoError(t, err) + + for i := 0; i < 2; i++ { + _, err = zeebe_test.CreateProcessInstance(client, ctx, map[string]interface{}{ + "bpmnProcessId": id, + }) + require.NoError(t, err) + } + + for i := 0; i < 2; i++ { + select { + case <-ch: + case <-time.After(30 * time.Second): + assert.FailNow(t, "read timeout") + } + } + + assert.Equal(t, int32(2), atomic.LoadInt32(&count)) + assert.Equal(t, int64(1), atomic.LoadInt64(&oauthRequests)) + + return nil + } + + flow.New(t, "Test jobworker with OAuth metadata"). + Step(dockercompose.Run("zeebe", zeebe_test.DockerComposeYaml)). + Step("Waiting for Zeebe Readiness...", retry.Do(time.Second*3, 10, zeebe_test.CheckZeebeConnection)). + Step(app.Run("workerApp", fmt.Sprintf(":%d", appPort), workers)). + Step(sidecar.Run(zeebe_test.SidecarName, + append(componentRuntimeOptions(), + embedded.WithAppProtocol(protocol.HTTPProtocol, strconv.Itoa(appPort)), + embedded.WithDaprGRPCPort(strconv.Itoa(grpcPort)), + embedded.WithDaprHTTPPort(strconv.Itoa(httpPort)), + embedded.WithResourcesPath(resourcesPath), + )..., + )). + Step("Waiting for the component to start", flow.Sleep(10*time.Second)). + Step("Execute process", executeProcess). + Step("Allow worker completion", flow.Sleep(2*time.Second)). + Run() +} + +func createOAuthJobworkerTestResources(t *testing.T, oauthServerURL string) string { + t.Helper() + + resourcesPath := filepath.Join(t.TempDir(), "components") + err := os.MkdirAll(resourcesPath, 0o755) + require.NoError(t, err) + + cachePath := filepath.Join(t.TempDir(), "zeebe-credentials.yaml") + + commandComponentPath := filepath.Join(resourcesPath, "zeebe-command.yaml") + commandComponentYAML := fmt.Sprintf(`apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: zeebe-command +spec: + type: bindings.zeebe.command + version: v1 + metadata: + - name: gatewayAddr + value: localhost:26500 + - name: gatewayKeepAlive + value: 45s + - name: usePlainTextConnection + value: true +`) + err = os.WriteFile(commandComponentPath, []byte(commandComponentYAML), 0o600) + require.NoError(t, err) + + jobworkerComponentPath := filepath.Join(resourcesPath, "zeebe-jobworker-test.yaml") + jobworkerComponentYAML := fmt.Sprintf(`apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: zeebe-jobworker-test +spec: + type: bindings.zeebe.jobworker + version: v1 + metadata: + - name: gatewayAddr + value: localhost:26500 + - name: gatewayKeepAlive + value: 45s + - name: usePlainTextConnection + value: true + - name: jobType + value: zeebe-jobworker-test + - name: clientId + value: cert-test-client + - name: clientSecret + value: cert-test-secret + - name: authorizationServerUrl + value: "%s" + - name: tokenAudience + value: localhost + - name: tokenScope + value: test.scope + - name: clientConfigPath + value: "%s" +`, oauthServerURL, cachePath) + err = os.WriteFile(jobworkerComponentPath, []byte(jobworkerComponentYAML), 0o600) + require.NoError(t, err) + + return resourcesPath +}