mirror of
https://github.com/zitadel/zitadel.git
synced 2025-03-01 03:07:22 +00:00
feat: actions v2 execution targets command side (#7384)
Adds the API to create, update, delete targets for execution in a new ExecutionService (v3alpha)
This commit is contained in:
parent
518c8f486e
commit
198bc017b8
@ -982,6 +982,8 @@ InternalAuthZ:
|
|||||||
- "events.read"
|
- "events.read"
|
||||||
- "milestones.read"
|
- "milestones.read"
|
||||||
- "session.delete"
|
- "session.delete"
|
||||||
|
- "execution.target.write"
|
||||||
|
- "execution.target.delete"
|
||||||
- Role: "IAM_OWNER_VIEWER"
|
- Role: "IAM_OWNER_VIEWER"
|
||||||
Permissions:
|
Permissions:
|
||||||
- "iam.read"
|
- "iam.read"
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/admin"
|
"github.com/zitadel/zitadel/internal/api/grpc/admin"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/auth"
|
"github.com/zitadel/zitadel/internal/api/grpc/auth"
|
||||||
|
execution_v3_alpha "github.com/zitadel/zitadel/internal/api/grpc/execution/v3alpha"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
||||||
oidc_v2 "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2"
|
oidc_v2 "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/org/v2"
|
"github.com/zitadel/zitadel/internal/api/grpc/org/v2"
|
||||||
@ -400,6 +401,9 @@ func startAPIs(
|
|||||||
if err := apis.RegisterService(ctx, org.CreateServer(commands, queries, permissionCheck)); err != nil {
|
if err := apis.RegisterService(ctx, org.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := apis.RegisterService(ctx, execution_v3_alpha.CreateServer(commands, queries)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
||||||
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
||||||
apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle))
|
apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle))
|
||||||
|
@ -289,6 +289,13 @@ module.exports = {
|
|||||||
sidebarOptions: {
|
sidebarOptions: {
|
||||||
groupPathsBy: "tag",
|
groupPathsBy: "tag",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
execution_v3: {
|
||||||
|
specPath: ".artifacts/openapi/zitadel/execution/v3alpha/execution_service.swagger.json",
|
||||||
|
outputDir: "docs/apis/resources/execution_service_v3",
|
||||||
|
sidebarOptions: {
|
||||||
|
groupPathsBy: "tag",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -631,6 +631,20 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
items: require("./docs/apis/resources/settings_service/sidebar.js"),
|
items: require("./docs/apis/resources/settings_service/sidebar.js"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Execution Lifecycle (Alpha)",
|
||||||
|
link: {
|
||||||
|
type: "generated-index",
|
||||||
|
title: "Execution Service API (Alpha)",
|
||||||
|
slug: "/apis/resources/execution_service_v3",
|
||||||
|
description:
|
||||||
|
"This API is intended to manage custom executions (previously known as actions) in a ZITADEL instance.\n"+
|
||||||
|
"\n"+
|
||||||
|
"This project is in alpha state. It can AND will continue breaking until the services provide the same functionality as the current actions.",
|
||||||
|
},
|
||||||
|
items: require("./docs/apis/resources/execution_service_v3/sidebar.js"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "Assets",
|
label: "Assets",
|
||||||
|
51
internal/api/grpc/execution/v3alpha/server.go
Normal file
51
internal/api/grpc/execution/v3alpha/server.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package execution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ execution.ExecutionServiceServer = (*Server)(nil)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
execution.UnimplementedExecutionServiceServer
|
||||||
|
command *command.Commands
|
||||||
|
query *query.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct{}
|
||||||
|
|
||||||
|
func CreateServer(
|
||||||
|
command *command.Commands,
|
||||||
|
query *query.Queries,
|
||||||
|
) *Server {
|
||||||
|
return &Server{
|
||||||
|
command: command,
|
||||||
|
query: query,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||||
|
execution.RegisterExecutionServiceServer(grpcServer, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AppName() string {
|
||||||
|
return execution.ExecutionService_ServiceDesc.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) MethodPrefix() string {
|
||||||
|
return execution.ExecutionService_ServiceDesc.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||||
|
return execution.ExecutionService_AuthMethods
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||||
|
return execution.RegisterExecutionServiceHandler
|
||||||
|
}
|
95
internal/api/grpc/execution/v3alpha/target.go
Normal file
95
internal/api/grpc/execution/v3alpha/target.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package execution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"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/eventstore/v1/models"
|
||||||
|
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) CreateTarget(ctx context.Context, req *execution.CreateTargetRequest) (*execution.CreateTargetResponse, error) {
|
||||||
|
add := createTargetToCommand(req)
|
||||||
|
details, err := s.command.AddTarget(ctx, add, authz.GetInstance(ctx).InstanceID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &execution.CreateTargetResponse{
|
||||||
|
Id: add.AggregateID,
|
||||||
|
Details: object.DomainToDetailsPb(details),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UpdateTarget(ctx context.Context, req *execution.UpdateTargetRequest) (*execution.UpdateTargetResponse, error) {
|
||||||
|
details, err := s.command.ChangeTarget(ctx, updateTargetToCommand(req), authz.GetInstance(ctx).InstanceID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &execution.UpdateTargetResponse{
|
||||||
|
Details: object.DomainToDetailsPb(details),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) DeleteTarget(ctx context.Context, req *execution.DeleteTargetRequest) (*execution.DeleteTargetResponse, error) {
|
||||||
|
details, err := s.command.DeleteTarget(ctx, req.GetTargetId(), authz.GetInstance(ctx).InstanceID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &execution.DeleteTargetResponse{
|
||||||
|
Details: object.DomainToDetailsPb(details),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTargetToCommand(req *execution.CreateTargetRequest) *command.AddTarget {
|
||||||
|
var targetType domain.TargetType
|
||||||
|
var url string
|
||||||
|
switch t := req.GetTargetType().(type) {
|
||||||
|
case *execution.CreateTargetRequest_RestWebhook:
|
||||||
|
targetType = domain.TargetTypeWebhook
|
||||||
|
url = t.RestWebhook.GetUrl()
|
||||||
|
case *execution.CreateTargetRequest_RestRequestResponse:
|
||||||
|
targetType = domain.TargetTypeRequestResponse
|
||||||
|
url = t.RestRequestResponse.GetUrl()
|
||||||
|
}
|
||||||
|
return &command.AddTarget{
|
||||||
|
Name: req.GetName(),
|
||||||
|
TargetType: targetType,
|
||||||
|
URL: url,
|
||||||
|
Timeout: req.GetTimeout().AsDuration(),
|
||||||
|
Async: req.GetIsAsync(),
|
||||||
|
InterruptOnError: req.GetInterruptOnError(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTargetToCommand(req *execution.UpdateTargetRequest) *command.ChangeTarget {
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
target := &command.ChangeTarget{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: req.GetTargetId(),
|
||||||
|
},
|
||||||
|
Name: req.Name,
|
||||||
|
}
|
||||||
|
switch t := req.GetTargetType().(type) {
|
||||||
|
case *execution.UpdateTargetRequest_RestWebhook:
|
||||||
|
target.TargetType = gu.Ptr(domain.TargetTypeWebhook)
|
||||||
|
target.URL = gu.Ptr(t.RestWebhook.GetUrl())
|
||||||
|
case *execution.UpdateTargetRequest_RestRequestResponse:
|
||||||
|
target.TargetType = gu.Ptr(domain.TargetTypeRequestResponse)
|
||||||
|
target.URL = gu.Ptr(t.RestRequestResponse.GetUrl())
|
||||||
|
}
|
||||||
|
if req.Timeout != nil {
|
||||||
|
target.Timeout = gu.Ptr(req.GetTimeout().AsDuration())
|
||||||
|
}
|
||||||
|
if req.ExecutionType != nil {
|
||||||
|
target.Async = gu.Ptr(req.GetIsAsync())
|
||||||
|
target.InterruptOnError = gu.Ptr(req.GetInterruptOnError())
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
411
internal/api/grpc/execution/v3alpha/target_integration_test.go
Normal file
411
internal/api/grpc/execution/v3alpha/target_integration_test.go
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package execution_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
|
||||||
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CTX context.Context
|
||||||
|
Tester *integration.Tester
|
||||||
|
Client execution.ExecutionServiceClient
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(func() int {
|
||||||
|
ctx, errCtx, cancel := integration.Contexts(5 * time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
Tester = integration.NewTester(ctx)
|
||||||
|
defer Tester.Done()
|
||||||
|
Client = Tester.Client.ExecutionV3
|
||||||
|
|
||||||
|
CTX, _ = Tester.WithAuthorization(ctx, integration.IAMOwner), errCtx
|
||||||
|
return m.Run()
|
||||||
|
}())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_CreateTarget(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *execution.CreateTargetRequest
|
||||||
|
want *execution.CreateTargetResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
req: &execution.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty name",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.CreateTargetRequest{
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty type",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: nil,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty webhook url",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: &execution.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &execution.SetRESTWebhook{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty request response url",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: &execution.CreateTargetRequest_RestRequestResponse{
|
||||||
|
RestRequestResponse: &execution.SetRESTRequestResponse{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty timeout",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: &execution.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &execution.SetRESTWebhook{
|
||||||
|
Url: "https://example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: nil,
|
||||||
|
ExecutionType: nil,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty execution type, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: &execution.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &execution.SetRESTWebhook{
|
||||||
|
Url: "https://example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
ExecutionType: nil,
|
||||||
|
},
|
||||||
|
want: &execution.CreateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "async execution, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: &execution.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &execution.SetRESTWebhook{
|
||||||
|
Url: "https://example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
ExecutionType: &execution.CreateTargetRequest_IsAsync{
|
||||||
|
IsAsync: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &execution.CreateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "interrupt on error execution, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: &execution.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &execution.SetRESTWebhook{
|
||||||
|
Url: "https://example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
ExecutionType: &execution.CreateTargetRequest_InterruptOnError{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &execution.CreateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Client.CreateTarget(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
integration.AssertDetails(t, tt.want, got)
|
||||||
|
assert.NotEmpty(t, got.GetId())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_UpdateTarget(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
req *execution.UpdateTargetRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
prepare func(request *execution.UpdateTargetRequest) error
|
||||||
|
args args
|
||||||
|
want *execution.UpdateTargetResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
prepare: func(request *execution.UpdateTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||||
|
request.TargetId = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
req: &execution.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not existing",
|
||||||
|
prepare: func(request *execution.UpdateTargetRequest) error {
|
||||||
|
request.TargetId = "notexisting"
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change name, ok",
|
||||||
|
prepare: func(request *execution.UpdateTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||||
|
request.TargetId = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &execution.UpdateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change type, ok",
|
||||||
|
prepare: func(request *execution.UpdateTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||||
|
request.TargetId = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.UpdateTargetRequest{
|
||||||
|
TargetType: &execution.UpdateTargetRequest_RestRequestResponse{
|
||||||
|
RestRequestResponse: &execution.SetRESTRequestResponse{
|
||||||
|
Url: "https://example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &execution.UpdateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change url, ok",
|
||||||
|
prepare: func(request *execution.UpdateTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||||
|
request.TargetId = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.UpdateTargetRequest{
|
||||||
|
TargetType: &execution.UpdateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &execution.SetRESTWebhook{
|
||||||
|
Url: "https://example.com/hooks/new",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &execution.UpdateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change timeout, ok",
|
||||||
|
prepare: func(request *execution.UpdateTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||||
|
request.TargetId = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.UpdateTargetRequest{
|
||||||
|
Timeout: durationpb.New(20 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &execution.UpdateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change execution type, ok",
|
||||||
|
prepare: func(request *execution.UpdateTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||||
|
request.TargetId = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.UpdateTargetRequest{
|
||||||
|
ExecutionType: &execution.UpdateTargetRequest_IsAsync{
|
||||||
|
IsAsync: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &execution.UpdateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.prepare(tt.args.req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, err := Client.UpdateTarget(tt.args.ctx, tt.args.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
integration.AssertDetails(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_DeleteTarget(t *testing.T) {
|
||||||
|
target := Tester.CreateTarget(CTX, t)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *execution.DeleteTargetRequest
|
||||||
|
want *execution.DeleteTargetResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
req: &execution.DeleteTargetRequest{
|
||||||
|
TargetId: target.GetId(),
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty id",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.DeleteTargetRequest{
|
||||||
|
TargetId: "",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete target",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &execution.DeleteTargetRequest{
|
||||||
|
TargetId: target.GetId(),
|
||||||
|
},
|
||||||
|
want: &execution.DeleteTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Client.DeleteTarget(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
integration.AssertDetails(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
193
internal/api/grpc/execution/v3alpha/target_test.go
Normal file
193
internal/api/grpc/execution/v3alpha/target_test.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package execution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_createTargetToCommand(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
req *execution.CreateTargetRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *command.AddTarget
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
args: args{nil},
|
||||||
|
want: &command.AddTarget{
|
||||||
|
Name: "",
|
||||||
|
TargetType: domain.TargetTypeUnspecified,
|
||||||
|
URL: "",
|
||||||
|
Timeout: 0,
|
||||||
|
Async: false,
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (async webhook)",
|
||||||
|
args: args{&execution.CreateTargetRequest{
|
||||||
|
Name: "target 1",
|
||||||
|
TargetType: &execution.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &execution.SetRESTWebhook{
|
||||||
|
Url: "https://example.com/hooks/1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
ExecutionType: &execution.CreateTargetRequest_IsAsync{
|
||||||
|
IsAsync: true,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
want: &command.AddTarget{
|
||||||
|
Name: "target 1",
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
URL: "https://example.com/hooks/1",
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
Async: true,
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (interrupting response)",
|
||||||
|
args: args{&execution.CreateTargetRequest{
|
||||||
|
Name: "target 1",
|
||||||
|
TargetType: &execution.CreateTargetRequest_RestRequestResponse{
|
||||||
|
RestRequestResponse: &execution.SetRESTRequestResponse{
|
||||||
|
Url: "https://example.com/hooks/1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
ExecutionType: &execution.CreateTargetRequest_InterruptOnError{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
want: &command.AddTarget{
|
||||||
|
Name: "target 1",
|
||||||
|
TargetType: domain.TargetTypeRequestResponse,
|
||||||
|
URL: "https://example.com/hooks/1",
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
Async: false,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := createTargetToCommand(tt.args.req)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_updateTargetToCommand(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
req *execution.UpdateTargetRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *command.ChangeTarget
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
args: args{nil},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields nil",
|
||||||
|
args: args{&execution.UpdateTargetRequest{
|
||||||
|
Name: nil,
|
||||||
|
TargetType: nil,
|
||||||
|
Timeout: nil,
|
||||||
|
ExecutionType: nil,
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: nil,
|
||||||
|
TargetType: nil,
|
||||||
|
URL: nil,
|
||||||
|
Timeout: nil,
|
||||||
|
Async: nil,
|
||||||
|
InterruptOnError: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields empty",
|
||||||
|
args: args{&execution.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr(""),
|
||||||
|
TargetType: nil,
|
||||||
|
Timeout: durationpb.New(0),
|
||||||
|
ExecutionType: nil,
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr(""),
|
||||||
|
TargetType: nil,
|
||||||
|
URL: nil,
|
||||||
|
Timeout: gu.Ptr(0 * time.Second),
|
||||||
|
Async: nil,
|
||||||
|
InterruptOnError: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (async webhook)",
|
||||||
|
args: args{&execution.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: &execution.UpdateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &execution.SetRESTWebhook{
|
||||||
|
Url: "https://example.com/hooks/1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
ExecutionType: &execution.UpdateTargetRequest_IsAsync{
|
||||||
|
IsAsync: true,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||||
|
URL: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
|
Async: gu.Ptr(true),
|
||||||
|
InterruptOnError: gu.Ptr(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (interrupting response)",
|
||||||
|
args: args{&execution.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: &execution.UpdateTargetRequest_RestRequestResponse{
|
||||||
|
RestRequestResponse: &execution.SetRESTRequestResponse{
|
||||||
|
Url: "https://example.com/hooks/1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
ExecutionType: &execution.UpdateTargetRequest_InterruptOnError{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeRequestResponse),
|
||||||
|
URL: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
|
Async: gu.Ptr(false),
|
||||||
|
InterruptOnError: gu.Ptr(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := updateTargetToCommand(tt.args.req)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
177
internal/command/action_v2_target.go
Normal file
177
internal/command/action_v2_target.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/target"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddTarget struct {
|
||||||
|
models.ObjectRoot
|
||||||
|
|
||||||
|
Name string
|
||||||
|
TargetType domain.TargetType
|
||||||
|
URL string
|
||||||
|
Timeout time.Duration
|
||||||
|
Async bool
|
||||||
|
InterruptOnError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AddTarget) IsValid() error {
|
||||||
|
if a.Name == "" {
|
||||||
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-ddqbm9us5p", "Errors.Target.Invalid")
|
||||||
|
}
|
||||||
|
if a.Timeout == 0 {
|
||||||
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-39f35d8uri", "Errors.Target.NoTimeout")
|
||||||
|
}
|
||||||
|
_, err := url.Parse(a.URL)
|
||||||
|
if err != nil || a.URL == "" {
|
||||||
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-1r2k6qo6wg", "Errors.Target.InvalidURL")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) AddTarget(ctx context.Context, add *AddTarget, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||||
|
if resourceOwner == "" {
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-brml926e2d", "Errors.IDMissing")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := add.IsValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if add.AggregateID == "" {
|
||||||
|
add.AggregateID, err = c.idGenerator.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wm := NewTargetWriteModel(add.AggregateID, resourceOwner)
|
||||||
|
pushedEvents, err := c.eventstore.Push(ctx, target.NewAddedEvent(
|
||||||
|
ctx,
|
||||||
|
TargetAggregateFromWriteModel(&wm.WriteModel),
|
||||||
|
add.Name,
|
||||||
|
add.TargetType,
|
||||||
|
add.URL,
|
||||||
|
add.Timeout,
|
||||||
|
add.Async,
|
||||||
|
add.InterruptOnError,
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := AppendAndReduce(wm, pushedEvents...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeTarget struct {
|
||||||
|
models.ObjectRoot
|
||||||
|
|
||||||
|
Name *string
|
||||||
|
TargetType *domain.TargetType
|
||||||
|
URL *string
|
||||||
|
Timeout *time.Duration
|
||||||
|
Async *bool
|
||||||
|
InterruptOnError *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ChangeTarget) IsValid() error {
|
||||||
|
if a.AggregateID == "" {
|
||||||
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-1l6ympeagp", "Errors.IDMissing")
|
||||||
|
}
|
||||||
|
if a.Name != nil && *a.Name == "" {
|
||||||
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-d1wx4lm0zr", "Errors.Target.Invalid")
|
||||||
|
}
|
||||||
|
if a.Timeout != nil && *a.Timeout == 0 {
|
||||||
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-08b39vdi57", "Errors.Target.NoTimeout")
|
||||||
|
}
|
||||||
|
if a.URL != nil {
|
||||||
|
_, err := url.Parse(*a.URL)
|
||||||
|
if err != nil || *a.URL == "" {
|
||||||
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-jsbaera7b6", "Errors.Target.InvalidURL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) ChangeTarget(ctx context.Context, change *ChangeTarget, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
|
if resourceOwner == "" {
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-zqibgg0wwh", "Errors.IDMissing")
|
||||||
|
}
|
||||||
|
if err := change.IsValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err := c.getTargetWriteModelByID(ctx, change.AggregateID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existing.State.Exists() {
|
||||||
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-xj14f2cccn", "Errors.Target.NotFound")
|
||||||
|
}
|
||||||
|
|
||||||
|
changedEvent := existing.NewChangedEvent(
|
||||||
|
ctx,
|
||||||
|
TargetAggregateFromWriteModel(&existing.WriteModel),
|
||||||
|
change.Name,
|
||||||
|
change.TargetType,
|
||||||
|
change.URL,
|
||||||
|
change.Timeout,
|
||||||
|
change.Async,
|
||||||
|
change.InterruptOnError)
|
||||||
|
if changedEvent == nil {
|
||||||
|
return writeModelToObjectDetails(&existing.WriteModel), nil
|
||||||
|
}
|
||||||
|
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = AppendAndReduce(existing, pushedEvents...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModelToObjectDetails(&existing.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) DeleteTarget(ctx context.Context, id, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
|
if id == "" || resourceOwner == "" {
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-obqos2l3no", "Errors.IDMissing")
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err := c.getTargetWriteModelByID(ctx, id, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existing.State.Exists() {
|
||||||
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-k4s7ucu0ax", "Errors.Target.NotFound")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.pushAppendAndReduce(ctx,
|
||||||
|
existing,
|
||||||
|
target.NewRemovedEvent(ctx,
|
||||||
|
TargetAggregateFromWriteModel(&existing.WriteModel),
|
||||||
|
existing.Name,
|
||||||
|
),
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModelToObjectDetails(&existing.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) getTargetWriteModelByID(ctx context.Context, id string, resourceOwner string) (*TargetWriteModel, error) {
|
||||||
|
wm := NewTargetWriteModel(id, resourceOwner)
|
||||||
|
err := c.eventstore.FilterToQueryReducer(ctx, wm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return wm, nil
|
||||||
|
}
|
127
internal/command/action_v2_target_model.go
Normal file
127
internal/command/action_v2_target_model.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/action"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/target"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TargetWriteModel struct {
|
||||||
|
eventstore.WriteModel
|
||||||
|
|
||||||
|
Name string
|
||||||
|
TargetType domain.TargetType
|
||||||
|
URL string
|
||||||
|
Timeout time.Duration
|
||||||
|
Async bool
|
||||||
|
InterruptOnError bool
|
||||||
|
|
||||||
|
State domain.TargetState
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTargetWriteModel(id string, resourceOwner string) *TargetWriteModel {
|
||||||
|
return &TargetWriteModel{
|
||||||
|
WriteModel: eventstore.WriteModel{
|
||||||
|
AggregateID: id,
|
||||||
|
ResourceOwner: resourceOwner,
|
||||||
|
InstanceID: resourceOwner,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *TargetWriteModel) Reduce() error {
|
||||||
|
for _, event := range wm.Events {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *target.AddedEvent:
|
||||||
|
wm.Name = e.Name
|
||||||
|
wm.TargetType = e.TargetType
|
||||||
|
wm.URL = e.URL
|
||||||
|
wm.Timeout = e.Timeout
|
||||||
|
wm.Async = e.Async
|
||||||
|
wm.State = domain.TargetActive
|
||||||
|
case *target.ChangedEvent:
|
||||||
|
if e.Name != nil {
|
||||||
|
wm.Name = *e.Name
|
||||||
|
}
|
||||||
|
if e.TargetType != nil {
|
||||||
|
wm.TargetType = *e.TargetType
|
||||||
|
}
|
||||||
|
if e.URL != nil {
|
||||||
|
wm.URL = *e.URL
|
||||||
|
}
|
||||||
|
if e.Timeout != nil {
|
||||||
|
wm.Timeout = *e.Timeout
|
||||||
|
}
|
||||||
|
if e.Async != nil {
|
||||||
|
wm.Async = *e.Async
|
||||||
|
}
|
||||||
|
if e.InterruptOnError != nil {
|
||||||
|
wm.InterruptOnError = *e.InterruptOnError
|
||||||
|
}
|
||||||
|
case *action.RemovedEvent:
|
||||||
|
wm.State = domain.TargetRemoved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wm.WriteModel.Reduce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *TargetWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
|
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
|
ResourceOwner(wm.ResourceOwner).
|
||||||
|
AddQuery().
|
||||||
|
AggregateTypes(target.AggregateType).
|
||||||
|
AggregateIDs(wm.AggregateID).
|
||||||
|
EventTypes(target.AddedEventType,
|
||||||
|
target.ChangedEventType,
|
||||||
|
target.RemovedEventType).
|
||||||
|
Builder()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *TargetWriteModel) NewChangedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
agg *eventstore.Aggregate,
|
||||||
|
name *string,
|
||||||
|
targetType *domain.TargetType,
|
||||||
|
url *string,
|
||||||
|
timeout *time.Duration,
|
||||||
|
async *bool,
|
||||||
|
interruptOnError *bool,
|
||||||
|
) *target.ChangedEvent {
|
||||||
|
changes := make([]target.Changes, 0)
|
||||||
|
if name != nil && wm.Name != *name {
|
||||||
|
changes = append(changes, target.ChangeName(wm.Name, *name))
|
||||||
|
}
|
||||||
|
if targetType != nil && wm.TargetType != *targetType {
|
||||||
|
changes = append(changes, target.ChangeTargetType(*targetType))
|
||||||
|
}
|
||||||
|
if url != nil && wm.URL != *url {
|
||||||
|
changes = append(changes, target.ChangeURL(*url))
|
||||||
|
}
|
||||||
|
if timeout != nil && wm.Timeout != *timeout {
|
||||||
|
changes = append(changes, target.ChangeTimeout(*timeout))
|
||||||
|
}
|
||||||
|
if async != nil && wm.Async != *async {
|
||||||
|
changes = append(changes, target.ChangeAsync(*async))
|
||||||
|
}
|
||||||
|
if interruptOnError != nil && wm.InterruptOnError != *interruptOnError {
|
||||||
|
changes = append(changes, target.ChangeInterruptOnError(*interruptOnError))
|
||||||
|
}
|
||||||
|
if len(changes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return target.NewChangedEvent(ctx, agg, changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TargetAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||||
|
return &eventstore.Aggregate{
|
||||||
|
ID: wm.AggregateID,
|
||||||
|
Type: target.AggregateType,
|
||||||
|
ResourceOwner: wm.ResourceOwner,
|
||||||
|
InstanceID: wm.InstanceID,
|
||||||
|
Version: target.AggregateVersion,
|
||||||
|
}
|
||||||
|
}
|
676
internal/command/action_v2_target_test.go
Normal file
676
internal/command/action_v2_target_test.go
Normal file
@ -0,0 +1,676 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
"github.com/zitadel/zitadel/internal/id"
|
||||||
|
"github.com/zitadel/zitadel/internal/id/mock"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/target"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommands_AddTarget(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
idGenerator id.Generator
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
add *AddTarget
|
||||||
|
resourceOwner string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
id string
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no resourceowner, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
add: &AddTarget{},
|
||||||
|
resourceOwner: "",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no name, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
add: &AddTarget{},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no timeout, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
add: &AddTarget{
|
||||||
|
Name: "name",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no url, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
add: &AddTarget{
|
||||||
|
Name: "name",
|
||||||
|
Timeout: time.Second,
|
||||||
|
URL: "",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no parsable url, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
add: &AddTarget{
|
||||||
|
Name: "name",
|
||||||
|
Timeout: time.Second,
|
||||||
|
URL: "://",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unique constraint failed, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectPushFailed(
|
||||||
|
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||||
|
target.NewAddedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
"name",
|
||||||
|
domain.TargetTypeWebhook,
|
||||||
|
"https://example.com",
|
||||||
|
time.Second,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: mock.ExpectID(t, "id1"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
add: &AddTarget{
|
||||||
|
Name: "name",
|
||||||
|
URL: "https://example.com",
|
||||||
|
Timeout: time.Second,
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"push ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectPush(
|
||||||
|
target.NewAddedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
"name",
|
||||||
|
domain.TargetTypeWebhook,
|
||||||
|
"https://example.com",
|
||||||
|
time.Second,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: mock.ExpectID(t, "id1"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
add: &AddTarget{
|
||||||
|
Name: "name",
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
Timeout: time.Second,
|
||||||
|
URL: "https://example.com",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
id: "id1",
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"push full ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectPush(
|
||||||
|
target.NewAddedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
"name",
|
||||||
|
domain.TargetTypeWebhook,
|
||||||
|
"https://example.com",
|
||||||
|
time.Second,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: mock.ExpectID(t, "id1"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
add: &AddTarget{
|
||||||
|
Name: "name",
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
URL: "https://example.com",
|
||||||
|
Timeout: time.Second,
|
||||||
|
Async: true,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
id: "id1",
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
idGenerator: tt.fields.idGenerator,
|
||||||
|
}
|
||||||
|
details, err := c.AddTarget(tt.args.ctx, tt.args.add, tt.args.resourceOwner)
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
if tt.res.err != nil && !tt.res.err(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.Equal(t, tt.res.id, tt.args.add.AggregateID)
|
||||||
|
assert.Equal(t, tt.res.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommands_ChangeTarget(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
change *ChangeTarget
|
||||||
|
resourceOwner string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"resourceowner missing, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{},
|
||||||
|
resourceOwner: "",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id missing, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name empty, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{
|
||||||
|
Name: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timeout empty, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{
|
||||||
|
Timeout: gu.Ptr(time.Duration(0)),
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url empty, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{
|
||||||
|
URL: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url not parsable, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{
|
||||||
|
URL: gu.Ptr("://"),
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not found, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "id1",
|
||||||
|
},
|
||||||
|
Name: gu.Ptr("name"),
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no changes",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
target.NewAddedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
"name",
|
||||||
|
domain.TargetTypeWebhook,
|
||||||
|
"https://example.com",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "id1",
|
||||||
|
},
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unique constraint failed, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
target.NewAddedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
"name",
|
||||||
|
domain.TargetTypeWebhook,
|
||||||
|
"https://example.com",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPushFailed(
|
||||||
|
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||||
|
target.NewChangedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
[]target.Changes{
|
||||||
|
target.ChangeName("name", "name2"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "id1",
|
||||||
|
},
|
||||||
|
Name: gu.Ptr("name2"),
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"push ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
target.NewAddedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
"name",
|
||||||
|
domain.TargetTypeWebhook,
|
||||||
|
"https://example.com",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
target.NewChangedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
[]target.Changes{
|
||||||
|
target.ChangeName("name", "name2"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "id1",
|
||||||
|
},
|
||||||
|
Name: gu.Ptr("name2"),
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"push full ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
target.NewAddedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
"name",
|
||||||
|
domain.TargetTypeWebhook,
|
||||||
|
"https://example.com",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
target.NewChangedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
[]target.Changes{
|
||||||
|
target.ChangeName("name", "name2"),
|
||||||
|
target.ChangeURL("https://example2.com"),
|
||||||
|
target.ChangeTargetType(domain.TargetTypeRequestResponse),
|
||||||
|
target.ChangeTimeout(time.Second),
|
||||||
|
target.ChangeAsync(true),
|
||||||
|
target.ChangeInterruptOnError(true),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
change: &ChangeTarget{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "id1",
|
||||||
|
},
|
||||||
|
Name: gu.Ptr("name2"),
|
||||||
|
URL: gu.Ptr("https://example2.com"),
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeRequestResponse),
|
||||||
|
Timeout: gu.Ptr(time.Second),
|
||||||
|
Async: gu.Ptr(true),
|
||||||
|
InterruptOnError: gu.Ptr(true),
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
}
|
||||||
|
details, err := c.ChangeTarget(tt.args.ctx, tt.args.change, tt.args.resourceOwner)
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
if tt.res.err != nil && !tt.res.err(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.Equal(t, tt.res.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommands_DeleteTarget(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
id string
|
||||||
|
resourceOwner string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"id missing, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
id: "",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not found, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
id: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: zerrors.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"remove ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
target.NewAddedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
"name",
|
||||||
|
domain.TargetTypeWebhook,
|
||||||
|
"https://example.com",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
target.NewRemovedEvent(context.Background(),
|
||||||
|
target.NewAggregate("id1", "org1"),
|
||||||
|
"name",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
id: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
}
|
||||||
|
details, err := c.DeleteTarget(tt.args.ctx, tt.args.id, tt.args.resourceOwner)
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
if tt.res.err != nil && !tt.res.err(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.Equal(t, tt.res.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
28
internal/domain/target.go
Normal file
28
internal/domain/target.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type TargetType uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
TargetTypeUnspecified TargetType = iota
|
||||||
|
TargetTypeWebhook
|
||||||
|
TargetTypeRequestResponse
|
||||||
|
|
||||||
|
TargetTypeStateCount
|
||||||
|
)
|
||||||
|
|
||||||
|
type TargetState int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
TargetUnspecified TargetState = iota
|
||||||
|
TargetActive
|
||||||
|
TargetRemoved
|
||||||
|
targetStateCount
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s TargetState) Valid() bool {
|
||||||
|
return s >= 0 && s < targetStateCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TargetState) Exists() bool {
|
||||||
|
return s != TargetUnspecified && s != TargetRemoved
|
||||||
|
}
|
@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||||
|
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
|
||||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
||||||
@ -38,28 +39,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
CC *grpc.ClientConn
|
CC *grpc.ClientConn
|
||||||
Admin admin.AdminServiceClient
|
Admin admin.AdminServiceClient
|
||||||
Mgmt mgmt.ManagementServiceClient
|
Mgmt mgmt.ManagementServiceClient
|
||||||
Auth auth.AuthServiceClient
|
Auth auth.AuthServiceClient
|
||||||
UserV2 user.UserServiceClient
|
UserV2 user.UserServiceClient
|
||||||
SessionV2 session.SessionServiceClient
|
SessionV2 session.SessionServiceClient
|
||||||
OIDCv2 oidc_pb.OIDCServiceClient
|
OIDCv2 oidc_pb.OIDCServiceClient
|
||||||
OrgV2 organisation.OrganizationServiceClient
|
OrgV2 organisation.OrganizationServiceClient
|
||||||
System system.SystemServiceClient
|
System system.SystemServiceClient
|
||||||
|
ExecutionV3 execution.ExecutionServiceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClient(cc *grpc.ClientConn) Client {
|
func newClient(cc *grpc.ClientConn) Client {
|
||||||
return Client{
|
return Client{
|
||||||
CC: cc,
|
CC: cc,
|
||||||
Admin: admin.NewAdminServiceClient(cc),
|
Admin: admin.NewAdminServiceClient(cc),
|
||||||
Mgmt: mgmt.NewManagementServiceClient(cc),
|
Mgmt: mgmt.NewManagementServiceClient(cc),
|
||||||
Auth: auth.NewAuthServiceClient(cc),
|
Auth: auth.NewAuthServiceClient(cc),
|
||||||
UserV2: user.NewUserServiceClient(cc),
|
UserV2: user.NewUserServiceClient(cc),
|
||||||
SessionV2: session.NewSessionServiceClient(cc),
|
SessionV2: session.NewSessionServiceClient(cc),
|
||||||
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
||||||
OrgV2: organisation.NewOrganizationServiceClient(cc),
|
OrgV2: organisation.NewOrganizationServiceClient(cc),
|
||||||
System: system.NewSystemServiceClient(cc),
|
System: system.NewSystemServiceClient(cc),
|
||||||
|
ExecutionV3: execution.NewExecutionServiceClient(cc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,3 +506,20 @@ func (s *Tester) CreateProjectMembership(t *testing.T, ctx context.Context, proj
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Tester) CreateTarget(ctx context.Context, t *testing.T) *execution.CreateTargetResponse {
|
||||||
|
target, err := s.Client.ExecutionV3.CreateTarget(ctx, &execution.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: &execution.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &execution.SetRESTWebhook{
|
||||||
|
Url: "https://example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
ExecutionType: &execution.CreateTargetRequest_InterruptOnError{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
18
internal/repository/target/aggregate.go
Normal file
18
internal/repository/target/aggregate.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package target
|
||||||
|
|
||||||
|
import "github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
|
||||||
|
const (
|
||||||
|
AggregateType = "target"
|
||||||
|
AggregateVersion = "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAggregate(aggrID, instanceID string) *eventstore.Aggregate {
|
||||||
|
return &eventstore.Aggregate{
|
||||||
|
ID: aggrID,
|
||||||
|
Type: AggregateType,
|
||||||
|
ResourceOwner: instanceID,
|
||||||
|
InstanceID: instanceID,
|
||||||
|
Version: AggregateVersion,
|
||||||
|
}
|
||||||
|
}
|
25
internal/repository/target/constraints.go
Normal file
25
internal/repository/target/constraints.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package target
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UniqueTarget = "target"
|
||||||
|
DuplicateTarget = "Errors.Target.AlreadyExists"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAddUniqueConstraint(name string) *eventstore.UniqueConstraint {
|
||||||
|
return eventstore.NewAddEventUniqueConstraint(
|
||||||
|
UniqueTarget,
|
||||||
|
name,
|
||||||
|
DuplicateTarget,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoveUniqueConstraint(name string) *eventstore.UniqueConstraint {
|
||||||
|
return eventstore.NewRemoveUniqueConstraint(
|
||||||
|
UniqueTarget,
|
||||||
|
name,
|
||||||
|
)
|
||||||
|
}
|
9
internal/repository/target/eventstore.go
Normal file
9
internal/repository/target/eventstore.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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)
|
||||||
|
}
|
199
internal/repository/target/target.go
Normal file
199
internal/repository/target/target.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
package target
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
eventTypePrefix eventstore.EventType = "target."
|
||||||
|
AddedEventType = eventTypePrefix + "added"
|
||||||
|
ChangedEventType = eventTypePrefix + "changed"
|
||||||
|
RemovedEventType = eventTypePrefix + "removed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddedEvent struct {
|
||||||
|
*eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
TargetType domain.TargetType `json:"targetType"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Timeout time.Duration `json:"timeout"`
|
||||||
|
Async bool `json:"async"`
|
||||||
|
InterruptOnError bool `json:"interruptOnError"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AddedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
|
||||||
|
e.BaseEvent = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AddedEvent) Payload() any {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||||
|
return []*eventstore.UniqueConstraint{NewAddUniqueConstraint(e.Name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
name string,
|
||||||
|
targetType domain.TargetType,
|
||||||
|
url string,
|
||||||
|
timeout time.Duration,
|
||||||
|
async bool,
|
||||||
|
interruptOnError bool,
|
||||||
|
) *AddedEvent {
|
||||||
|
return &AddedEvent{
|
||||||
|
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:"-"`
|
||||||
|
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
TargetType *domain.TargetType `json:"targetType,omitempty"`
|
||||||
|
URL *string `json:"url,omitempty"`
|
||||||
|
Timeout *time.Duration `json:"timeout,omitempty"`
|
||||||
|
Async *bool `json:"async,omitempty"`
|
||||||
|
InterruptOnError *bool `json:"interruptOnError,omitempty"`
|
||||||
|
|
||||||
|
oldName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ChangedEvent) Payload() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||||
|
if e.oldName == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []*eventstore.UniqueConstraint{
|
||||||
|
NewRemoveUniqueConstraint(e.oldName),
|
||||||
|
NewAddUniqueConstraint(*e.Name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChangedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
changes []Changes,
|
||||||
|
) *ChangedEvent {
|
||||||
|
changeEvent := &ChangedEvent{
|
||||||
|
BaseEvent: eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
ChangedEventType,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
for _, change := range changes {
|
||||||
|
change(changeEvent)
|
||||||
|
}
|
||||||
|
return changeEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
type Changes func(event *ChangedEvent)
|
||||||
|
|
||||||
|
func ChangeName(oldName, name string) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.Name = &name
|
||||||
|
e.oldName = oldName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeTargetType(targetType domain.TargetType) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.TargetType = &targetType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeURL(url string) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.URL = &url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeTimeout(timeout time.Duration) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.Timeout = &timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeAsync(async bool) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.Async = &async
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeInterruptOnError(interruptOnError bool) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.InterruptOnError = &interruptOnError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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:"-"`
|
||||||
|
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RemovedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
|
||||||
|
e.BaseEvent = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RemovedEvent) Payload() any {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||||
|
return []*eventstore.UniqueConstraint{NewRemoveUniqueConstraint(e.name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -552,6 +552,11 @@ Errors:
|
|||||||
NotExisting: Функцията не съществува
|
NotExisting: Функцията не съществува
|
||||||
TypeNotSupported: Типът функция не се поддържа
|
TypeNotSupported: Типът функция не се поддържа
|
||||||
InvalidValue: Невалидна стойност за тази функция
|
InvalidValue: Невалидна стойност за тази функция
|
||||||
|
Target:
|
||||||
|
Invalid: Целта е невалидна
|
||||||
|
NoTimeout: Целта няма време за изчакване
|
||||||
|
InvalidURL: Целта има невалиден URL адрес
|
||||||
|
NotFound: Целта не е намерена
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Действие
|
action: Действие
|
||||||
@ -563,8 +568,13 @@ AggregateTypes:
|
|||||||
usergrant: Предоставяне на потребител
|
usergrant: Предоставяне на потребител
|
||||||
quota: Квота
|
quota: Квота
|
||||||
feature: Особеност
|
feature: Особеност
|
||||||
|
target: Целта
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Целта е създадена
|
||||||
|
changed: Целта е променена
|
||||||
|
removed: Целта е изтрита
|
||||||
user:
|
user:
|
||||||
added: Добавен потребител
|
added: Добавен потребител
|
||||||
selfregistered: Потребителят се регистрира сам
|
selfregistered: Потребителят се регистрира сам
|
||||||
|
@ -532,6 +532,11 @@ Errors:
|
|||||||
NotExisting: Funkce neexistuje
|
NotExisting: Funkce neexistuje
|
||||||
TypeNotSupported: Typ funkce není podporován
|
TypeNotSupported: Typ funkce není podporován
|
||||||
InvalidValue: Neplatná hodnota pro tuto funkci
|
InvalidValue: Neplatná hodnota pro tuto funkci
|
||||||
|
Target:
|
||||||
|
Invalid: Cíl je neplatný
|
||||||
|
NoTimeout: Cíl nemá časový limit
|
||||||
|
InvalidURL: Cíl má neplatnou adresu URL
|
||||||
|
NotFound: Cíl nenalezen
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Akce
|
action: Akce
|
||||||
@ -543,8 +548,13 @@ AggregateTypes:
|
|||||||
usergrant: Uživatelský grant
|
usergrant: Uživatelský grant
|
||||||
quota: Kvóta
|
quota: Kvóta
|
||||||
feature: Funkce
|
feature: Funkce
|
||||||
|
target: Cíl
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Cíl vytvořen
|
||||||
|
changed: Cíl změněn
|
||||||
|
removed: Cíl smazán
|
||||||
user:
|
user:
|
||||||
added: Uživatel přidán
|
added: Uživatel přidán
|
||||||
selfregistered: Uživatel se zaregistroval sám
|
selfregistered: Uživatel se zaregistroval sám
|
||||||
|
@ -535,6 +535,11 @@ Errors:
|
|||||||
NotExisting: Feature existiert nicht
|
NotExisting: Feature existiert nicht
|
||||||
TypeNotSupported: Feature Typ wird nicht unterstützt
|
TypeNotSupported: Feature Typ wird nicht unterstützt
|
||||||
InvalidValue: Ungültiger Wert für dieses Feature
|
InvalidValue: Ungültiger Wert für dieses Feature
|
||||||
|
Target:
|
||||||
|
Invalid: Ziel ist ungültig
|
||||||
|
NoTimeout: Ziel hat keinen Timeout
|
||||||
|
InvalidURL: Ziel hat eine ungültige URL
|
||||||
|
NotFound: Ziel nicht gefunden
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Action
|
action: Action
|
||||||
@ -546,8 +551,13 @@ AggregateTypes:
|
|||||||
usergrant: Benutzerberechtigung
|
usergrant: Benutzerberechtigung
|
||||||
quota: Kontingent
|
quota: Kontingent
|
||||||
feature: Feature
|
feature: Feature
|
||||||
|
target: Ziel
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Ziel erstellt
|
||||||
|
changed: Ziel geändert
|
||||||
|
removed: Ziel gelöscht
|
||||||
user:
|
user:
|
||||||
added: Benutzer hinzugefügt
|
added: Benutzer hinzugefügt
|
||||||
selfregistered: Benutzer hat sich selbst registriert
|
selfregistered: Benutzer hat sich selbst registriert
|
||||||
|
@ -535,6 +535,11 @@ Errors:
|
|||||||
NotExisting: Feature does not exist
|
NotExisting: Feature does not exist
|
||||||
TypeNotSupported: Feature type is not supported
|
TypeNotSupported: Feature type is not supported
|
||||||
InvalidValue: Invalid value for this feature
|
InvalidValue: Invalid value for this feature
|
||||||
|
Target:
|
||||||
|
Invalid: Target is invalid
|
||||||
|
NoTimeout: Target has no timeout
|
||||||
|
InvalidURL: Target has an invalid URL
|
||||||
|
NotFound: Target not found
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Action
|
action: Action
|
||||||
@ -546,8 +551,13 @@ AggregateTypes:
|
|||||||
usergrant: User grant
|
usergrant: User grant
|
||||||
quota: Quota
|
quota: Quota
|
||||||
feature: Feature
|
feature: Feature
|
||||||
|
target: Target
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Target created
|
||||||
|
changed: Target changed
|
||||||
|
removed: Target deleted
|
||||||
user:
|
user:
|
||||||
added: User added
|
added: User added
|
||||||
selfregistered: User registered himself
|
selfregistered: User registered himself
|
||||||
|
@ -535,6 +535,11 @@ Errors:
|
|||||||
NotExisting: La característica no existe
|
NotExisting: La característica no existe
|
||||||
TypeNotSupported: El tipo de característica no es compatible
|
TypeNotSupported: El tipo de característica no es compatible
|
||||||
InvalidValue: Valor no válido para esta característica
|
InvalidValue: Valor no válido para esta característica
|
||||||
|
Target:
|
||||||
|
Invalid: El objetivo no es válido
|
||||||
|
NoTimeout: El objetivo no tiene tiempo de espera
|
||||||
|
InvalidURL: El objetivo tiene una URL no válida
|
||||||
|
NotFound: El objetivo no encontrado
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Acción
|
action: Acción
|
||||||
@ -546,8 +551,13 @@ AggregateTypes:
|
|||||||
usergrant: Concesión de usuario
|
usergrant: Concesión de usuario
|
||||||
quota: Cuota
|
quota: Cuota
|
||||||
feature: Característica
|
feature: Característica
|
||||||
|
target: Objectivo
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Objetivo creado
|
||||||
|
changed: Objetivo cambiado
|
||||||
|
removed: Objetivo eliminado
|
||||||
user:
|
user:
|
||||||
added: Usuario añadido
|
added: Usuario añadido
|
||||||
selfregistered: El usuario se registró por sí mismo
|
selfregistered: El usuario se registró por sí mismo
|
||||||
|
@ -535,6 +535,11 @@ Errors:
|
|||||||
NotExisting: La fonctionnalité n'existe pas
|
NotExisting: La fonctionnalité n'existe pas
|
||||||
TypeNotSupported: Le type de fonctionnalité n'est pas pris en charge
|
TypeNotSupported: Le type de fonctionnalité n'est pas pris en charge
|
||||||
InvalidValue: Valeur non valide pour cette fonctionnalité
|
InvalidValue: Valeur non valide pour cette fonctionnalité
|
||||||
|
Target:
|
||||||
|
Invalid: La cible n'est pas valide
|
||||||
|
NoTimeout: La cible n'a pas de délai d'attente
|
||||||
|
InvalidURL: La cible a une URL non valide
|
||||||
|
NotFound: La cible introuvable
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Action
|
action: Action
|
||||||
@ -546,8 +551,13 @@ AggregateTypes:
|
|||||||
usergrant: Subvention de l'utilisateur
|
usergrant: Subvention de l'utilisateur
|
||||||
quota: Contingent
|
quota: Contingent
|
||||||
feature: Fonctionnalité
|
feature: Fonctionnalité
|
||||||
|
target: Cible
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Cible créée
|
||||||
|
changed: Cible modifiée
|
||||||
|
removed: Cible supprimée
|
||||||
user:
|
user:
|
||||||
added: Utilisateur ajouté
|
added: Utilisateur ajouté
|
||||||
selfregistered: L'utilisateur s'est enregistré lui-même
|
selfregistered: L'utilisateur s'est enregistré lui-même
|
||||||
|
@ -536,6 +536,11 @@ Errors:
|
|||||||
NotExisting: La funzionalità non esiste
|
NotExisting: La funzionalità non esiste
|
||||||
TypeNotSupported: Il tipo di funzionalità non è supportato
|
TypeNotSupported: Il tipo di funzionalità non è supportato
|
||||||
InvalidValue: Valore non valido per questa funzionalità
|
InvalidValue: Valore non valido per questa funzionalità
|
||||||
|
Target:
|
||||||
|
Invalid: Il target non è valido
|
||||||
|
NoTimeout: Il target non ha timeout
|
||||||
|
InvalidURL: La destinazione ha un URL non valido
|
||||||
|
NotFound: Obiettivo non trovato
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Azione
|
action: Azione
|
||||||
@ -547,8 +552,13 @@ AggregateTypes:
|
|||||||
usergrant: Sovvenzione utente
|
usergrant: Sovvenzione utente
|
||||||
quota: Quota
|
quota: Quota
|
||||||
feature: Funzionalità
|
feature: Funzionalità
|
||||||
|
target: Bersaglio
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Obiettivo creato
|
||||||
|
changed: Obiettivo cambiato
|
||||||
|
removed: Obiettivo eliminato
|
||||||
user:
|
user:
|
||||||
added: Utente aggiunto
|
added: Utente aggiunto
|
||||||
selfregistered: L'utente si è registrato
|
selfregistered: L'utente si è registrato
|
||||||
|
@ -524,6 +524,11 @@ Errors:
|
|||||||
NotExisting: 機能が存在しません
|
NotExisting: 機能が存在しません
|
||||||
TypeNotSupported: 機能タイプはサポートされていません
|
TypeNotSupported: 機能タイプはサポートされていません
|
||||||
InvalidValue: この機能には無効な値です
|
InvalidValue: この機能には無効な値です
|
||||||
|
Target:
|
||||||
|
Invalid: ターゲットが無効です
|
||||||
|
NoTimeout: ターゲットにはタイムアウトがありません
|
||||||
|
InvalidURL: ターゲットに無効な URL があります
|
||||||
|
NotFound: ターゲットが見つかりません
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: アクション
|
action: アクション
|
||||||
@ -535,8 +540,13 @@ AggregateTypes:
|
|||||||
usergrant: ユーザーグラント
|
usergrant: ユーザーグラント
|
||||||
quota: クォータ
|
quota: クォータ
|
||||||
feature: 特徴
|
feature: 特徴
|
||||||
|
target: 目標
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: ターゲットが作成されました
|
||||||
|
changed: ターゲットが変更されました
|
||||||
|
removed: ターゲットが削除されました
|
||||||
user:
|
user:
|
||||||
added: ユーザーの追加
|
added: ユーザーの追加
|
||||||
selfregistered: ユーザー自身の登録
|
selfregistered: ユーザー自身の登録
|
||||||
|
@ -534,6 +534,11 @@ Errors:
|
|||||||
NotExisting: Функцијата не постои
|
NotExisting: Функцијата не постои
|
||||||
TypeNotSupported: Типот на функција не е поддржан
|
TypeNotSupported: Типот на функција не е поддржан
|
||||||
InvalidValue: Неважечка вредност за оваа функција
|
InvalidValue: Неважечка вредност за оваа функција
|
||||||
|
Target:
|
||||||
|
Invalid: Целта е неважечка
|
||||||
|
NoTimeout: Целта нема тајмаут
|
||||||
|
InvalidURL: Целта има неважечка URL-адреса
|
||||||
|
NotFound: Целта не е пронајдена
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Акција
|
action: Акција
|
||||||
@ -545,8 +550,13 @@ AggregateTypes:
|
|||||||
usergrant: Овластување на корисник
|
usergrant: Овластување на корисник
|
||||||
quota: Квота
|
quota: Квота
|
||||||
feature: Карактеристика
|
feature: Карактеристика
|
||||||
|
target: Цел
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Целта е избришана
|
||||||
|
changed: Целта е променета
|
||||||
|
removed: Целта е избришана
|
||||||
user:
|
user:
|
||||||
added: Додаден корисник
|
added: Додаден корисник
|
||||||
selfregistered: Корисникот се регистрираше сам
|
selfregistered: Корисникот се регистрираше сам
|
||||||
|
@ -535,6 +535,11 @@ Errors:
|
|||||||
NotExisting: Functie bestaat niet
|
NotExisting: Functie bestaat niet
|
||||||
TypeNotSupported: Functie type wordt niet ondersteund
|
TypeNotSupported: Functie type wordt niet ondersteund
|
||||||
InvalidValue: Ongeldige waarde voor deze functie
|
InvalidValue: Ongeldige waarde voor deze functie
|
||||||
|
Target:
|
||||||
|
Invalid: Doel is ongeldig
|
||||||
|
NoTimeout: Doel heeft geen time-out
|
||||||
|
InvalidURL: Doel heeft een ongeldige URL
|
||||||
|
NotFound: Doel niet gevonden
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Actie
|
action: Actie
|
||||||
@ -546,8 +551,13 @@ AggregateTypes:
|
|||||||
usergrant: Gebruikerstoekenning
|
usergrant: Gebruikerstoekenning
|
||||||
quota: Quota
|
quota: Quota
|
||||||
feature: Functie
|
feature: Functie
|
||||||
|
target: Doel
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Doel gemaakt
|
||||||
|
changed: Doel gewijzigd
|
||||||
|
removed: Doel verwijderd
|
||||||
user:
|
user:
|
||||||
added: Gebruiker toegevoegd
|
added: Gebruiker toegevoegd
|
||||||
selfregistered: Gebruiker heeft zichzelf geregistreerd
|
selfregistered: Gebruiker heeft zichzelf geregistreerd
|
||||||
|
@ -535,6 +535,11 @@ Errors:
|
|||||||
NotExisting: Funkcja nie istnieje
|
NotExisting: Funkcja nie istnieje
|
||||||
TypeNotSupported: Typ funkcji nie jest obsługiwany
|
TypeNotSupported: Typ funkcji nie jest obsługiwany
|
||||||
InvalidValue: Nieprawidłowa wartość dla tej funkcji
|
InvalidValue: Nieprawidłowa wartość dla tej funkcji
|
||||||
|
Target:
|
||||||
|
Invalid: Cel jest nieprawidłowy
|
||||||
|
NoTimeout: Cel nie ma limitu czasu
|
||||||
|
InvalidURL: Cel ma nieprawidłowy adres URL
|
||||||
|
NotFound: Nie znaleziono celu
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Działanie
|
action: Działanie
|
||||||
@ -546,8 +551,13 @@ AggregateTypes:
|
|||||||
usergrant: Uprawnienie użytkownika
|
usergrant: Uprawnienie użytkownika
|
||||||
quota: Limit
|
quota: Limit
|
||||||
feature: Funkcja
|
feature: Funkcja
|
||||||
|
target: Cel
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Cel został utworzony
|
||||||
|
changed: Cel zmieniony
|
||||||
|
removed: Cel usunięty
|
||||||
user:
|
user:
|
||||||
added: Użytkownik dodany
|
added: Użytkownik dodany
|
||||||
selfregistered: Użytkownik zarejestrował się
|
selfregistered: Użytkownik zarejestrował się
|
||||||
|
@ -529,6 +529,11 @@ Errors:
|
|||||||
NotExisting: O recurso não existe
|
NotExisting: O recurso não existe
|
||||||
TypeNotSupported: O tipo de recurso não é compatível
|
TypeNotSupported: O tipo de recurso não é compatível
|
||||||
InvalidValue: Valor inválido para este recurso
|
InvalidValue: Valor inválido para este recurso
|
||||||
|
Target:
|
||||||
|
Invalid: A meta é inválida
|
||||||
|
NoTimeout: O destino não tem tempo limite
|
||||||
|
InvalidURL: O destino tem um URL inválido
|
||||||
|
NotFound: Destino não encontrado
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Ação
|
action: Ação
|
||||||
@ -540,8 +545,13 @@ AggregateTypes:
|
|||||||
usergrant: Concessão de usuário
|
usergrant: Concessão de usuário
|
||||||
quota: Cota
|
quota: Cota
|
||||||
feature: Recurso
|
feature: Recurso
|
||||||
|
target: objetivo
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Destino criado
|
||||||
|
changed: Destino alterada
|
||||||
|
removed: Destino excluído
|
||||||
user:
|
user:
|
||||||
added: Usuário adicionado
|
added: Usuário adicionado
|
||||||
selfregistered: Usuário se registrou
|
selfregistered: Usuário se registrou
|
||||||
|
@ -519,6 +519,16 @@ Errors:
|
|||||||
Invalid: Токен недействителен
|
Invalid: Токен недействителен
|
||||||
Expired: Срок действия токена истек
|
Expired: Срок действия токена истек
|
||||||
InvalidClient: Токен не был выпущен для этого клиента
|
InvalidClient: Токен не был выпущен для этого клиента
|
||||||
|
Feature:
|
||||||
|
NotExisting: ункция не существует
|
||||||
|
TypeNotSupported: Тип объекта не поддерживается
|
||||||
|
InvalidValue: Недопустимое значение для этой функции.
|
||||||
|
Target:
|
||||||
|
Invalid: Цель недействительна.
|
||||||
|
NoTimeout: У цели нет тайм-аута
|
||||||
|
InvalidURL: Цель имеет неверный URL-адрес
|
||||||
|
NotFound: Цель не найдена
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Действие
|
action: Действие
|
||||||
instance: Пример
|
instance: Пример
|
||||||
@ -528,7 +538,14 @@ AggregateTypes:
|
|||||||
user: Пользователь
|
user: Пользователь
|
||||||
usergrant: Разрешение пользователя
|
usergrant: Разрешение пользователя
|
||||||
quota: Квота
|
quota: Квота
|
||||||
|
feature: Особенность
|
||||||
|
target: мишень
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: Цель создана
|
||||||
|
changed: Цель изменена
|
||||||
|
removed: Цель удалена.
|
||||||
user:
|
user:
|
||||||
added: Добавлено пользователем
|
added: Добавлено пользователем
|
||||||
selfregistered: Пользователь зарегистрировался сам
|
selfregistered: Пользователь зарегистрировался сам
|
||||||
|
@ -535,6 +535,11 @@ Errors:
|
|||||||
NotExisting: 功能不存在
|
NotExisting: 功能不存在
|
||||||
TypeNotSupported: 不支持功能类型
|
TypeNotSupported: 不支持功能类型
|
||||||
InvalidValue: 此功能的值无效
|
InvalidValue: 此功能的值无效
|
||||||
|
Target:
|
||||||
|
Invalid: 目标无效
|
||||||
|
NoTimeout: 目标没有超时
|
||||||
|
InvalidURL: 目标的 URL 无效
|
||||||
|
NotFound: 未找到目标
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: 动作
|
action: 动作
|
||||||
@ -546,8 +551,13 @@ AggregateTypes:
|
|||||||
usergrant: 用户授权
|
usergrant: 用户授权
|
||||||
quota: 配额
|
quota: 配额
|
||||||
feature: 特征
|
feature: 特征
|
||||||
|
target: 靶
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
target:
|
||||||
|
added: 目标已创建
|
||||||
|
changed: 目标改变
|
||||||
|
removed: 目标已删除
|
||||||
user:
|
user:
|
||||||
added: 已添加用户
|
added: 已添加用户
|
||||||
selfregistered: 自注册用户
|
selfregistered: 自注册用户
|
||||||
|
293
proto/zitadel/execution/v3alpha/execution_service.proto
Normal file
293
proto/zitadel/execution/v3alpha/execution_service.proto
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.execution.v3alpha;
|
||||||
|
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "google/api/field_behavior.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
import "google/protobuf/struct.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "zitadel/execution/v3alpha/target.proto";
|
||||||
|
import "zitadel/object/v2beta/object.proto";
|
||||||
|
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha;execution";
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||||
|
info: {
|
||||||
|
title: "Execution Service";
|
||||||
|
version: "3.0-alpha";
|
||||||
|
description: "This API is intended to manage custom executions (previously known as actions) in a ZITADEL instance. This project is in alpha state. It can AND will continue breaking until the services provide the same functionality as the current actions.";
|
||||||
|
contact:{
|
||||||
|
name: "ZITADEL"
|
||||||
|
url: "https://zitadel.com"
|
||||||
|
email: "hi@zitadel.com"
|
||||||
|
}
|
||||||
|
license: {
|
||||||
|
name: "Apache 2.0",
|
||||||
|
url: "https://github.com/zitadel/zitadel/blob/main/LICENSE";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
schemes: HTTPS;
|
||||||
|
schemes: HTTP;
|
||||||
|
|
||||||
|
consumes: "application/json";
|
||||||
|
consumes: "application/grpc";
|
||||||
|
|
||||||
|
produces: "application/json";
|
||||||
|
produces: "application/grpc";
|
||||||
|
|
||||||
|
consumes: "application/grpc-web+proto";
|
||||||
|
produces: "application/grpc-web+proto";
|
||||||
|
|
||||||
|
host: "$CUSTOM-DOMAIN";
|
||||||
|
base_path: "/";
|
||||||
|
|
||||||
|
external_docs: {
|
||||||
|
description: "Detailed information about ZITADEL",
|
||||||
|
url: "https://zitadel.com/docs"
|
||||||
|
}
|
||||||
|
security_definitions: {
|
||||||
|
security: {
|
||||||
|
key: "OAuth2";
|
||||||
|
value: {
|
||||||
|
type: TYPE_OAUTH2;
|
||||||
|
flow: FLOW_ACCESS_CODE;
|
||||||
|
authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize";
|
||||||
|
token_url: "$CUSTOM-DOMAIN/oauth/v2/token";
|
||||||
|
scopes: {
|
||||||
|
scope: {
|
||||||
|
key: "openid";
|
||||||
|
value: "openid";
|
||||||
|
}
|
||||||
|
scope: {
|
||||||
|
key: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
value: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
security: {
|
||||||
|
security_requirement: {
|
||||||
|
key: "OAuth2";
|
||||||
|
value: {
|
||||||
|
scope: "openid";
|
||||||
|
scope: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses: {
|
||||||
|
key: "403";
|
||||||
|
value: {
|
||||||
|
description: "Returned when the user does not have permission to access the resource.";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/rpcStatus";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses: {
|
||||||
|
key: "404";
|
||||||
|
value: {
|
||||||
|
description: "Returned when the resource does not exist.";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/rpcStatus";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
service ExecutionService {
|
||||||
|
|
||||||
|
// Create a target
|
||||||
|
//
|
||||||
|
// Create a new target, which can be used in executions.
|
||||||
|
rpc CreateTarget (CreateTargetRequest) returns (CreateTargetResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v3alpha/targets"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "execution.target.write"
|
||||||
|
}
|
||||||
|
http_response: {
|
||||||
|
success_code: 201
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "201";
|
||||||
|
value: {
|
||||||
|
description: "Target successfully created";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/v3alphaCreateTargetResponse";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update a target
|
||||||
|
//
|
||||||
|
// Update an existing target.
|
||||||
|
rpc UpdateTarget (UpdateTargetRequest) returns (UpdateTargetResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
put: "/v3alpha/targets/{target_id}"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "execution.target.write"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "Target successfully updated";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a target
|
||||||
|
//
|
||||||
|
// Delete an existing target. This will remove it from any configured execution as well.
|
||||||
|
rpc DeleteTarget (DeleteTargetRequest) returns (DeleteTargetResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
delete: "/v3alpha/targets/{target_id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "execution.target.delete"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "Target successfully deleted";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateTargetRequest {
|
||||||
|
// Unique name of the target.
|
||||||
|
string name = 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: "\"ip_allow_list\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// Defines the target type and how the response of the target is treated.
|
||||||
|
oneof target_type {
|
||||||
|
option (validate.required) = true;
|
||||||
|
|
||||||
|
SetRESTWebhook rest_webhook = 2;
|
||||||
|
SetRESTRequestResponse rest_request_response = 3;
|
||||||
|
}
|
||||||
|
// Timeout defines the duration until ZITADEL cancels the execution.
|
||||||
|
google.protobuf.Duration timeout = 4 [
|
||||||
|
(validate.rules).duration = {gt: {seconds: 0}, required: true},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"10s\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
oneof execution_type {
|
||||||
|
// Set the execution to run asynchronously.
|
||||||
|
bool is_async = 5;
|
||||||
|
// Define if any error stops the whole execution. By default the process continues as normal.
|
||||||
|
bool interrupt_on_error = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateTargetResponse {
|
||||||
|
// ID is the read-only unique identifier of the target.
|
||||||
|
string id = 1;
|
||||||
|
// Details provide some base information (such as the last change date) of the target.
|
||||||
|
zitadel.object.v2beta.Details details = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateTargetRequest {
|
||||||
|
// 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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// Optionally change the unique name of the target.
|
||||||
|
optional string name = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1,
|
||||||
|
max_length: 200,
|
||||||
|
example: "\"ip_allow_list\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// Optionally change the target type and how the response of the target is treated,
|
||||||
|
// or its target URL.
|
||||||
|
oneof target_type {
|
||||||
|
SetRESTWebhook rest_webhook = 3;
|
||||||
|
SetRESTRequestResponse rest_request_response = 4;
|
||||||
|
}
|
||||||
|
// Optionally change the timeout, which defines the duration until ZITADEL cancels the execution.
|
||||||
|
optional google.protobuf.Duration timeout = 5 [
|
||||||
|
(validate.rules).duration = {gt: {seconds: 0}},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"10s\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
oneof execution_type {
|
||||||
|
// Set the execution to run asynchronously.
|
||||||
|
bool is_async = 6;
|
||||||
|
// Define if any error stops the whole execution. By default the process continues as normal.
|
||||||
|
bool interrupt_on_error = 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateTargetResponse {
|
||||||
|
// Details provide some base information (such as the last change date) of the target.
|
||||||
|
zitadel.object.v2beta.Details details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteTargetRequest {
|
||||||
|
// 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 DeleteTargetResponse {
|
||||||
|
// Details provide some base information (such as the last change date) of the target.
|
||||||
|
zitadel.object.v2beta.Details details = 1;
|
||||||
|
}
|
38
proto/zitadel/execution/v3alpha/target.proto
Normal file
38
proto/zitadel/execution/v3alpha/target.proto
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.execution.v3alpha;
|
||||||
|
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "google/api/field_behavior.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
import "google/protobuf/struct.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "zitadel/object/v2beta/object.proto";
|
||||||
|
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha;execution";
|
||||||
|
|
||||||
|
message SetRESTWebhook {
|
||||||
|
string url = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 1000, uri: true},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1,
|
||||||
|
max_length: 1000,
|
||||||
|
example: "\"https://example.com/hooks/ip_check\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetRESTRequestResponse {
|
||||||
|
string url = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 1000, uri: true},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1,
|
||||||
|
max_length: 1000,
|
||||||
|
example: "\"https://example.com/hooks/ip_check\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user