Skip to content
Snippets Groups Projects
Commit 932eb6c5 authored by Pavel Antonov's avatar Pavel Antonov :asterisk:
Browse files

Merge branch 'feature/PRXS-2838-SetupRuntimeConfig' into 'master'

Обновление работы Setup

See merge request perxis/perxis-go!328
parents a2533d80 47a4a6f7
No related branches found
No related tags found
No related merge requests found
Showing
with 505 additions and 339 deletions
File moved
File moved
File moved
---
type: object
params:
inline: false
fields:
name:
title: Название
description: Название коллекции
type: string
params:
required: true
metadata:
collection_id: collection_a
collection_name: Коллекция A
---
type: object
params:
inline: false
fields:
name:
title: Название
description: Название коллекции
type: string
params:
required: true
metadata:
collection_id: collection_b
collection_name: Коллекция B
---
id: collection_c
name: Коллекция C
schema:
type: object
params:
inline: false
fields:
name:
title: Название
description: Название коллекции
type: string
params:
required: true
\ No newline at end of file
......@@ -47,7 +47,7 @@ func TestFromFS(t *testing.T) {
i3.ID = "item3"
assets := NewAssets[*testEntry]()
r, err := assets.FromFS(os.DirFS("assets/tests"))
r, err := assets.FromFS(os.DirFS("assets/tests/assets"))
require.NoError(t, err)
require.Len(t, r, 3)
assert.ElementsMatch(t, []*testEntry{i1, &i2, &i3}, r)
......@@ -74,12 +74,12 @@ func TestFrom(t *testing.T) {
i3.ID = "item3"
assets := NewAssets[*testEntry]()
r, err := assets.From(os.DirFS("assets"), "tests")
r, err := assets.From(os.DirFS("assets"), "tests/assets")
require.NoError(t, err)
require.Len(t, r, 3)
assert.ElementsMatch(t, []*testEntry{i1, &i2, &i3}, r)
r, err = assets.From(os.DirFS("assets"), "tests/items.yaml")
r, err = assets.From(os.DirFS("assets"), "tests/assets/items.yaml")
require.NoError(t, err)
require.Len(t, r, 2)
assert.Equal(t, []*testEntry{i1, &i2}, r)
......
......@@ -45,6 +45,11 @@ type TLS struct {
Key string `json:"key,omitempty"`
}
// GetID возвращает идентификатор клиента
func (c Client) GetID() string {
return c.ID
}
func (c *Client) SetDisabled(b bool) *Client {
c.Disabled = &b
return c
......
package collections
import (
"io/fs"
"strconv"
"time"
"git.perx.ru/perxis/perxis-go/pkg/optional"
"git.perx.ru/perxis/perxis-go/pkg/data"
"git.perx.ru/perxis/perxis-go/pkg/permission"
"git.perx.ru/perxis/perxis-go/pkg/schema"
)
......@@ -94,6 +92,33 @@ type Collection struct {
Config *Config `json:"-" bson:"-"`
}
// GetID возвращает идентификатор коллекции
func (c Collection) GetID() string {
return c.ID
}
// Equal сравнивает две коллекции, за исключением Schema, Access, StateInfo и Config
func (c Collection) Equal(other *Collection) bool {
if c.ID != other.ID ||
c.SpaceID != other.SpaceID ||
c.EnvID != other.EnvID ||
c.Name != other.Name ||
c.IsNoData() != other.IsNoData() ||
c.IsSingle() != other.IsSingle() ||
c.IsSystem() != other.IsSystem() ||
c.Hidden != other.Hidden ||
c.NoPublish != other.NoPublish ||
c.NoArchive != other.NoArchive ||
c.NoRevisions != other.NoRevisions ||
c.MaxRevisions != other.MaxRevisions ||
c.RevisionTTL != other.RevisionTTL ||
!c.View.Equal(other.View) ||
!data.ElementsMatch(c.Tags, other.Tags) {
return false
}
return true
}
type View struct {
SpaceID string `json:"space_id" bson:"space_id"` // SpaceID оригинальной коллекции
EnvID string `json:"environment_id" bson:"environment_id"` // EnvID оригинальной коллекции
......@@ -148,7 +173,6 @@ const (
)
func (c Collection) Clone() *Collection {
clone := &Collection{
ID: c.ID,
SpaceID: c.SpaceID,
......@@ -219,69 +243,3 @@ func GetCollectionsIDs(collections []*Collection) []string {
}
return res
}
func FromSchemaMetadata(schemas ...*schema.Schema) []*Collection {
result := make([]*Collection, 0, len(schemas))
for _, sch := range schemas {
coll := &Collection{Schema: sch}
coll.ID = sch.Metadata["collection_id"]
coll.Name = sch.Metadata["collection_name"]
if single, ok := sch.Metadata["collection_single"]; ok && single == "true" {
coll.Single = optional.True
}
if system, ok := sch.Metadata["collection_system"]; ok && system == "true" {
coll.System = optional.True
}
if nodata, ok := sch.Metadata["collection_nodata"]; ok && nodata == "true" {
coll.NoData = optional.True
}
if hidden, ok := sch.Metadata["collection_hidden"]; ok && hidden == "true" {
coll.Hidden = true
}
if disablePublishing, ok := sch.Metadata["collection_no_publish"]; ok && disablePublishing == "true" {
coll.NoPublish = true
}
if noArchive, ok := sch.Metadata["collection_no_archive"]; ok && noArchive == "true" {
coll.NoArchive = true
}
if noRevisions, ok := sch.Metadata["collection_no_revisions"]; ok && noRevisions == "true" {
coll.NoRevisions = true
}
if mr, ok := sch.Metadata["collection_max_revisions"]; ok {
if maxRevisions, err := strconv.ParseUint(mr, 10, 32); err == nil {
coll.MaxRevisions = uint32(maxRevisions)
}
}
if ttl, ok := sch.Metadata["collection_revisions_ttl"]; ok {
if revisionTTL, err := time.ParseDuration(ttl); err == nil {
coll.RevisionTTL = revisionTTL
}
}
if _, ok := sch.Metadata["collection_view_id"]; ok {
coll.View = &View{
SpaceID: sch.Metadata["collection_view_space"],
EnvID: sch.Metadata["collection_view_env"],
CollectionID: sch.Metadata["collection_view_id"],
Filter: sch.Metadata["collection_view_filter"],
}
}
result = append(result, coll)
}
return result
}
// TODO: использовать загрузку из файлов коллекций вместо файлов схем
func FromFS(filesystem fs.FS) ([]*Collection, error) {
schemas, err := schema.FromFS(filesystem)
if err != nil {
return nil, err
}
return FromSchemaMetadata(schemas...), nil
}
......@@ -3,9 +3,6 @@ package collections
import (
"testing"
"git.perx.ru/perxis/perxis-go/pkg/optional"
"git.perx.ru/perxis/perxis-go/pkg/schema"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
"github.com/stretchr/testify/require"
)
......@@ -103,106 +100,3 @@ func TestView_Equal(t *testing.T) {
})
}
}
func TestFromSchemaMetadata(t *testing.T) {
testCases := []struct {
name string
schemas []*schema.Schema
want []*Collection
}{
{
name: "Nil",
schemas: nil,
want: []*Collection{},
},
{
name: "Empty",
schemas: []*schema.Schema{},
want: []*Collection{},
},
{
name: "Without metadata",
schemas: []*schema.Schema{schema.New("a", field.String())},
want: []*Collection{{Schema: schema.New("a", field.String())}},
},
{
name: "With metadata",
schemas: []*schema.Schema{schema.New("a", field.String()).WithMetadata(
"collection_id", "collID",
"collection_name", "collName",
"collection_single", "true",
"collection_system", "true",
"collection_nodata", "true",
"collection_hidden", "true",
"collection_no_publish", "true",
"collection_no_archive", "true",
"collection_view_space", "viewSpaceID",
"collection_view_env", "viewEnvID",
"collection_view_id", "viewCollID",
"collection_view_filter", "viewFilter",
)},
want: []*Collection{{
ID: "collID",
Name: "collName",
Single: optional.True,
System: optional.True,
NoData: optional.True,
Hidden: true,
NoPublish: true,
NoArchive: true,
Schema: schema.New("a", field.String()).WithMetadata("collection_id", "collID", "collection_name", "collName", "collection_single", "true", "collection_system", "true", "collection_nodata", "true", "collection_hidden", "true", "collection_no_publish", "true", "collection_no_archive", "true", "collection_view_space", "viewSpaceID", "collection_view_env", "viewEnvID", "collection_view_id", "viewCollID", "collection_view_filter", "viewFilter"),
View: &View{
SpaceID: "viewSpaceID",
EnvID: "viewEnvID",
CollectionID: "viewCollID",
Filter: "viewFilter",
},
}},
},
{
name: "With metadata revisions settings",
schemas: []*schema.Schema{schema.New("a", field.String()).WithMetadata(
"collection_id", "collID",
"collection_name", "collName",
"collection_no_revisions", "true",
"collection_max_revisions", "10",
"collection_revisions_ttl", "1h",
)},
want: []*Collection{{
ID: "collID",
Name: "collName",
NoRevisions: true,
MaxRevisions: 10,
RevisionTTL: 3600000000000,
Schema: schema.New("a", field.String()).WithMetadata("collection_id", "collID", "collection_name", "collName", "collection_no_revisions", "true", "collection_max_revisions", "10", "collection_revisions_ttl", "1h"),
}},
},
{
name: "Multiple",
schemas: []*schema.Schema{
schema.New("a", field.String()).WithMetadata("collection_id", "collID"),
schema.New("b", field.String()).WithMetadata("collection_name", "collName"),
},
want: []*Collection{
{
ID: "collID",
Name: "",
Schema: schema.New("a", field.String()).WithMetadata("collection_id", "collID"),
},
{
ID: "",
Name: "collName",
Schema: schema.New("b", field.String()).WithMetadata("collection_name", "collName"),
},
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
result := FromSchemaMetadata(tt.schemas...)
require.Equal(t, tt.want, result)
})
}
}
package collections
import (
"reflect"
"strconv"
"time"
"git.perx.ru/perxis/perxis-go/pkg/optional"
"git.perx.ru/perxis/perxis-go/pkg/schema"
jsoniter "github.com/json-iterator/go"
)
// UnmarshalJSON implements json.Unmarshaler interface
func (c *Collection) UnmarshalJSON(b []byte) error {
type collection Collection
var cc, zero collection
// Пытаемся распарсить как коллекцию
if err := jsoniter.Unmarshal(b, &cc); err != nil {
return err
}
// Если это не пустая коллекция, то просто присваиваем
if !reflect.DeepEqual(cc, zero) {
*c = Collection(cc)
return nil
}
// Пытаемся распарсить как схему
var s schema.Schema
if err := jsoniter.Unmarshal(b, &s); err != nil {
return err
}
*c = *FromSchema(&s)
return nil
}
// FromSchema создает новую коллекцию из схемы
func FromSchema(sch *schema.Schema) *Collection {
if sch == nil {
return nil
}
coll := &Collection{Schema: sch}
coll.ID = sch.Metadata["collection_id"]
coll.Name = sch.Metadata["collection_name"]
if single, ok := sch.Metadata["collection_single"]; ok && single == "true" {
coll.Single = optional.True
}
if system, ok := sch.Metadata["collection_system"]; ok && system == "true" {
coll.System = optional.True
}
if nodata, ok := sch.Metadata["collection_nodata"]; ok && nodata == "true" {
coll.NoData = optional.True
}
if hidden, ok := sch.Metadata["collection_hidden"]; ok && hidden == "true" {
coll.Hidden = true
}
if disablePublishing, ok := sch.Metadata["collection_no_publish"]; ok && disablePublishing == "true" {
coll.NoPublish = true
}
if noArchive, ok := sch.Metadata["collection_no_archive"]; ok && noArchive == "true" {
coll.NoArchive = true
}
if noRevisions, ok := sch.Metadata["collection_no_revisions"]; ok && noRevisions == "true" {
coll.NoRevisions = true
}
if mr, ok := sch.Metadata["collection_max_revisions"]; ok {
if maxRevisions, err := strconv.ParseUint(mr, 10, 32); err == nil {
coll.MaxRevisions = uint32(maxRevisions)
}
}
if ttl, ok := sch.Metadata["collection_revisions_ttl"]; ok {
if revisionTTL, err := time.ParseDuration(ttl); err == nil {
coll.RevisionTTL = revisionTTL
}
}
if _, ok := sch.Metadata["collection_view_id"]; ok {
coll.View = &View{
SpaceID: sch.Metadata["collection_view_space"],
EnvID: sch.Metadata["collection_view_env"],
CollectionID: sch.Metadata["collection_view_id"],
Filter: sch.Metadata["collection_view_filter"],
}
}
return coll
}
package collections
import (
"testing"
"git.perx.ru/perxis/perxis-go/pkg/optional"
"git.perx.ru/perxis/perxis-go/pkg/schema"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
jsoniter "github.com/json-iterator/go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCollection_UnmarshalJSON(t *testing.T) {
sch := schema.New("a", field.String()).WithMetadata(
"collection_id", "collID",
"collection_name", "collName")
sch.ClearState()
tests := []struct {
name string
in any
expect *Collection
wantErr bool
}{
{name: "from schema",
in: sch,
expect: &Collection{Schema: sch, ID: "collID", Name: "collName"},
wantErr: false},
{name: "from collection",
in: &Collection{Schema: sch, ID: "id", Name: "name"},
expect: &Collection{Schema: sch, ID: "id", Name: "name"},
wantErr: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var c Collection
b, _ := jsoniter.Marshal(tt.in)
if err := jsoniter.Unmarshal(b, &c); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
assert.True(t, c.Equal(tt.expect))
assert.True(t, c.Schema.Equal(tt.expect.Schema))
})
}
}
func TestFromSchemaMetadata(t *testing.T) {
testCases := []struct {
name string
schema *schema.Schema
want *Collection
}{
{
name: "Nil",
schema: nil,
want: nil,
},
{
name: "Empty",
schema: &schema.Schema{},
want: &Collection{Schema: &schema.Schema{}},
},
{
name: "Without metadata",
schema: schema.New("a", field.String()),
want: &Collection{Schema: schema.New("a", field.String())},
},
{
name: "With metadata",
schema: schema.New("a", field.String()).WithMetadata(
"collection_id", "collID",
"collection_name", "collName",
"collection_single", "true",
"collection_system", "true",
"collection_nodata", "true",
"collection_hidden", "true",
"collection_no_publish", "true",
"collection_no_archive", "true",
"collection_view_space", "viewSpaceID",
"collection_view_env", "viewEnvID",
"collection_view_id", "viewCollID",
"collection_view_filter", "viewFilter",
),
want: &Collection{
ID: "collID",
Name: "collName",
Single: optional.True,
System: optional.True,
NoData: optional.True,
Hidden: true,
NoPublish: true,
NoArchive: true,
Schema: schema.New("a", field.String()).WithMetadata("collection_id", "collID", "collection_name", "collName", "collection_single", "true", "collection_system", "true", "collection_nodata", "true", "collection_hidden", "true", "collection_no_publish", "true", "collection_no_archive", "true", "collection_view_space", "viewSpaceID", "collection_view_env", "viewEnvID", "collection_view_id", "viewCollID", "collection_view_filter", "viewFilter"),
View: &View{
SpaceID: "viewSpaceID",
EnvID: "viewEnvID",
CollectionID: "viewCollID",
Filter: "viewFilter",
},
},
},
{
name: "With metadata revisions settings",
schema: schema.New("a", field.String()).WithMetadata(
"collection_id", "collID",
"collection_name", "collName",
"collection_no_revisions", "true",
"collection_max_revisions", "10",
"collection_revisions_ttl", "1h",
),
want: &Collection{
ID: "collID",
Name: "collName",
NoRevisions: true,
MaxRevisions: 10,
RevisionTTL: 3600000000000,
Schema: schema.New("a", field.String()).WithMetadata("collection_id", "collID", "collection_name", "collName", "collection_no_revisions", "true", "collection_max_revisions", "10", "collection_revisions_ttl", "1h"),
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
result := FromSchema(tt.schema)
require.Equal(t, tt.want, result)
})
}
}
......@@ -185,7 +185,13 @@ func mergeMaps(a, b map[string]interface{}) map[string]interface{} {
return out
}
// CloneSlice возвращает копию среза с клонированными элементами
func CloneSlice[T interface{ Clone() T }](s []T) []T {
// Если s == nil, то возвращаем nil
if s == nil {
return nil
}
result := make([]T, 0, len(s))
for _, t := range s {
result = append(result, t.Clone())
......
......@@ -3,6 +3,8 @@ package extension
import (
"context"
"go.uber.org/zap"
"git.perx.ru/perxis/perxis-go"
"git.perx.ru/perxis/perxis-go/pkg/collections"
"git.perx.ru/perxis/perxis-go/pkg/content"
......@@ -18,7 +20,6 @@ const (
StateInstalled = pb.State_INSTALLED
StateInProgress = pb.State_IN_PROGRESS
StateFail = pb.State_FAIL
MetadataKey = "extension"
)
......@@ -26,7 +27,6 @@ type (
InstallRequest = pb.InstallRequest
CheckRequest = pb.CheckRequest
UninstallRequest = pb.UninstallRequest
ExtensionDescriptor = pb.ExtensionDescriptor
State = pb.State
)
......@@ -34,12 +34,10 @@ type (
var (
ErrStart = errors.New("start failed")
ErrStop = errors.New("stop failed")
ErrInstall = errors.New("install failed")
ErrUpdate = errors.New("update failed")
ErrCheck = errors.New("check failed")
ErrUninstall = errors.New("uninstall failed")
ErrNotInstalled = errors.New("not installed")
ErrUnknownExtension = errors.New("unknown extension")
......@@ -79,7 +77,8 @@ func CheckInstalled(ctx context.Context, content *content.Content, spaceID, envI
return status.State == StateInstalled, nil
}
func isMetadataEqual(s1, s2 *schema.Schema) bool {
// isSameExtension возвращает true, если значение метаданных для ключа расширения совпадает
func isSameExtension(s1, s2 *schema.Schema) bool {
if s1.Metadata == nil && s2.Metadata == nil {
return true
}
......@@ -89,13 +88,16 @@ func isMetadataEqual(s1, s2 *schema.Schema) bool {
return s1.Metadata[MetadataKey] == s2.Metadata[MetadataKey]
}
// UpdateCollectionStrategy В дополнение к стратегии по умолчанию делает проверку, что обновляемая
// коллекция была установлена расширением. Если в метаданных схемы отсутствует специальный ключ `MetadataKey`,
// это означает, что коллекция была создана пользователем и отношения к расширению не имеет - вернется ошибка.
func UpdateCollectionStrategy(s *setup.Setup, exist, collection *collections.Collection) (*collections.Collection, bool, bool, error) {
if !s.IsForce() && !collection.IsView() && !exist.IsView() && !isMetadataEqual(collection.Schema, exist.Schema) {
return nil, false, false, errors.WithDetailf(collections.ErrAlreadyExists, "Коллекция с идентификатором '%s' "+
"уже существует. Удалите ее или вызовите установку расширения с флагом Force", collection.ID)
// UpdateExtensionCollections возвращает опцию для обновления коллекций расширения
func UpdateExtensionCollections() setup.CollectionOption {
return func(e *setup.Collection) {
next := e.UpdateFunc
e.UpdateFunc = func(s *setup.Setup, old, new *collections.Collection) (*collections.Collection, bool) {
if !s.IsForce() && !new.IsView() && !old.IsView() && !isSameExtension(new.Schema, old.Schema) {
s.Logger().Warn("Collection is already exists and not updated", zap.String("ID", new.ID))
return nil, false
}
return next(s, old, new)
}
}
return setup.DefaultUpdateCollectionStrategyFn(s, exist, collection)
}
......@@ -3,8 +3,11 @@ package extension
import (
"testing"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"git.perx.ru/perxis/perxis-go/pkg/collections"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/schema"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
"git.perx.ru/perxis/perxis-go/pkg/setup"
......@@ -45,69 +48,80 @@ func Test_isMetadataEqual(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, isMetadataEqual(tt.s1, tt.s2), "isMetadataExtensionEqual(%v, %v)", tt.s1, tt.s2)
assert.Equalf(t, tt.want, isSameExtension(tt.s1, tt.s2), "isMetadataExtensionEqual(%v, %v)", tt.s1, tt.s2)
})
}
}
func TestDefaultUpdateCollectionStrategyFn(t *testing.T) {
func TestDefaultUpdate_ExtensionCollections(t *testing.T) {
tests := []struct {
name string
exist *collections.Collection
collection *collections.Collection
force bool
wantErr func(err error)
update bool
wantLogs int
}{
{
name: "collection belongs to extension",
name: "Same extension",
exist: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
collection: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env", Name: "new name",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
wantErr: nil,
update: true,
},
{
name: "collection belongs to another extension",
name: "Other extension",
exist: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
collection: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env", Name: "new name",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-2")},
wantErr: func(err error) {
assert.ErrorIs(t, err, collections.ErrAlreadyExists)
assert.Equal(t, "Коллекция с идентификатором 'coll' уже существует. Удалите ее или "+
"вызовите установку расширения с флагом Force", errors.GetDetail(err))
update: false,
wantLogs: 1,
},
{
name: "Other extension force",
exist: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
collection: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env", Name: "new name",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-2")},
force: true,
update: true,
},
{
name: "collection was created by user",
name: "User collection",
exist: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env",
Schema: schema.New("name", field.String())},
collection: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env", Name: "new name",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
wantErr: func(err error) {
assert.ErrorIs(t, err, collections.ErrAlreadyExists)
assert.Equal(t, "Коллекция с идентификатором 'coll' уже существует. Удалите ее или "+
"вызовите установку расширения с флагом Force", errors.GetDetail(err))
},
update: false,
wantLogs: 1,
},
{
name: "collection was created by user with force",
name: "User collection force",
exist: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
Schema: schema.New("name", field.String())},
collection: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env", Name: "new name",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
force: true,
wantErr: nil,
update: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
set := setup.NewSetup(nil, "sp", "env", nil).WithForce(tt.force)
_, _, _, err := UpdateCollectionStrategy(set, tt.exist, tt.collection)
if tt.wantErr != nil {
tt.wantErr(err)
return
}
cfg := setup.NewConfig()
cfg.Collections.Add(tt.collection, UpdateExtensionCollections())
core, ob := observer.New(zap.DebugLevel)
logger := zap.New(core)
set := setup.NewSetup(nil, "sp", "env", logger).WithForce(tt.force)
_, update := cfg.Collections[0].UpdateFunc(set, tt.exist, tt.collection)
assert.Equal(t, tt.update, update)
logs := ob.TakeAll()
assert.Len(t, logs, tt.wantLogs)
})
}
}
......@@ -98,7 +98,7 @@ func (s *Extension) setupExtensionClient(set *setup.Setup, spaceID string) {
role := *s.role
role.SpaceID = spaceID
set.AddRole(&role, setup.DeleteRoleIfRemove())
set.AddRole(&role, setup.DeleteRoleIfRemoveFlag())
if s.client == nil {
s.client = &clients.Client{}
......@@ -130,13 +130,13 @@ func (s *Extension) setupExtensionClient(set *setup.Setup, spaceID string) {
}
}
set.AddClient(&client, setup.OverwriteClient(), setup.DeleteClientIfRemove())
set.AddClient(&client, setup.OverwriteClient(), setup.DeleteClientIfRemoveFlag())
}
func (s *Extension) GetSetup(spaceID, envID string) (*setup.Setup, error) {
set := s.setupFunc(spaceID, envID)
set.WithCollectionOptions(
func(c *collections.Collection) bool { return c.Schema != nil },
set.Config.Collections.WithOptions(
func(c *collections.Collection) bool { return true },
setup.AddMetadata(extension.MetadataKey, s.GetName()),
)
if set.HasErrors() {
......
......@@ -145,6 +145,11 @@ func NewItem(spaceID, envID, collID, id string, data map[string]interface{}, tra
}
}
// GetID возвращает идентификатор записи
func (i *Item) GetID() string {
return i.ID
}
func (i *Item) Clone() *Item {
itm := *i
itm.Data = data.CloneMap(i.Data)
......
......@@ -35,6 +35,11 @@ type Role struct {
AllowManagement bool `json:"allow_management" bson:"allow_management"`
}
// GetID возвращает идентификатор роли
func (r Role) GetID() string {
return r.ID
}
func (r Role) Clone() *Role {
return &Role{
ID: r.ID,
......
......@@ -123,6 +123,10 @@ func (f *Field) EnableState() {
}
func (f Field) GetType() Type {
if f.Params == nil {
return nil
}
return f.Params.Type()
}
......
......@@ -70,6 +70,10 @@ func (f *Field) UnmarshalJSON(b []byte) error {
}
func (f *Field) MarshalJSON() ([]byte, error) {
if f.Params == nil {
return nil, errors.New("field parameters is nil")
}
j := jsonField{
FieldData: FieldData(*f),
}
......
......@@ -4,8 +4,9 @@ import (
"context"
"strings"
"git.perx.ru/perxis/perxis-go/pkg/clients"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/clients"
"go.uber.org/zap"
)
......@@ -15,56 +16,32 @@ var (
ErrUninstallClients = errors.New("failed to uninstall clients")
)
type ClientsOption func(c *ClientConfig)
type UpdateClientFn func(s *Setup, exist, new *clients.Client) (*clients.Client, bool)
type DeleteClientFn func(s *Setup, client *clients.Client) bool
type ClientConfig struct {
client *clients.Client
UpdateFn UpdateClientFn
DeleteFn DeleteClientFn
}
func NewClientConfig(client *clients.Client, opt ...ClientsOption) ClientConfig {
c := ClientConfig{client: client}
UpdateExistingClient()(&c)
DeleteClientIfRemove()(&c)
for _, o := range opt {
o(&c)
}
return c
}
func OverwriteClient() ClientsOption {
return func(c *ClientConfig) {
c.UpdateFn = func(s *Setup, old, new *clients.Client) (*clients.Client, bool) { return new, true }
}
}
type (
Client = Entity[ClientConf, *clients.Client]
Clients = EntityList[ClientConf, *clients.Client]
ClientOption = EntityOption[ClientConf, *clients.Client]
ClientConf struct{}
)
func KeepExistingClient() ClientsOption {
return func(c *ClientConfig) {
c.UpdateFn = func(s *Setup, old, new *clients.Client) (*clients.Client, bool) { return old, false }
}
func (ClientConf) Init(e *Entity[ClientConf, *clients.Client]) {
UpdateExistingClient()(e)
DeleteClientIfRemoveFlag()(e)
}
func DeleteClient() ClientsOption {
return func(c *ClientConfig) {
c.DeleteFn = func(s *Setup, client *clients.Client) bool { return true }
}
}
var (
OverwriteClient = Overwrite[ClientConf, *clients.Client]
KeepExistingClient = Keep[ClientConf, *clients.Client]
DeleteClient = Delete[ClientConf, *clients.Client]
DeleteClientIfRemoveFlag = DeleteIfRemoveFlag[ClientConf, *clients.Client]
)
func DeleteClientIfRemove() ClientsOption {
return func(c *ClientConfig) {
c.DeleteFn = func(s *Setup, client *clients.Client) bool { return s.IsRemove() }
}
}
/////
func UpdateExistingClient() ClientsOption {
return func(c *ClientConfig) {
c.UpdateFn = func(s *Setup, exist, client *clients.Client) (*clients.Client, bool) {
// UpdateExistingClient обновляет существующий клиент, если он существует
// и не содержит необходимых данных
func UpdateExistingClient() ClientOption {
return func(c *Client) {
c.UpdateFunc = func(s *Setup, exist, client *clients.Client) (*clients.Client, bool) {
if exist.Name == "" {
exist.Name = client.Name
}
......@@ -93,61 +70,90 @@ func UpdateExistingClient() ClientsOption {
}
}
func (s *Setup) InstallClients(ctx context.Context) error {
if len(s.Clients) == 0 {
return nil
}
s.logger.Debug("Install clients", zap.String("Space ID", s.SpaceID))
//
// Client Setup
//
for _, c := range s.Clients {
err := s.InstallClient(ctx, c)
if err != nil {
s.logger.Error("Failed to install client", zap.String("Client ID", c.client.ID), zap.String("Client Name", c.client.Name), zap.Error(err))
return errors.WithDetailf(errors.Wrap(err, "failed to install client"), "Возникла ошибка при настройке клиента %s(%s)", c.client.Name, c.client.ID)
// AddClient добавляет требования к настройке элементов в пространстве
func (s *Setup) AddClient(client *clients.Client, opt ...ClientOption) *Setup {
s.Config.Clients.Add(client, opt...)
return s
}
}
return nil
// AddClients добавляет требования к настройке элементов в пространстве
func (s *Setup) AddClients(clients []*clients.Client, opt ...ClientOption) *Setup {
s.Config.Clients.AddMany(clients, opt...)
return s
}
func (s *Setup) InstallClient(ctx context.Context, c ClientConfig) error {
client := c.client
// InstallClient устанавливает клиент в пространстве
func (s *Setup) InstallClient(ctx context.Context, c *Client) error {
client := c.Value(s)
client.SpaceID = s.SpaceID
if s.IsForce() {
_ = s.content.Clients.Delete(ctx, s.SpaceID, c.client.ID)
_, err := s.content.Clients.Create(ctx, c.client)
_ = s.content.Clients.Delete(ctx, s.SpaceID, client.ID)
_, err := s.content.Clients.Create(ctx, client)
return err
}
exist, err := s.content.Clients.Get(ctx, s.SpaceID, c.client.ID)
exist, err := s.content.Clients.Get(ctx, s.SpaceID, client.ID)
if err != nil {
if !strings.Contains(err.Error(), clients.ErrNotFound.Error()) {
return err
}
_, err = s.content.Clients.Create(ctx, c.client)
_, err = s.content.Clients.Create(ctx, client)
return err
}
if client, upd := c.UpdateFn(s, exist, c.client); upd {
if client, upd := c.UpdateFunc(s, exist, client); upd {
return s.content.Clients.Update(ctx, client)
}
return nil
}
// InstallClients устанавливает все клиенты в пространстве
func (s *Setup) InstallClients(ctx context.Context) error {
if len(s.Clients) == 0 {
return nil
}
s.logger.Debug("Install clients", zap.String("Space ID", s.SpaceID))
for _, c := range s.Clients {
err := s.InstallClient(ctx, c)
if err != nil {
client := c.Value(s)
s.logger.Error("Failed to install client", zap.String("Client ID", client.ID), zap.String("Client Name", client.Name), zap.Error(err))
return errors.WithDetailf(errors.Wrap(err, "failed to install client"), "Возникла ошибка при настройке клиента %s(%s)", client.Name, client.ID)
}
}
return nil
}
// CheckClient проверяет наличие клиента в пространстве
func (s *Setup) CheckClient(ctx context.Context, c *Client) error {
client := c.Value(s)
_, err := s.content.Clients.Get(ctx, s.SpaceID, client.ID)
return err
}
// CheckClients проверяет наличие всех клиентов в пространстве
func (s *Setup) CheckClients(ctx context.Context) (err error) {
if len(s.Clients) == 0 {
return nil
}
var errs []error
s.logger.Debug("Check clients", zap.String("Space ID", s.SpaceID))
var errs []error
for _, c := range s.Clients {
err := s.CheckClient(ctx, c.client)
err := s.CheckClient(ctx, c)
if err != nil {
errs = append(errs, errors.WithDetailf(err, "Не найден клиент %s(%s)", c.client.Name, c.client.ID))
client := c.Value(s)
errs = append(errs, errors.WithDetailf(err, "Не найден клиент %s(%s)", client.Name, client.ID))
}
}
......@@ -158,11 +164,18 @@ func (s *Setup) CheckClients(ctx context.Context) (err error) {
return nil
}
func (s *Setup) CheckClient(ctx context.Context, client *clients.Client) error {
_, err := s.content.Clients.Get(ctx, s.SpaceID, client.ID)
// UninstallClient удаляет клиент из пространства
func (s *Setup) UninstallClient(ctx context.Context, c *Client) error {
client := c.Value(s)
if c.DeleteFunc(s, client) {
if err := s.content.Clients.Delete(ctx, s.SpaceID, client.ID); err != nil && !strings.Contains(err.Error(), clients.ErrNotFound.Error()) {
return err
}
}
return nil
}
// UninstallClients удаляет все клиенты из пространства
func (s *Setup) UninstallClients(ctx context.Context) error {
if len(s.Clients) == 0 {
return nil
......@@ -172,19 +185,13 @@ func (s *Setup) UninstallClients(ctx context.Context) error {
for _, c := range s.Clients {
if err := s.UninstallClient(ctx, c); err != nil {
s.logger.Error("Failed to uninstall client", zap.String("Client ID", c.client.ID), zap.String("Client Name", c.client.Name), zap.Error(err))
return errors.WithDetailf(errors.Wrap(err, "failed to uninstall client"), "Возникла ошибка при удалении клиента %s(%s)", c.client.Name, c.client.ID)
client := c.Value(s)
s.logger.Error("Failed to uninstall client", zap.String("Client ID", client.ID),
zap.String("Client Name", client.Name), zap.Error(err))
return errors.WithDetailf(errors.Wrap(err, "failed to uninstall client"),
"Возникла ошибка при удалении клиента %s(%s)", client.Name, client.ID)
}
}
return nil
}
func (s *Setup) UninstallClient(ctx context.Context, c ClientConfig) error {
if c.DeleteFn(s, c.client) {
if err := s.content.Clients.Delete(ctx, s.SpaceID, c.client.ID); err != nil && !strings.Contains(err.Error(), clients.ErrNotFound.Error()) {
return err
}
}
return nil
}
package setup
import (
"context"
"testing"
"git.perx.ru/perxis/perxis-go/pkg/clients"
clientsMock "git.perx.ru/perxis/perxis-go/pkg/clients/mocks"
"git.perx.ru/perxis/perxis-go/pkg/content"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
......@@ -89,9 +87,9 @@ func TestSetup_InstallClients(t *testing.T) {
tt.clientsCall(c)
}
s := NewSetup(&content.Content{Clients: c}, "sp", "env", nil)
s.AddClients(tt.clients)
tt.wantErr(t, s.InstallClients(context.Background()))
//s := NewSetup(&content.Content{Clients: c}, "sp", "env", nil)
//s.AddClients(tt.clients)
//tt.wantErr(t, s.InstallClients(context.Background()))
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment