feat: query side for executions and targets for actions v2 (#7524)

* feat: add projections and query side to executions and targets

* feat: add list and get endpoints for targets

* feat: add integration tests for query endpoints target and execution

* fix: linting

* fix: linting

* fix: review changes, renames and corrections

* fix: review changes, renames and corrections

* fix: review changes, renames and corrections

* fix: review changes, renames and corrections

* fix: review changes, renames and corrections

* fix: review changes, renames and corrections

* fix: remove position from list details
This commit is contained in:
Stefan Benz
2024-03-14 10:56:23 +01:00
committed by GitHub
parent 5d2cfc06d5
commit fb3c6f791b
30 changed files with 3266 additions and 326 deletions

View File

@@ -1011,6 +1011,7 @@ InternalAuthZ:
- "events.read"
- "milestones.read"
- "session.delete"
- "execution.target.read"
- "execution.target.write"
- "execution.target.delete"
- "execution.read"
@@ -1048,6 +1049,8 @@ InternalAuthZ:
- "project.grant.member.read"
- "events.read"
- "milestones.read"
- "execution.target.read"
- "execution.read"
- Role: "IAM_ORG_MANAGER"
Permissions:
- "org.read"

View File

@@ -37,7 +37,7 @@ func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRe
var err error
var details *domain.ObjectDetails
switch t := req.GetCondition().GetConditionType().(type) {
case *execution.SetConditions_Request:
case *execution.Condition_Request:
cond := &command.ExecutionAPICondition{
Method: t.Request.GetMethod(),
Service: t.Request.GetService(),
@@ -47,7 +47,7 @@ func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRe
if err != nil {
return nil, err
}
case *execution.SetConditions_Response:
case *execution.Condition_Response:
cond := &command.ExecutionAPICondition{
Method: t.Response.GetMethod(),
Service: t.Response.GetService(),
@@ -57,7 +57,7 @@ func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRe
if err != nil {
return nil, err
}
case *execution.SetConditions_Event:
case *execution.Condition_Event:
cond := &command.ExecutionEventCondition{
Event: t.Event.GetEvent(),
Group: t.Event.GetGroup(),
@@ -67,7 +67,7 @@ func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRe
if err != nil {
return nil, err
}
case *execution.SetConditions_Function:
case *execution.Condition_Function:
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), set, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
@@ -82,7 +82,7 @@ func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecu
var err error
var details *domain.ObjectDetails
switch t := req.GetCondition().GetConditionType().(type) {
case *execution.SetConditions_Request:
case *execution.Condition_Request:
cond := &command.ExecutionAPICondition{
Method: t.Request.GetMethod(),
Service: t.Request.GetService(),
@@ -92,7 +92,7 @@ func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecu
if err != nil {
return nil, err
}
case *execution.SetConditions_Response:
case *execution.Condition_Response:
cond := &command.ExecutionAPICondition{
Method: t.Response.GetMethod(),
Service: t.Response.GetService(),
@@ -102,7 +102,7 @@ func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecu
if err != nil {
return nil, err
}
case *execution.SetConditions_Event:
case *execution.Condition_Event:
cond := &command.ExecutionEventCondition{
Event: t.Event.GetEvent(),
Group: t.Event.GetGroup(),
@@ -112,7 +112,7 @@ func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecu
if err != nil {
return nil, err
}
case *execution.SetConditions_Function:
case *execution.Condition_Function:
details, err = s.command.DeleteExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err

View File

@@ -28,10 +28,10 @@ func TestServer_SetExecution_Request(t *testing.T) {
name: "missing permission",
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_All{All: true},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_All{All: true},
},
},
},
@@ -42,9 +42,9 @@ func TestServer_SetExecution_Request(t *testing.T) {
name: "no condition, error",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{},
},
},
Targets: []string{targetResp.GetId()},
@@ -55,10 +55,10 @@ func TestServer_SetExecution_Request(t *testing.T) {
name: "method, not existing",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Method{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.session.v2beta.NotExistingService/List",
},
},
@@ -72,10 +72,10 @@ func TestServer_SetExecution_Request(t *testing.T) {
name: "method, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Method{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
},
},
@@ -94,10 +94,10 @@ func TestServer_SetExecution_Request(t *testing.T) {
name: "service, not existing",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Service{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Service{
Service: "NotExistingService",
},
},
@@ -111,10 +111,10 @@ func TestServer_SetExecution_Request(t *testing.T) {
name: "service, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Service{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Service{
Service: "zitadel.session.v2beta.SessionService",
},
},
@@ -133,10 +133,10 @@ func TestServer_SetExecution_Request(t *testing.T) {
name: "all, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_All{
All: true,
},
},
@@ -170,16 +170,17 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
targetResp := Tester.CreateTarget(CTX, t)
executionCond := "request"
Tester.SetExecution(CTX, t,
&execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_All{
&execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_All{
All: true,
},
},
},
},
[]string{targetResp.GetId()},
[]string{},
)
tests := []struct {
@@ -193,10 +194,10 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
name: "method, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Method{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
},
},
@@ -215,10 +216,10 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
name: "service, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Service{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Service{
Service: "zitadel.session.v2beta.SessionService",
},
},
@@ -237,10 +238,10 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
name: "all, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_All{
All: true,
},
},
@@ -285,10 +286,10 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
name: "missing permission",
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_All{All: true},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_All{All: true},
},
},
},
@@ -299,9 +300,9 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
name: "no condition, error",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{},
},
},
},
@@ -311,10 +312,10 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
name: "method, not existing",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Method{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/NotExisting",
},
},
@@ -327,14 +328,14 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
name: "method, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Method{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/GetSession",
},
},
@@ -352,10 +353,10 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
name: "service, not existing",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Service{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Service{
Service: "NotExistingService",
},
},
@@ -368,14 +369,14 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
name: "service, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_Service{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Service{
Service: "zitadel.user.v2beta.UserService",
},
},
@@ -393,14 +394,14 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
name: "all, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Request{
Request: &execution.SetRequestExecution{
Condition: &execution.SetRequestExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_All{
All: true,
},
},
@@ -448,10 +449,10 @@ func TestServer_SetExecution_Response(t *testing.T) {
name: "missing permission",
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_All{All: true},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_All{All: true},
},
},
},
@@ -462,9 +463,9 @@ func TestServer_SetExecution_Response(t *testing.T) {
name: "no condition, error",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{},
},
},
Targets: []string{targetResp.GetId()},
@@ -475,10 +476,10 @@ func TestServer_SetExecution_Response(t *testing.T) {
name: "method, not existing",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_Method{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_Method{
Method: "/zitadel.session.v2beta.NotExistingService/List",
},
},
@@ -492,10 +493,10 @@ func TestServer_SetExecution_Response(t *testing.T) {
name: "method, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_Method{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
},
},
@@ -514,10 +515,10 @@ func TestServer_SetExecution_Response(t *testing.T) {
name: "service, not existing",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_Service{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_Service{
Service: "NotExistingService",
},
},
@@ -531,10 +532,10 @@ func TestServer_SetExecution_Response(t *testing.T) {
name: "service, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_Service{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_Service{
Service: "zitadel.session.v2beta.SessionService",
},
},
@@ -553,10 +554,10 @@ func TestServer_SetExecution_Response(t *testing.T) {
name: "all, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_All{
All: true,
},
},
@@ -601,10 +602,10 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
name: "missing permission",
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_All{
All: true,
},
},
@@ -617,9 +618,9 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
name: "no condition, error",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{},
},
},
},
@@ -629,10 +630,10 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
name: "method, not existing",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_Method{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/NotExisting",
},
},
@@ -645,14 +646,14 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
name: "method, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_Method{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/GetSession",
},
},
@@ -670,10 +671,10 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
name: "service, not existing",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_Service{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_Service{
Service: "NotExistingService",
},
},
@@ -686,14 +687,14 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
name: "service, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_Service{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_Service{
Service: "zitadel.user.v2beta.UserService",
},
},
@@ -711,14 +712,14 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
name: "all, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_All{
All: true,
},
},
@@ -766,10 +767,10 @@ func TestServer_SetExecution_Event(t *testing.T) {
name: "missing permission",
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_All{
All: true,
},
},
@@ -782,9 +783,9 @@ func TestServer_SetExecution_Event(t *testing.T) {
name: "no condition, error",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{},
},
},
Targets: []string{targetResp.GetId()},
@@ -798,10 +799,10 @@ func TestServer_SetExecution_Event(t *testing.T) {
name: "event, not existing",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_Event{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_Event{
Event: "xxx",
},
},
@@ -816,10 +817,10 @@ func TestServer_SetExecution_Event(t *testing.T) {
name: "event, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_Event{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_Event{
Event: "xxx",
},
},
@@ -841,10 +842,10 @@ func TestServer_SetExecution_Event(t *testing.T) {
name: "group, not existing",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_Group{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_Group{
Group: "xxx",
},
},
@@ -859,10 +860,10 @@ func TestServer_SetExecution_Event(t *testing.T) {
name: "group, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_Group{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_Group{
Group: "xxx",
},
},
@@ -881,10 +882,10 @@ func TestServer_SetExecution_Event(t *testing.T) {
name: "all, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_All{
All: true,
},
},
@@ -929,10 +930,10 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
name: "missing permission",
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_All{
All: true,
},
},
@@ -945,9 +946,9 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
name: "no condition, error",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{},
},
},
},
@@ -959,10 +960,10 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
name: "event, not existing",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_Event{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_Event{
Event: "xxx",
},
},
@@ -976,14 +977,14 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
name: "event, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_Event{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_Event{
Event: "xxx",
},
},
@@ -1001,10 +1002,10 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
name: "group, not existing",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_Group{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_Group{
Group: "xxx",
},
},
@@ -1017,14 +1018,14 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
name: "group, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_Group{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_Group{
Group: "xxx",
},
},
@@ -1042,10 +1043,10 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
name: "all, not existing",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_All{
All: true,
},
},
@@ -1063,14 +1064,14 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
name: "all, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Event{
Event: &execution.SetEventExecution{
Condition: &execution.SetEventExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Event{
Event: &execution.EventExecution{
Condition: &execution.EventExecution_All{
All: true,
},
},
@@ -1118,10 +1119,10 @@ func TestServer_SetExecution_Function(t *testing.T) {
name: "missing permission",
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_All{All: true},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_All{All: true},
},
},
},
@@ -1132,9 +1133,9 @@ func TestServer_SetExecution_Function(t *testing.T) {
name: "no condition, error",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{},
},
},
Targets: []string{targetResp.GetId()},
@@ -1145,8 +1146,8 @@ func TestServer_SetExecution_Function(t *testing.T) {
name: "function, not existing",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Function{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Function{
Function: "xxx",
},
},
@@ -1158,8 +1159,8 @@ func TestServer_SetExecution_Function(t *testing.T) {
name: "function, ok",
ctx: CTX,
req: &execution.SetExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Function{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Function{
Function: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication",
},
},
@@ -1202,10 +1203,10 @@ func TestServer_DeleteExecution_Function(t *testing.T) {
name: "missing permission",
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{
Condition: &execution.SetResponseExecution_All{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{
Condition: &execution.ResponseExecution_All{
All: true,
},
},
@@ -1218,9 +1219,9 @@ func TestServer_DeleteExecution_Function(t *testing.T) {
name: "no condition, error",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Response{
Response: &execution.SetResponseExecution{},
Condition: &execution.Condition{
ConditionType: &execution.Condition_Response{
Response: &execution.ResponseExecution{},
},
},
},
@@ -1230,8 +1231,8 @@ func TestServer_DeleteExecution_Function(t *testing.T) {
name: "function, not existing",
ctx: CTX,
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Function{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Function{
Function: "xxx",
},
},
@@ -1242,12 +1243,12 @@ func TestServer_DeleteExecution_Function(t *testing.T) {
name: "function, ok",
ctx: CTX,
dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error {
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()})
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
return nil
},
req: &execution.DeleteExecutionRequest{
Condition: &execution.SetConditions{
ConditionType: &execution.SetConditions_Function{
Condition: &execution.Condition{
ConditionType: &execution.Condition_Function{
Function: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication",
},
},

View File

@@ -0,0 +1,286 @@
package execution
import (
"context"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
)
func (s *Server) ListTargets(ctx context.Context, req *execution.ListTargetsRequest) (*execution.ListTargetsResponse, error) {
queries, err := listTargetsRequestToModel(req)
if err != nil {
return nil, err
}
resp, err := s.query.SearchTargets(ctx, queries)
if err != nil {
return nil, err
}
return &execution.ListTargetsResponse{
Result: targetsToPb(resp.Targets),
Details: object.ToListDetails(resp.SearchResponse),
}, nil
}
func listTargetsRequestToModel(req *execution.ListTargetsRequest) (*query.TargetSearchQueries, error) {
offset, limit, asc := object.ListQueryToQuery(req.Query)
queries, err := targetQueriesToQuery(req.Queries)
if err != nil {
return nil, err
}
return &query.TargetSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: targetFieldNameToSortingColumn(req.SortingColumn),
},
Queries: queries,
}, nil
}
func targetFieldNameToSortingColumn(field execution.TargetFieldName) query.Column {
switch field {
case execution.TargetFieldName_FIELD_NAME_UNSPECIFIED:
return query.TargetColumnID
case execution.TargetFieldName_FIELD_NAME_ID:
return query.TargetColumnID
case execution.TargetFieldName_FIELD_NAME_CREATION_DATE:
return query.TargetColumnCreationDate
case execution.TargetFieldName_FIELD_NAME_CHANGE_DATE:
return query.TargetColumnChangeDate
case execution.TargetFieldName_FIELD_NAME_NAME:
return query.TargetColumnName
case execution.TargetFieldName_FIELD_NAME_TARGET_TYPE:
return query.TargetColumnTargetType
case execution.TargetFieldName_FIELD_NAME_URL:
return query.TargetColumnURL
case execution.TargetFieldName_FIELD_NAME_TIMEOUT:
return query.TargetColumnTimeout
case execution.TargetFieldName_FIELD_NAME_ASYNC:
return query.TargetColumnAsync
case execution.TargetFieldName_FIELD_NAME_INTERRUPT_ON_ERROR:
return query.TargetColumnInterruptOnError
default:
return query.TargetColumnID
}
}
func targetQueriesToQuery(queries []*execution.TargetSearchQuery) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = targetQueryToQuery(query)
if err != nil {
return nil, err
}
}
return q, nil
}
func targetQueryToQuery(query *execution.TargetSearchQuery) (query.SearchQuery, error) {
switch q := query.Query.(type) {
case *execution.TargetSearchQuery_TargetNameQuery:
return targetNameQueryToQuery(q.TargetNameQuery)
case *execution.TargetSearchQuery_InTargetIdsQuery:
return targetInTargetIdsQueryToQuery(q.InTargetIdsQuery)
default:
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
}
}
func targetNameQueryToQuery(q *execution.TargetNameQuery) (query.SearchQuery, error) {
return query.NewTargetNameSearchQuery(object.TextMethodToQuery(q.Method), q.GetTargetName())
}
func targetInTargetIdsQueryToQuery(q *execution.InTargetIDsQuery) (query.SearchQuery, error) {
return query.NewTargetInIDsSearchQuery(q.GetTargetIds())
}
func (s *Server) GetTargetByID(ctx context.Context, req *execution.GetTargetByIDRequest) (_ *execution.GetTargetByIDResponse, err error) {
resp, err := s.query.GetTargetByID(ctx, req.GetTargetId())
if err != nil {
return nil, err
}
return &execution.GetTargetByIDResponse{
Target: targetToPb(resp),
}, nil
}
func targetsToPb(targets []*query.Target) []*execution.Target {
t := make([]*execution.Target, len(targets))
for i, target := range targets {
t[i] = targetToPb(target)
}
return t
}
func targetToPb(t *query.Target) *execution.Target {
target := &execution.Target{
Details: object.DomainToDetailsPb(&t.ObjectDetails),
TargetId: t.ID,
Name: t.Name,
Timeout: durationpb.New(t.Timeout),
}
if t.Async {
target.ExecutionType = &execution.Target_IsAsync{IsAsync: t.Async}
}
if t.InterruptOnError {
target.ExecutionType = &execution.Target_InterruptOnError{InterruptOnError: t.InterruptOnError}
}
switch t.TargetType {
case domain.TargetTypeWebhook:
target.TargetType = &execution.Target_RestWebhook{RestWebhook: &execution.SetRESTWebhook{Url: t.URL}}
case domain.TargetTypeRequestResponse:
target.TargetType = &execution.Target_RestRequestResponse{RestRequestResponse: &execution.SetRESTRequestResponse{Url: t.URL}}
default:
target.TargetType = nil
}
return target
}
func (s *Server) ListExecutions(ctx context.Context, req *execution.ListExecutionsRequest) (*execution.ListExecutionsResponse, error) {
queries, err := listExecutionsRequestToModel(req)
if err != nil {
return nil, err
}
resp, err := s.query.SearchExecutions(ctx, queries)
if err != nil {
return nil, err
}
return &execution.ListExecutionsResponse{
Result: executionsToPb(resp.Executions),
Details: object.ToListDetails(resp.SearchResponse),
}, nil
}
func listExecutionsRequestToModel(req *execution.ListExecutionsRequest) (*query.ExecutionSearchQueries, error) {
offset, limit, asc := object.ListQueryToQuery(req.Query)
queries, err := executionQueriesToQuery(req.Queries)
if err != nil {
return nil, err
}
return &query.ExecutionSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
},
Queries: queries,
}, nil
}
func executionQueriesToQuery(queries []*execution.SearchQuery) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = executionQueryToQuery(query)
if err != nil {
return nil, err
}
}
return q, nil
}
func executionQueryToQuery(searchQuery *execution.SearchQuery) (query.SearchQuery, error) {
switch q := searchQuery.Query.(type) {
case *execution.SearchQuery_InConditionsQuery:
return inConditionsQueryToQuery(q.InConditionsQuery)
case *execution.SearchQuery_ExecutionTypeQuery:
return executionTypeToQuery(q.ExecutionTypeQuery)
case *execution.SearchQuery_TargetQuery:
return query.NewExecutionTargetSearchQuery(q.TargetQuery.GetTargetId())
case *execution.SearchQuery_IncludeQuery:
return query.NewExecutionIncludeSearchQuery(q.IncludeQuery.GetInclude())
default:
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
}
}
func executionTypeToQuery(q *execution.ExecutionTypeQuery) (query.SearchQuery, error) {
switch q.ExecutionType {
case execution.ExecutionType_EXECUTION_TYPE_UNSPECIFIED:
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified)
case execution.ExecutionType_EXECUTION_TYPE_REQUEST:
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeRequest)
case execution.ExecutionType_EXECUTION_TYPE_RESPONSE:
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeResponse)
case execution.ExecutionType_EXECUTION_TYPE_EVENT:
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeEvent)
case execution.ExecutionType_EXECUTION_TYPE_FUNCTION:
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeFunction)
default:
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified)
}
}
func inConditionsQueryToQuery(q *execution.InConditionsQuery) (query.SearchQuery, error) {
values := make([]string, len(q.GetConditions()))
for i, condition := range q.GetConditions() {
id, err := conditionToID(condition)
if err != nil {
return nil, err
}
values[i] = id
}
return query.NewExecutionInIDsSearchQuery(values)
}
func conditionToID(q *execution.Condition) (string, error) {
switch t := q.GetConditionType().(type) {
case *execution.Condition_Request:
cond := &command.ExecutionAPICondition{
Method: t.Request.GetMethod(),
Service: t.Request.GetService(),
All: t.Request.GetAll(),
}
return cond.ID(domain.ExecutionTypeRequest), nil
case *execution.Condition_Response:
cond := &command.ExecutionAPICondition{
Method: t.Response.GetMethod(),
Service: t.Response.GetService(),
All: t.Response.GetAll(),
}
return cond.ID(domain.ExecutionTypeResponse), nil
case *execution.Condition_Event:
cond := &command.ExecutionEventCondition{
Event: t.Event.GetEvent(),
Group: t.Event.GetGroup(),
All: t.Event.GetAll(),
}
return cond.ID(), nil
case *execution.Condition_Function:
return t.Function, nil
default:
return "", zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
}
}
func executionsToPb(executions []*query.Execution) []*execution.Execution {
e := make([]*execution.Execution, len(executions))
for i, execution := range executions {
e[i] = executionToPb(execution)
}
return e
}
func executionToPb(e *query.Execution) *execution.Execution {
var targets, includes []string
if len(e.Targets) > 0 {
targets = e.Targets
}
if len(e.Includes) > 0 {
includes = e.Includes
}
return &execution.Execution{
Details: object.DomainToDetailsPb(&e.ObjectDetails),
ExecutionId: e.ID,
Targets: targets,
Includes: includes,
}
}

