Skip to content

Commit d003ee9

Browse files
authored
Merge branch 'main' into fix-postgres-opensearch-setup
2 parents 355edb3 + 80d59e2 commit d003ee9

12 files changed

Lines changed: 352 additions & 153 deletions

File tree

.github/workflows/extensibility.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
- name: Set up Go
2727
uses: actions/setup-go@v6
2828
with:
29-
go-version: '1.22'
30-
- name: build
29+
go-version-file: extensibility/go.mod
30+
- name: build and test
3131
working-directory: extensibility
32-
run: go build ./...
32+
run: go test -count=1 -timeout 120s ./...

compose/.env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ COMPOSE_PROJECT_NAME=temporal
22
CASSANDRA_VERSION=3.11.9
33
ELASTICSEARCH_VERSION=7.17.27
44
MYSQL_VERSION=8
5-
TEMPORAL_VERSION=1.29.1
6-
TEMPORAL_ADMINTOOLS_VERSION=1.29.1-tctl-1.18.4-cli-1.5.0
5+
TEMPORAL_VERSION=1.30.1
6+
TEMPORAL_ADMINTOOLS_VERSION=1.30.1
77
TEMPORAL_UI_VERSION=2.34.0
88
POSTGRESQL_VERSION=16
99
POSTGRES_PASSWORD=temporal

compose/scripts/validate-temporal.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/bin/sh
22

33
set -e
44

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package authorizer
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"go.temporal.io/server/common/authorization"
8+
)
9+
10+
func TestMyAuthorizer(t *testing.T) {
11+
a := NewMyAuthorizer()
12+
ctx := context.Background()
13+
14+
tests := []struct {
15+
name string
16+
claims *authorization.Claims
17+
target *authorization.CallTarget
18+
decision authorization.Decision
19+
}{
20+
{
21+
name: "allow temporal-system namespace",
22+
claims: nil,
23+
target: &authorization.CallTarget{Namespace: "temporal-system", APIName: "UpdateNamespace"},
24+
decision: authorization.DecisionAllow,
25+
},
26+
{
27+
name: "allow system admin",
28+
claims: &authorization.Claims{System: authorization.RoleAdmin},
29+
target: &authorization.CallTarget{Namespace: "test", APIName: "UpdateNamespace"},
30+
decision: authorization.DecisionAllow,
31+
},
32+
{
33+
name: "deny UpdateNamespace without claims",
34+
claims: nil,
35+
target: &authorization.CallTarget{Namespace: "test", APIName: "UpdateNamespace"},
36+
decision: authorization.DecisionDeny,
37+
},
38+
{
39+
name: "deny UpdateNamespace with reader role",
40+
claims: &authorization.Claims{Namespaces: map[string]authorization.Role{"test": authorization.RoleReader}},
41+
target: &authorization.CallTarget{Namespace: "test", APIName: "UpdateNamespace"},
42+
decision: authorization.DecisionDeny,
43+
},
44+
{
45+
name: "allow UpdateNamespace with namespace writer role",
46+
claims: &authorization.Claims{Namespaces: map[string]authorization.Role{"test": authorization.RoleWriter}},
47+
target: &authorization.CallTarget{Namespace: "test", APIName: "UpdateNamespace"},
48+
decision: authorization.DecisionAllow,
49+
},
50+
{
51+
name: "allow other calls without claims",
52+
claims: nil,
53+
target: &authorization.CallTarget{Namespace: "test", APIName: "ListNamespaces"},
54+
decision: authorization.DecisionAllow,
55+
},
56+
}
57+
58+
for _, tt := range tests {
59+
t.Run(tt.name, func(t *testing.T) {
60+
result, err := a.Authorize(ctx, tt.claims, tt.target)
61+
if err != nil {
62+
t.Fatalf("unexpected error: %v", err)
63+
}
64+
if result.Decision != tt.decision {
65+
t.Fatalf("expected %v, got %v", tt.decision, result.Decision)
66+
}
67+
})
68+
}
69+
}

extensibility/authorizer/server/main.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,28 @@ import (
3434
"github.com/temporalio/service-samples/authorizer"
3535
)
3636

