feat(v3alpha): read actions (#8357)

# Which Problems Are Solved

The current v3alpha actions APIs don't exactly adhere to the [new
resources API
design](https://zitadel.com/docs/apis/v3#standard-resources).

# How the Problems Are Solved

- **Improved ID access**: The aggregate ID is added to the resource
details object, so accessing resource IDs and constructing proto
messages for resources is easier
- **Explicit Instances**: Optionally, the instance can be explicitly
given in each request
- **Pagination**: A default search limit and a max search limit are
added to the defaults.yaml. They apply to the new v3 APIs (currently
only actions). The search query defaults are changed to ascending by
creation date, because this makes the pagination results the most
deterministic. The creation date is also added to the object details.
The bug with updated creation dates is fixed for executions and targets.
- **Removed Sequences**: Removed Sequence from object details and
ProcessedSequence from search details

# Additional Changes

Object details IDs are checked in unit test only if an empty ID is
expected. Centralizing the details check also makes this internal object
more flexible for future evolutions.

# Additional Context

- Closes #8169 
- Depends on https://github.com/zitadel/zitadel/pull/8225

---------

Co-authored-by: Silvan <silvan.reusser@gmail.com>
Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
Elio Bischof
2024-08-12 22:32:01 +02:00
committed by GitHub
parent 18c3f574a9
commit 042c438813
130 changed files with 3253 additions and 605 deletions

View File

@@ -20,3 +20,10 @@ message Owner {
string id = 2;
}
message Instance {
oneof property {
option (validate.required) = true;
string id = 1;
string domain = 2;
}
}

View File

@@ -10,10 +10,13 @@ import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "zitadel/protoc_gen_zitadel/v2/options.proto";
import "zitadel/resources/action/v3alpha/target.proto";
import "zitadel/resources/action/v3alpha/execution.proto";
import "zitadel/resources/action/v3alpha/query.proto";
import "zitadel/resources/object/v3alpha/object.proto";
import "zitadel/settings/object/v3alpha/object.proto";
import "zitadel/object/v3alpha/object.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha;action";
@@ -44,7 +47,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
consumes: "application/grpc-web+proto";
produces: "application/grpc-web+proto";
host: "$ZITADEL_DOMAIN";
host: "${ZITADEL_DOMAIN}";
base_path: "/resources/v3alpha/actions";
external_docs: {
@@ -57,8 +60,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
value: {
type: TYPE_OAUTH2;
flow: FLOW_ACCESS_CODE;
authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize";
token_url: "$CUSTOM-DOMAIN/oauth/v2/token";
authorization_url: "${ZITADEL_DOMAIN}/oauth/v2/authorize";
token_url: "${ZITADEL_DOMAIN}/oauth/v2/token";
scopes: {
scope: {
key: "openid";
@@ -189,6 +192,67 @@ service ZITADELActions {
};
}
// Target by ID
//
// Returns the target identified by the requested ID.
rpc GetTarget (GetTargetRequest) returns (GetTargetResponse) {
option (google.api.http) = {
get: "/targets/{id}"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "action.target.read"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200"
value: {
description: "Target successfully retrieved";
}
};
};
}
// Search targets
//
// Search all matching targets. By default all targets of the instance are returned.
// Make sure to include a limit and sorting for pagination.
rpc SearchTargets (SearchTargetsRequest) returns (SearchTargetsResponse) {
option (google.api.http) = {
post: "/targets/_search",
body: "filters"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "action.target.read"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "A list of all targets matching the query";
};
};
responses: {
key: "400";
value: {
description: "invalid list query";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
};
};
};
};
};
}
// Sets an execution to call a target or include the targets of another execution.
//
// Setting an empty list of targets will remove all targets from the execution, making it a noop.
@@ -222,6 +286,43 @@ service ZITADELActions {
};
}
// Search executions
//
// Search all matching executions. By default all executions of the instance are returned that have at least one execution target.
// Make sure to include a limit and sorting for pagination.
rpc SearchExecutions (SearchExecutionsRequest) returns (SearchExecutionsResponse) {
option (google.api.http) = {
post: "/executions/_search"
body: "filters"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "action.execution.read"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "A list of all non noop executions matching the query";
};
};
responses: {
key: "400";
value: {
description: "invalid list query";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
};
};
};
};
};
}
// List all available functions
//
// List all available functions which can be used as condition for executions.
@@ -294,7 +395,16 @@ service ZITADELActions {
}
message CreateTargetRequest {
Target target = 1;
optional zitadel.object.v3alpha.Instance instance = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
default: "\"domain from HOST or :authority header\""
}
];
Target target = 2 [
(validate.rules).message = {
required: true
}
];
}
message CreateTargetResponse {
@@ -302,16 +412,24 @@ message CreateTargetResponse {
}
message PatchTargetRequest {
string id = 1 [
optional zitadel.object.v3alpha.Instance instance = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
default: "\"domain from HOST or :authority header\""
}
];
string id = 2 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1,
max_length: 200,
example: "\"69629026806489455\"";
}
];
PatchTarget target = 2;
PatchTarget target = 3 [
(validate.rules).message = {
required: true
}
];
}
message PatchTargetResponse {
@@ -319,9 +437,13 @@ message PatchTargetResponse {
}
message DeleteTargetRequest {
string id = 1 [
optional zitadel.object.v3alpha.Instance instance = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
default: "\"domain from HOST or :authority header\""
}
];
string id = 2 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1,
max_length: 200,
@@ -334,13 +456,84 @@ message DeleteTargetResponse {
zitadel.resources.object.v3alpha.Details details = 1;
}
message GetTargetRequest {
optional zitadel.object.v3alpha.Instance instance = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
default: "\"domain from HOST or :authority header\""
}
];
string id = 2 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1,
max_length: 200,
example: "\"69629026806489455\"";
}
];
}
message GetTargetResponse {
GetTarget target = 1;
}
message SearchTargetsRequest {
optional zitadel.object.v3alpha.Instance instance = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
default: "\"domain from HOST or :authority header\""
}
];
// list limitations and ordering.
optional zitadel.resources.object.v3alpha.SearchQuery query = 2;
// The field the result is sorted by. The default is the creation date. Beware that if you change this, your result pagination might be inconsistent.
optional TargetFieldName sorting_column = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
default: "\"TARGET_FIELD_NAME_CREATION_DATE\""
}
];
// Define the criteria to query for.
repeated TargetSearchFilter filters = 4;
}
message SearchTargetsResponse {
zitadel.resources.object.v3alpha.ListDetails details = 1;
repeated GetTarget result = 2;
}
message SetExecutionRequest {
Condition condition = 1;
Execution execution = 2;
optional zitadel.object.v3alpha.Instance instance = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
default: "\"domain from HOST or :authority header\""
}
];
Condition condition = 2;
Execution execution = 3;
}
message SetExecutionResponse {
zitadel.settings.object.v3alpha.Details details = 1;
zitadel.resources.object.v3alpha.Details details = 1;
}
message SearchExecutionsRequest {
optional zitadel.object.v3alpha.Instance instance = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
default: "\"domain from HOST or :authority header\""
}
];
// list limitations and ordering.
optional zitadel.resources.object.v3alpha.SearchQuery query = 2;
// The field the result is sorted by. The default is the creation date. Beware that if you change this, your result pagination might be inconsistent.
optional ExecutionFieldName sorting_column = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
default: "\"EXECUTION_FIELD_NAME_CREATION_DATE\""
}
];
// Define the criteria to query for.
repeated ExecutionSearchFilter filters = 4;
}
message SearchExecutionsResponse {
zitadel.resources.object.v3alpha.ListDetails details = 1;
repeated GetExecution result = 2;
}
message ListExecutionFunctionsRequest{}

