mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 23:27:23 +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"
|
||||
- "milestones.read"
|
||||
- "session.delete"
|
||||
- "execution.target.write"
|
||||
- "execution.target.delete"
|
||||
- Role: "IAM_OWNER_VIEWER"
|
||||
Permissions:
|
||||
- "iam.read"
|
||||
|
@ -36,6 +36,7 @@ import (
|
||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/admin"
|
||||
"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"
|
||||
oidc_v2 "github.com/zitadel/zitadel/internal/api/grpc/oidc/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 {
|
||||
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...)
|
||||
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))
|
||||
|
@ -289,6 +289,13 @@ module.exports = {
|
||||
sidebarOptions: {
|
||||
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"),
|
||||
},
|
||||
{
|
||||
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",
|
||||
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/pkg/grpc/admin"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
|
||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
||||
@ -38,28 +39,30 @@ import (
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
CC *grpc.ClientConn
|
||||
Admin admin.AdminServiceClient
|
||||
Mgmt mgmt.ManagementServiceClient
|
||||
Auth auth.AuthServiceClient
|
||||
UserV2 user.UserServiceClient
|
||||
SessionV2 session.SessionServiceClient
|
||||
OIDCv2 oidc_pb.OIDCServiceClient
|
||||
OrgV2 organisation.OrganizationServiceClient
|
||||
System system.SystemServiceClient
|
||||
CC *grpc.ClientConn
|
||||
Admin admin.AdminServiceClient
|
||||
Mgmt mgmt.ManagementServiceClient
|
||||
Auth auth.AuthServiceClient
|
||||
UserV2 user.UserServiceClient
|
||||
SessionV2 session.SessionServiceClient
|
||||
OIDCv2 oidc_pb.OIDCServiceClient
|
||||
OrgV2 organisation.OrganizationServiceClient
|
||||
System system.SystemServiceClient
|
||||
ExecutionV3 execution.ExecutionServiceClient
|
||||
}
|
||||
|
||||
func newClient(cc *grpc.ClientConn) Client {
|
||||
return Client{
|
||||
CC: cc,
|
||||
Admin: admin.NewAdminServiceClient(cc),
|
||||
Mgmt: mgmt.NewManagementServiceClient(cc),
|
||||
Auth: auth.NewAuthServiceClient(cc),
|
||||
UserV2: user.NewUserServiceClient(cc),
|
||||
SessionV2: session.NewSessionServiceClient(cc),
|
||||
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
||||
OrgV2: organisation.NewOrganizationServiceClient(cc),
|
||||
System: system.NewSystemServiceClient(cc),
|
||||
CC: cc,
|
||||
Admin: admin.NewAdminServiceClient(cc),
|
||||
Mgmt: mgmt.NewManagementServiceClient(cc),
|
||||
Auth: auth.NewAuthServiceClient(cc),
|
||||
UserV2: user.NewUserServiceClient(cc),
|
||||
SessionV2: session.NewSessionServiceClient(cc),
|
||||
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
||||
OrgV2: organisation.NewOrganizationServiceClient(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)
|
||||
}
|
||||
|
||||
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: Функцията не съществува
|
||||
TypeNotSupported: Типът функция не се поддържа
|
||||
InvalidValue: Невалидна стойност за тази функция
|
||||
Target:
|
||||
Invalid: Целта е невалидна
|
||||
NoTimeout: Целта няма време за изчакване
|
||||
InvalidURL: Целта има невалиден URL адрес
|
||||
NotFound: Целта не е намерена
|
||||
|
||||
AggregateTypes:
|
||||
action: Действие
|
||||
@ -563,8 +568,13 @@ AggregateTypes:
|
||||
usergrant: Предоставяне на потребител
|
||||
quota: Квота
|
||||
feature: Особеност
|
||||
target: Целта
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Целта е създадена
|
||||
changed: Целта е променена
|
||||
removed: Целта е изтрита
|
||||
user:
|
||||
added: Добавен потребител
|
||||
selfregistered: Потребителят се регистрира сам
|
||||
|
@ -532,6 +532,11 @@ Errors:
|
||||
NotExisting: Funkce neexistuje
|
||||
TypeNotSupported: Typ funkce není podporován
|
||||
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:
|
||||
action: Akce
|
||||
@ -543,8 +548,13 @@ AggregateTypes:
|
||||
usergrant: Uživatelský grant
|
||||
quota: Kvóta
|
||||
feature: Funkce
|
||||
target: Cíl
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Cíl vytvořen
|
||||
changed: Cíl změněn
|
||||
removed: Cíl smazán
|
||||
user:
|
||||
added: Uživatel přidán
|
||||
selfregistered: Uživatel se zaregistroval sám
|
||||
|
@ -535,6 +535,11 @@ Errors:
|
||||
NotExisting: Feature existiert nicht
|
||||
TypeNotSupported: Feature Typ wird nicht unterstützt
|
||||
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:
|
||||
action: Action
|
||||
@ -546,8 +551,13 @@ AggregateTypes:
|
||||
usergrant: Benutzerberechtigung
|
||||
quota: Kontingent
|
||||
feature: Feature
|
||||
target: Ziel
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Ziel erstellt
|
||||
changed: Ziel geändert
|
||||
removed: Ziel gelöscht
|
||||
user:
|
||||
added: Benutzer hinzugefügt
|
||||
selfregistered: Benutzer hat sich selbst registriert
|
||||
|
@ -535,6 +535,11 @@ Errors:
|
||||
NotExisting: Feature does not exist
|
||||
TypeNotSupported: Feature type is not supported
|
||||
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:
|
||||
action: Action
|
||||
@ -546,8 +551,13 @@ AggregateTypes:
|
||||
usergrant: User grant
|
||||
quota: Quota
|
||||
feature: Feature
|
||||
target: Target
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Target created
|
||||
changed: Target changed
|
||||
removed: Target deleted
|
||||
user:
|
||||
added: User added
|
||||
selfregistered: User registered himself
|
||||
|
@ -535,6 +535,11 @@ Errors:
|
||||
NotExisting: La característica no existe
|
||||
TypeNotSupported: El tipo de característica no es compatible
|
||||
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:
|
||||
action: Acción
|
||||
@ -546,8 +551,13 @@ AggregateTypes:
|
||||
usergrant: Concesión de usuario
|
||||
quota: Cuota
|
||||
feature: Característica
|
||||
target: Objectivo
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Objetivo creado
|
||||
changed: Objetivo cambiado
|
||||
removed: Objetivo eliminado
|
||||
user:
|
||||
added: Usuario añadido
|
||||
selfregistered: El usuario se registró por sí mismo
|
||||
|
@ -535,6 +535,11 @@ Errors:
|
||||
NotExisting: La fonctionnalité n'existe pas
|
||||
TypeNotSupported: Le type de fonctionnalité n'est pas pris en charge
|
||||
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:
|
||||
action: Action
|
||||
@ -546,8 +551,13 @@ AggregateTypes:
|
||||
usergrant: Subvention de l'utilisateur
|
||||
quota: Contingent
|
||||
feature: Fonctionnalité
|
||||
target: Cible
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Cible créée
|
||||
changed: Cible modifiée
|
||||
removed: Cible supprimée
|
||||
user:
|
||||
added: Utilisateur ajouté
|
||||
selfregistered: L'utilisateur s'est enregistré lui-même
|
||||
|
@ -536,6 +536,11 @@ Errors:
|
||||
NotExisting: La funzionalità non esiste
|
||||
TypeNotSupported: Il tipo di funzionalità non è supportato
|
||||
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:
|
||||
action: Azione
|
||||
@ -547,8 +552,13 @@ AggregateTypes:
|
||||
usergrant: Sovvenzione utente
|
||||
quota: Quota
|
||||
feature: Funzionalità
|
||||
target: Bersaglio
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Obiettivo creato
|
||||
changed: Obiettivo cambiato
|
||||
removed: Obiettivo eliminato
|
||||
user:
|
||||
added: Utente aggiunto
|
||||
selfregistered: L'utente si è registrato
|
||||
|
@ -524,6 +524,11 @@ Errors:
|
||||
NotExisting: 機能が存在しません
|
||||
TypeNotSupported: 機能タイプはサポートされていません
|
||||
InvalidValue: この機能には無効な値です
|
||||
Target:
|
||||
Invalid: ターゲットが無効です
|
||||
NoTimeout: ターゲットにはタイムアウトがありません
|
||||
InvalidURL: ターゲットに無効な URL があります
|
||||
NotFound: ターゲットが見つかりません
|
||||
|
||||
AggregateTypes:
|
||||
action: アクション
|
||||
@ -535,8 +540,13 @@ AggregateTypes:
|
||||
usergrant: ユーザーグラント
|
||||
quota: クォータ
|
||||
feature: 特徴
|
||||
target: 目標
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: ターゲットが作成されました
|
||||
changed: ターゲットが変更されました
|
||||
removed: ターゲットが削除されました
|
||||
user:
|
||||
added: ユーザーの追加
|
||||
selfregistered: ユーザー自身の登録
|
||||
|
@ -534,6 +534,11 @@ Errors:
|
||||
NotExisting: Функцијата не постои
|
||||
TypeNotSupported: Типот на функција не е поддржан
|
||||
InvalidValue: Неважечка вредност за оваа функција
|
||||
Target:
|
||||
Invalid: Целта е неважечка
|
||||
NoTimeout: Целта нема тајмаут
|
||||
InvalidURL: Целта има неважечка URL-адреса
|
||||
NotFound: Целта не е пронајдена
|
||||
|
||||
AggregateTypes:
|
||||
action: Акција
|
||||
@ -545,8 +550,13 @@ AggregateTypes:
|
||||
usergrant: Овластување на корисник
|
||||
quota: Квота
|
||||
feature: Карактеристика
|
||||
target: Цел
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Целта е избришана
|
||||
changed: Целта е променета
|
||||
removed: Целта е избришана
|
||||
user:
|
||||
added: Додаден корисник
|
||||
selfregistered: Корисникот се регистрираше сам
|
||||
|
@ -535,6 +535,11 @@ Errors:
|
||||
NotExisting: Functie bestaat niet
|
||||
TypeNotSupported: Functie type wordt niet ondersteund
|
||||
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:
|
||||
action: Actie
|
||||
@ -546,8 +551,13 @@ AggregateTypes:
|
||||
usergrant: Gebruikerstoekenning
|
||||
quota: Quota
|
||||
feature: Functie
|
||||
target: Doel
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Doel gemaakt
|
||||
changed: Doel gewijzigd
|
||||
removed: Doel verwijderd
|
||||
user:
|
||||
added: Gebruiker toegevoegd
|
||||
selfregistered: Gebruiker heeft zichzelf geregistreerd
|
||||
|
@ -535,6 +535,11 @@ Errors:
|
||||
NotExisting: Funkcja nie istnieje
|
||||
TypeNotSupported: Typ funkcji nie jest obsługiwany
|
||||
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:
|
||||
action: Działanie
|
||||
@ -546,8 +551,13 @@ AggregateTypes:
|
||||
usergrant: Uprawnienie użytkownika
|
||||
quota: Limit
|
||||
feature: Funkcja
|
||||
target: Cel
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Cel został utworzony
|
||||
changed: Cel zmieniony
|
||||
removed: Cel usunięty
|
||||
user:
|
||||
added: Użytkownik dodany
|
||||
selfregistered: Użytkownik zarejestrował się
|
||||
|
@ -529,6 +529,11 @@ Errors:
|
||||
NotExisting: O recurso não existe
|
||||
TypeNotSupported: O tipo de recurso não é compatível
|
||||
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:
|
||||
action: Ação
|
||||
@ -540,8 +545,13 @@ AggregateTypes:
|
||||
usergrant: Concessão de usuário
|
||||
quota: Cota
|
||||
feature: Recurso
|
||||
target: objetivo
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Destino criado
|
||||
changed: Destino alterada
|
||||
removed: Destino excluído
|
||||
user:
|
||||
added: Usuário adicionado
|
||||
selfregistered: Usuário se registrou
|
||||
|
@ -519,6 +519,16 @@ Errors:
|
||||
Invalid: Токен недействителен
|
||||
Expired: Срок действия токена истек
|
||||
InvalidClient: Токен не был выпущен для этого клиента
|
||||
Feature:
|
||||
NotExisting: ункция не существует
|
||||
TypeNotSupported: Тип объекта не поддерживается
|
||||
InvalidValue: Недопустимое значение для этой функции.
|
||||
Target:
|
||||
Invalid: Цель недействительна.
|
||||
NoTimeout: У цели нет тайм-аута
|
||||
InvalidURL: Цель имеет неверный URL-адрес
|
||||
NotFound: Цель не найдена
|
||||
|
||||
AggregateTypes:
|
||||
action: Действие
|
||||
instance: Пример
|
||||
@ -528,7 +538,14 @@ AggregateTypes:
|
||||
user: Пользователь
|
||||
usergrant: Разрешение пользователя
|
||||
quota: Квота
|
||||
feature: Особенность
|
||||
target: мишень
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: Цель создана
|
||||
changed: Цель изменена
|
||||
removed: Цель удалена.
|
||||
user:
|
||||
added: Добавлено пользователем
|
||||
selfregistered: Пользователь зарегистрировался сам
|
||||
|
@ -535,6 +535,11 @@ Errors:
|
||||
NotExisting: 功能不存在
|
||||
TypeNotSupported: 不支持功能类型
|
||||
InvalidValue: 此功能的值无效
|
||||
Target:
|
||||
Invalid: 目标无效
|
||||
NoTimeout: 目标没有超时
|
||||
InvalidURL: 目标的 URL 无效
|
||||
NotFound: 未找到目标
|
||||
|
||||
AggregateTypes:
|
||||
action: 动作
|
||||
@ -546,8 +551,13 @@ AggregateTypes:
|
||||
usergrant: 用户授权
|
||||
quota: 配额
|
||||
feature: 特征
|
||||
target: 靶
|
||||
|
||||
EventTypes:
|
||||
target:
|
||||
added: 目标已创建
|
||||
changed: 目标改变
|
||||
removed: 目标已删除
|
||||
user:
|
||||
added: 已添加用户
|
||||
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