feat(system api): list instances by domains (#6806)

Allow to list instances by their domains on the system API.

closes #6785
This commit is contained in:
Tim Möhlmann 2023-10-25 22:20:12 +03:00 committed by GitHub
parent b9061ffadc
commit ad26ca88d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 2 deletions

View File

@ -63,6 +63,8 @@ func InstanceQueryToModel(searchQuery *instance_pb.Query) (query.SearchQuery, er
switch q := searchQuery.Query.(type) { switch q := searchQuery.Query.(type) {
case *instance_pb.Query_IdQuery: case *instance_pb.Query_IdQuery:
return query.NewInstanceIDsListSearchQuery(q.IdQuery.Ids...) return query.NewInstanceIDsListSearchQuery(q.IdQuery.Ids...)
case *instance_pb.Query_DomainQuery:
return query.NewInstanceDomainsListSearchQuery(q.DomainQuery.Domains...)
default: default:
return nil, errors.ThrowInvalidArgument(nil, "INST-3m0se", "List.Query.Invalid") return nil, errors.ThrowInvalidArgument(nil, "INST-3m0se", "List.Query.Invalid")
} }

View File

@ -0,0 +1,109 @@
//go:build integration
package system_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/pkg/grpc/instance"
"github.com/zitadel/zitadel/pkg/grpc/object"
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
)
func TestServer_ListInstances(t *testing.T) {
domain, instanceID, _ := Tester.UseIsolatedInstance(CTX, SystemCTX)
tests := []struct {
name string
req *system_pb.ListInstancesRequest
want []*instance.Instance
wantErr bool
}{
{
name: "empty query error",
req: &system_pb.ListInstancesRequest{
Queries: []*instance.Query{{}},
},
wantErr: true,
},
{
name: "non-existing id",
req: &system_pb.ListInstancesRequest{
Queries: []*instance.Query{{
Query: &instance.Query_IdQuery{
IdQuery: &instance.IdsQuery{
Ids: []string{"foo"},
},
},
}},
},
want: []*instance.Instance{},
},
{
name: "get 1 by id",
req: &system_pb.ListInstancesRequest{
Query: &object.ListQuery{
Limit: 1,
},
Queries: []*instance.Query{{
Query: &instance.Query_IdQuery{
IdQuery: &instance.IdsQuery{
Ids: []string{instanceID},
},
},
}},
},
want: []*instance.Instance{{
Id: instanceID,
}},
},
{
name: "non-existing domain",
req: &system_pb.ListInstancesRequest{
Queries: []*instance.Query{{
Query: &instance.Query_DomainQuery{
DomainQuery: &instance.DomainsQuery{
Domains: []string{"foo"},
},
},
}},
},
want: []*instance.Instance{},
},
{
name: "get 1 by domain",
req: &system_pb.ListInstancesRequest{
Query: &object.ListQuery{
Limit: 1,
},
Queries: []*instance.Query{{
Query: &instance.Query_DomainQuery{
DomainQuery: &instance.DomainsQuery{
Domains: []string{domain},
},
},
}},
},
want: []*instance.Instance{{
Id: instanceID,
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := Tester.Client.System.ListInstances(SystemCTX, tt.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
got := resp.GetResult()
assert.Len(t, got, len(tt.want))
for i := 0; i < len(tt.want); i++ {
assert.Equalf(t, tt.want[i].GetId(), got[i].GetId(), "instance[%d] id", i)
}
})
}
}

View File

@ -150,6 +150,15 @@ func NewInstanceIDsListSearchQuery(ids ...string) (SearchQuery, error) {
return NewListQuery(InstanceColumnID, list, ListIn) return NewListQuery(InstanceColumnID, list, ListIn)
} }
func NewInstanceDomainsListSearchQuery(domains ...string) (SearchQuery, error) {
list := make([]interface{}, len(domains))
for i, value := range domains {
list[i] = value
}
return NewListQuery(InstanceDomainDomainCol, list, ListIn)
}
func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
query = q.SearchRequest.toQuery(query) query = q.SearchRequest.toQuery(query)
for _, q := range q.Queries { for _, q := range q.Queries {
@ -280,7 +289,8 @@ func prepareInstancesQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
return sq.Select( return sq.Select(
InstanceColumnID.identifier(), InstanceColumnID.identifier(),
countColumn.identifier(), countColumn.identifier(),
).From(instanceTable.identifier()), ).From(instanceTable.identifier()).
LeftJoin(join(InstanceDomainInstanceIDCol, InstanceColumnID)),
func(builder sq.SelectBuilder) sq.SelectBuilder { func(builder sq.SelectBuilder) sq.SelectBuilder {
return sq.Select( return sq.Select(
instanceFilterCountColumn, instanceFilterCountColumn,

View File

@ -55,7 +55,8 @@ var (
` projections.instance_domains.creation_date,` + ` projections.instance_domains.creation_date,` +
` projections.instance_domains.change_date, ` + ` projections.instance_domains.change_date, ` +
` projections.instance_domains.sequence` + ` projections.instance_domains.sequence` +
` FROM (SELECT projections.instances.id, COUNT(*) OVER () FROM projections.instances) AS f` + ` FROM (SELECT projections.instances.id, COUNT(*) OVER () FROM projections.instances` +
` LEFT JOIN projections.instance_domains ON projections.instances.id = projections.instance_domains.instance_id) AS f` +
` LEFT JOIN projections.instances ON f.id = projections.instances.id` + ` LEFT JOIN projections.instances ON f.id = projections.instances.id` +
` LEFT JOIN projections.instance_domains ON f.id = projections.instance_domains.instance_id` + ` LEFT JOIN projections.instance_domains ON f.id = projections.instance_domains.instance_id` +
` AS OF SYSTEM TIME '-1 ms'` ` AS OF SYSTEM TIME '-1 ms'`

View File

@ -71,6 +71,7 @@ message Query {
option (validate.required) = true; option (validate.required) = true;
IdsQuery id_query = 1; IdsQuery id_query = 1;
DomainsQuery domain_query = 2;
} }
} }
@ -83,6 +84,17 @@ message IdsQuery {
]; ];
} }
message DomainsQuery {
repeated string domains = 1 [
(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
max_items: 20,
example: "[\"my-instace.zitadel.cloud\", \"auth.custom.com\"]";
description: "Return the instances that have the requested domains";
}
];
}
enum FieldName { enum FieldName {
FIELD_NAME_UNSPECIFIED = 0; FIELD_NAME_UNSPECIFIED = 0;
FIELD_NAME_ID = 1; FIELD_NAME_ID = 1;