View File

@@ -21,6 +21,12 @@ message Execution {
repeated ExecutionTargetType targets = 1;
}
message GetExecution {
zitadel.resources.object.v3alpha.Details details = 1;
Condition condition = 2;
Execution execution = 3;
}
message ExecutionTargetType {
oneof type {
option (validate.required) = true;

View File

@@ -0,0 +1,117 @@
syntax = "proto3";
package zitadel.resources.action.v3alpha;
option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha;action";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "zitadel/resources/object/v3alpha/object.proto";
import "zitadel/resources/action/v3alpha/execution.proto";
message ExecutionSearchFilter {
oneof filter {
option (validate.required) = true;
InConditionsFilter in_conditions_filter = 1;
ExecutionTypeFilter execution_type_filter = 2;
TargetFilter target_filter = 3;
IncludeFilter include_filter = 4;
}
}
message InConditionsFilter {
// Defines the conditions to query for.
repeated Condition conditions = 1;
}
message ExecutionTypeFilter {
// Defines the type to query for.
ExecutionType execution_type = 1;
}
message TargetFilter {
// Defines the id to query for.
string target_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the id of the targets to include"
example: "\"69629023906488334\"";
}
];
}
message IncludeFilter {
// Defines the include to query for.
Condition include = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the id of the include"
example: "\"request.zitadel.session.v2.SessionService\"";
}
];
}
message TargetSearchFilter {
oneof filter {
option (validate.required) = true;
TargetNameFilter target_name_filter = 1;
InTargetIDsFilter in_target_ids_filter = 2;
}
}
message TargetNameFilter {
// Defines the name of the target to query for.
string target_name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
max_length: 200;
example: "\"ip_allow_list\"";
}
];
// Defines which text comparison method used for the name query.
zitadel.resources.object.v3alpha.TextFilterMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
message InTargetIDsFilter {
// Defines the ids to query for.
repeated string target_ids = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the ids of the targets to include"
example: "[\"69629023906488334\",\"69622366012355662\"]";
}
];
}
enum ExecutionType {
EXECUTION_TYPE_UNSPECIFIED = 0;
EXECUTION_TYPE_REQUEST = 1;
EXECUTION_TYPE_RESPONSE = 2;
EXECUTION_TYPE_EVENT = 3;
EXECUTION_TYPE_FUNCTION = 4;
}
enum TargetFieldName {
TARGET_FIELD_NAME_UNSPECIFIED = 0;
TARGET_FIELD_NAME_ID = 1;
TARGET_FIELD_NAME_CREATED_DATE = 2;
TARGET_FIELD_NAME_CHANGED_DATE = 3;
TARGET_FIELD_NAME_NAME = 4;
TARGET_FIELD_NAME_TARGET_TYPE = 5;
TARGET_FIELD_NAME_URL = 6;
TARGET_FIELD_NAME_TIMEOUT = 7;
TARGET_FIELD_NAME_INTERRUPT_ON_ERROR = 8;
}
enum ExecutionFieldName {
EXECUTION_FIELD_NAME_UNSPECIFIED = 0;
EXECUTION_FIELD_NAME_ID = 1;
EXECUTION_FIELD_NAME_CREATED_DATE = 2;
EXECUTION_FIELD_NAME_CHANGED_DATE = 3;
}

View File

@@ -10,12 +10,17 @@ import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "zitadel/protoc_gen_zitadel/v2/options.proto";
import "zitadel/resources/object/v3alpha/object.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha;action";
message Target {
string name = 1 [
(validate.rules).string = {min_len: 1, max_len: 1000},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"ip_allow_list\"";
min_length: 1
max_length: 1000
}
];
// Defines the target type and how the response of the target is treated.
@@ -27,21 +32,34 @@ message Target {
}
// Timeout defines the duration until ZITADEL cancels the execution.
google.protobuf.Duration timeout = 5 [
(validate.rules).duration = {gte: {}, lte: {seconds: 270}},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "if the target doesn't respond before this timeout expires, the the connection is closed and the action fails";
example: "\"10s\"";
}
];
string endpoint = 6 [
(validate.rules).string = {min_len: 1, max_len: 1000},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://example.com/hooks/ip_check\"";
example: "\"https://example.com/hooks/ip_check\""
min_length: 1
max_length: 1000
}
];
}
message GetTarget {
zitadel.resources.object.v3alpha.Details details = 1;
Target config = 2;
}
message PatchTarget {
optional string name = 1 [
(validate.rules).string = {min_len: 1, max_len: 1000},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"ip_allow_list\"";
example: "\"ip_allow_list\""
min_length: 1
max_length: 1000
}
];
// Defines the target type and how the response of the target is treated.
@@ -52,13 +70,18 @@ message PatchTarget {
}
// Timeout defines the duration until ZITADEL cancels the execution.
optional google.protobuf.Duration timeout = 5 [
(validate.rules).duration = {gte: {}, lte: {seconds: 270}},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "if the target doesn't respond before this timeout expires, the the connection is closed and the action fails";
example: "\"10s\"";
}
];
optional string endpoint = 6 [
(validate.rules).string = {min_len: 1, max_len: 1000},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://example.com/hooks/ip_check\"";
example: "\"https://example.com/hooks/ip_check\""
min_length: 1
max_length: 1000
}
];
}

View File

@@ -17,27 +17,66 @@ message Details {
example: "\"69629012906488334\"";
}
];
//sequence represents the order of events. It's always counting
//
// on read: the sequence of the last event reduced by the projection
//
// on manipulation: the timestamp of the event(s) added by the manipulation
uint64 sequence = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2\"";
}
];
//change_date is the timestamp when the object was changed
//
// on read: the timestamp of the last event reduced by the projection
//
// on manipulation: the timestamp of the event(s) added by the manipulation
google.protobuf.Timestamp change_date = 3;
//resource_owner represents the context an object belongs to
zitadel.object.v3alpha.Owner owner = 4 [
//the timestamp of the first event applied to the object.
google.protobuf.Timestamp created = 3;
//the timestamp of the last event applied to the object.
google.protobuf.Timestamp changed = 4;
//the parent object representing the returned objects context.
zitadel.object.v3alpha.Owner owner = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\"";
}
];
}
}
message SearchQuery {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
json_schema: {
title: "General List Query"
description: "Object unspecific list filters like offset, limit and asc/desc."
}
};
uint64 offset = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"0\"";
}
];
uint32 limit = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "100";
description: "Maximum amount of events returned. If not configured otherwise, the default is 100, the maximum is 1000. If the limit exceeds the maximum, ZITADEL throws an error.";
}
];
// If desc is true, the result is sorted by in descending order. Beware that if desc is true or the sorting column is not the creation date, the pagination results might be inconsistent.
bool desc = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "default is ascending, because together with the creation date sorting column, this returns the most deterministic pagination results.";
}
];
}
message ListDetails {
uint64 applied_limit = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "100";
}
];
uint64 total_result = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2\"";
}
];
google.protobuf.Timestamp timestamp = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the last time the projection got updated"
}
];
}
enum TextFilterMethod {
TEXT_FILTER_METHOD_EQUALS = 0;
TEXT_FILTER_METHOD_EQUALS_IGNORE_CASE = 1;
TEXT_FILTER_METHOD_STARTS_WITH = 2;
TEXT_FILTER_METHOD_STARTS_WITH_IGNORE_CASE = 3;
TEXT_FILTER_METHOD_CONTAINS = 4;
}