37-
func main() {
38-
39-
cfg, err := config.LoadConfig("development", "./config", "")
37+
func newServer(configFile string, opts ...temporal.ServerOption) (temporal.Server, error) {
38+
cfg, err := config.Load(config.WithConfigFile(configFile))
4039
if err != nil {
41-
log.Fatal(err)
40+
return nil, err
4241
}
4342

44-
s, err := temporal.NewServer(
43+
defaults := []temporal.ServerOption{
4544
temporal.ForServices(temporal.DefaultServices),
4645
temporal.WithConfig(cfg),
47-
temporal.InterruptOn(temporal.InterruptCh()),
4846
temporal.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper {
4947
return authorizer.NewMyClaimMapper(cfg)
5048
}),
5149
temporal.WithAuthorizer(authorizer.NewMyAuthorizer()),
52-
)
50+
}
51+
52+
return temporal.NewServer(append(defaults, opts...)...)
53+
}
54+
55+
func main() {
56+
// InterruptOn is passed here rather than in newServer so tests can call s.Stop() directly.
57+
// Include this in production scenarios to enable graceful shutdown on SIGINT/SIGTERM.
58+
s, err := newServer("./config/development.yaml", temporal.InterruptOn(temporal.InterruptCh()))
5359
if err != nil {
5460
log.Fatal(err)
5561
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
"time"
8+
9+
"go.temporal.io/api/operatorservice/v1"
10+
"go.temporal.io/api/workflowservice/v1"
11+
"go.temporal.io/server/common/config"
12+
_ "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite"
13+
"google.golang.org/grpc"
14+
"google.golang.org/grpc/codes"
15+
"google.golang.org/grpc/credentials/insecure"
16+
"google.golang.org/grpc/status"
17+
"google.golang.org/protobuf/types/known/durationpb"
18+
)
19+
20+
func TestAuthorizerDeniesUpdateNamespace(t *testing.T) {
21+
s, err := newServer("testdata/config.yaml")
22+
if err != nil {
23+
t.Fatal(err)
24+
}
25+
26+
if err := s.Start(); err != nil {
27+
t.Fatal(err)
28+
}
29+
t.Cleanup(func() { _ = s.Stop() })
30+
31+
cfg, err := config.Load(config.WithConfigFile("testdata/config.yaml"))
32+
if err != nil {
33+
t.Fatal(err)
34+
}
35+
frontendAddr := fmt.Sprintf("127.0.0.1:%d", cfg.Services["frontend"].RPC.GRPCPort)
36+
37+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
38+
defer cancel()
39+
40+
conn, err := grpc.NewClient(frontendAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
defer conn.Close()
45+
46+
wfClient := workflowservice.NewWorkflowServiceClient(conn)
47+
opClient := operatorservice.NewOperatorServiceClient(conn)
48+
49+
// Wait for server to be ready
50+
for {
51+
_, err := wfClient.GetSystemInfo(ctx, &workflowservice.GetSystemInfoRequest{})
52+
if err == nil {
53+
break
54+
}
55+
select {
56+
case <-ctx.Done():
57+
t.Fatal("timed out waiting for server")
58+
case <-time.After(200 * time.Millisecond):
59+
}
60+
}
61+
62+
// Create a namespace (should succeed)
63+
ns := "test-authorizer"
64+
_, err = wfClient.RegisterNamespace(ctx, &workflowservice.RegisterNamespaceRequest{
65+
Namespace: ns,
66+
WorkflowExecutionRetentionPeriod: durationpb.New(24 * time.Hour),
67+
})
68+
if err != nil {
69+
t.Fatalf("RegisterNamespace should succeed: %v", err)
70+
}
71+
72+
// Wait for namespace to propagate
73+
time.Sleep(2 * time.Second)
74+
75+
// UpdateNamespace should be denied by the authorizer
76+
_, err = wfClient.UpdateNamespace(ctx, &workflowservice.UpdateNamespaceRequest{
77+
Namespace: ns,
78+
})
79+
if err == nil {
80+
t.Fatal("UpdateNamespace should have been denied")
81+
}
82+
if s, ok := status.FromError(err); !ok || s.Code() != codes.PermissionDenied {
83+
t.Fatalf("expected PermissionDenied, got: %v", err)
84+
}
85+
86+
// DeleteNamespace should still be allowed
87+
_, err = opClient.DeleteNamespace(ctx, &operatorservice.DeleteNamespaceRequest{
88+
Namespace: ns,
89+
})
90+
if err != nil {
91+
t.Fatalf("DeleteNamespace should succeed: %v", err)
92+
}
93+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
log:
2+
stdout: true
3+
level: error
4+
5+
persistence:
6+
defaultStore: sqlite-default
7+
visibilityStore: sqlite-visibility
8+
numHistoryShards: 1
9+
datastores:
10+
sqlite-default:
11+
sql:
12+
pluginName: "sqlite"
13+
databaseName: "default"
14+
connectAddr: "localhost"
15+
connectProtocol: "tcp"
16+
connectAttributes:
17+
mode: "memory"
18+
cache: "private"
19+
maxConns: 1
20+
maxIdleConns: 1
21+
maxConnLifetime: "1h"
22+
sqlite-visibility:
23+
sql:
24+
pluginName: "sqlite"
25+
databaseName: "default"
26+
connectAddr: "localhost"
27+
connectProtocol: "tcp"
28+
connectAttributes:
29+
mode: "memory"
30+
cache: "private"
31+
maxConns: 1
32+
maxIdleConns: 1
33+
maxConnLifetime: "1h"
34+
35+
global:
36+
membership:
37+
maxJoinDuration: 30s
38+
broadcastAddress: "127.0.0.1"
39+
metrics:
40+
prometheus:
41+
framework: "tally"
42+
listenAddress: ":0"
43+
44+
services:
45+
frontend:
46+
rpc:
47+
grpcPort: 17233
48+
membershipPort: 17234
49+
bindOnLocalHost: true
50+
matching:
51+
rpc:
52+
grpcPort: 17235
53+
membershipPort: 17236
54+
bindOnLocalHost: true
55+
history:
56+
rpc:
57+
grpcPort: 17237
58+
membershipPort: 17238
59+
bindOnLocalHost: true
60+
worker:
61+
rpc:
62+
grpcPort: 17239
63+
membershipPort: 17240
64+
bindOnLocalHost: true
65+
66+
clusterMetadata:
67+
enableGlobalNamespace: false
68+
failoverVersionIncrement: 10
69+
masterClusterName: "active"
70+
currentClusterName: "active"
71+
clusterInformation:
72+
active:
73+
enabled: true
74+
initialFailoverVersion: 1
75+
rpcName: "frontend"
76+
rpcAddress: "localhost:17233"
77+
78+
dcRedirectionPolicy:
79+
policy: "noop"
80+
81+
publicClient:
82+
hostPort: "localhost:17233"

extensibility/config/development.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
log:
2+
stdout: true
3+
level: info
4+
15
persistence:
26
defaultStore: cass-default
37
visibilityStore: es-visibility

0 commit comments

Comments
 (0)