View File

@@ -0,0 +1,715 @@
//go:build integration
package execution_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/integration"
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
)
func TestServer_GetTargetByID(t *testing.T) {
type args struct {
ctx context.Context
dep func(context.Context, *execution.GetTargetByIDRequest, *execution.GetTargetByIDResponse) error
req *execution.GetTargetByIDRequest
}
tests := []struct {
name string
args args
want *execution.GetTargetByIDResponse
wantErr bool
}{
{
name: "missing permission",
args: args{
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.GetTargetByIDRequest{},
},
wantErr: true,
},
{
name: "not found",
args: args{
ctx: CTX,
req: &execution.GetTargetByIDRequest{TargetId: "notexisting"},
},
wantErr: true,
},
{
name: "get, ok",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.GetTargetByIDRequest, response *execution.GetTargetByIDResponse) error {
name := fmt.Sprint(time.Now().UnixNano() + 1)
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false)
request.TargetId = resp.GetId()
response.Target.TargetId = resp.GetId()
response.Target.Name = name
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
return nil
},
req: &execution.GetTargetByIDRequest{},
},
want: &execution.GetTargetByIDResponse{
Target: &execution.Target{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
TargetType: &execution.Target_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
Url: "https://example.com",
},
},
Timeout: durationpb.New(10 * time.Second),
},
},
},
{
name: "get, async, ok",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.GetTargetByIDRequest, response *execution.GetTargetByIDResponse) error {
name := fmt.Sprint(time.Now().UnixNano() + 1)
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, true, false)
request.TargetId = resp.GetId()
response.Target.TargetId = resp.GetId()
response.Target.Name = name
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
return nil
},
req: &execution.GetTargetByIDRequest{},
},
want: &execution.GetTargetByIDResponse{
Target: &execution.Target{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
TargetType: &execution.Target_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
Url: "https://example.com",
},
},
Timeout: durationpb.New(10 * time.Second),
ExecutionType: &execution.Target_IsAsync{IsAsync: true},
},
},
},
{
name: "get, interruptOnError, ok",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.GetTargetByIDRequest, response *execution.GetTargetByIDResponse) error {
name := fmt.Sprint(time.Now().UnixNano() + 1)
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, true)
request.TargetId = resp.GetId()
response.Target.TargetId = resp.GetId()
response.Target.Name = name
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
return nil
},
req: &execution.GetTargetByIDRequest{},
},
want: &execution.GetTargetByIDResponse{
Target: &execution.Target{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
TargetType: &execution.Target_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
Url: "https://example.com",
},
},
Timeout: durationpb.New(10 * time.Second),
ExecutionType: &execution.Target_InterruptOnError{InterruptOnError: true},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.dep != nil {
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
require.NoError(t, err)
}
retryDuration := 5 * time.Second
if ctxDeadline, ok := CTX.Deadline(); ok {
retryDuration = time.Until(ctxDeadline)
}
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
got, getErr := Client.GetTargetByID(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(ttt, getErr, "Error: "+getErr.Error())
} else {
assert.NoError(ttt, getErr)
}
if getErr != nil {
fmt.Println("Error: " + getErr.Error())
return
}
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
assert.Equal(t, tt.want.Target, got.Target)
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
})
}
}
func TestServer_ListTargets(t *testing.T) {
type args struct {
ctx context.Context
dep func(context.Context, *execution.ListTargetsRequest, *execution.ListTargetsResponse) error
req *execution.ListTargetsRequest
}
tests := []struct {
name string
args args
want *execution.ListTargetsResponse
wantErr bool
}{
{
name: "missing permission",
args: args{
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.ListTargetsRequest{},
},
wantErr: true,
},
{
name: "list, not found",
args: args{
ctx: CTX,
req: &execution.ListTargetsRequest{
Queries: []*execution.TargetSearchQuery{
{Query: &execution.TargetSearchQuery_InTargetIdsQuery{
InTargetIdsQuery: &execution.InTargetIDsQuery{
TargetIds: []string{"notfound"},
},
},
},
},
},
},
want: &execution.ListTargetsResponse{
Details: &object.ListDetails{
TotalResult: 0,
},
Result: []*execution.Target{},
},
},
{
name: "list single id",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.ListTargetsRequest, response *execution.ListTargetsResponse) error {
name := fmt.Sprint(time.Now().UnixNano() + 1)
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false)
request.Queries[0].Query = &execution.TargetSearchQuery_InTargetIdsQuery{
InTargetIdsQuery: &execution.InTargetIDsQuery{
TargetIds: []string{resp.GetId()},
},
}
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
response.Result[0].TargetId = resp.GetId()
response.Result[0].Name = name
return nil
},
req: &execution.ListTargetsRequest{
Queries: []*execution.TargetSearchQuery{{}},
},
},
want: &execution.ListTargetsResponse{
Details: &object.ListDetails{
TotalResult: 1,
},
Result: []*execution.Target{
{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
TargetType: &execution.Target_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
Url: "https://example.com",
},
},
Timeout: durationpb.New(10 * time.Second),
},
},
},
}, {
name: "list single name",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.ListTargetsRequest, response *execution.ListTargetsResponse) error {
name := fmt.Sprint(time.Now().UnixNano() + 1)
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false)
request.Queries[0].Query = &execution.TargetSearchQuery_TargetNameQuery{
TargetNameQuery: &execution.TargetNameQuery{
TargetName: name,
},
}
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
response.Result[0].TargetId = resp.GetId()
response.Result[0].Name = name
return nil
},
req: &execution.ListTargetsRequest{
Queries: []*execution.TargetSearchQuery{{}},
},
},
want: &execution.ListTargetsResponse{
Details: &object.ListDetails{
TotalResult: 1,
},
Result: []*execution.Target{
{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
TargetType: &execution.Target_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
Url: "https://example.com",
},
},
Timeout: durationpb.New(10 * time.Second),
},
},
},
},
{
name: "list multiple id",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.ListTargetsRequest, response *execution.ListTargetsResponse) error {
name1 := fmt.Sprint(time.Now().UnixNano() + 1)
name2 := fmt.Sprint(time.Now().UnixNano() + 3)
name3 := fmt.Sprint(time.Now().UnixNano() + 5)
resp1 := Tester.CreateTargetWithNameAndType(ctx, t, name1, false, false)
resp2 := Tester.CreateTargetWithNameAndType(ctx, t, name2, true, false)
resp3 := Tester.CreateTargetWithNameAndType(ctx, t, name3, false, true)
request.Queries[0].Query = &execution.TargetSearchQuery_InTargetIdsQuery{
InTargetIdsQuery: &execution.InTargetIDsQuery{
TargetIds: []string{resp1.GetId(), resp2.GetId(), resp3.GetId()},
},
}
response.Details.Timestamp = resp3.GetDetails().GetChangeDate()
response.Details.ProcessedSequence = resp3.GetDetails().GetSequence()
response.Result[0].Details.ChangeDate = resp1.GetDetails().GetChangeDate()
response.Result[0].Details.Sequence = resp1.GetDetails().GetSequence()
response.Result[0].TargetId = resp1.GetId()
response.Result[0].Name = name1
response.Result[1].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
response.Result[1].Details.Sequence = resp2.GetDetails().GetSequence()
response.Result[1].TargetId = resp2.GetId()
response.Result[1].Name = name2
response.Result[2].Details.ChangeDate = resp3.GetDetails().GetChangeDate()
response.Result[2].Details.Sequence = resp3.GetDetails().GetSequence()
response.Result[2].TargetId = resp3.GetId()
response.Result[2].Name = name3
return nil
},
req: &execution.ListTargetsRequest{
Queries: []*execution.TargetSearchQuery{{}},
},
},
want: &execution.ListTargetsResponse{
Details: &object.ListDetails{
TotalResult: 3,
},
Result: []*execution.Target{
{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
TargetType: &execution.Target_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
Url: "https://example.com",
},
},
Timeout: durationpb.New(10 * time.Second),
},
{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
TargetType: &execution.Target_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
Url: "https://example.com",
},
},
Timeout: durationpb.New(10 * time.Second),
ExecutionType: &execution.Target_IsAsync{IsAsync: true},
},
{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
TargetType: &execution.Target_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
Url: "https://example.com",
},
},
Timeout: durationpb.New(10 * time.Second),
ExecutionType: &execution.Target_InterruptOnError{InterruptOnError: true},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.dep != nil {
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
require.NoError(t, err)
}
retryDuration := 5 * time.Second
if ctxDeadline, ok := CTX.Deadline(); ok {
retryDuration = time.Until(ctxDeadline)
}
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
got, listErr := Client.ListTargets(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(ttt, listErr, "Error: "+listErr.Error())
} else {
assert.NoError(ttt, listErr)
}
if listErr != nil {
return
}
// always first check length, otherwise its failed anyway
assert.Len(ttt, got.Result, len(tt.want.Result))
for i := range tt.want.Result {
assert.Contains(ttt, got.Result, tt.want.Result[i])
}
integration.AssertListDetails(t, tt.want, got)
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
})
}
}
func TestServer_ListExecutions_Request(t *testing.T) {
targetResp := Tester.CreateTarget(CTX, t)
type args struct {
ctx context.Context
dep func(context.Context, *execution.ListExecutionsRequest, *execution.ListExecutionsResponse) error
req *execution.ListExecutionsRequest
}
tests := []struct {
name string
args args
want *execution.ListExecutionsResponse
wantErr bool
}{
{
name: "missing permission",
args: args{
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &execution.ListExecutionsRequest{},
},
wantErr: true,
},
{
name: "list single condition",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.ListExecutionsRequest, response *execution.ListExecutionsResponse) error {
resp := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[0], []string{targetResp.GetId()}, []string{})
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
return nil
},
req: &execution.ListExecutionsRequest{
Queries: []*execution.SearchQuery{{
Query: &execution.SearchQuery_InConditionsQuery{
InConditionsQuery: &execution.InConditionsQuery{
Conditions: []*execution.Condition{{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/GetSession",
},
},
},
},
},
},
},
}},
},
},
want: &execution.ListExecutionsResponse{
Details: &object.ListDetails{
TotalResult: 1,
},
Result: []*execution.Execution{
{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
ExecutionId: "request./zitadel.session.v2beta.SessionService/GetSession",
Targets: []string{targetResp.GetId()},
},
},
},
},
{
name: "list single target",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.ListExecutionsRequest, response *execution.ListExecutionsResponse) error {
target := Tester.CreateTarget(ctx, t)
// add target as query to the request
request.Queries[0] = &execution.SearchQuery{
Query: &execution.SearchQuery_TargetQuery{
TargetQuery: &execution.TargetQuery{
TargetId: target.GetId(),
},
},
}
resp := Tester.SetExecution(ctx, t, &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.management.v1.ManagementService/UpdateAction",
},
},
},
}, []string{target.GetId()}, []string{})
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
response.Result[0].Targets[0] = target.GetId()
return nil
},
req: &execution.ListExecutionsRequest{
Queries: []*execution.SearchQuery{{}},
},
},
want: &execution.ListExecutionsResponse{
Details: &object.ListDetails{
TotalResult: 1,
},
Result: []*execution.Execution{
{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
ExecutionId: "request./zitadel.management.v1.ManagementService/UpdateAction",
Targets: []string{""},
},
},
},
}, {
name: "list single include",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.ListExecutionsRequest, response *execution.ListExecutionsResponse) error {
Tester.SetExecution(ctx, t, &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.management.v1.ManagementService/GetAction",
},
},
},
}, []string{targetResp.GetId()}, []string{})
resp2 := Tester.SetExecution(ctx, t, &execution.Condition{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.management.v1.ManagementService/ListActions",
},
},
},
}, []string{}, []string{"request./zitadel.management.v1.ManagementService/GetAction"})
response.Details.Timestamp = resp2.GetDetails().GetChangeDate()
response.Details.ProcessedSequence = resp2.GetDetails().GetSequence()
response.Result[0].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
response.Result[0].Details.Sequence = resp2.GetDetails().GetSequence()
return nil
},
req: &execution.ListExecutionsRequest{
Queries: []*execution.SearchQuery{{
Query: &execution.SearchQuery_IncludeQuery{
IncludeQuery: &execution.IncludeQuery{Include: "request./zitadel.management.v1.ManagementService/GetAction"},
},
}},
},
},
want: &execution.ListExecutionsResponse{
Details: &object.ListDetails{
TotalResult: 1,
},
Result: []*execution.Execution{
{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
ExecutionId: "request./zitadel.management.v1.ManagementService/ListActions",
Includes: []string{"request./zitadel.management.v1.ManagementService/GetAction"},
},
},
},
},
{
name: "list multiple conditions",
args: args{
ctx: CTX,
dep: func(ctx context.Context, request *execution.ListExecutionsRequest, response *execution.ListExecutionsResponse) error {
resp1 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[0], []string{targetResp.GetId()}, []string{})
response.Result[0].Details.ChangeDate = resp1.GetDetails().GetChangeDate()
response.Result[0].Details.Sequence = resp1.GetDetails().GetSequence()
resp2 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[1], []string{targetResp.GetId()}, []string{})
response.Result[1].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
response.Result[1].Details.Sequence = resp2.GetDetails().GetSequence()
resp3 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[2], []string{targetResp.GetId()}, []string{})
response.Details.Timestamp = resp3.GetDetails().GetChangeDate()
response.Details.ProcessedSequence = resp3.GetDetails().GetSequence()
response.Result[2].Details.ChangeDate = resp3.GetDetails().GetChangeDate()
response.Result[2].Details.Sequence = resp3.GetDetails().GetSequence()
return nil
},
req: &execution.ListExecutionsRequest{
Queries: []*execution.SearchQuery{{
Query: &execution.SearchQuery_InConditionsQuery{
InConditionsQuery: &execution.InConditionsQuery{
Conditions: []*execution.Condition{
{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/GetSession",
},
},
},
},
{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/CreateSession",
},
},
},
},
{
ConditionType: &execution.Condition_Request{
Request: &execution.RequestExecution{
Condition: &execution.RequestExecution_Method{
Method: "/zitadel.session.v2beta.SessionService/SetSession",
},
},
},
},
},
},
},
}},
},
},
want: &execution.ListExecutionsResponse{
Details: &object.ListDetails{
TotalResult: 3,
},
Result: []*execution.Execution{
{
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
ExecutionId: "request./zitadel.session.v2beta.SessionService/GetSession",
Targets: []string{targetResp.GetId()},
}, {
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
ExecutionId: "request./zitadel.session.v2beta.SessionService/CreateSession",
Targets: []string{targetResp.GetId()},
}, {
Details: &object.Details{
ResourceOwner: Tester.Instance.InstanceID(),
},
ExecutionId: "request./zitadel.session.v2beta.SessionService/SetSession",
Targets: []string{targetResp.GetId()},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.dep != nil {
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
require.NoError(t, err)
}
retryDuration := 5 * time.Second
if ctxDeadline, ok := CTX.Deadline(); ok {
retryDuration = time.Until(ctxDeadline)
}
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
got, listErr := Client.ListExecutions(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(ttt, listErr, "Error: "+listErr.Error())
} else {
assert.NoError(ttt, listErr)
}
if listErr != nil {
return
}
// always first check length, otherwise its failed anyway
assert.Len(ttt, got.Result, len(tt.want.Result))
for i := range tt.want.Result {
assert.Contains(ttt, got.Result, tt.want.Result[i])
}
integration.AssertListDetails(t, tt.want, got)
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
})
}
}

View File

@@ -27,7 +27,6 @@ func Test_createTargetToCommand(t *testing.T) {
args: args{nil},
want: &command.AddTarget{
Name: "",
TargetType: domain.TargetTypeUnspecified,
URL: "",
Timeout: 0,
Async: false,

View File

@@ -2360,6 +2360,7 @@ func TestCommands_DeleteExecutionEvent(t *testing.T) {
})
}
}
func TestCommands_DeleteExecutionFunction(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore

View File

@@ -3,11 +3,8 @@ package domain
type TargetType uint
const (
TargetTypeUnspecified TargetType = iota
TargetTypeWebhook
TargetTypeWebhook TargetType = iota
TargetTypeRequestResponse
TargetTypeStateCount
)
type TargetState int32

View File

@@ -63,9 +63,9 @@ func AssertListDetails[D ListDetailsMsg](t testing.TB, expected, actual D) {
return
}
gotCD := gotDetails.GetTimestamp().AsTime()
now := time.Now()
assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute))
assert.Equal(t, wantDetails.GetTotalResult(), gotDetails.GetTotalResult())
gotCD := gotDetails.GetTimestamp().AsTime()
wantCD := time.Now()
assert.WithinRange(t, gotCD, wantCD.Add(-time.Minute), wantCD.Add(time.Minute))
}

View File

@@ -520,7 +520,7 @@ func (s *Tester) CreateProjectMembership(t *testing.T, ctx context.Context, proj
}
func (s *Tester) CreateTarget(ctx context.Context, t *testing.T) *execution.CreateTargetResponse {
target, err := s.Client.ExecutionV3.CreateTarget(ctx, &execution.CreateTargetRequest{
req := &execution.CreateTargetRequest{
Name: fmt.Sprint(time.Now().UnixNano() + 1),
TargetType: &execution.CreateTargetRequest_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
@@ -528,18 +528,42 @@ func (s *Tester) CreateTarget(ctx context.Context, t *testing.T) *execution.Crea
},
},
Timeout: durationpb.New(10 * time.Second),
ExecutionType: &execution.CreateTargetRequest_InterruptOnError{
InterruptOnError: true,
},
})
}
target, err := s.Client.ExecutionV3.CreateTarget(ctx, req)
require.NoError(t, err)
return target
}
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *execution.SetConditions, targets []string) *execution.SetExecutionResponse {
func (s *Tester) CreateTargetWithNameAndType(ctx context.Context, t *testing.T, name string, async bool, interrupt bool) *execution.CreateTargetResponse {
req := &execution.CreateTargetRequest{
Name: name,
TargetType: &execution.CreateTargetRequest_RestWebhook{
RestWebhook: &execution.SetRESTWebhook{
Url: "https://example.com",
},
},
Timeout: durationpb.New(10 * time.Second),
}
if async {
req.ExecutionType = &execution.CreateTargetRequest_IsAsync{
IsAsync: true,
}
}
if interrupt {
req.ExecutionType = &execution.CreateTargetRequest_InterruptOnError{
InterruptOnError: true,
}
}
target, err := s.Client.ExecutionV3.CreateTarget(ctx, req)
require.NoError(t, err)
return target
}
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *execution.Condition, targets []string, includes []string) *execution.SetExecutionResponse {
target, err := s.Client.ExecutionV3.SetExecution(ctx, &execution.SetExecutionRequest{
Condition: cond,
Targets: targets,
Includes: includes,
})
require.NoError(t, err)
return target

View File

@@ -318,6 +318,7 @@ func (s *Tester) Done() {
//
// Note: the database must already be setup and initialized before
// using NewTester. See the CONTRIBUTING.md document for details.
func NewTester(ctx context.Context, zitadelConfigYAML ...string) *Tester {
args := strings.Split(commandLine, " ")

View File

@@ -12,7 +12,6 @@ import (
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/call"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query/projection"
@@ -20,6 +19,10 @@ import (
"github.com/zitadel/zitadel/internal/zerrors"
)
type Stateful interface {
SetState(*State)
}
type State struct {
LastRun time.Time
@@ -83,29 +86,7 @@ func (q *Queries) SearchCurrentStates(ctx context.Context, queries *CurrentState
}
func (q *Queries) latestState(ctx context.Context, projections ...table) (state *State, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
query, scan := prepareLatestState(ctx, q.client)
or := make(sq.Or, len(projections))
for i, projection := range projections {
or[i] = sq.Eq{CurrentStateColProjectionName.identifier(): projection.name}
}
stmt, args, err := query.
Where(or).
Where(sq.Eq{CurrentStateColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}).
OrderBy(CurrentStateColEventDate.identifier() + " DESC").
ToSql()
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-5CfX9", "Errors.Query.SQLStatement")
}
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
state, err = scan(row)
return err
}, stmt, args...)
return state, err
return latestState(ctx, q.client, projections...)
}
func (q *Queries) ClearCurrentSequence(ctx context.Context, projectionName string) (err error) {

191
internal/query/execution.go Normal file
View File

@@ -0,0 +1,191 @@
package query
import (
"context"
"database/sql"
"errors"
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
executionTable = table{
name: projection.ExecutionTable,
instanceIDCol: projection.ExecutionInstanceIDCol,
}
ExecutionColumnID = Column{
name: projection.ExecutionIDCol,
table: executionTable,
}
ExecutionColumnCreationDate = Column{
name: projection.ExecutionCreationDateCol,
table: executionTable,
}
ExecutionColumnChangeDate = Column{
name: projection.ExecutionChangeDateCol,
table: executionTable,
}
ExecutionColumnResourceOwner = Column{
name: projection.ExecutionResourceOwnerCol,
table: executionTable,
}
ExecutionColumnInstanceID = Column{
name: projection.ExecutionInstanceIDCol,
table: executionTable,
}
ExecutionColumnSequence = Column{
name: projection.ExecutionSequenceCol,
table: executionTable,
}
ExecutionColumnTargets = Column{
name: projection.ExecutionTargetsCol,
table: executionTable,
}
ExecutionColumnIncludes = Column{
name: projection.ExecutionIncludesCol,
table: executionTable,
}
)
type Executions struct {
SearchResponse
Executions []*Execution
}
func (e *Executions) SetState(s *State) {
e.State = s
}
type Execution struct {
ID string
domain.ObjectDetails
Targets database.TextArray[string]
Includes database.TextArray[string]
}
type ExecutionSearchQueries struct {
SearchRequest
Queries []SearchQuery
}
func (q *ExecutionSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
query = q.SearchRequest.toQuery(query)
for _, q := range q.Queries {
query = q.toQuery(query)
}
return query
}
func (q *Queries) SearchExecutions(ctx context.Context, queries *ExecutionSearchQueries) (executions *Executions, err error) {
eq := sq.Eq{
ExecutionColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}
query, scan := prepareExecutionsQuery(ctx, q.client)
return genericRowsQueryWithState[*Executions](ctx, q.client, executionTable, combineToWhereStmt(query, queries.toQuery, eq), scan)
}
func (q *Queries) GetExecutionByID(ctx context.Context, id string) (execution *Execution, err error) {
eq := sq.Eq{
ExecutionColumnID.identifier(): id,
ExecutionColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}
query, scan := prepareExecutionQuery(ctx, q.client)
return genericRowQuery[*Execution](ctx, q.client, query.Where(eq), scan)
}
func NewExecutionInIDsSearchQuery(values []string) (SearchQuery, error) {
return NewInTextQuery(ExecutionColumnID, values)
}
func NewExecutionTypeSearchQuery(t domain.ExecutionType) (SearchQuery, error) {
return NewTextQuery(ExecutionColumnID, t.String(), TextStartsWith)
}
func NewExecutionTargetSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(ExecutionColumnTargets, value, TextListContains)
}
func NewExecutionIncludeSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(ExecutionColumnIncludes, value, TextListContains)
}
func prepareExecutionsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Executions, error)) {
return sq.Select(
ExecutionColumnID.identifier(),
ExecutionColumnChangeDate.identifier(),
ExecutionColumnResourceOwner.identifier(),
ExecutionColumnSequence.identifier(),
ExecutionColumnTargets.identifier(),
ExecutionColumnIncludes.identifier(),
countColumn.identifier(),
).From(executionTable.identifier()).
PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*Executions, error) {
executions := make([]*Execution, 0)
var count uint64
for rows.Next() {
execution := new(Execution)
err := rows.Scan(
&execution.ID,
&execution.EventDate,
&execution.ResourceOwner,
&execution.Sequence,
&execution.Targets,
&execution.Includes,
&count,
)
if err != nil {
return nil, err
}
executions = append(executions, execution)
}
if err := rows.Close(); err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-72xfx5jlj7", "Errors.Query.CloseRows")
}
return &Executions{
Executions: executions,
SearchResponse: SearchResponse{
Count: count,
},
}, nil
}
}
func prepareExecutionQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(row *sql.Row) (*Execution, error)) {
return sq.Select(
ExecutionColumnID.identifier(),
ExecutionColumnChangeDate.identifier(),
ExecutionColumnResourceOwner.identifier(),
ExecutionColumnSequence.identifier(),
ExecutionColumnTargets.identifier(),
ExecutionColumnIncludes.identifier(),
).From(executionTable.identifier()).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*Execution, error) {
execution := new(Execution)
err := row.Scan(
&execution.ID,
&execution.EventDate,
&execution.ResourceOwner,
&execution.Sequence,
&execution.Targets,
&execution.Includes,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, zerrors.ThrowNotFound(err, "QUERY-qzn1xycesh", "Errors.Execution.NotFound")
}
return nil, zerrors.ThrowInternal(err, "QUERY-f8sjvm4tb8", "Errors.Internal")
}
return execution, nil
}
}

View File

