mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 18:07:31 +00:00
feat: add executions for actions v2 (#7433)
* feat: add events for execution * feat: add events for execution and command side * feat: add events for execution and command side * feat: add api endpoints for set and delete executions with integration tests * feat: add integration and unit tests and more existence checks * feat: add integration and unit tests and more existence checks * feat: unit tests for includes in executions * feat: integration tests for includes in executions * fix: linting * fix: update internal/api/api.go Co-authored-by: Tim Möhlmann <tim+github@zitadel.com> * fix: update internal/command/command.go Co-authored-by: Tim Möhlmann <tim+github@zitadel.com> * fix: apply suggestions from code review Co-authored-by: Tim Möhlmann <tim+github@zitadel.com> * fix: change api return * fix: change aggregateID with prefix of execution type and add to documentation * fix: change body in proto for documentation and correct linting * fix: changed existing check to single query in separate writemodel * fix: linter changes and list endpoints for conditions in executions * fix: remove writemodel query on exeuction set as state before is irrelevant * fix: testing for exists write models and correction * fix: translations for errors and event types --------- Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
This commit is contained in:
@@ -998,6 +998,9 @@ InternalAuthZ:
|
||||
- "session.delete"
|
||||
- "execution.target.write"
|
||||
- "execution.target.delete"
|
||||
- "execution.read"
|
||||
- "execution.write"
|
||||
- "execution.delete"
|
||||
- Role: "IAM_OWNER_VIEWER"
|
||||
Permissions:
|
||||
- "iam.read"
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"slices"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -264,7 +265,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = startAPIs(
|
||||
api, err := startAPIs(
|
||||
ctx,
|
||||
clock,
|
||||
router,
|
||||
@@ -281,6 +282,8 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commands.GrpcMethodExisting = checkExisting(api.ListGrpcMethods())
|
||||
commands.GrpcServiceExisting = checkExisting(api.ListGrpcServices())
|
||||
|
||||
shutdown := make(chan os.Signal, 1)
|
||||
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
|
||||
@@ -319,7 +322,7 @@ func startAPIs(
|
||||
authZRepo authz_repo.Repository,
|
||||
keys *encryption.EncryptionKeys,
|
||||
permissionCheck domain.PermissionCheck,
|
||||
) error {
|
||||
) (*api.API, error) {
|
||||
repo := struct {
|
||||
authz_repo.Repository
|
||||
*query.Queries
|
||||
@@ -332,22 +335,22 @@ func startAPIs(
|
||||
router.Use(middleware.WithOrigin(config.ExternalSecure))
|
||||
systemTokenVerifier, err := internal_authz.StartSystemTokenVerifierFromConfig(http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), config.SystemAPIUsers)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
accessTokenVerifer := internal_authz.StartAccessTokenVerifierFromRepo(repo)
|
||||
verifier := internal_authz.StartAPITokenVerifier(repo, accessTokenVerifer, systemTokenVerifier)
|
||||
tlsConfig, err := config.TLS.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessStdoutEmitter, err := logstore.NewEmitter[*record.AccessLog](ctx, clock, &logstore.EmitterConfig{Enabled: config.LogStore.Access.Stdout.Enabled}, stdout.NewStdoutEmitter[*record.AccessLog]())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
accessDBEmitter, err := logstore.NewEmitter[*record.AccessLog](ctx, clock, &config.Quotas.Access.EmitterConfig, access.NewDatabaseLogStorage(dbClient, commands, queries))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessSvc := logstore.New[*record.AccessLog](queries, accessDBEmitter, accessStdoutEmitter)
|
||||
@@ -359,50 +362,50 @@ func startAPIs(
|
||||
limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, &config.Quotas.Access.AccessConfig)
|
||||
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, limitingAccessInterceptor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating api %w", err)
|
||||
return nil, fmt.Errorf("error creating api %w", err)
|
||||
}
|
||||
|
||||
config.Auth.Spooler.Client = dbClient
|
||||
config.Auth.Spooler.Eventstore = eventstore
|
||||
authRepo, err := auth_es.Start(ctx, config.Auth, config.SystemDefaults, commands, queries, dbClient, eventstore, keys.OIDC, keys.User)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error starting auth repo: %w", err)
|
||||
return nil, fmt.Errorf("error starting auth repo: %w", err)
|
||||
}
|
||||
|
||||
config.Admin.Spooler.Client = dbClient
|
||||
config.Admin.Spooler.Eventstore = eventstore
|
||||
err = admin_es.Start(ctx, config.Admin, store, dbClient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error starting admin repo: %w", err)
|
||||
return nil, fmt.Errorf("error starting admin repo: %w", err)
|
||||
}
|
||||
|
||||
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, config.Database.DatabaseName(), config.DefaultInstance, config.ExternalDomain), tlsConfig); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.DatabaseName(), commands, queries, config.SystemDefaults, config.ExternalSecure, keys.User, config.AuditLogRetention), tlsConfig); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, keys.User, config.ExternalSecure), tlsConfig); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, keys.User, config.ExternalSecure), tlsConfig); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, user_v2.CreateServer(commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(config.ExternalSecure), idp.SAMLRootURL(config.ExternalSecure), assets.AssetAPI(config.ExternalSecure), permissionCheck)); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, session.CreateServer(commands, queries)); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := apis.RegisterService(ctx, settings.CreateServer(commands, queries, config.ExternalSecure)); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, org.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, execution_v3_alpha.CreateServer(commands, queries)); err != nil {
|
||||
return err
|
||||
if err := apis.RegisterService(ctx, execution_v3_alpha.CreateServer(commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
||||
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
||||
@@ -412,38 +415,38 @@ func startAPIs(
|
||||
|
||||
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources, login.EndpointExternalLoginCallbackFormPost, login.EndpointSAMLACS)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// robots.txt handler
|
||||
robotsTxtHandler, err := robots_txt.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start robots txt handler: %w", err)
|
||||
return nil, fmt.Errorf("unable to start robots txt handler: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerOnPrefix(robots_txt.HandlerPrefix, robotsTxtHandler)
|
||||
|
||||
// TODO: Record openapi access logs?
|
||||
openAPIHandler, err := openapi.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start openapi handler: %w", err)
|
||||
return nil, fmt.Errorf("unable to start openapi handler: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerOnPrefix(openapi.HandlerPrefix, openAPIHandler)
|
||||
|
||||
oidcServer, err := oidc.NewServer(config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, limitingAccessInterceptor, config.Log.Slog())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start oidc provider: %w", err)
|
||||
return nil, fmt.Errorf("unable to start oidc provider: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerPrefixes(oidcServer, oidcPrefixes...)
|
||||
|
||||
samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, limitingAccessInterceptor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start saml provider: %w", err)
|
||||
return nil, fmt.Errorf("unable to start saml provider: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerOnPrefix(saml.HandlerPrefix, samlProvider.HttpHandler())
|
||||
|
||||
c, err := console.Start(config.Console, config.ExternalSecure, oidcServer.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, limitingAccessInterceptor, config.CustomerPortal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start console: %w", err)
|
||||
return nil, fmt.Errorf("unable to start console: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerOnPrefix(console.HandlerPrefix, c)
|
||||
consolePath := console.HandlerPrefix + "/"
|
||||
@@ -469,18 +472,18 @@ func startAPIs(
|
||||
feature.NewCheck(eventstore),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start login: %w", err)
|
||||
return nil, fmt.Errorf("unable to start login: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerOnPrefix(login.HandlerPrefix, l.Handler())
|
||||
apis.HandleFunc(login.EndpointDeviceAuth, login.RedirectDeviceAuthToPrefix)
|
||||
|
||||
// After OIDC provider so that the callback endpoint can be used
|
||||
if err := apis.RegisterService(ctx, oidc_v2.CreateServer(commands, queries, oidcServer, config.ExternalSecure)); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
// handle grpc at last to be able to handle the root, because grpc and gateway require a lot of different prefixes
|
||||
apis.RouteGRPC()
|
||||
return nil
|
||||
return apis, nil
|
||||
}
|
||||
|
||||
func listen(ctx context.Context, router *mux.Router, port uint16, tlsConfig *tls.Config, shutdown <-chan os.Signal) error {
|
||||
@@ -551,3 +554,9 @@ func showBasicInformation(startConfig *Config) {
|
||||
}
|
||||
fmt.Printf("\n ===============================================================\n\n")
|
||||
}
|
||||
|
||||
func checkExisting(values []string) func(string) bool {
|
||||
return func(value string) bool {
|
||||
return slices.Contains(values, value)
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -38,6 +39,30 @@ type API struct {
|
||||
queries *query.Queries
|
||||
}
|
||||
|
||||
func (a *API) ListGrpcServices() []string {
|
||||
serviceInfo := a.grpcServer.GetServiceInfo()
|
||||
services := make([]string, len(serviceInfo))
|
||||
i := 0
|
||||
for servicename := range serviceInfo {
|
||||
services[i] = servicename
|
||||
i++
|
||||
}
|
||||
sort.Strings(services)
|
||||
return services
|
||||
}
|
||||
|
||||
func (a *API) ListGrpcMethods() []string {
|
||||
serviceInfo := a.grpcServer.GetServiceInfo()
|
||||
methods := make([]string, 0)
|
||||
for servicename, service := range serviceInfo {
|
||||
for _, method := range service.Methods {
|
||||
methods = append(methods, "/"+servicename+"/"+method.Name)
|
||||
}
|
||||
}
|
||||
sort.Strings(methods)
|
||||
return methods
|
||||
}
|
||||
|
||||
type healthCheck interface {
|
||||
Health(ctx context.Context) error
|
||||
}
|
||||
|
124
internal/api/grpc/execution/v3alpha/execution.go
Normal file
124
internal/api/grpc/execution/v3alpha/execution.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package execution
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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"
|
||||
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) ListExecutionFunctions(_ context.Context, _ *execution.ListExecutionFunctionsRequest) (*execution.ListExecutionFunctionsResponse, error) {
|
||||
return &execution.ListExecutionFunctionsResponse{
|
||||
Functions: s.ListActionFunctions(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListExecutionMethods(_ context.Context, _ *execution.ListExecutionMethodsRequest) (*execution.ListExecutionMethodsResponse, error) {
|
||||
return &execution.ListExecutionMethodsResponse{
|
||||
Methods: s.ListGRPCMethods(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListExecutionServices(_ context.Context, _ *execution.ListExecutionServicesRequest) (*execution.ListExecutionServicesResponse, error) {
|
||||
return &execution.ListExecutionServicesResponse{
|
||||
Services: s.ListGRPCServices(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRequest) (*execution.SetExecutionResponse, error) {
|
||||
set := &command.SetExecution{
|
||||
Targets: req.GetTargets(),
|
||||
Includes: req.GetIncludes(),
|
||||
}
|
||||
|
||||
var err error
|
||||
var details *domain.ObjectDetails
|
||||
switch t := req.GetCondition().GetConditionType().(type) {
|
||||
case *execution.SetConditions_Request:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Request.GetMethod(),
|
||||
Service: t.Request.GetService(),
|
||||
All: t.Request.GetAll(),
|
||||
}
|
||||
details, err = s.command.SetExecutionRequest(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *execution.SetConditions_Response:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Response.GetMethod(),
|
||||
Service: t.Response.GetService(),
|
||||
All: t.Response.GetAll(),
|
||||
}
|
||||
details, err = s.command.SetExecutionResponse(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *execution.SetConditions_Event:
|
||||
cond := &command.ExecutionEventCondition{
|
||||
Event: t.Event.GetEvent(),
|
||||
Group: t.Event.GetGroup(),
|
||||
All: t.Event.GetAll(),
|
||||
}
|
||||
details, err = s.command.SetExecutionEvent(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *execution.SetConditions_Function:
|
||||
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), set, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &execution.SetExecutionResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecutionRequest) (*execution.DeleteExecutionResponse, error) {
|
||||
var err error
|
||||
var details *domain.ObjectDetails
|
||||
switch t := req.GetCondition().GetConditionType().(type) {
|
||||
case *execution.SetConditions_Request:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Request.GetMethod(),
|
||||
Service: t.Request.GetService(),
|
||||
All: t.Request.GetAll(),
|
||||
}
|
||||
details, err = s.command.DeleteExecutionRequest(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *execution.SetConditions_Response:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Response.GetMethod(),
|
||||
Service: t.Response.GetService(),
|
||||
All: t.Response.GetAll(),
|
||||
}
|
||||
details, err = s.command.DeleteExecutionResponse(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *execution.SetConditions_Event:
|
||||
cond := &command.ExecutionEventCondition{
|
||||
Event: t.Event.GetEvent(),
|
||||
Group: t.Event.GetGroup(),
|
||||
All: t.Event.GetAll(),
|
||||
}
|
||||
details, err = s.command.DeleteExecutionEvent(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *execution.SetConditions_Function:
|
||||
details, err = s.command.DeleteExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &execution.DeleteExecutionResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
1280
internal/api/grpc/execution/v3alpha/execution_integration_test.go
Normal file
1280
internal/api/grpc/execution/v3alpha/execution_integration_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,11 @@ var _ execution.ExecutionServiceServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
execution.UnimplementedExecutionServiceServer
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
ListActionFunctions func() []string
|
||||
ListGRPCMethods func() []string
|
||||
ListGRPCServices func() []string
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
@@ -23,10 +26,16 @@ type Config struct{}
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
listActionFunctions func() []string,
|
||||
listGRPCMethods func() []string,
|
||||
listGRPCServices func() []string,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
command: command,
|
||||
query: query,
|
||||
ListActionFunctions: listActionFunctions,
|
||||
ListGRPCMethods: listGRPCMethods,
|
||||
ListGRPCServices: listGRPCServices,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,33 @@
|
||||
//go:build integration
|
||||
|
||||
package execution_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
|
||||
)
|
||||
|
||||
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()
|
||||
}())
|
||||
}
|
@@ -5,7 +5,6 @@ package execution_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -20,26 +19,6 @@ import (
|
||||
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
|
||||
|
285
internal/command/action_v2_execution.go
Normal file
285
internal/command/action_v2_execution.go
Normal file
@@ -0,0 +1,285 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type ExecutionAPICondition struct {
|
||||
Method string
|
||||
Service string
|
||||
All bool
|
||||
}
|
||||
|
||||
func (e *ExecutionAPICondition) IsValid() error {
|
||||
if e.Method == "" && e.Service == "" && !e.All {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-3tkej630e6", "Errors.Execution.Invalid")
|
||||
}
|
||||
// never set two conditions
|
||||
if e.Method != "" && (e.Service != "" || e.All) ||
|
||||
e.Service != "" && (e.Method != "" || e.All) ||
|
||||
e.All && (e.Method != "" || e.Service != "") {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-nee5q8aszq", "Errors.Execution.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ExecutionAPICondition) ID(executionType domain.ExecutionType) string {
|
||||
if e.Method != "" {
|
||||
return execution.ID(executionType, e.Method)
|
||||
}
|
||||
if e.Service != "" {
|
||||
return execution.ID(executionType, e.Service)
|
||||
}
|
||||
if e.All {
|
||||
return execution.IDAll(executionType)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *ExecutionAPICondition) Existing(c *Commands) error {
|
||||
if e.Method != "" && !c.GrpcMethodExisting(e.Method) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-vysplsevt8", "Errors.Execution.ConditionInvalid")
|
||||
}
|
||||
if e.Service != "" && !c.GrpcServiceExisting(e.Service) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-qu6dfhiioq", "Errors.Execution.ConditionInvalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetExecutionRequest(ctx context.Context, cond *ExecutionAPICondition, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cond.Existing(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if set.AggregateID == "" {
|
||||
set.AggregateID = cond.ID(domain.ExecutionTypeRequest)
|
||||
}
|
||||
return c.setExecution(ctx, set, resourceOwner)
|
||||
}
|
||||
|
||||
func (c *Commands) SetExecutionResponse(ctx context.Context, cond *ExecutionAPICondition, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cond.Existing(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if set.AggregateID == "" {
|
||||
set.AggregateID = cond.ID(domain.ExecutionTypeResponse)
|
||||
}
|
||||
return c.setExecution(ctx, set, resourceOwner)
|
||||
}
|
||||
|
||||
type ExecutionFunctionCondition string
|
||||
|
||||
func (e ExecutionFunctionCondition) IsValid() error {
|
||||
if e == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-5folwn5jws", "Errors.Execution.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ExecutionFunctionCondition) ID() string {
|
||||
return execution.ID(domain.ExecutionTypeFunction, string(e))
|
||||
}
|
||||
|
||||
func (e ExecutionFunctionCondition) Existing(c *Commands) error {
|
||||
if !c.ActionFunctionExisting(string(e)) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-cdy39t0ksr", "Errors.Execution.ConditionInvalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetExecutionFunction(ctx context.Context, cond ExecutionFunctionCondition, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cond.Existing(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if set.AggregateID == "" {
|
||||
set.AggregateID = cond.ID()
|
||||
}
|
||||
return c.setExecution(ctx, set, resourceOwner)
|
||||
}
|
||||
|
||||
type ExecutionEventCondition struct {
|
||||
Event string
|
||||
Group string
|
||||
All bool
|
||||
}
|
||||
|
||||
func (e *ExecutionEventCondition) IsValid() error {
|
||||
if e.Event == "" && e.Group == "" && !e.All {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-w5smb6v7qu", "Errors.Execution.Invalid")
|
||||
}
|
||||
// never set two conditions
|
||||
if e.Event != "" && (e.Group != "" || e.All) ||
|
||||
e.Group != "" && (e.Event != "" || e.All) ||
|
||||
e.All && (e.Event != "" || e.Group != "") {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-hdm4zl1hmd", "Errors.Execution.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ExecutionEventCondition) ID() string {
|
||||
if e.Event != "" {
|
||||
return execution.ID(domain.ExecutionTypeEvent, e.Event)
|
||||
}
|
||||
if e.Group != "" {
|
||||
return execution.ID(domain.ExecutionTypeEvent, e.Group)
|
||||
}
|
||||
if e.All {
|
||||
return execution.IDAll(domain.ExecutionTypeEvent)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *ExecutionEventCondition) Existing(c *Commands) error {
|
||||
if e.Event != "" && !c.EventExisting(e.Event) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-74aaqj8fv9", "Errors.Execution.ConditionInvalid")
|
||||
}
|
||||
if e.Group != "" && !c.EventGroupExisting(e.Group) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-er5oneb5lz", "Errors.Execution.ConditionInvalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetExecutionEvent(ctx context.Context, cond *ExecutionEventCondition, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cond.Existing(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if set.AggregateID == "" {
|
||||
set.AggregateID = cond.ID()
|
||||
}
|
||||
return c.setExecution(ctx, set, resourceOwner)
|
||||
}
|
||||
|
||||
type SetExecution struct {
|
||||
models.ObjectRoot
|
||||
|
||||
Targets []string
|
||||
Includes []string
|
||||
}
|
||||
|
||||
func (e *SetExecution) IsValid() error {
|
||||
if len(e.Targets) == 0 && len(e.Includes) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-56bteot2uj", "Errors.Execution.NoTargets")
|
||||
}
|
||||
if len(e.Targets) > 0 && len(e.Includes) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-5zleae34r1", "Errors.Execution.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *SetExecution) Existing(c *Commands, ctx context.Context, resourceOwner string) error {
|
||||
if len(e.Targets) > 0 && !c.existsTargetsByIDs(ctx, e.Targets, resourceOwner) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-17e8fq1ggk", "Errors.Target.NotFound")
|
||||
}
|
||||
if len(e.Includes) > 0 && !c.existsExecutionsByIDs(ctx, e.Includes, resourceOwner) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-slgj0l4cdz", "Errors.Execution.IncludeNotFound")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) setExecution(ctx context.Context, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if resourceOwner == "" || set.AggregateID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-gg3a6ol4om", "Errors.IDMissing")
|
||||
}
|
||||
if err := set.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wm := NewExecutionWriteModel(set.AggregateID, resourceOwner)
|
||||
// Check if targets and includes for execution are existing
|
||||
if err := set.Existing(c, ctx, resourceOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, wm, execution.NewSetEvent(
|
||||
ctx,
|
||||
ExecutionAggregateFromWriteModel(&wm.WriteModel),
|
||||
set.Targets,
|
||||
set.Includes,
|
||||
)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteExecutionRequest(ctx context.Context, cond *ExecutionAPICondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.deleteExecution(ctx, cond.ID(domain.ExecutionTypeRequest), resourceOwner)
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteExecutionResponse(ctx context.Context, cond *ExecutionAPICondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.deleteExecution(ctx, cond.ID(domain.ExecutionTypeResponse), resourceOwner)
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteExecutionFunction(ctx context.Context, cond ExecutionFunctionCondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.deleteExecution(ctx, cond.ID(), resourceOwner)
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteExecutionEvent(ctx context.Context, cond *ExecutionEventCondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.deleteExecution(ctx, cond.ID(), resourceOwner)
|
||||
}
|
||||
|
||||
func (c *Commands) deleteExecution(ctx context.Context, aggID string, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if resourceOwner == "" || aggID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-cnic97c0g3", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
wm, err := c.getExecutionWriteModelByID(ctx, aggID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !wm.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-suq2upd3rt", "Errors.Execution.NotFound")
|
||||
}
|
||||
if err := c.pushAppendAndReduce(ctx, wm, execution.NewRemovedEvent(
|
||||
ctx,
|
||||
ExecutionAggregateFromWriteModel(&wm.WriteModel),
|
||||
)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) existsExecutionsByIDs(ctx context.Context, ids []string, resourceOwner string) bool {
|
||||
wm := NewExecutionsExistWriteModel(ids, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, wm)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return wm.AllExists()
|
||||
}
|
||||
|
||||
func (c *Commands) getExecutionWriteModelByID(ctx context.Context, id string, resourceOwner string) (*ExecutionWriteModel, error) {
|
||||
wm := NewExecutionWriteModel(id, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, wm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wm, nil
|
||||
}
|
113
internal/command/action_v2_execution_model.go
Normal file
113
internal/command/action_v2_execution_model.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||
)
|
||||
|
||||
type ExecutionWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Targets []string
|
||||
Includes []string
|
||||
}
|
||||
|
||||
func (e *ExecutionWriteModel) Exists() bool {
|
||||
return len(e.Targets) > 0 || len(e.Includes) > 0
|
||||
}
|
||||
|
||||
func NewExecutionWriteModel(id string, resourceOwner string) *ExecutionWriteModel {
|
||||
return &ExecutionWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: id,
|
||||
ResourceOwner: resourceOwner,
|
||||
InstanceID: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *ExecutionWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *execution.SetEvent:
|
||||
wm.Targets = e.Targets
|
||||
wm.Includes = e.Includes
|
||||
case *execution.RemovedEvent:
|
||||
wm.Targets = nil
|
||||
wm.Includes = nil
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *ExecutionWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(execution.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(execution.SetEventType,
|
||||
execution.RemovedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func ExecutionAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return &eventstore.Aggregate{
|
||||
ID: wm.AggregateID,
|
||||
Type: execution.AggregateType,
|
||||
ResourceOwner: wm.ResourceOwner,
|
||||
InstanceID: wm.InstanceID,
|
||||
Version: execution.AggregateVersion,
|
||||
}
|
||||
}
|
||||
|
||||
type ExecutionsExistWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
ids []string
|
||||
existingIDs []string
|
||||
}
|
||||
|
||||
func (e *ExecutionsExistWriteModel) AllExists() bool {
|
||||
return len(e.ids) == len(e.existingIDs)
|
||||
}
|
||||
|
||||
func NewExecutionsExistWriteModel(ids []string, resourceOwner string) *ExecutionsExistWriteModel {
|
||||
return &ExecutionsExistWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
ResourceOwner: resourceOwner,
|
||||
InstanceID: resourceOwner,
|
||||
},
|
||||
ids: ids,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *ExecutionsExistWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *execution.SetEvent:
|
||||
if !slices.Contains(wm.existingIDs, e.Aggregate().ID) {
|
||||
wm.existingIDs = append(wm.existingIDs, e.Aggregate().ID)
|
||||
}
|
||||
case *execution.RemovedEvent:
|
||||
i := slices.Index(wm.existingIDs, e.Aggregate().ID)
|
||||
if i >= 0 {
|
||||
wm.existingIDs = slices.Delete(wm.existingIDs, i, i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *ExecutionsExistWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(execution.AggregateType).
|
||||
AggregateIDs(wm.ids...).
|
||||
EventTypes(execution.SetEventType,
|
||||
execution.RemovedEventType).
|
||||
Builder()
|
||||
}
|
437
internal/command/action_v2_execution_model_test.go
Normal file
437
internal/command/action_v2_execution_model_test.go
Normal file
@@ -0,0 +1,437 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||
)
|
||||
|
||||
func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
ids []string
|
||||
resourceOwner string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res bool
|
||||
}{
|
||||
{
|
||||
name: "execution, single",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
{
|
||||
name: "execution, single reset",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
{
|
||||
name: "execution, single before removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
{
|
||||
name: "execution, single removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
name: "execution, multiple",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution1", "execution2", "execution3"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "execution, multiple, first removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution1", "execution2", "execution3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
name: "execution, multiple, second removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution1", "execution2", "execution3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
name: "execution, multiple, third removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution1", "execution2", "execution3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
name: "execution, multiple, before removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution1", "execution2", "execution3"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
{
|
||||
name: "execution, multiple, all removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution1", "execution2", "execution3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "execution, multiple, two removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewRemovedEvent(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"execution1", "execution2", "execution3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
assert.Equal(t, tt.res, c.existsExecutionsByIDs(tt.args.ctx, tt.args.ids, tt.args.resourceOwner))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
2505
internal/command/action_v2_execution_test.go
Normal file
2505
internal/command/action_v2_execution_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -52,8 +52,14 @@ func (c *Commands) AddTarget(ctx context.Context, add *AddTarget, resourceOwner
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
wm, err := c.getTargetWriteModelByID(ctx, add.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wm.State.Exists() {
|
||||
return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-9axkz0jvzm", "Errors.Target.AlreadyExists")
|
||||
}
|
||||
|
||||
wm := NewTargetWriteModel(add.AggregateID, resourceOwner)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, target.NewAddedEvent(
|
||||
ctx,
|
||||
TargetAggregateFromWriteModel(&wm.WriteModel),
|
||||
@@ -167,6 +173,15 @@ func (c *Commands) DeleteTarget(ctx context.Context, id, resourceOwner string) (
|
||||
return writeModelToObjectDetails(&existing.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) existsTargetsByIDs(ctx context.Context, ids []string, resourceOwner string) bool {
|
||||
wm := NewTargetsExistsWriteModel(ids, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, wm)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return wm.AllExists()
|
||||
}
|
||||
|
||||
func (c *Commands) getTargetWriteModelByID(ctx context.Context, id string, resourceOwner string) (*TargetWriteModel, error) {
|
||||
wm := NewTargetWriteModel(id, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, wm)
|
||||
|
@@ -2,11 +2,11 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -62,7 +62,7 @@ func (wm *TargetWriteModel) Reduce() error {
|
||||
if e.InterruptOnError != nil {
|
||||
wm.InterruptOnError = *e.InterruptOnError
|
||||
}
|
||||
case *action.RemovedEvent:
|
||||
case *target.RemovedEvent:
|
||||
wm.State = domain.TargetRemoved
|
||||
}
|
||||
}
|
||||
@@ -116,6 +116,54 @@ func (wm *TargetWriteModel) NewChangedEvent(
|
||||
return target.NewChangedEvent(ctx, agg, changes)
|
||||
}
|
||||
|
||||
type TargetsExistsWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
ids []string
|
||||
existingIDs []string
|
||||
}
|
||||
|
||||
func (e *TargetsExistsWriteModel) AllExists() bool {
|
||||
return len(e.ids) == len(e.existingIDs)
|
||||
}
|
||||
|
||||
func NewTargetsExistsWriteModel(ids []string, resourceOwner string) *TargetsExistsWriteModel {
|
||||
return &TargetsExistsWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
ResourceOwner: resourceOwner,
|
||||
InstanceID: resourceOwner,
|
||||
},
|
||||
ids: ids,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *TargetsExistsWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *target.AddedEvent:
|
||||
if !slices.Contains(wm.existingIDs, e.Aggregate().ID) {
|
||||
wm.existingIDs = append(wm.existingIDs, e.Aggregate().ID)
|
||||
}
|
||||
case *target.RemovedEvent:
|
||||
i := slices.Index(wm.existingIDs, e.Aggregate().ID)
|
||||
if i >= 0 {
|
||||
wm.existingIDs = slices.Delete(wm.existingIDs, i, i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *TargetsExistsWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(target.AggregateType).
|
||||
AggregateIDs(wm.ids...).
|
||||
EventTypes(target.AddedEventType,
|
||||
target.RemovedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func TargetAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return &eventstore.Aggregate{
|
||||
ID: wm.AggregateID,
|
||||
|
330
internal/command/action_v2_target_model_test.go
Normal file
330
internal/command/action_v2_target_model_test.go
Normal file
@@ -0,0 +1,330 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/target"
|
||||
)
|
||||
|
||||
func targetAddEvent(aggID, resourceOwner string) *target.AddedEvent {
|
||||
return target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate(aggID, resourceOwner),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
time.Second,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func targetRemoveEvent(aggID, resourceOwner string) *target.RemovedEvent {
|
||||
return target.NewRemovedEvent(context.Background(),
|
||||
target.NewAggregate(aggID, resourceOwner),
|
||||
"name",
|
||||
)
|
||||
}
|
||||
|
||||
func TestCommandSide_targetsExistsWriteModel(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
ids []string
|
||||
resourceOwner string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res bool
|
||||
}{
|
||||
{
|
||||
name: "target, single",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
{
|
||||
name: "target, single reset",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
|
||||
targetAddEvent("target", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
{
|
||||
name: "target, single before removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
{
|
||||
name: "target, single removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
name: "target, multiple",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target2", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target3", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target1", "target2", "target3"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
{
|
||||
name: "target, multiple, first removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target2", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target3", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target1", "target2", "target3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
name: "target, multiple, second removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target2", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target2", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target3", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target1", "target2", "target3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
name: "target, multiple, third removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target2", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target3", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target3", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target1", "target2", "target3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
name: "target, multiple, before removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target2", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target3", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target1", "target2", "target3"},
|
||||
},
|
||||
res: true,
|
||||
},
|
||||
{
|
||||
name: "target, multiple, all removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target2", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target3", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target2", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target3", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target1", "target2", "target3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "target, multiple, two removed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target1", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target2", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target3", "org1"),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
targetRemoveEvent("target2", "org1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ids: []string{"target1", "target2", "target3"},
|
||||
},
|
||||
res: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
assert.Equal(t, tt.res, c.existsTargetsByIDs(tt.args.ctx, tt.args.ids, tt.args.resourceOwner))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
@@ -122,6 +122,7 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
"unique constraint failed, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPushFailed(
|
||||
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||
target.NewAddedEvent(context.Background(),
|
||||
@@ -151,10 +152,43 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"already existing",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
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{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
"push ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
@@ -190,6 +224,7 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
"push full ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
|
@@ -74,6 +74,12 @@ type Commands struct {
|
||||
defaultSecretGenerators *SecretGenerators
|
||||
|
||||
samlCertificateAndKeyGenerator func(id string) ([]byte, []byte, error)
|
||||
|
||||
GrpcMethodExisting func(method string) bool
|
||||
GrpcServiceExisting func(method string) bool
|
||||
ActionFunctionExisting func(function string) bool
|
||||
EventExisting func(event string) bool
|
||||
EventGroupExisting func(group string) bool
|
||||
}
|
||||
|
||||
func StartCommands(
|
||||
@@ -132,6 +138,13 @@ func StartCommands(
|
||||
defaultRefreshTokenIdleLifetime: defaultRefreshTokenIdleLifetime,
|
||||
defaultSecretGenerators: defaultSecretGenerators,
|
||||
samlCertificateAndKeyGenerator: samlCertificateAndKeyGenerator(defaults.KeyConfig.Size),
|
||||
// always true for now until we can check with an eventlist
|
||||
EventExisting: func(event string) bool { return true },
|
||||
// always true for now until we can check with an eventlist
|
||||
EventGroupExisting: func(group string) bool { return true },
|
||||
GrpcServiceExisting: func(service string) bool { return false },
|
||||
GrpcMethodExisting: func(method string) bool { return false },
|
||||
ActionFunctionExisting: domain.FunctionExists(),
|
||||
}
|
||||
|
||||
repo.codeAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost)
|
||||
|
33
internal/domain/execution.go
Normal file
33
internal/domain/execution.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package domain
|
||||
|
||||
type ExecutionType uint
|
||||
|
||||
func (s ExecutionType) Valid() bool {
|
||||
return s < executionTypeStateCount
|
||||
}
|
||||
|
||||
const (
|
||||
ExecutionTypeUnspecified ExecutionType = iota
|
||||
ExecutionTypeRequest
|
||||
ExecutionTypeResponse
|
||||
ExecutionTypeFunction
|
||||
ExecutionTypeEvent
|
||||
|
||||
executionTypeStateCount
|
||||
)
|
||||
|
||||
func (e ExecutionType) String() string {
|
||||
switch e {
|
||||
case ExecutionTypeUnspecified, executionTypeStateCount:
|
||||
return ""
|
||||
case ExecutionTypeRequest:
|
||||
return "request"
|
||||
case ExecutionTypeResponse:
|
||||
return "response"
|
||||
case ExecutionTypeFunction:
|
||||
return "function"
|
||||
case ExecutionTypeEvent:
|
||||
return "event"
|
||||
}
|
||||
return ""
|
||||
}
|
@@ -1,6 +1,9 @@
|
||||
package domain
|
||||
|
||||
import "strconv"
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type FlowState int32
|
||||
|
||||
@@ -25,6 +28,15 @@ const (
|
||||
flowTypeCount
|
||||
)
|
||||
|
||||
func AllFlowTypes() []FlowType {
|
||||
return []FlowType{
|
||||
FlowTypeExternalAuthentication,
|
||||
FlowTypeCustomiseToken,
|
||||
FlowTypeInternalAuthentication,
|
||||
FlowTypeCustomizeSAMLResponse,
|
||||
}
|
||||
}
|
||||
|
||||
func (s FlowType) Valid() bool {
|
||||
return s > 0 && s < flowTypeCount
|
||||
}
|
||||
@@ -138,3 +150,20 @@ func (s TriggerType) LocalizationKey() string {
|
||||
return "Action.TriggerType.Unspecified"
|
||||
}
|
||||
}
|
||||
|
||||
func AllFunctions() []string {
|
||||
functions := make([]string, 0)
|
||||
for _, flowType := range AllFlowTypes() {
|
||||
for _, triggerType := range flowType.TriggerTypes() {
|
||||
functions = append(functions, flowType.LocalizationKey()+"."+triggerType.LocalizationKey())
|
||||
}
|
||||
}
|
||||
return functions
|
||||
}
|
||||
|
||||
func FunctionExists() func(string) bool {
|
||||
functions := AllFunctions()
|
||||
return func(s string) bool {
|
||||
return slices.Contains(functions, s)
|
||||
}
|
||||
}
|
||||
|
@@ -525,3 +525,12 @@ func (s *Tester) CreateTarget(ctx context.Context, t *testing.T) *execution.Crea
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *execution.SetConditions, targets []string) *execution.SetExecutionResponse {
|
||||
target, err := s.Client.ExecutionV3.SetExecution(ctx, &execution.SetExecutionRequest{
|
||||
Condition: cond,
|
||||
Targets: targets,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}
|
||||
|
31
internal/repository/execution/aggregate.go
Normal file
31
internal/repository/execution/aggregate.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package execution
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
const (
|
||||
AggregateType = "execution"
|
||||
AggregateVersion = "v1"
|
||||
)
|
||||
|
||||
func NewAggregate(aggrID, instanceID string) *eventstore.Aggregate {
|
||||
return &eventstore.Aggregate{
|
||||
ID: aggrID,
|
||||
Type: AggregateType,
|
||||
ResourceOwner: instanceID,
|
||||
InstanceID: instanceID,
|
||||
Version: AggregateVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func ID(executionType domain.ExecutionType, value string) string {
|
||||
return strings.Join([]string{executionType.String(), value}, ".")
|
||||
}
|
||||
|
||||
func IDAll(executionType domain.ExecutionType) string {
|
||||
return executionType.String()
|
||||
}
|
8
internal/repository/execution/eventstore.go
Normal file
8
internal/repository/execution/eventstore.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package execution
|
||||
|
||||
import "github.com/zitadel/zitadel/internal/eventstore"
|
||||
|
||||
func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SetEventType, eventstore.GenericEventMapper[SetEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, RemovedEventType, eventstore.GenericEventMapper[RemovedEvent])
|
||||
}
|
71
internal/repository/execution/execution.go
Normal file
71
internal/repository/execution/execution.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package execution
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
const (
|
||||
eventTypePrefix eventstore.EventType = "execution."
|
||||
SetEventType = eventTypePrefix + "set"
|
||||
RemovedEventType = eventTypePrefix + "removed"
|
||||
)
|
||||
|
||||
type SetEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
ExecutionType domain.ExecutionType `json:"executionType"`
|
||||
Targets []string `json:"targets"`
|
||||
Includes []string `json:"includes"`
|
||||
}
|
||||
|
||||
func (e *SetEvent) SetBaseEvent(b *eventstore.BaseEvent) {
|
||||
e.BaseEvent = b
|
||||
}
|
||||
|
||||
func (e *SetEvent) Payload() any {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *SetEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSetEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
targets []string,
|
||||
includes []string,
|
||||
) *SetEvent {
|
||||
return &SetEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx, aggregate, SetEventType,
|
||||
),
|
||||
Targets: targets,
|
||||
Includes: includes,
|
||||
}
|
||||
}
|
||||
|
||||
type RemovedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *RemovedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
|
||||
e.BaseEvent = b
|
||||
}
|
||||
|
||||
func (e *RemovedEvent) Payload() any {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *RemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *RemovedEvent {
|
||||
return &RemovedEvent{
|
||||
eventstore.NewBaseEventForPush(ctx, aggregate, RemovedEventType),
|
||||
}
|
||||
}
|
@@ -557,6 +557,12 @@ Errors:
|
||||
NoTimeout: Целта няма време за изчакване
|
||||
InvalidURL: Целта има невалиден URL адрес
|
||||
NotFound: Целта не е намерена
|
||||
Execution:
|
||||
ConditionInvalid: Условието за изпълнение е невалидно
|
||||
Invalid: Изпълнението е невалидно
|
||||
NotFound: Изпълнението не е намерено
|
||||
IncludeNotFound: Включването не е намерено
|
||||
NoTargets: Няма определени цели
|
||||
|
||||
AggregateTypes:
|
||||
action: Действие
|
||||
@@ -569,8 +575,12 @@ AggregateTypes:
|
||||
quota: Квота
|
||||
feature: Особеност
|
||||
target: Целта
|
||||
execution: Екзекуция
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Комплект за изпълнение
|
||||
removed: Изпълнението е изтрито
|
||||
target:
|
||||
added: Целта е създадена
|
||||
changed: Целта е променена
|
||||
|
@@ -537,6 +537,12 @@ Errors:
|
||||
NoTimeout: Cíl nemá časový limit
|
||||
InvalidURL: Cíl má neplatnou adresu URL
|
||||
NotFound: Cíl nenalezen
|
||||
Execution:
|
||||
ConditionInvalid: Podmínka provedení je neplatná
|
||||
Invalid: Provedení je neplatné
|
||||
NotFound: Provedení nenalezeno
|
||||
IncludeNotFound: Zahrnout nenalezeno
|
||||
NoTargets: Nejsou definovány žádné cíle
|
||||
|
||||
AggregateTypes:
|
||||
action: Akce
|
||||
@@ -549,8 +555,12 @@ AggregateTypes:
|
||||
quota: Kvóta
|
||||
feature: Funkce
|
||||
target: Cíl
|
||||
execution: Provedení
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Prováděcí sada
|
||||
removed: Provedení smazáno
|
||||
target:
|
||||
added: Cíl vytvořen
|
||||
changed: Cíl změněn
|
||||
|
@@ -540,6 +540,12 @@ Errors:
|
||||
NoTimeout: Ziel hat keinen Timeout
|
||||
InvalidURL: Ziel hat eine ungültige URL
|
||||
NotFound: Ziel nicht gefunden
|
||||
Execution:
|
||||
ConditionInvalid: Die Ausführungsbedingung ist ungültig
|
||||
Invalid: Die Ausführung ist ungültig
|
||||
NotFound: Ausführung nicht gefunden
|
||||
IncludeNotFound: Einschließen nicht gefunden
|
||||
NoTargets: Keine Ziele definiert
|
||||
|
||||
AggregateTypes:
|
||||
action: Action
|
||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
||||
quota: Kontingent
|
||||
feature: Feature
|
||||
target: Ziel
|
||||
execution: Ausführung
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Ausführung gesetzt
|
||||
removed: Ausführung gelöscht
|
||||
target:
|
||||
added: Ziel erstellt
|
||||
changed: Ziel geändert
|
||||
|
@@ -540,6 +540,12 @@ Errors:
|
||||
NoTimeout: Target has no timeout
|
||||
InvalidURL: Target has an invalid URL
|
||||
NotFound: Target not found
|
||||
Execution:
|
||||
ConditionInvalid: Execution condition is invalid
|
||||
Invalid: Execution is invalid
|
||||
NotFound: Execution not found
|
||||
IncludeNotFound: Include not found
|
||||
NoTargets: No targets defined
|
||||
|
||||
AggregateTypes:
|
||||
action: Action
|
||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
||||
quota: Quota
|
||||
feature: Feature
|
||||
target: Target
|
||||
execution: Execution
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Execution set
|
||||
removed: Execution deleted
|
||||
target:
|
||||
added: Target created
|
||||
changed: Target changed
|
||||
|
@@ -540,6 +540,12 @@ Errors:
|
||||
NoTimeout: El objetivo no tiene tiempo de espera
|
||||
InvalidURL: El objetivo tiene una URL no válida
|
||||
NotFound: El objetivo no encontrado
|
||||
Execution:
|
||||
ConditionInvalid: La condición de ejecución no es válida
|
||||
Invalid: La ejecución no es válida
|
||||
NotFound: Ejecución no encontrada
|
||||
IncludeNotFound: Incluir no encontrado
|
||||
NoTargets: No hay objetivos definidos
|
||||
|
||||
AggregateTypes:
|
||||
action: Acción
|
||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
||||
quota: Cuota
|
||||
feature: Característica
|
||||
target: Objectivo
|
||||
execution: Ejecución
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Conjunto de ejecución
|
||||
removed: Ejecución eliminada
|
||||
target:
|
||||
added: Objetivo creado
|
||||
changed: Objetivo cambiado
|
||||
|
@@ -540,6 +540,12 @@ Errors:
|
||||
NoTimeout: La cible n'a pas de délai d'attente
|
||||
InvalidURL: La cible a une URL non valide
|
||||
NotFound: La cible introuvable
|
||||
Execution:
|
||||
ConditionInvalid: La condition d'exécution n'est pas valide
|
||||
Invalid: L'exécution est invalide
|
||||
NotFound: Exécution introuvable
|
||||
IncludeNotFound: Inclure introuvable
|
||||
NoTargets: Aucune cible définie
|
||||
|
||||
AggregateTypes:
|
||||
action: Action
|
||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
||||
quota: Contingent
|
||||
feature: Fonctionnalité
|
||||
target: Cible
|
||||
execution: Exécution
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Ensemble d'exécution
|
||||
removed: Exécution supprimée
|
||||
target:
|
||||
added: Cible créée
|
||||
changed: Cible modifiée
|
||||
|
@@ -541,6 +541,13 @@ Errors:
|
||||
NoTimeout: Il target non ha timeout
|
||||
InvalidURL: La destinazione ha un URL non valido
|
||||
NotFound: Obiettivo non trovato
|
||||
Execution:
|
||||
ConditionInvalid: La condizione di esecuzione non è valida
|
||||
Invalid: L'esecuzione non è valida
|
||||
NotFound: Esecuzione non trovata
|
||||
IncludeNotFound: Includi non trovato
|
||||
NoTargets: Nessun obiettivo definito
|
||||
|
||||
|
||||
AggregateTypes:
|
||||
action: Azione
|
||||
@@ -553,8 +560,12 @@ AggregateTypes:
|
||||
quota: Quota
|
||||
feature: Funzionalità
|
||||
target: Bersaglio
|
||||
execution: Esecuzione
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Insieme di esecuzione
|
||||
removed: Esecuzione cancellata
|
||||
target:
|
||||
added: Obiettivo creato
|
||||
changed: Obiettivo cambiato
|
||||
|
@@ -529,6 +529,12 @@ Errors:
|
||||
NoTimeout: ターゲットにはタイムアウトがありません
|
||||
InvalidURL: ターゲットに無効な URL があります
|
||||
NotFound: ターゲットが見つかりません
|
||||
Execution:
|
||||
ConditionInvalid: 実行条件が不正です
|
||||
Invalid: 実行は無効です
|
||||
NotFound: 実行が見つかりませんでした
|
||||
IncludeNotFound: 見つからないものを含める
|
||||
NoTargets: ターゲットが定義されていません
|
||||
|
||||
AggregateTypes:
|
||||
action: アクション
|
||||
@@ -541,8 +547,12 @@ AggregateTypes:
|
||||
quota: クォータ
|
||||
feature: 特徴
|
||||
target: 目標
|
||||
execution: 実行
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: 実行セット
|
||||
removed: 実行は削除されました
|
||||
target:
|
||||
added: ターゲットが作成されました
|
||||
changed: ターゲットが変更されました
|
||||
|
@@ -539,6 +539,12 @@ Errors:
|
||||
NoTimeout: Целта нема тајмаут
|
||||
InvalidURL: Целта има неважечка URL-адреса
|
||||
NotFound: Целта не е пронајдена
|
||||
Execution:
|
||||
ConditionInvalid: Условот за извршување е неважечки
|
||||
Invalid: Извршувањето е неважечко
|
||||
NotFound: Извршувањето не е пронајдено
|
||||
IncludeNotFound: Вклучете не е пронајден
|
||||
NoTargets: Не се дефинирани цели
|
||||
|
||||
AggregateTypes:
|
||||
action: Акција
|
||||
@@ -551,8 +557,12 @@ AggregateTypes:
|
||||
quota: Квота
|
||||
feature: Карактеристика
|
||||
target: Цел
|
||||
execution: Извршување
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Комплет за извршување
|
||||
removed: Извршувањето е избришано
|
||||
target:
|
||||
added: Целта е избришана
|
||||
changed: Целта е променета
|
||||
|
@@ -540,6 +540,12 @@ Errors:
|
||||
NoTimeout: Doel heeft geen time-out
|
||||
InvalidURL: Doel heeft een ongeldige URL
|
||||
NotFound: Doel niet gevonden
|
||||
Execution:
|
||||
ConditionInvalid: Uitvoeringsvoorwaarde is ongeldig
|
||||
Invalid: Uitvoering is ongeldig
|
||||
NotFound: Uitvoering niet gevonden
|
||||
IncludeNotFound: Inclusief niet gevonden
|
||||
NoTargets: Geen doelstellingen gedefinieerd
|
||||
|
||||
AggregateTypes:
|
||||
action: Actie
|
||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
||||
quota: Quota
|
||||
feature: Functie
|
||||
target: Doel
|
||||
execution: Executie
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Uitvoering ingesteld
|
||||
removed: Uitvoering verwijderd
|
||||
target:
|
||||
added: Doel gemaakt
|
||||
changed: Doel gewijzigd
|
||||
|
@@ -540,6 +540,12 @@ Errors:
|
||||
NoTimeout: Cel nie ma limitu czasu
|
||||
InvalidURL: Cel ma nieprawidłowy adres URL
|
||||
NotFound: Nie znaleziono celu
|
||||
Execution:
|
||||
ConditionInvalid: Warunek wykonania jest nieprawidłowy
|
||||
Invalid: Wykonanie jest nieprawidłowe
|
||||
NotFound: Nie znaleziono wykonania
|
||||
IncludeNotFound: Nie znaleziono uwzględnienia
|
||||
NoTargets: Nie zdefiniowano celów
|
||||
|
||||
AggregateTypes:
|
||||
action: Działanie
|
||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
||||
quota: Limit
|
||||
feature: Funkcja
|
||||
target: Cel
|
||||
execution: Wykonanie
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Zestaw wykonawczy
|
||||
removed: Wykonanie usunięte
|
||||
target:
|
||||
added: Cel został utworzony
|
||||
changed: Cel zmieniony
|
||||
|
@@ -534,6 +534,12 @@ Errors:
|
||||
NoTimeout: O destino não tem tempo limite
|
||||
InvalidURL: O destino tem um URL inválido
|
||||
NotFound: Destino não encontrado
|
||||
Execution:
|
||||
ConditionInvalid: A condição de execução é inválida
|
||||
Invalid: A execução é inválida
|
||||
NotFound: Execução não encontrada
|
||||
IncludeNotFound: Incluir não encontrado
|
||||
NoTargets: Nenhuma meta definida
|
||||
|
||||
AggregateTypes:
|
||||
action: Ação
|
||||
@@ -545,9 +551,13 @@ AggregateTypes:
|
||||
usergrant: Concessão de usuário
|
||||
quota: Cota
|
||||
feature: Recurso
|
||||
target: objetivo
|
||||
target: Objetivo
|
||||
execution: Execução
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Conjunto de execução
|
||||
removed: Execução excluída
|
||||
target:
|
||||
added: Destino criado
|
||||
changed: Destino alterada
|
||||
|
@@ -528,6 +528,12 @@ Errors:
|
||||
NoTimeout: У цели нет тайм-аута
|
||||
InvalidURL: Цель имеет неверный URL-адрес
|
||||
NotFound: Цель не найдена
|
||||
Execution:
|
||||
ConditionInvalid: Недопустимое условие выполнения
|
||||
Invalid: Исполнение недействительно
|
||||
NotFound: Исполнение не найдено
|
||||
IncludeNotFound: Включить не найдено
|
||||
NoTargets: Цели не определены
|
||||
|
||||
AggregateTypes:
|
||||
action: Действие
|
||||
@@ -540,8 +546,12 @@ AggregateTypes:
|
||||
quota: Квота
|
||||
feature: Особенность
|
||||
target: мишень
|
||||
execution: Исполнение
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: Набор исполнения
|
||||
removed: Исполнение удалено
|
||||
target:
|
||||
added: Цель создана
|
||||
changed: Цель изменена
|
||||
|
@@ -540,6 +540,12 @@ Errors:
|
||||
NoTimeout: 目标没有超时
|
||||
InvalidURL: 目标的 URL 无效
|
||||
NotFound: 未找到目标
|
||||
Execution:
|
||||
ConditionInvalid: 执行条件无效
|
||||
Invalid: 执行无效
|
||||
NotFound: 未找到执行
|
||||
IncludeNotFound: 包括未找到的内容
|
||||
NoTargets: 没有定义目标
|
||||
|
||||
AggregateTypes:
|
||||
action: 动作
|
||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
||||
quota: 配额
|
||||
feature: 特征
|
||||
target: 靶
|
||||
execution: 执行
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
set: 执行集
|
||||
removed: 执行已删除
|
||||
target:
|
||||
added: 目标已创建
|
||||
changed: 目标改变
|
||||
|
109
proto/zitadel/execution/v3alpha/execution.proto
Normal file
109
proto/zitadel/execution/v3alpha/execution.proto
Normal file
@@ -0,0 +1,109 @@
|
||||
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 SetConditions{
|
||||
// Condition-types under which conditions the execution should happen, only one possible.
|
||||
oneof condition_type {
|
||||
option (validate.required) = true;
|
||||
|
||||
// Condition-type to execute if a request on the defined API point happens.
|
||||
SetRequestExecution request = 1;
|
||||
// Condition-type to execute on response if a request on the defined API point happens.
|
||||
SetResponseExecution response = 2;
|
||||
// Condition-type to execute if function is used, replaces actions v1.
|
||||
string function = 3;
|
||||
// Condition-type to execute if an event is created in the system.
|
||||
SetEventExecution event = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message SetRequestExecution{
|
||||
// Condition for the request execution, only one possible.
|
||||
oneof condition{
|
||||
// GRPC-method as condition.
|
||||
string method = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 1000},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1,
|
||||
max_length: 1000,
|
||||
example: "\"/zitadel.session.v2beta.SessionService/ListSessions\"";
|
||||
}
|
||||
];
|
||||
// GRPC-service as condition.
|
||||
string service = 2 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 1000},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1,
|
||||
max_length: 1000,
|
||||
example: "\"zitadel.session.v2beta.SessionService\"";
|
||||
}
|
||||
];
|
||||
// All calls to any available service and endpoint as condition.
|
||||
bool all = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message SetResponseExecution{
|
||||
// Condition for the response execution, only one possible.
|
||||
oneof condition{
|
||||
// GRPC-method as condition.
|
||||
string method = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 1000},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1,
|
||||
max_length: 1000,
|
||||
example: "\"/zitadel.session.v2beta.SessionService/ListSessions\"";
|
||||
}
|
||||
];
|
||||
// GRPC-service as condition.
|
||||
string service = 2 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 1000},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1,
|
||||
max_length: 1000,
|
||||
example: "\"zitadel.session.v2beta.SessionService\"";
|
||||
}
|
||||
];
|
||||
// All calls to any available service and endpoint as condition.
|
||||
bool all = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message SetEventExecution{
|
||||
// Condition for the event execution, only one possible.
|
||||
oneof condition{
|
||||
// Event name as condition.
|
||||
string event = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 1000},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1,
|
||||
max_length: 1000,
|
||||
example: "\"user.human.added\"";
|
||||
}
|
||||
];
|
||||
// Event group as condition, all events under this group.
|
||||
string group = 2 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 1000},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1,
|
||||
max_length: 1000,
|
||||
example: "\"user.human\"";
|
||||
}
|
||||
];
|
||||
// all events as condition.
|
||||
bool all = 3;
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import "google/protobuf/struct.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "validate/validate.proto";
|
||||
import "zitadel/execution/v3alpha/target.proto";
|
||||
import "zitadel/execution/v3alpha/execution.proto";
|
||||
import "zitadel/object/v2beta/object.proto";
|
||||
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||
|
||||
@@ -185,6 +186,125 @@ service ExecutionService {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Set an execution
|
||||
//
|
||||
// Set an execution to call a previously defined target or include the targets of a previously defined execution.
|
||||
rpc SetExecution (SetExecutionRequest) returns (SetExecutionResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v3alpha/executions"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "execution.write"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "Execution successfully set";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Delete an execution
|
||||
//
|
||||
// Delete an existing execution.
|
||||
rpc DeleteExecution (DeleteExecutionRequest) returns (DeleteExecutionResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v3alpha/executions"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "execution.delete"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "Execution successfully deleted";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
// List all available functions
|
||||
//
|
||||
// List all available functions which can be used as condition for executions.
|
||||
rpc ListExecutionFunctions (ListExecutionFunctionsRequest) returns (ListExecutionFunctionsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v3alpha/executions/functions"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "execution.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "List all functions successfully";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
// List all available methods
|
||||
//
|
||||
// List all available methods which can be used as condition for executions.
|
||||
rpc ListExecutionMethods (ListExecutionMethodsRequest) returns (ListExecutionMethodsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v3alpha/executions/methods"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "execution.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "List all methods successfully";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
// List all available service
|
||||
//
|
||||
// List all available services which can be used as condition for executions.
|
||||
rpc ListExecutionServices (ListExecutionServicesRequest) returns (ListExecutionServicesResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v3alpha/executions/services"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "execution.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "List all services successfully";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
message CreateTargetRequest {
|
||||
@@ -291,3 +411,44 @@ message DeleteTargetResponse {
|
||||
// Details provide some base information (such as the last change date) of the target.
|
||||
zitadel.object.v2beta.Details details = 1;
|
||||
}
|
||||
|
||||
message SetExecutionRequest {
|
||||
// Defines the condition type and content of the condition for execution.
|
||||
SetConditions condition = 1;
|
||||
// Defines the execution targets which are defined as a different resource, which are called in the defined conditions.
|
||||
repeated string targets = 2;
|
||||
// Defines other executions as included with the same condition-types.
|
||||
repeated string includes = 3;
|
||||
}
|
||||
|
||||
message SetExecutionResponse {
|
||||
// Details provide some base information (such as the last change date) of the execution.
|
||||
zitadel.object.v2beta.Details details = 2;
|
||||
}
|
||||
|
||||
message DeleteExecutionRequest {
|
||||
// Unique identifier of the execution.
|
||||
SetConditions condition = 1;
|
||||
}
|
||||
|
||||
message DeleteExecutionResponse {
|
||||
// Details provide some base information (such as the last change date) of the execution.
|
||||
zitadel.object.v2beta.Details details = 1;
|
||||
}
|
||||
|
||||
message ListExecutionFunctionsRequest{}
|
||||
message ListExecutionFunctionsResponse{
|
||||
// All available methods
|
||||
repeated string functions = 1;
|
||||
}
|
||||
message ListExecutionMethodsRequest{}
|
||||
message ListExecutionMethodsResponse{
|
||||
// All available methods
|
||||
repeated string methods = 1;
|
||||
}
|
||||
|
||||
message ListExecutionServicesRequest{}
|
||||
message ListExecutionServicesResponse{
|
||||
// All available methods
|
||||
repeated string services = 1;
|
||||
}
|
Reference in New Issue
Block a user