mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 00:07:36 +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"
|
- "session.delete"
|
||||||
- "execution.target.write"
|
- "execution.target.write"
|
||||||
- "execution.target.delete"
|
- "execution.target.delete"
|
||||||
|
- "execution.read"
|
||||||
|
- "execution.write"
|
||||||
|
- "execution.delete"
|
||||||
- Role: "IAM_OWNER_VIEWER"
|
- Role: "IAM_OWNER_VIEWER"
|
||||||
Permissions:
|
Permissions:
|
||||||
- "iam.read"
|
- "iam.read"
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"slices"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -264,7 +265,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = startAPIs(
|
api, err := startAPIs(
|
||||||
ctx,
|
ctx,
|
||||||
clock,
|
clock,
|
||||||
router,
|
router,
|
||||||
@@ -281,6 +282,8 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
commands.GrpcMethodExisting = checkExisting(api.ListGrpcMethods())
|
||||||
|
commands.GrpcServiceExisting = checkExisting(api.ListGrpcServices())
|
||||||
|
|
||||||
shutdown := make(chan os.Signal, 1)
|
shutdown := make(chan os.Signal, 1)
|
||||||
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
|
||||||
@@ -319,7 +322,7 @@ func startAPIs(
|
|||||||
authZRepo authz_repo.Repository,
|
authZRepo authz_repo.Repository,
|
||||||
keys *encryption.EncryptionKeys,
|
keys *encryption.EncryptionKeys,
|
||||||
permissionCheck domain.PermissionCheck,
|
permissionCheck domain.PermissionCheck,
|
||||||
) error {
|
) (*api.API, error) {
|
||||||
repo := struct {
|
repo := struct {
|
||||||
authz_repo.Repository
|
authz_repo.Repository
|
||||||
*query.Queries
|
*query.Queries
|
||||||
@@ -332,22 +335,22 @@ func startAPIs(
|
|||||||
router.Use(middleware.WithOrigin(config.ExternalSecure))
|
router.Use(middleware.WithOrigin(config.ExternalSecure))
|
||||||
systemTokenVerifier, err := internal_authz.StartSystemTokenVerifierFromConfig(http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), config.SystemAPIUsers)
|
systemTokenVerifier, err := internal_authz.StartSystemTokenVerifierFromConfig(http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), config.SystemAPIUsers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
accessTokenVerifer := internal_authz.StartAccessTokenVerifierFromRepo(repo)
|
accessTokenVerifer := internal_authz.StartAccessTokenVerifierFromRepo(repo)
|
||||||
verifier := internal_authz.StartAPITokenVerifier(repo, accessTokenVerifer, systemTokenVerifier)
|
verifier := internal_authz.StartAPITokenVerifier(repo, accessTokenVerifer, systemTokenVerifier)
|
||||||
tlsConfig, err := config.TLS.Config()
|
tlsConfig, err := config.TLS.Config()
|
||||||
if err != nil {
|
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]())
|
accessStdoutEmitter, err := logstore.NewEmitter[*record.AccessLog](ctx, clock, &logstore.EmitterConfig{Enabled: config.LogStore.Access.Stdout.Enabled}, stdout.NewStdoutEmitter[*record.AccessLog]())
|
||||||
if err != nil {
|
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))
|
accessDBEmitter, err := logstore.NewEmitter[*record.AccessLog](ctx, clock, &config.Quotas.Access.EmitterConfig, access.NewDatabaseLogStorage(dbClient, commands, queries))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accessSvc := logstore.New[*record.AccessLog](queries, accessDBEmitter, accessStdoutEmitter)
|
accessSvc := logstore.New[*record.AccessLog](queries, accessDBEmitter, accessStdoutEmitter)
|
||||||
@@ -359,50 +362,50 @@ func startAPIs(
|
|||||||
limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, &config.Quotas.Access.AccessConfig)
|
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)
|
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, limitingAccessInterceptor)
|
||||||
if err != nil {
|
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.Client = dbClient
|
||||||
config.Auth.Spooler.Eventstore = eventstore
|
config.Auth.Spooler.Eventstore = eventstore
|
||||||
authRepo, err := auth_es.Start(ctx, config.Auth, config.SystemDefaults, commands, queries, dbClient, eventstore, keys.OIDC, keys.User)
|
authRepo, err := auth_es.Start(ctx, config.Auth, config.SystemDefaults, commands, queries, dbClient, eventstore, keys.OIDC, keys.User)
|
||||||
if err != nil {
|
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.Client = dbClient
|
||||||
config.Admin.Spooler.Eventstore = eventstore
|
config.Admin.Spooler.Eventstore = eventstore
|
||||||
err = admin_es.Start(ctx, config.Admin, store, dbClient)
|
err = admin_es.Start(ctx, config.Admin, store, dbClient)
|
||||||
if err != nil {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
if err := apis.RegisterService(ctx, execution_v3_alpha.CreateServer(commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
||||||
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
||||||
@@ -412,38 +415,38 @@ func startAPIs(
|
|||||||
|
|
||||||
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources, login.EndpointExternalLoginCallbackFormPost, login.EndpointSAMLACS)
|
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources, login.EndpointExternalLoginCallbackFormPost, login.EndpointSAMLACS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// robots.txt handler
|
// robots.txt handler
|
||||||
robotsTxtHandler, err := robots_txt.Start()
|
robotsTxtHandler, err := robots_txt.Start()
|
||||||
if err != nil {
|
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)
|
apis.RegisterHandlerOnPrefix(robots_txt.HandlerPrefix, robotsTxtHandler)
|
||||||
|
|
||||||
// TODO: Record openapi access logs?
|
// TODO: Record openapi access logs?
|
||||||
openAPIHandler, err := openapi.Start()
|
openAPIHandler, err := openapi.Start()
|
||||||
if err != nil {
|
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)
|
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())
|
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 {
|
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...)
|
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)
|
samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, limitingAccessInterceptor)
|
||||||
if err != nil {
|
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())
|
apis.RegisterHandlerOnPrefix(saml.HandlerPrefix, samlProvider.HttpHandler())
|
||||||
|
|
||||||
c, err := console.Start(config.Console, config.ExternalSecure, oidcServer.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, limitingAccessInterceptor, config.CustomerPortal)
|
c, err := console.Start(config.Console, config.ExternalSecure, oidcServer.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, limitingAccessInterceptor, config.CustomerPortal)
|
||||||
if err != nil {
|
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)
|
apis.RegisterHandlerOnPrefix(console.HandlerPrefix, c)
|
||||||
consolePath := console.HandlerPrefix + "/"
|
consolePath := console.HandlerPrefix + "/"
|
||||||
@@ -469,18 +472,18 @@ func startAPIs(
|
|||||||
feature.NewCheck(eventstore),
|
feature.NewCheck(eventstore),
|
||||||
)
|
)
|
||||||
if err != nil {
|
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.RegisterHandlerOnPrefix(login.HandlerPrefix, l.Handler())
|
||||||
apis.HandleFunc(login.EndpointDeviceAuth, login.RedirectDeviceAuthToPrefix)
|
apis.HandleFunc(login.EndpointDeviceAuth, login.RedirectDeviceAuthToPrefix)
|
||||||
|
|
||||||
// After OIDC provider so that the callback endpoint can be used
|
// 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 {
|
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
|
// handle grpc at last to be able to handle the root, because grpc and gateway require a lot of different prefixes
|
||||||
apis.RouteGRPC()
|
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 {
|
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")
|
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"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@@ -38,6 +39,30 @@ type API struct {
|
|||||||
queries *query.Queries
|
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 {
|
type healthCheck interface {
|
||||||
Health(ctx context.Context) error
|
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 {
|
type Server struct {
|
||||||
execution.UnimplementedExecutionServiceServer
|
execution.UnimplementedExecutionServiceServer
|
||||||
command *command.Commands
|
command *command.Commands
|
||||||
query *query.Queries
|
query *query.Queries
|
||||||
|
ListActionFunctions func() []string
|
||||||
|
ListGRPCMethods func() []string
|
||||||
|
ListGRPCServices func() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct{}
|
type Config struct{}
|
||||||
@@ -23,10 +26,16 @@ type Config struct{}
|
|||||||
func CreateServer(
|
func CreateServer(
|
||||||
command *command.Commands,
|
command *command.Commands,
|
||||||
query *query.Queries,
|
query *query.Queries,
|
||||||
|
listActionFunctions func() []string,
|
||||||
|
listGRPCMethods func() []string,
|
||||||
|
listGRPCServices func() []string,
|
||||||
) *Server {
|
) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
command: command,
|
command: command,
|
||||||
query: query,
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -20,26 +19,6 @@ import (
|
|||||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
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) {
|
func TestServer_CreateTarget(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
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
|
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(
|
pushedEvents, err := c.eventstore.Push(ctx, target.NewAddedEvent(
|
||||||
ctx,
|
ctx,
|
||||||
TargetAggregateFromWriteModel(&wm.WriteModel),
|
TargetAggregateFromWriteModel(&wm.WriteModel),
|
||||||
@@ -167,6 +173,15 @@ func (c *Commands) DeleteTarget(ctx context.Context, id, resourceOwner string) (
|
|||||||
return writeModelToObjectDetails(&existing.WriteModel), nil
|
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) {
|
func (c *Commands) getTargetWriteModelByID(ctx context.Context, id string, resourceOwner string) (*TargetWriteModel, error) {
|
||||||
wm := NewTargetWriteModel(id, resourceOwner)
|
wm := NewTargetWriteModel(id, resourceOwner)
|
||||||
err := c.eventstore.FilterToQueryReducer(ctx, wm)
|
err := c.eventstore.FilterToQueryReducer(ctx, wm)
|
||||||
|
@@ -2,11 +2,11 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/repository/action"
|
|
||||||
"github.com/zitadel/zitadel/internal/repository/target"
|
"github.com/zitadel/zitadel/internal/repository/target"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ func (wm *TargetWriteModel) Reduce() error {
|
|||||||
if e.InterruptOnError != nil {
|
if e.InterruptOnError != nil {
|
||||||
wm.InterruptOnError = *e.InterruptOnError
|
wm.InterruptOnError = *e.InterruptOnError
|
||||||
}
|
}
|
||||||
case *action.RemovedEvent:
|
case *target.RemovedEvent:
|
||||||
wm.State = domain.TargetRemoved
|
wm.State = domain.TargetRemoved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,6 +116,54 @@ func (wm *TargetWriteModel) NewChangedEvent(
|
|||||||
return target.NewChangedEvent(ctx, agg, changes)
|
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 {
|
func TargetAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||||
return &eventstore.Aggregate{
|
return &eventstore.Aggregate{
|
||||||
ID: wm.AggregateID,
|
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",
|
"unique constraint failed, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
expectPushFailed(
|
expectPushFailed(
|
||||||
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||||
target.NewAddedEvent(context.Background(),
|
target.NewAddedEvent(context.Background(),
|
||||||
@@ -151,10 +152,43 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
err: zerrors.IsPreconditionFailed,
|
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",
|
"push ok",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
target.NewAddedEvent(context.Background(),
|
target.NewAddedEvent(context.Background(),
|
||||||
target.NewAggregate("id1", "org1"),
|
target.NewAggregate("id1", "org1"),
|
||||||
@@ -190,6 +224,7 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
"push full ok",
|
"push full ok",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
target.NewAddedEvent(context.Background(),
|
target.NewAddedEvent(context.Background(),
|
||||||
target.NewAggregate("id1", "org1"),
|
target.NewAggregate("id1", "org1"),
|
||||||
|
@@ -74,6 +74,12 @@ type Commands struct {
|
|||||||
defaultSecretGenerators *SecretGenerators
|
defaultSecretGenerators *SecretGenerators
|
||||||
|
|
||||||
samlCertificateAndKeyGenerator func(id string) ([]byte, []byte, error)
|
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(
|
func StartCommands(
|
||||||
@@ -132,6 +138,13 @@ func StartCommands(
|
|||||||
defaultRefreshTokenIdleLifetime: defaultRefreshTokenIdleLifetime,
|
defaultRefreshTokenIdleLifetime: defaultRefreshTokenIdleLifetime,
|
||||||
defaultSecretGenerators: defaultSecretGenerators,
|
defaultSecretGenerators: defaultSecretGenerators,
|
||||||
samlCertificateAndKeyGenerator: samlCertificateAndKeyGenerator(defaults.KeyConfig.Size),
|
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)
|
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
|
package domain
|
||||||
|
|
||||||
import "strconv"
|
import (
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
type FlowState int32
|
type FlowState int32
|
||||||
|
|
||||||
@@ -25,6 +28,15 @@ const (
|
|||||||
flowTypeCount
|
flowTypeCount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func AllFlowTypes() []FlowType {
|
||||||
|
return []FlowType{
|
||||||
|
FlowTypeExternalAuthentication,
|
||||||
|
FlowTypeCustomiseToken,
|
||||||
|
FlowTypeInternalAuthentication,
|
||||||
|
FlowTypeCustomizeSAMLResponse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s FlowType) Valid() bool {
|
func (s FlowType) Valid() bool {
|
||||||
return s > 0 && s < flowTypeCount
|
return s > 0 && s < flowTypeCount
|
||||||
}
|
}
|
||||||
@@ -138,3 +150,20 @@ func (s TriggerType) LocalizationKey() string {
|
|||||||
return "Action.TriggerType.Unspecified"
|
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)
|
require.NoError(t, err)
|
||||||
return target
|
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: Целта няма време за изчакване
|
NoTimeout: Целта няма време за изчакване
|
||||||
InvalidURL: Целта има невалиден URL адрес
|
InvalidURL: Целта има невалиден URL адрес
|
||||||
NotFound: Целта не е намерена
|
NotFound: Целта не е намерена
|
||||||
|
Execution:
|
||||||
|
ConditionInvalid: Условието за изпълнение е невалидно
|
||||||
|
Invalid: Изпълнението е невалидно
|
||||||
|
NotFound: Изпълнението не е намерено
|
||||||
|
IncludeNotFound: Включването не е намерено
|
||||||
|
NoTargets: Няма определени цели
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Действие
|
action: Действие
|
||||||
@@ -569,8 +575,12 @@ AggregateTypes:
|
|||||||
quota: Квота
|
quota: Квота
|
||||||
feature: Особеност
|
feature: Особеност
|
||||||
target: Целта
|
target: Целта
|
||||||
|
execution: Екзекуция
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Комплект за изпълнение
|
||||||
|
removed: Изпълнението е изтрито
|
||||||
target:
|
target:
|
||||||
added: Целта е създадена
|
added: Целта е създадена
|
||||||
changed: Целта е променена
|
changed: Целта е променена
|
||||||
|
@@ -537,6 +537,12 @@ Errors:
|
|||||||
NoTimeout: Cíl nemá časový limit
|
NoTimeout: Cíl nemá časový limit
|
||||||
InvalidURL: Cíl má neplatnou adresu URL
|
InvalidURL: Cíl má neplatnou adresu URL
|
||||||
NotFound: Cíl nenalezen
|
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:
|
AggregateTypes:
|
||||||
action: Akce
|
action: Akce
|
||||||
@@ -549,8 +555,12 @@ AggregateTypes:
|
|||||||
quota: Kvóta
|
quota: Kvóta
|
||||||
feature: Funkce
|
feature: Funkce
|
||||||
target: Cíl
|
target: Cíl
|
||||||
|
execution: Provedení
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Prováděcí sada
|
||||||
|
removed: Provedení smazáno
|
||||||
target:
|
target:
|
||||||
added: Cíl vytvořen
|
added: Cíl vytvořen
|
||||||
changed: Cíl změněn
|
changed: Cíl změněn
|
||||||
|
@@ -540,6 +540,12 @@ Errors:
|
|||||||
NoTimeout: Ziel hat keinen Timeout
|
NoTimeout: Ziel hat keinen Timeout
|
||||||
InvalidURL: Ziel hat eine ungültige URL
|
InvalidURL: Ziel hat eine ungültige URL
|
||||||
NotFound: Ziel nicht gefunden
|
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:
|
AggregateTypes:
|
||||||
action: Action
|
action: Action
|
||||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
|||||||
quota: Kontingent
|
quota: Kontingent
|
||||||
feature: Feature
|
feature: Feature
|
||||||
target: Ziel
|
target: Ziel
|
||||||
|
execution: Ausführung
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Ausführung gesetzt
|
||||||
|
removed: Ausführung gelöscht
|
||||||
target:
|
target:
|
||||||
added: Ziel erstellt
|
added: Ziel erstellt
|
||||||
changed: Ziel geändert
|
changed: Ziel geändert
|
||||||
|
@@ -540,6 +540,12 @@ Errors:
|
|||||||
NoTimeout: Target has no timeout
|
NoTimeout: Target has no timeout
|
||||||
InvalidURL: Target has an invalid URL
|
InvalidURL: Target has an invalid URL
|
||||||
NotFound: Target not found
|
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:
|
AggregateTypes:
|
||||||
action: Action
|
action: Action
|
||||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
|||||||
quota: Quota
|
quota: Quota
|
||||||
feature: Feature
|
feature: Feature
|
||||||
target: Target
|
target: Target
|
||||||
|
execution: Execution
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Execution set
|
||||||
|
removed: Execution deleted
|
||||||
target:
|
target:
|
||||||
added: Target created
|
added: Target created
|
||||||
changed: Target changed
|
changed: Target changed
|
||||||
|
@@ -540,6 +540,12 @@ Errors:
|
|||||||
NoTimeout: El objetivo no tiene tiempo de espera
|
NoTimeout: El objetivo no tiene tiempo de espera
|
||||||
InvalidURL: El objetivo tiene una URL no válida
|
InvalidURL: El objetivo tiene una URL no válida
|
||||||
NotFound: El objetivo no encontrado
|
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:
|
AggregateTypes:
|
||||||
action: Acción
|
action: Acción
|
||||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
|||||||
quota: Cuota
|
quota: Cuota
|
||||||
feature: Característica
|
feature: Característica
|
||||||
target: Objectivo
|
target: Objectivo
|
||||||
|
execution: Ejecución
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Conjunto de ejecución
|
||||||
|
removed: Ejecución eliminada
|
||||||
target:
|
target:
|
||||||
added: Objetivo creado
|
added: Objetivo creado
|
||||||
changed: Objetivo cambiado
|
changed: Objetivo cambiado
|
||||||
|
@@ -540,6 +540,12 @@ Errors:
|
|||||||
NoTimeout: La cible n'a pas de délai d'attente
|
NoTimeout: La cible n'a pas de délai d'attente
|
||||||
InvalidURL: La cible a une URL non valide
|
InvalidURL: La cible a une URL non valide
|
||||||
NotFound: La cible introuvable
|
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:
|
AggregateTypes:
|
||||||
action: Action
|
action: Action
|
||||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
|||||||
quota: Contingent
|
quota: Contingent
|
||||||
feature: Fonctionnalité
|
feature: Fonctionnalité
|
||||||
target: Cible
|
target: Cible
|
||||||
|
execution: Exécution
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Ensemble d'exécution
|
||||||
|
removed: Exécution supprimée
|
||||||
target:
|
target:
|
||||||
added: Cible créée
|
added: Cible créée
|
||||||
changed: Cible modifiée
|
changed: Cible modifiée
|
||||||
|
@@ -541,6 +541,13 @@ Errors:
|
|||||||
NoTimeout: Il target non ha timeout
|
NoTimeout: Il target non ha timeout
|
||||||
InvalidURL: La destinazione ha un URL non valido
|
InvalidURL: La destinazione ha un URL non valido
|
||||||
NotFound: Obiettivo non trovato
|
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:
|
AggregateTypes:
|
||||||
action: Azione
|
action: Azione
|
||||||
@@ -553,8 +560,12 @@ AggregateTypes:
|
|||||||
quota: Quota
|
quota: Quota
|
||||||
feature: Funzionalità
|
feature: Funzionalità
|
||||||
target: Bersaglio
|
target: Bersaglio
|
||||||
|
execution: Esecuzione
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Insieme di esecuzione
|
||||||
|
removed: Esecuzione cancellata
|
||||||
target:
|
target:
|
||||||
added: Obiettivo creato
|
added: Obiettivo creato
|
||||||
changed: Obiettivo cambiato
|
changed: Obiettivo cambiato
|
||||||
|
@@ -529,6 +529,12 @@ Errors:
|
|||||||
NoTimeout: ターゲットにはタイムアウトがありません
|
NoTimeout: ターゲットにはタイムアウトがありません
|
||||||
InvalidURL: ターゲットに無効な URL があります
|
InvalidURL: ターゲットに無効な URL があります
|
||||||
NotFound: ターゲットが見つかりません
|
NotFound: ターゲットが見つかりません
|
||||||
|
Execution:
|
||||||
|
ConditionInvalid: 実行条件が不正です
|
||||||
|
Invalid: 実行は無効です
|
||||||
|
NotFound: 実行が見つかりませんでした
|
||||||
|
IncludeNotFound: 見つからないものを含める
|
||||||
|
NoTargets: ターゲットが定義されていません
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: アクション
|
action: アクション
|
||||||
@@ -541,8 +547,12 @@ AggregateTypes:
|
|||||||
quota: クォータ
|
quota: クォータ
|
||||||
feature: 特徴
|
feature: 特徴
|
||||||
target: 目標
|
target: 目標
|
||||||
|
execution: 実行
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: 実行セット
|
||||||
|
removed: 実行は削除されました
|
||||||
target:
|
target:
|
||||||
added: ターゲットが作成されました
|
added: ターゲットが作成されました
|
||||||
changed: ターゲットが変更されました
|
changed: ターゲットが変更されました
|
||||||
|
@@ -539,6 +539,12 @@ Errors:
|
|||||||
NoTimeout: Целта нема тајмаут
|
NoTimeout: Целта нема тајмаут
|
||||||
InvalidURL: Целта има неважечка URL-адреса
|
InvalidURL: Целта има неважечка URL-адреса
|
||||||
NotFound: Целта не е пронајдена
|
NotFound: Целта не е пронајдена
|
||||||
|
Execution:
|
||||||
|
ConditionInvalid: Условот за извршување е неважечки
|
||||||
|
Invalid: Извршувањето е неважечко
|
||||||
|
NotFound: Извршувањето не е пронајдено
|
||||||
|
IncludeNotFound: Вклучете не е пронајден
|
||||||
|
NoTargets: Не се дефинирани цели
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Акција
|
action: Акција
|
||||||
@@ -551,8 +557,12 @@ AggregateTypes:
|
|||||||
quota: Квота
|
quota: Квота
|
||||||
feature: Карактеристика
|
feature: Карактеристика
|
||||||
target: Цел
|
target: Цел
|
||||||
|
execution: Извршување
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Комплет за извршување
|
||||||
|
removed: Извршувањето е избришано
|
||||||
target:
|
target:
|
||||||
added: Целта е избришана
|
added: Целта е избришана
|
||||||
changed: Целта е променета
|
changed: Целта е променета
|
||||||
|
@@ -540,6 +540,12 @@ Errors:
|
|||||||
NoTimeout: Doel heeft geen time-out
|
NoTimeout: Doel heeft geen time-out
|
||||||
InvalidURL: Doel heeft een ongeldige URL
|
InvalidURL: Doel heeft een ongeldige URL
|
||||||
NotFound: Doel niet gevonden
|
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:
|
AggregateTypes:
|
||||||
action: Actie
|
action: Actie
|
||||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
|||||||
quota: Quota
|
quota: Quota
|
||||||
feature: Functie
|
feature: Functie
|
||||||
target: Doel
|
target: Doel
|
||||||
|
execution: Executie
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Uitvoering ingesteld
|
||||||
|
removed: Uitvoering verwijderd
|
||||||
target:
|
target:
|
||||||
added: Doel gemaakt
|
added: Doel gemaakt
|
||||||
changed: Doel gewijzigd
|
changed: Doel gewijzigd
|
||||||
|
@@ -540,6 +540,12 @@ Errors:
|
|||||||
NoTimeout: Cel nie ma limitu czasu
|
NoTimeout: Cel nie ma limitu czasu
|
||||||
InvalidURL: Cel ma nieprawidłowy adres URL
|
InvalidURL: Cel ma nieprawidłowy adres URL
|
||||||
NotFound: Nie znaleziono celu
|
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:
|
AggregateTypes:
|
||||||
action: Działanie
|
action: Działanie
|
||||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
|||||||
quota: Limit
|
quota: Limit
|
||||||
feature: Funkcja
|
feature: Funkcja
|
||||||
target: Cel
|
target: Cel
|
||||||
|
execution: Wykonanie
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Zestaw wykonawczy
|
||||||
|
removed: Wykonanie usunięte
|
||||||
target:
|
target:
|
||||||
added: Cel został utworzony
|
added: Cel został utworzony
|
||||||
changed: Cel zmieniony
|
changed: Cel zmieniony
|
||||||
|
@@ -534,6 +534,12 @@ Errors:
|
|||||||
NoTimeout: O destino não tem tempo limite
|
NoTimeout: O destino não tem tempo limite
|
||||||
InvalidURL: O destino tem um URL inválido
|
InvalidURL: O destino tem um URL inválido
|
||||||
NotFound: Destino não encontrado
|
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:
|
AggregateTypes:
|
||||||
action: Ação
|
action: Ação
|
||||||
@@ -545,9 +551,13 @@ AggregateTypes:
|
|||||||
usergrant: Concessão de usuário
|
usergrant: Concessão de usuário
|
||||||
quota: Cota
|
quota: Cota
|
||||||
feature: Recurso
|
feature: Recurso
|
||||||
target: objetivo
|
target: Objetivo
|
||||||
|
execution: Execução
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Conjunto de execução
|
||||||
|
removed: Execução excluída
|
||||||
target:
|
target:
|
||||||
added: Destino criado
|
added: Destino criado
|
||||||
changed: Destino alterada
|
changed: Destino alterada
|
||||||
|
@@ -528,6 +528,12 @@ Errors:
|
|||||||
NoTimeout: У цели нет тайм-аута
|
NoTimeout: У цели нет тайм-аута
|
||||||
InvalidURL: Цель имеет неверный URL-адрес
|
InvalidURL: Цель имеет неверный URL-адрес
|
||||||
NotFound: Цель не найдена
|
NotFound: Цель не найдена
|
||||||
|
Execution:
|
||||||
|
ConditionInvalid: Недопустимое условие выполнения
|
||||||
|
Invalid: Исполнение недействительно
|
||||||
|
NotFound: Исполнение не найдено
|
||||||
|
IncludeNotFound: Включить не найдено
|
||||||
|
NoTargets: Цели не определены
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: Действие
|
action: Действие
|
||||||
@@ -540,8 +546,12 @@ AggregateTypes:
|
|||||||
quota: Квота
|
quota: Квота
|
||||||
feature: Особенность
|
feature: Особенность
|
||||||
target: мишень
|
target: мишень
|
||||||
|
execution: Исполнение
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: Набор исполнения
|
||||||
|
removed: Исполнение удалено
|
||||||
target:
|
target:
|
||||||
added: Цель создана
|
added: Цель создана
|
||||||
changed: Цель изменена
|
changed: Цель изменена
|
||||||
|
@@ -540,6 +540,12 @@ Errors:
|
|||||||
NoTimeout: 目标没有超时
|
NoTimeout: 目标没有超时
|
||||||
InvalidURL: 目标的 URL 无效
|
InvalidURL: 目标的 URL 无效
|
||||||
NotFound: 未找到目标
|
NotFound: 未找到目标
|
||||||
|
Execution:
|
||||||
|
ConditionInvalid: 执行条件无效
|
||||||
|
Invalid: 执行无效
|
||||||
|
NotFound: 未找到执行
|
||||||
|
IncludeNotFound: 包括未找到的内容
|
||||||
|
NoTargets: 没有定义目标
|
||||||
|
|
||||||
AggregateTypes:
|
AggregateTypes:
|
||||||
action: 动作
|
action: 动作
|
||||||
@@ -552,8 +558,12 @@ AggregateTypes:
|
|||||||
quota: 配额
|
quota: 配额
|
||||||
feature: 特征
|
feature: 特征
|
||||||
target: 靶
|
target: 靶
|
||||||
|
execution: 执行
|
||||||
|
|
||||||
EventTypes:
|
EventTypes:
|
||||||
|
execution:
|
||||||
|
set: 执行集
|
||||||
|
removed: 执行已删除
|
||||||
target:
|
target:
|
||||||
added: 目标已创建
|
added: 目标已创建
|
||||||
changed: 目标改变
|
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 "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
import "validate/validate.proto";
|
import "validate/validate.proto";
|
||||||
import "zitadel/execution/v3alpha/target.proto";
|
import "zitadel/execution/v3alpha/target.proto";
|
||||||
|
import "zitadel/execution/v3alpha/execution.proto";
|
||||||
import "zitadel/object/v2beta/object.proto";
|
import "zitadel/object/v2beta/object.proto";
|
||||||
import "zitadel/protoc_gen_zitadel/v2/options.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 {
|
message CreateTargetRequest {
|
||||||
@@ -291,3 +411,44 @@ message DeleteTargetResponse {
|
|||||||
// Details provide some base information (such as the last change date) of the target.
|
// Details provide some base information (such as the last change date) of the target.
|
||||||
zitadel.object.v2beta.Details details = 1;
|
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