@@ -0,0 +1,253 @@
package query
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"regexp"
"testing"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
prepareExecutionsStmt = `SELECT projections.executions.id,` +
` projections.executions.change_date,` +
` projections.executions.resource_owner,` +
` projections.executions.sequence,` +
` projections.executions.targets,` +
` projections.executions.includes,` +
` COUNT(*) OVER ()` +
` FROM projections.executions`
prepareExecutionsCols = []string{
"id",
"change_date",
"resource_owner",
"sequence",
"targets",
"includes",
"count",
}
prepareExecutionStmt = `SELECT projections.executions.id,` +
` projections.executions.change_date,` +
` projections.executions.resource_owner,` +
` projections.executions.sequence,` +
` projections.executions.targets,` +
` projections.executions.includes` +
` FROM projections.executions`
prepareExecutionCols = []string{
"id",
"change_date",
"resource_owner",
"sequence",
"targets",
"includes",
}
)
func Test_ExecutionPrepares(t *testing.T) {
type want struct {
sqlExpectations sqlExpectation
err checkErr
}
tests := []struct {
name string
prepare interface{}
want want
object interface{}
}{
{
name: "prepareExecutionsQuery no result",
prepare: prepareExecutionsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareExecutionsStmt),
nil,
nil,
),
},
object: &Executions{Executions: []*Execution{}},
},
{
name: "prepareExecutionsQuery one result",
prepare: prepareExecutionsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareExecutionsStmt),
prepareExecutionsCols,
[][]driver.Value{
{
"id",
testNow,
"ro",
uint64(20211109),
database.TextArray[string]{"target"},
database.TextArray[string]{"include"},
},
},
),
},
object: &Executions{
SearchResponse: SearchResponse{
Count: 1,
},
Executions: []*Execution{
{
ID: "id",
ObjectDetails: domain.ObjectDetails{
EventDate: testNow,
ResourceOwner: "ro",
Sequence: 20211109,
},
Targets: database.TextArray[string]{"target"},
Includes: database.TextArray[string]{"include"},
},
},
},
},
{
name: "prepareExecutionsQuery multiple result",
prepare: prepareExecutionsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareExecutionsStmt),
prepareExecutionsCols,
[][]driver.Value{
{
"id-1",
testNow,
"ro",
uint64(20211109),
database.TextArray[string]{"target1"},
database.TextArray[string]{"include1"},
},
{
"id-2",
testNow,
"ro",
uint64(20211110),
database.TextArray[string]{"target2"},
database.TextArray[string]{"include2"},
},
},
),
},
object: &Executions{
SearchResponse: SearchResponse{
Count: 2,
},
Executions: []*Execution{
{
ID: "id-1",
ObjectDetails: domain.ObjectDetails{
EventDate: testNow,
ResourceOwner: "ro",
Sequence: 20211109,
},
Targets: database.TextArray[string]{"target1"},
Includes: database.TextArray[string]{"include1"},
},
{
ID: "id-2",
ObjectDetails: domain.ObjectDetails{
EventDate: testNow,
ResourceOwner: "ro",
Sequence: 20211110,
},
Targets: database.TextArray[string]{"target2"},
Includes: database.TextArray[string]{"include2"},
},
},
},
},
{
name: "prepareExecutionsQuery sql err",
prepare: prepareExecutionsQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(prepareExecutionsStmt),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
if !errors.Is(err, sql.ErrConnDone) {
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
}
return nil, true
},
},
object: (*Execution)(nil),
},
{
name: "prepareExecutionQuery no result",
prepare: prepareExecutionQuery,
want: want{
sqlExpectations: mockQueriesScanErr(
regexp.QuoteMeta(prepareExecutionStmt),
nil,
nil,
),
err: func(err error) (error, bool) {
if !zerrors.IsNotFound(err) {
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
}
return nil, true
},
},
object: (*Execution)(nil),
},
{
name: "prepareExecutionQuery found",
prepare: prepareExecutionQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(prepareExecutionStmt),
prepareExecutionCols,
[]driver.Value{
"id",
testNow,
"ro",
uint64(20211109),
database.TextArray[string]{"target"},
database.TextArray[string]{"include"},
},
),
},
object: &Execution{
ID: "id",
ObjectDetails: domain.ObjectDetails{
EventDate: testNow,
ResourceOwner: "ro",
Sequence: 20211109,
},
Targets: database.TextArray[string]{"target"},
Includes: database.TextArray[string]{"include"},
},
},
{
name: "prepareExecutionQuery sql err",
prepare: prepareExecutionQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(prepareExecutionStmt),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
if !errors.Is(err, sql.ErrConnDone) {
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
}
return nil, true
},
},
object: (*Execution)(nil),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err, defaultPrepareArgs...)
})
}
}

109
internal/query/generic.go Normal file
View File

@@ -0,0 +1,109 @@
package query
import (
"context"
"database/sql"
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
func genericRowsQuery[R any](
ctx context.Context,
client *database.DB,
query sq.SelectBuilder,
scan func(rows *sql.Rows) (R, error),
) (resp R, err error) {
var rnil R
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
stmt, args, err := query.ToSql()
if err != nil {
return rnil, zerrors.ThrowInvalidArgument(err, "QUERY-05wf2q36ji", "Errors.Query.InvalidRequest")
}
err = client.QueryContext(ctx, func(rows *sql.Rows) error {
resp, err = scan(rows)
return err
}, stmt, args...)
if err != nil {
return rnil, zerrors.ThrowInternal(err, "QUERY-y2u7vctrha", "Errors.Internal")
}
return resp, err
}
func genericRowsQueryWithState[R Stateful](
ctx context.Context,
client *database.DB,
projection table,
query sq.SelectBuilder,
scan func(rows *sql.Rows) (R, error),
) (resp R, err error) {
var rnil R
resp, err = genericRowsQuery[R](ctx, client, query, scan)
if err != nil {
return rnil, err
}
state, err := latestState(ctx, client, projection)
if err != nil {
return rnil, err
}
resp.SetState(state)
return resp, err
}
func latestState(ctx context.Context, client *database.DB, projections ...table) (state *State, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
query, scan := prepareLatestState(ctx, client)
or := make(sq.Or, len(projections))
for i, projection := range projections {
or[i] = sq.Eq{CurrentStateColProjectionName.identifier(): projection.name}
}
stmt, args, err := query.
Where(or).
Where(sq.Eq{CurrentStateColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}).
OrderBy(CurrentStateColEventDate.identifier() + " DESC").
ToSql()
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-5CfX9", "Errors.Query.SQLStatement")
}
err = client.QueryRowContext(ctx, func(row *sql.Row) error {
state, err = scan(row)
return err
}, stmt, args...)
return state, err
}
func genericRowQuery[R any](
ctx context.Context,
client *database.DB,
query sq.SelectBuilder,
scan func(row *sql.Row) (R, error),
) (resp R, err error) {
var rnil R
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
stmt, args, err := query.ToSql()
if err != nil {
return rnil, zerrors.ThrowInternal(err, "QUERY-s969t763z4", "Errors.Query.SQLStatement")
}
err = client.QueryRowContext(ctx, func(row *sql.Row) error {
resp, err = scan(row)
return err
}, stmt, args...)
return resp, err
}
func combineToWhereStmt(query sq.SelectBuilder, toQuery func(query sq.SelectBuilder) sq.SelectBuilder, eq interface{}) sq.SelectBuilder {
return toQuery(query).Where(eq)
}

View File

@@ -99,7 +99,7 @@ func TestActionProjection_reduces(t *testing.T) {
args: args{
event: getEvent(
testEvent(
action.ChangedEventType,
action.DeactivatedEventType,
action.AggregateType,
[]byte(`{}`),
),
@@ -131,7 +131,7 @@ func TestActionProjection_reduces(t *testing.T) {
args: args{
event: getEvent(
testEvent(
action.ChangedEventType,
action.ReactivatedEventType,
action.AggregateType,
[]byte(`{}`),
),
@@ -163,7 +163,7 @@ func TestActionProjection_reduces(t *testing.T) {
args: args{
event: getEvent(
testEvent(
action.ChangedEventType,
action.RemovedEventType,
action.AggregateType,
[]byte(`{}`),
),

View File

@@ -0,0 +1,109 @@
package projection
import (
"context"
"github.com/zitadel/zitadel/internal/eventstore"
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
exec "github.com/zitadel/zitadel/internal/repository/execution"
"github.com/zitadel/zitadel/internal/repository/instance"
)
const (
ExecutionTable = "projections.executions"
ExecutionIDCol = "id"
ExecutionCreationDateCol = "creation_date"
ExecutionChangeDateCol = "change_date"
ExecutionResourceOwnerCol = "resource_owner"
ExecutionInstanceIDCol = "instance_id"
ExecutionSequenceCol = "sequence"
ExecutionTargetsCol = "targets"
ExecutionIncludesCol = "includes"
)
type executionProjection struct{}
func newExecutionProjection(ctx context.Context, config handler.Config) *handler.Handler {
return handler.NewHandler(ctx, &config, new(executionProjection))
}
func (*executionProjection) Name() string {
return ExecutionTable
}
func (*executionProjection) Init() *old_handler.Check {
return handler.NewTableCheck(
handler.NewTable([]*handler.InitColumn{
handler.NewColumn(ExecutionIDCol, handler.ColumnTypeText),
handler.NewColumn(ExecutionCreationDateCol, handler.ColumnTypeTimestamp),
handler.NewColumn(ExecutionChangeDateCol, handler.ColumnTypeTimestamp),
handler.NewColumn(ExecutionResourceOwnerCol, handler.ColumnTypeText),
handler.NewColumn(ExecutionInstanceIDCol, handler.ColumnTypeText),
handler.NewColumn(ExecutionSequenceCol, handler.ColumnTypeInt64),
handler.NewColumn(ExecutionTargetsCol, handler.ColumnTypeTextArray, handler.Nullable()),
handler.NewColumn(ExecutionIncludesCol, handler.ColumnTypeTextArray, handler.Nullable()),
},
handler.NewPrimaryKey(ExecutionInstanceIDCol, ExecutionIDCol),
),
)
}
func (p *executionProjection) Reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: exec.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: exec.SetEventType,
Reduce: p.reduceExecutionSet,
},
{
Event: exec.RemovedEventType,
Reduce: p.reduceExecutionRemoved,
},
},
},
{
Aggregate: instance.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: instance.InstanceRemovedEventType,
Reduce: reduceInstanceRemovedHelper(ExecutionInstanceIDCol),
},
},
},
}
}
func (p *executionProjection) reduceExecutionSet(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*exec.SetEvent](event)
if err != nil {
return nil, err
}
columns := []handler.Column{
handler.NewCol(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCol(ExecutionIDCol, e.Aggregate().ID),
handler.NewCol(ExecutionResourceOwnerCol, e.Aggregate().ResourceOwner),
handler.NewCol(ExecutionCreationDateCol, handler.OnlySetValueOnInsert(ExecutionTable, e.CreationDate())),
handler.NewCol(ExecutionChangeDateCol, e.CreationDate()),
handler.NewCol(ExecutionSequenceCol, e.Sequence()),
handler.NewCol(ExecutionTargetsCol, e.Targets),
handler.NewCol(ExecutionIncludesCol, e.Includes),
}
return handler.NewUpsertStatement(e, columns[0:2], columns), nil
}
func (p *executionProjection) reduceExecutionRemoved(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*exec.RemovedEvent](event)
if err != nil {
return nil, err
}
return handler.NewDeleteStatement(
e,
[]handler.Condition{
handler.NewCond(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCond(ExecutionIDCol, e.Aggregate().ID),
},
), nil
}

View File

@@ -0,0 +1,129 @@
package projection
import (
"testing"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
exec "github.com/zitadel/zitadel/internal/repository/execution"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestExecutionProjection_reduces(t *testing.T) {
type args struct {
event func(t *testing.T) eventstore.Event
}
tests := []struct {
name string
args args
reduce func(event eventstore.Event) (*handler.Statement, error)
want wantReduce
}{
{
name: "reduceExecutionSet",
args: args{
event: getEvent(
testEvent(
exec.SetEventType,
exec.AggregateType,
[]byte(`{"targets": ["target"], "includes": ["include"]}`),
),
eventstore.GenericEventMapper[exec.SetEvent],
),
},
reduce: (&executionProjection{}).reduceExecutionSet,
want: wantReduce{
aggregateType: eventstore.AggregateType("execution"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.executions (instance_id, id, resource_owner, creation_date, change_date, sequence, targets, includes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (instance_id, id) DO UPDATE SET (resource_owner, creation_date, change_date, sequence, targets, includes) = (EXCLUDED.resource_owner, projections.executions.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.targets, EXCLUDED.includes)",
expectedArgs: []interface{}{
"instance-id",
"agg-id",
"ro-id",
anyArg{},
anyArg{},
uint64(15),
[]string{"target"},
[]string{"include"},
},
},
},
},
},
},
{
name: "reduceExecutionRemoved",
args: args{
event: getEvent(
testEvent(
exec.RemovedEventType,
exec.AggregateType,
[]byte(`{}`),
),
eventstore.GenericEventMapper[exec.RemovedEvent],
),
},
reduce: (&executionProjection{}).reduceExecutionRemoved,
want: wantReduce{
aggregateType: eventstore.AggregateType("execution"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.executions WHERE (instance_id = $1) AND (id = $2)",
expectedArgs: []interface{}{
"instance-id",
"agg-id",
},
},
},
},
},
},
{
name: "reduceInstanceRemoved",
args: args{
event: getEvent(
testEvent(
instance.InstanceRemovedEventType,
instance.AggregateType,
nil,
),
instance.InstanceRemovedEventMapper,
),
},
reduce: reduceInstanceRemovedHelper(ExecutionInstanceIDCol),
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.executions WHERE (instance_id = $1)",
expectedArgs: []interface{}{
"agg-id",
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := baseEvent(t)
got, err := tt.reduce(event)
if ok := zerrors.IsErrorInvalidArgument(err); !ok {
t.Errorf("no wrong event mapping: %v, got: %v", err, got)
}
event = tt.args.event(t)
got, err = tt.reduce(event)
assertReduce(t, got, err, ExecutionTable, tt.want)
})
}
}

View File

@@ -74,6 +74,8 @@ var (
RestrictionsProjection *handler.Handler
SystemFeatureProjection *handler.Handler
InstanceFeatureProjection *handler.Handler
TargetProjection *handler.Handler
ExecutionProjection *handler.Handler
)
type projection interface {
@@ -152,6 +154,8 @@ func Create(ctx context.Context, sqlClient *database.DB, es handler.EventStore,
RestrictionsProjection = newRestrictionsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["restrictions"]))
SystemFeatureProjection = newSystemFeatureProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["system_features"]))
InstanceFeatureProjection = newInstanceFeatureProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["instance_features"]))
TargetProjection = newTargetProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["targets"]))
ExecutionProjection = newExecutionProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["executions"]))
newProjectionsList()
return nil
}
@@ -263,5 +267,7 @@ func newProjectionsList() {
RestrictionsProjection,
SystemFeatureProjection,
InstanceFeatureProjection,
ExecutionProjection,
TargetProjection,
}
}

View File

@@ -0,0 +1,165 @@
package projection
import (
"context"
"github.com/zitadel/zitadel/internal/eventstore"
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/target"
)
const (
TargetTable = "projections.targets"
TargetIDCol = "id"
TargetCreationDateCol = "creation_date"
TargetChangeDateCol = "change_date"
TargetResourceOwnerCol = "resource_owner"
TargetInstanceIDCol = "instance_id"
TargetSequenceCol = "sequence"
TargetNameCol = "name"
TargetTargetType = "target_type"
TargetURLCol = "url"
TargetTimeoutCol = "timeout"
TargetAsyncCol = "async"
TargetInterruptOnErrorCol = "interrupt_on_error"
)
type targetProjection struct{}
func newTargetProjection(ctx context.Context, config handler.Config) *handler.Handler {
return handler.NewHandler(ctx, &config, new(targetProjection))
}
func (*targetProjection) Name() string {
return TargetTable
}
func (*targetProjection) Init() *old_handler.Check {
return handler.NewTableCheck(
handler.NewTable([]*handler.InitColumn{
handler.NewColumn(TargetIDCol, handler.ColumnTypeText),
handler.NewColumn(TargetCreationDateCol, handler.ColumnTypeTimestamp),
handler.NewColumn(TargetChangeDateCol, handler.ColumnTypeTimestamp),
handler.NewColumn(TargetResourceOwnerCol, handler.ColumnTypeText),
handler.NewColumn(TargetInstanceIDCol, handler.ColumnTypeText),
handler.NewColumn(TargetTargetType, handler.ColumnTypeEnum),
handler.NewColumn(TargetSequenceCol, handler.ColumnTypeInt64),
handler.NewColumn(TargetNameCol, handler.ColumnTypeText),
handler.NewColumn(TargetURLCol, handler.ColumnTypeText, handler.Default("")),
handler.NewColumn(TargetTimeoutCol, handler.ColumnTypeInt64, handler.Default(0)),
handler.NewColumn(TargetAsyncCol, handler.ColumnTypeBool, handler.Default(false)),
handler.NewColumn(TargetInterruptOnErrorCol, handler.ColumnTypeBool, handler.Default(false)),
},
handler.NewPrimaryKey(TargetInstanceIDCol, TargetIDCol),
),
)
}
func (p *targetProjection) Reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: target.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: target.AddedEventType,
Reduce: p.reduceTargetAdded,
},
{
Event: target.ChangedEventType,
Reduce: p.reduceTargetChanged,
},
{
Event: target.RemovedEventType,
Reduce: p.reduceTargetRemoved,
},
},
},
{
Aggregate: instance.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: instance.InstanceRemovedEventType,
Reduce: reduceInstanceRemovedHelper(TargetInstanceIDCol),
},
},
},
}
}
func (p *targetProjection) reduceTargetAdded(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*target.AddedEvent](event)
if err != nil {
return nil, err
}
return handler.NewCreateStatement(
e,
[]handler.Column{
handler.NewCol(TargetInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCol(TargetResourceOwnerCol, e.Aggregate().ResourceOwner),
handler.NewCol(TargetIDCol, e.Aggregate().ID),
handler.NewCol(TargetCreationDateCol, e.CreationDate()),
handler.NewCol(TargetChangeDateCol, e.CreationDate()),
handler.NewCol(TargetSequenceCol, e.Sequence()),
handler.NewCol(TargetNameCol, e.Name),
handler.NewCol(TargetURLCol, e.URL),
handler.NewCol(TargetTargetType, e.TargetType),
handler.NewCol(TargetTimeoutCol, e.Timeout),
handler.NewCol(TargetAsyncCol, e.Async),
handler.NewCol(TargetInterruptOnErrorCol, e.InterruptOnError),
},
), nil
}
func (p *targetProjection) reduceTargetChanged(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*target.ChangedEvent](event)
if err != nil {
return nil, err
}
values := []handler.Column{
handler.NewCol(TargetChangeDateCol, e.CreationDate()),
handler.NewCol(TargetSequenceCol, e.Sequence()),
handler.NewCol(TargetResourceOwnerCol, e.Aggregate().ResourceOwner),
}
if e.Name != nil {
values = append(values, handler.NewCol(TargetNameCol, *e.Name))
}
if e.TargetType != nil {
values = append(values, handler.NewCol(TargetTargetType, *e.TargetType))
}
if e.URL != nil {
values = append(values, handler.NewCol(TargetURLCol, *e.URL))
}
if e.Timeout != nil {
values = append(values, handler.NewCol(TargetTimeoutCol, *e.Timeout))
}
if e.Async != nil {
values = append(values, handler.NewCol(TargetAsyncCol, *e.Async))
}
if e.InterruptOnError != nil {
values = append(values, handler.NewCol(TargetInterruptOnErrorCol, *e.InterruptOnError))
}
return handler.NewUpdateStatement(
e,
values,
[]handler.Condition{
handler.NewCond(TargetInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCond(TargetIDCol, e.Aggregate().ID),
},
), nil
}
func (p *targetProjection) reduceTargetRemoved(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*target.RemovedEvent](event)
if err != nil {
return nil, err
}
return handler.NewDeleteStatement(
e,
[]handler.Condition{
handler.NewCond(TargetInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCond(TargetIDCol, e.Aggregate().ID),
},
), nil
}

View File

@@ -0,0 +1,173 @@
package projection
import (
"testing"
"time"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/target"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestTargetProjection_reduces(t *testing.T) {
type args struct {
event func(t *testing.T) eventstore.Event
}
tests := []struct {
name string
args args
reduce func(event eventstore.Event) (*handler.Statement, error)
want wantReduce
}{
{
name: "reduceTargetAdded",
args: args{
event: getEvent(
testEvent(
target.AddedEventType,
target.AggregateType,
[]byte(`{"name": "name", "targetType":0, "url":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
),
eventstore.GenericEventMapper[target.AddedEvent],
),
},
reduce: (&targetProjection{}).reduceTargetAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("target"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.targets (instance_id, resource_owner, id, creation_date, change_date, sequence, name, url, target_type, timeout, async, interrupt_on_error) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
expectedArgs: []interface{}{
"instance-id",
"ro-id",
"agg-id",
anyArg{},
anyArg{},
uint64(15),
"name",
"https://example.com",
domain.TargetTypeWebhook,
3 * time.Second,
true,
true,
},
},
},
},
},
},
{
name: "reduceTargetChanged",
args: args{
event: getEvent(
testEvent(
target.ChangedEventType,
target.AggregateType,
[]byte(`{"name": "name2", "targetType":0, "url":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
),
eventstore.GenericEventMapper[target.ChangedEvent],
),
},
reduce: (&targetProjection{}).reduceTargetChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("target"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.targets SET (change_date, sequence, resource_owner, name, target_type, url, timeout, async, interrupt_on_error) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (instance_id = $10) AND (id = $11)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"ro-id",
"name2",
domain.TargetTypeWebhook,
"https://example.com",
3 * time.Second,
true,
true,
"instance-id",
"agg-id",
},
},
},
},
},
},
{
name: "reduceTargetRemoved",
args: args{
event: getEvent(
testEvent(
target.RemovedEventType,
target.AggregateType,
[]byte(`{}`),
),
eventstore.GenericEventMapper[target.RemovedEvent],
),
},
reduce: (&targetProjection{}).reduceTargetRemoved,
want: wantReduce{
aggregateType: eventstore.AggregateType("target"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.targets WHERE (instance_id = $1) AND (id = $2)",
expectedArgs: []interface{}{
"instance-id",
"agg-id",
},
},
},
},
},
},
{
name: "reduceInstanceRemoved",
args: args{
event: getEvent(
testEvent(
instance.InstanceRemovedEventType,
instance.AggregateType,
nil,
),
instance.InstanceRemovedEventMapper,
),
},
reduce: reduceInstanceRemovedHelper(TargetInstanceIDCol),
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.targets WHERE (instance_id = $1)",
expectedArgs: []interface{}{
"agg-id",
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := baseEvent(t)
got, err := tt.reduce(event)
if ok := zerrors.IsErrorInvalidArgument(err); !ok {
t.Errorf("no wrong event mapping: %v, got: %v", err, got)
}
event = tt.args.event(t)
got, err = tt.reduce(event)
assertReduce(t, got, err, TargetTable, tt.want)
})
}
}

219
internal/query/target.go Normal file
View File

@@ -0,0 +1,219 @@
package query
import (
"context"
"database/sql"
"errors"
"time"
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
targetTable = table{
name: projection.TargetTable,
instanceIDCol: projection.TargetInstanceIDCol,
}
TargetColumnID = Column{
name: projection.TargetIDCol,
table: targetTable,
}
TargetColumnCreationDate = Column{
name: projection.TargetCreationDateCol,
table: targetTable,
}
TargetColumnChangeDate = Column{
name: projection.TargetChangeDateCol,
table: targetTable,
}
TargetColumnResourceOwner = Column{
name: projection.TargetResourceOwnerCol,
table: targetTable,
}
TargetColumnInstanceID = Column{
name: projection.TargetInstanceIDCol,
table: targetTable,
}
TargetColumnSequence = Column{
name: projection.TargetSequenceCol,
table: targetTable,
}
TargetColumnName = Column{
name: projection.TargetNameCol,
table: targetTable,
}
TargetColumnTargetType = Column{
name: projection.TargetTargetType,
table: targetTable,
}
TargetColumnURL = Column{
name: projection.TargetURLCol,
table: targetTable,
}
TargetColumnTimeout = Column{
name: projection.TargetTimeoutCol,
table: targetTable,
}
TargetColumnAsync = Column{
name: projection.TargetAsyncCol,
table: targetTable,
}
TargetColumnInterruptOnError = Column{
name: projection.TargetInterruptOnErrorCol,
table: targetTable,
}
)
type Targets struct {
SearchResponse
Targets []*Target
}
func (t *Targets) SetState(s *State) {
t.State = s
}
type Target struct {
ID string
domain.ObjectDetails
Name string
TargetType domain.TargetType
URL string
Timeout time.Duration
Async bool
InterruptOnError bool
}
type TargetSearchQueries struct {
SearchRequest
Queries []SearchQuery
}
func (q *TargetSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
query = q.SearchRequest.toQuery(query)
for _, q := range q.Queries {
query = q.toQuery(query)
}
return query
}
func (q *Queries) SearchTargets(ctx context.Context, queries *TargetSearchQueries) (targets *Targets, err error) {
eq := sq.Eq{
TargetColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}
query, scan := prepareTargetsQuery(ctx, q.client)
return genericRowsQueryWithState[*Targets](ctx, q.client, targetTable, combineToWhereStmt(query, queries.toQuery, eq), scan)
}
func (q *Queries) GetTargetByID(ctx context.Context, id string) (target *Target, err error) {
eq := sq.Eq{
TargetColumnID.identifier(): id,
TargetColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}
query, scan := prepareTargetQuery(ctx, q.client)
return genericRowQuery[*Target](ctx, q.client, query.Where(eq), scan)
}
func NewTargetNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
return NewTextQuery(TargetColumnName, value, method)
}
func NewTargetInIDsSearchQuery(values []string) (SearchQuery, error) {
return NewInTextQuery(TargetColumnID, values)
}
func prepareTargetsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Targets, error)) {
return sq.Select(
TargetColumnID.identifier(),
TargetColumnChangeDate.identifier(),
TargetColumnResourceOwner.identifier(),
TargetColumnSequence.identifier(),
TargetColumnName.identifier(),
TargetColumnTargetType.identifier(),
TargetColumnTimeout.identifier(),
TargetColumnURL.identifier(),
TargetColumnAsync.identifier(),
TargetColumnInterruptOnError.identifier(),
countColumn.identifier(),
).From(targetTable.identifier()).
PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*Targets, error) {
targets := make([]*Target, 0)
var count uint64
for rows.Next() {
target := new(Target)
err := rows.Scan(
&target.ID,
&target.EventDate,
&target.ResourceOwner,
&target.Sequence,
&target.Name,
&target.TargetType,
&target.Timeout,
&target.URL,
&target.Async,
&target.InterruptOnError,
&count,
)
if err != nil {
return nil, err
}
targets = append(targets, target)
}
if err := rows.Close(); err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-fzwi6cgxos", "Errors.Query.CloseRows")
}
return &Targets{
Targets: targets,
SearchResponse: SearchResponse{
Count: count,
},
}, nil
}
}
func prepareTargetQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(row *sql.Row) (*Target, error)) {
return sq.Select(
TargetColumnID.identifier(),
TargetColumnChangeDate.identifier(),
TargetColumnResourceOwner.identifier(),
TargetColumnSequence.identifier(),
TargetColumnName.identifier(),
TargetColumnTargetType.identifier(),
TargetColumnTimeout.identifier(),
TargetColumnURL.identifier(),
TargetColumnAsync.identifier(),
TargetColumnInterruptOnError.identifier(),
).From(targetTable.identifier()).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*Target, error) {
target := new(Target)
err := row.Scan(
&target.ID,
&target.EventDate,
&target.ResourceOwner,
&target.Sequence,
&target.Name,
&target.TargetType,
&target.Timeout,
&target.URL,
&target.Async,
&target.InterruptOnError,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, zerrors.ThrowNotFound(err, "QUERY-hj5oaniyrz", "Errors.Target.NotFound")
}
return nil, zerrors.ThrowInternal(err, "QUERY-5qhc19sc49", "Errors.Internal")
}
return target, nil
}
}

View File

@@ -0,0 +1,301 @@
package query
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"regexp"
"testing"
"time"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
prepareTargetsStmt = `SELECT projections.targets.id,` +
` projections.targets.change_date,` +
` projections.targets.resource_owner,` +
` projections.targets.sequence,` +
` projections.targets.name,` +
` projections.targets.target_type,` +
` projections.targets.timeout,` +
` projections.targets.url,` +
` projections.targets.async,` +
` projections.targets.interrupt_on_error,` +
` COUNT(*) OVER ()` +
` FROM projections.targets`
prepareTargetsCols = []string{
"id",
"change_date",
"resource_owner",
"sequence",
"name",
"target_type",
"timeout",
"url",
"async",
"interrupt_on_error",
"count",
}
prepareTargetStmt = `SELECT projections.targets.id,` +
` projections.targets.change_date,` +
` projections.targets.resource_owner,` +
` projections.targets.sequence,` +
` projections.targets.name,` +
` projections.targets.target_type,` +
` projections.targets.timeout,` +
` projections.targets.url,` +
` projections.targets.async,` +
` projections.targets.interrupt_on_error` +
` FROM projections.targets`
prepareTargetCols = []string{
"id",
"change_date",
"resource_owner",
"sequence",
"name",
"target_type",
"timeout",
"url",
"async",
"interrupt_on_error",
}
)
func Test_TargetPrepares(t *testing.T) {
type want struct {
sqlExpectations sqlExpectation
err checkErr
}
tests := []struct {
name string
prepare interface{}
want want
object interface{}
}{
{
name: "prepareTargetsQuery no result",
prepare: prepareTargetsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareTargetsStmt),
nil,
nil,
),
},
object: &Targets{Targets: []*Target{}},
},
{
name: "prepareTargetsQuery one result",
prepare: prepareTargetsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareTargetsStmt),
prepareTargetsCols,
[][]driver.Value{
{
"id",
testNow,
"ro",
uint64(20211109),
"target-name",
domain.TargetTypeWebhook,
1 * time.Second,
"https://example.com",
true,
true,
},
},
),
},
object: &Targets{
SearchResponse: SearchResponse{
Count: 1,
},
Targets: []*Target{
{
ID: "id",
ObjectDetails: domain.ObjectDetails{
EventDate: testNow,
ResourceOwner: "ro",
Sequence: 20211109,
},
Name: "target-name",
TargetType: domain.TargetTypeWebhook,
Timeout: 1 * time.Second,
URL: "https://example.com",
Async: true,
InterruptOnError: true,
},
},
},
},
{
name: "prepareTargetsQuery multiple result",
prepare: prepareTargetsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareTargetsStmt),
prepareTargetsCols,
[][]driver.Value{
{
"id-1",
testNow,
"ro",
uint64(20211109),
"target-name1",
domain.TargetTypeWebhook,
1 * time.Second,
"https://example.com",
true,
false,
},
{
"id-2",
testNow,
"ro",
uint64(20211110),
"target-name2",
domain.TargetTypeWebhook,
1 * time.Second,
"https://example.com",
false,
true,
},
},
),
},
object: &Targets{
SearchResponse: SearchResponse{
Count: 2,
},
Targets: []*Target{
{
ID: "id-1",
ObjectDetails: domain.ObjectDetails{
EventDate: testNow,
ResourceOwner: "ro",
Sequence: 20211109,
},
Name: "target-name1",
TargetType: domain.TargetTypeWebhook,
Timeout: 1 * time.Second,
URL: "https://example.com",
Async: true,
InterruptOnError: false,
},
{
ID: "id-2",
ObjectDetails: domain.ObjectDetails{
EventDate: testNow,
ResourceOwner: "ro",
Sequence: 20211110,
},
Name: "target-name2",
TargetType: domain.TargetTypeWebhook,
Timeout: 1 * time.Second,
URL: "https://example.com",
Async: false,
InterruptOnError: true,
},
},
},
},
{
name: "prepareTargetsQuery sql err",
prepare: prepareTargetsQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(prepareTargetsStmt),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
if !errors.Is(err, sql.ErrConnDone) {
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
}
return nil, true
},
},
object: (*Target)(nil),
},
{
name: "prepareTargetQuery no result",
prepare: prepareTargetQuery,
want: want{
sqlExpectations: mockQueriesScanErr(
regexp.QuoteMeta(prepareTargetStmt),
nil,
nil,
),
err: func(err error) (error, bool) {
if !zerrors.IsNotFound(err) {
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
}
return nil, true
},
},
object: (*Target)(nil),
},
{
name: "prepareTargetQuery found",
prepare: prepareTargetQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(prepareTargetStmt),
prepareTargetCols,
[]driver.Value{
"id",
testNow,
"ro",
uint64(20211109),
"target-name",
domain.TargetTypeWebhook,
1 * time.Second,
"https://example.com",
true,
false,
},
),
},
object: &Target{
ID: "id",
ObjectDetails: domain.ObjectDetails{
EventDate: testNow,
ResourceOwner: "ro",
Sequence: 20211109,
},
Name: "target-name",
TargetType: domain.TargetTypeWebhook,
Timeout: 1 * time.Second,
URL: "https://example.com",
Async: true,
InterruptOnError: false,
},
},
{
name: "prepareTargetQuery sql err",
prepare: prepareTargetQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(prepareTargetStmt),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
if !errors.Is(err, sql.ErrConnDone) {
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
}
return nil, true
},
},
object: (*Target)(nil),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err, defaultPrepareArgs...)
})
}
}

View File

@@ -3,7 +3,6 @@ package execution
import (
"context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
)
@@ -16,9 +15,8 @@ const (
type SetEvent struct {
*eventstore.BaseEvent `json:"-"`
ExecutionType domain.ExecutionType `json:"executionType"`
Targets []string `json:"targets"`
Includes []string `json:"includes"`
Targets []string `json:"targets"`
Includes []string `json:"includes"`
}
func (e *SetEvent) SetBaseEvent(b *eventstore.BaseEvent) {

View File

@@ -3,7 +3,7 @@ package target
import "github.com/zitadel/zitadel/internal/eventstore"
func init() {
eventstore.RegisterFilterEventMapper(AggregateType, AddedEventType, AddedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, ChangedEventType, ChangedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, RemovedEventType, RemovedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, AddedEventType, eventstore.GenericEventMapper[AddedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, ChangedEventType, eventstore.GenericEventMapper[ChangedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, RemovedEventType, eventstore.GenericEventMapper[RemovedEvent])
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors"
)
const (
@@ -17,7 +16,7 @@ const (
)
type AddedEvent struct {
*eventstore.BaseEvent `json:"-"`
eventstore.BaseEvent `json:"-"`
Name string `json:"name"`
TargetType domain.TargetType `json:"targetType"`
@@ -28,7 +27,7 @@ type AddedEvent struct {
}
func (e *AddedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
e.BaseEvent = *b
}
func (e *AddedEvent) Payload() any {
@@ -50,26 +49,14 @@ func NewAddedEvent(
interruptOnError bool,
) *AddedEvent {
return &AddedEvent{
eventstore.NewBaseEventForPush(
*eventstore.NewBaseEventForPush(
ctx, aggregate, AddedEventType,
),
name, targetType, url, timeout, async, interruptOnError}
}
func AddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
added := &AddedEvent{
BaseEvent: eventstore.BaseEventFromRepo(event),
}
err := event.Unmarshal(added)
if err != nil {
return nil, zerrors.ThrowInternal(err, "TARGET-fx8f8yfbn1", "unable to unmarshal target added")
}
return added, nil
}
type ChangedEvent struct {
*eventstore.BaseEvent `json:"-"`
eventstore.BaseEvent `json:"-"`
Name *string `json:"name,omitempty"`
TargetType *domain.TargetType `json:"targetType,omitempty"`
@@ -81,6 +68,10 @@ type ChangedEvent struct {
oldName string
}
func (e *ChangedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = *b
}
func (e *ChangedEvent) Payload() interface{} {
return e
}
@@ -101,7 +92,7 @@ func NewChangedEvent(
changes []Changes,
) *ChangedEvent {
changeEvent := &ChangedEvent{
BaseEvent: eventstore.NewBaseEventForPush(
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
ChangedEventType,
@@ -152,26 +143,14 @@ func ChangeInterruptOnError(interruptOnError bool) func(event *ChangedEvent) {
}
}
func ChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
changed := &ChangedEvent{
BaseEvent: eventstore.BaseEventFromRepo(event),
}
err := event.Unmarshal(changed)
if err != nil {
return nil, zerrors.ThrowInternal(err, "TARGET-w6402p4ek7", "unable to unmarshal target changed")
}
return changed, nil
}
type RemovedEvent struct {
*eventstore.BaseEvent `json:"-"`
eventstore.BaseEvent `json:"-"`
name string
}
func (e *RemovedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
e.BaseEvent = *b
}
func (e *RemovedEvent) Payload() any {
@@ -183,17 +162,5 @@ func (e *RemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
}
func NewRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, name string) *RemovedEvent {
return &RemovedEvent{eventstore.NewBaseEventForPush(ctx, aggregate, RemovedEventType), name}
}
func RemovedEventMapper(event eventstore.Event) (eventstore.Event, error) {
removed := &RemovedEvent{
BaseEvent: eventstore.BaseEventFromRepo(event),
}
err := event.Unmarshal(removed)
if err != nil {
return nil, zerrors.ThrowInternal(err, "TARGET-0kuc12c7bc", "unable to unmarshal target removed")
}
return removed, nil
return &RemovedEvent{*eventstore.NewBaseEventForPush(ctx, aggregate, RemovedEventType), name}
}

View File

@@ -13,23 +13,37 @@ import "zitadel/protoc_gen_zitadel/v2/options.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha;execution";
message SetConditions{
message Execution {
string execution_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"request.zitadel.session.v2beta.SessionService\"";
}
];
// Details provide some base information (such as the last change date) of the target.
zitadel.object.v2beta.Details details = 2;
// Targets which are called in the defined conditions.
repeated string targets = 3;
// Included executions with the same condition-types.
repeated string includes = 4;
}
message Condition {
// Condition-types under which conditions the execution should happen, only one possible.
oneof condition_type {
option (validate.required) = true;
// Condition-type to execute if a request on the defined API point happens.
SetRequestExecution request = 1;
RequestExecution request = 1;
// Condition-type to execute on response if a request on the defined API point happens.
SetResponseExecution response = 2;
ResponseExecution response = 2;
// Condition-type to execute if function is used, replaces actions v1.
string function = 3;
// Condition-type to execute if an event is created in the system.
SetEventExecution event = 4;
EventExecution event = 4;
}
}
message SetRequestExecution{
message RequestExecution {
// Condition for the request execution, only one possible.
oneof condition{
// GRPC-method as condition.
@@ -55,7 +69,7 @@ message SetRequestExecution{
}
}
message SetResponseExecution{
message ResponseExecution {
// Condition for the response execution, only one possible.
oneof condition{
// GRPC-method as condition.
@@ -81,7 +95,7 @@ message SetResponseExecution{
}
}
message SetEventExecution{
message EventExecution{
// Condition for the event execution, only one possible.
oneof condition{
// Event name as condition.

View File

@@ -10,6 +10,7 @@ import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "zitadel/execution/v3alpha/target.proto";
import "zitadel/execution/v3alpha/execution.proto";
import "zitadel/execution/v3alpha/query.proto";
import "zitadel/object/v2beta/object.proto";
import "zitadel/protoc_gen_zitadel/v2/options.proto";
@@ -187,12 +188,73 @@ service ExecutionService {
};
}
// List targets
//
// List all matching targets. By default, we will return all targets of your instance.
// Make sure to include a limit and sorting for pagination.
rpc ListTargets (ListTargetsRequest) returns (ListTargetsResponse) {
option (google.api.http) = {
post: "/v3alpha/targets/search"
body: "*"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "execution.target.read"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "A list of all targets matching the query";
};
};
responses: {
key: "400";
value: {
description: "invalid list query";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
};
};
};
};
};
}
// Target by ID
//
// Returns the target identified by the requested ID.
rpc GetTargetByID (GetTargetByIDRequest) returns (GetTargetByIDResponse) {
option (google.api.http) = {
get: "/v3alpha/targets/{target_id}"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "execution.target.read"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200"
value: {
description: "Target successfully retrieved";
}
};
};
}
// Set an execution
//
// Set an execution to call a previously defined target or include the targets of a previously defined execution.
rpc SetExecution (SetExecutionRequest) returns (SetExecutionResponse) {
option (google.api.http) = {
post: "/v3alpha/executions"
put: "/v3alpha/executions"
body: "*"
};
@@ -236,6 +298,44 @@ service ExecutionService {
};
};
}
// List executions
//
// List all matching executions. By default, we will return all executions of your instance.
// Make sure to include a limit and sorting for pagination.
rpc ListExecutions (ListExecutionsRequest) returns (ListExecutionsResponse) {
option (google.api.http) = {
post: "/v3alpha/executions/search"
body: "*"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "execution.read"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "A list of all executions matching the query";
};
};
responses: {
key: "400";
value: {
description: "invalid list query";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
};
};
};
};
};
}
// List all available functions
//
// List all available functions which can be used as condition for executions.
@@ -412,9 +512,48 @@ message DeleteTargetResponse {
zitadel.object.v2beta.Details details = 1;
}
message ListTargetsRequest {
// list limitations and ordering.
zitadel.object.v2beta.ListQuery query = 1;
// the field the result is sorted.
zitadel.execution.v3alpha.TargetFieldName sorting_column = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"FIELD_NAME_SCHEMA_TYPE\""
}
];
// Define the criteria to query for.
repeated zitadel.execution.v3alpha.TargetSearchQuery queries = 3;
}
message ListTargetsResponse {
// Details provides information about the returned result including total amount found.
zitadel.object.v2beta.ListDetails details = 1;
// States by which field the results are sorted.
zitadel.execution.v3alpha.TargetFieldName sorting_column = 2;
// The result contains the user schemas, which matched the queries.
repeated zitadel.execution.v3alpha.Target result = 3;
}
message GetTargetByIDRequest {
// unique identifier of the target.
string target_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1,
max_length: 200,
example: "\"69629026806489455\"";
}
];
}
message GetTargetByIDResponse {
zitadel.execution.v3alpha.Target target = 1;
}
message SetExecutionRequest {
// Defines the condition type and content of the condition for execution.
SetConditions condition = 1;
Condition condition = 1;
// Defines the execution targets which are defined as a different resource, which are called in the defined conditions.
repeated string targets = 2;
// Defines other executions as included with the same condition-types.
@@ -428,7 +567,7 @@ message SetExecutionResponse {
message DeleteExecutionRequest {
// Unique identifier of the execution.
SetConditions condition = 1;
Condition condition = 1;
}
message DeleteExecutionResponse {
@@ -436,6 +575,20 @@ message DeleteExecutionResponse {
zitadel.object.v2beta.Details details = 1;
}
message ListExecutionsRequest {
// list limitations and ordering.
zitadel.object.v2beta.ListQuery query = 1;
// Define the criteria to query for.
repeated zitadel.execution.v3alpha.SearchQuery queries = 2;
}
message ListExecutionsResponse {
// Details provides information about the returned result including total amount found.
zitadel.object.v2beta.ListDetails details = 1;
// The result contains the executions, which matched the queries.
repeated zitadel.execution.v3alpha.Execution result = 2;
}
message ListExecutionFunctionsRequest{}
message ListExecutionFunctionsResponse{
// All available methods

View File

@@ -0,0 +1,110 @@
syntax = "proto3";
package zitadel.execution.v3alpha;
option go_package = "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha;execution";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "zitadel/object/v2beta/object.proto";
import "zitadel/execution/v3alpha/execution.proto";
message SearchQuery {
oneof query {
option (validate.required) = true;
InConditionsQuery in_conditions_query = 1;
ExecutionTypeQuery execution_type_query = 2;
TargetQuery target_query = 3;
IncludeQuery include_query = 4;
}
}
message InConditionsQuery {
// Defines the conditions to query for.
repeated Condition conditions = 1;
}
message ExecutionTypeQuery {
// Defines the type to query for.
ExecutionType execution_type = 1;
}
message TargetQuery {
// Defines the id to query for.
string target_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the id of the targets to include"
example: "\"69629023906488334\"";
}
];
}
message IncludeQuery {
// Defines the include to query for.
string include = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the id of the include"
example: "\"request.zitadel.session.v2beta.SessionService\"";
}
];
}
message TargetSearchQuery {
oneof query {
option (validate.required) = true;
TargetNameQuery target_name_query = 1;
InTargetIDsQuery in_target_ids_query = 2;
}
}
message TargetNameQuery {
// Defines the name of the target to query for.
string target_name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
max_length: 200;
example: "\"ip_allow_list\"";
}
];
// Defines which text comparison method used for the name query.
zitadel.object.v2beta.TextQueryMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
message InTargetIDsQuery {
// Defines the ids to query for.
repeated string target_ids = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the ids of the targets to include"
example: "[\"69629023906488334\",\"69622366012355662\"]";
}
];
}
enum ExecutionType {
EXECUTION_TYPE_UNSPECIFIED = 0;
EXECUTION_TYPE_REQUEST = 1;
EXECUTION_TYPE_RESPONSE = 2;
EXECUTION_TYPE_EVENT = 3;
EXECUTION_TYPE_FUNCTION = 4;
}
enum TargetFieldName {
FIELD_NAME_UNSPECIFIED = 0;
FIELD_NAME_ID = 1;
FIELD_NAME_CREATION_DATE = 2;
FIELD_NAME_CHANGE_DATE = 3;
FIELD_NAME_NAME = 4;
FIELD_NAME_TARGET_TYPE = 5;
FIELD_NAME_URL = 6;
FIELD_NAME_TIMEOUT = 7;
FIELD_NAME_ASYNC = 8;
FIELD_NAME_INTERRUPT_ON_ERROR = 9;
}

View File

@@ -35,4 +35,39 @@ message SetRESTRequestResponse {
example: "\"https://example.com/hooks/ip_check\"";
}
];
}
message Target {
// ID is the read-only unique identifier of the target.
string target_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629012906488334\"";
}
];
// Details provide some base information (such as the last change date) of the target.
zitadel.object.v2beta.Details details = 2;
// Unique name of the target.
string name = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"ip_allow_list\"";
}
];
// Defines the target type and how the response of the target is treated.
oneof target_type {
SetRESTWebhook rest_webhook = 4;
SetRESTRequestResponse rest_request_response = 5;
}
// Timeout defines the duration until ZITADEL cancels the execution.
google.protobuf.Duration timeout = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"10s\"";
}
];
oneof execution_type {
// Set the execution to run asynchronously.
bool is_async = 7;
// Define if any error stops the whole execution. By default the process continues as normal.
bool interrupt_on_error = 8;
}
}