diff --git a/pkg/extension/extension.go b/pkg/extension/extension.go index 26d9b6d51171307161e68ceef0830857fbb569a8..fb8044c7a2f4c76b775840c5628319196a0493ae 100644 --- a/pkg/extension/extension.go +++ b/pkg/extension/extension.go @@ -21,6 +21,8 @@ const ( StateInstalled = pb.SpaceExtensions_INSTALLED StateInProgress = pb.SpaceExtensions_IN_PROGRESS StateFail = pb.SpaceExtensions_FAIL + + ExtensionMetadataKey = "extension" ) type ( diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index e0eca6b6ffc02511acbe3712b2f2bf83fc5bc05e..c468ee99f156b3acf09435474e23c5427702b2e7 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -12,7 +12,8 @@ import ( type Schema struct { field.Field - Loaded bool `json:"loaded"` + Loaded bool `json:"loaded"` + Metadata map[string]string `json:"metadata"` } func New(kv ...interface{}) *Schema { @@ -33,8 +34,9 @@ var ( func (s *Schema) Clone(reset bool) *Schema { return &Schema{ - Field: *s.Field.Clone(reset), - Loaded: s.Loaded, + Field: *s.Field.Clone(reset), + Loaded: s.Loaded, + Metadata: s.Metadata, } } @@ -43,6 +45,21 @@ func (s Schema) WithIncludes(includes ...interface{}) *Schema { return &s } +func (s *Schema) WithMetadata(kv ...string) *Schema { + if s.Metadata == nil { + s.Metadata = make(map[string]string) + } + for i := 0; i < len(kv); i += 2 { + s.Metadata[kv[i]] = kv[i+1] + } + return s +} + +func (s Schema) SetMetadata(md map[string]string) *Schema { + s.Metadata = md + return &s +} + func (s *Schema) Load(ctx context.Context) error { if s.Loaded { return nil diff --git a/pkg/schema/schema_json.go b/pkg/schema/schema_json.go index 906acb5d0d361611d01f43d10bd0f80377ccb4a7..72b4ac25bb78c6d8cb868bd7018e81acf2d2080c 100644 --- a/pkg/schema/schema_json.go +++ b/pkg/schema/schema_json.go @@ -6,8 +6,8 @@ import ( ) type jsonSchema struct { - //Field json.RawMessage `json:"field,inline"` - Loaded bool `json:"loaded"` + Loaded bool `json:"loaded"` + Metadata map[string]string `json:"metadata"` } func (s *Schema) UnmarshalJSON(b []byte) error { @@ -17,20 +17,12 @@ func (s *Schema) UnmarshalJSON(b []byte) error { return errors.Wrapf(err, "error unmarshal json into field") } s.Loaded = j.Loaded + s.Metadata = j.Metadata if err := s.Field.UnmarshalJSON(b); err != nil { return err } - //if len(j.Field) > 0 { - // if err := s.Field.UnmarshalJSON(j.Field); err != nil { - // return err - // } - // //if err := jsoniter.Unmarshal(j.Field, &s.Field); err != nil { - // // return err - // //} - //} - return nil } @@ -42,8 +34,8 @@ func (s *Schema) MarshalJSON() ([]byte, error) { } jsonSch, err := jsoniter.Marshal(jsonSchema{ - //Field: b, - Loaded: s.Loaded, + Loaded: s.Loaded, + Metadata: s.Metadata, }) if err != nil { return nil, err diff --git a/pkg/schema/test/object_test.go b/pkg/schema/test/object_test.go index e5af975cdb9ed8e86044e5d357530dd2420afee9..9ba618794f122ed8c5551324b02d6b8cf9e46829 100644 --- a/pkg/schema/test/object_test.go +++ b/pkg/schema/test/object_test.go @@ -110,6 +110,9 @@ func TestSchema_JSON(t *testing.T) { "evaluatedField", field.String(modify.Value("stringField2 + '_' ")), ) sch.Loaded = true + sch.Metadata = map[string]string{ + "extension": "test-extension", + } b, err := json.MarshalIndent(sch, "", " ") require.NoError(t, err) diff --git a/pkg/setup/collection.go b/pkg/setup/collection.go index d61ba14d18f2d8bc93d2a535392b8b608ba6e2d0..1ebd0a9933e72ab85622d39e86bf06e6421d8975 100644 --- a/pkg/setup/collection.go +++ b/pkg/setup/collection.go @@ -8,13 +8,15 @@ import ( "git.perx.ru/perxis/perxis-go/pkg/collections" "git.perx.ru/perxis/perxis-go/pkg/environments" "git.perx.ru/perxis/perxis-go/pkg/errors" + "git.perx.ru/perxis/perxis-go/pkg/extension" "go.uber.org/zap" ) var ( - ErrCheckCollections = errors.New("collections check error") - ErrInstallCollections = errors.New("failed to install collections") - ErrUninstallCollections = errors.New("failed to uninstall collections") + ErrCheckCollections = errors.New("collections check error") + ErrInstallCollections = errors.New("failed to install collections") + ErrUninstallCollections = errors.New("failed to uninstall collections") + ErrCollectionAlreadyExists = errors.New("failed to uninstall collections") ) type CollectionsOption func(c *CollectionConfig) @@ -23,6 +25,7 @@ type DeleteCollectionFn func(s *Setup, col *collections.Collection) bool type CollectionConfig struct { collection *collections.Collection + metadata map[string]string UpdateFn UpdateCollectionFn DeleteFn DeleteCollectionFn } @@ -80,6 +83,17 @@ func UpdateExistingCollection() CollectionsOption { } } +func SetSchemaMetadata(kv ...string) CollectionsOption { + return func(c *CollectionConfig) { + if c.metadata == nil { + c.metadata = make(map[string]string) + } + for i := 0; i < len(kv); i += 2 { + c.metadata[kv[i]] = kv[i+1] + } + } +} + func (s *Setup) InstallCollections(ctx context.Context) (err error) { if len(s.Collections) == 0 { return nil @@ -125,19 +139,31 @@ func (s *Setup) InstallCollection(ctx context.Context, c CollectionConfig) (setS collection.SpaceID, collection.EnvID = s.SpaceID, s.EnvironmentID var exist *collections.Collection - // isForce - не удалять коллекцию, если она уже существует + exist, err = s.content.Collections.Get(ctx, collection.SpaceID, collection.EnvID, collection.ID) if err != nil && !strings.Contains(err.Error(), collections.ErrNotFound.Error()) { return false, err } + if !collection.IsView() { + for k, v := range c.metadata { + collection.Schema.WithMetadata(k, v) + } + //setMD = true + } + if exist == nil { setSchema = !collection.IsView() - exist, err = s.content.Collections.Create(ctx, collection) + _, err = s.content.Collections.Create(ctx, collection) if err != nil { return false, err } } else { + if !collection.IsView() && !exist.IsView() { + if collection.Schema.Metadata != nil && exist.Schema.Metadata[extension.ExtensionMetadataKey] != collection.Schema.Metadata[extension.ExtensionMetadataKey] && !s.IsForce() { + return false, ErrCollectionAlreadyExists + } + } var upd bool collection, upd, setSchema = c.UpdateFn(s, exist, c.collection) if upd { diff --git a/pkg/setup/collection_test.go b/pkg/setup/collection_test.go index 433aa7397d450f8226a95af032a7cd177697fbed..f23aa2b4949d79605c39e356aafe1592c616301f 100644 --- a/pkg/setup/collection_test.go +++ b/pkg/setup/collection_test.go @@ -22,6 +22,8 @@ func TestSetup_InstallCollections(t *testing.T) { collections []*collections.Collection collectionsCall func(svc *mockscollections.Collections) envsCall func(svc *envmocks.Environments) + co CollectionsOption + force bool wantErr func(t *testing.T, err error) }{ { @@ -91,6 +93,81 @@ func TestSetup_InstallCollections(t *testing.T) { assert.Contains(t, errors.GetDetail(err), "Возникла ошибка при настройке коллекции space(1)") }, }, + { + name: "Update extension collection with metadata", + collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata("extension", "test-extension")}}, + collectionsCall: func(svc *mockscollections.Collections) { + svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata("extension", "test-extension")}, nil).Once() + svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata("extension", "test-extension")}).Return(nil).Once() + svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String())).Return(nil).Once() + }, + envsCall: func(svc *envmocks.Environments) { + svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Once() + }, + wantErr: func(t *testing.T, err error) { + assert.NoError(t, err) + }, + co: SetSchemaMetadata("extension", "test-extension"), + }, + { + name: "Fail to update collection with the same id", + collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata("extension", "test-extension")}}, + collectionsCall: func(svc *mockscollections.Collections) { + svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("surname", field.String())}, nil).Once() + }, + wantErr: func(t *testing.T, err error) { + assert.Error(t, err) + }, + co: SetSchemaMetadata("extension", "test-extension"), + }, + { + name: "Update collection with the same id, with force", + collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata("extension", "test-extension")}}, + collectionsCall: func(svc *mockscollections.Collections) { + svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata("extension", "test-extension")}, nil).Once() + svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata("extension", "test-extension")}).Return(nil).Once() + svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String())).Return(nil).Once() + }, + envsCall: func(svc *envmocks.Environments) { + svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Once() + }, + wantErr: func(t *testing.T, err error) { + assert.NoError(t, err) + }, + co: SetSchemaMetadata("extension", "test-extension"), + force: true, + }, + { + name: "Update exist view collection with the same id", + collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata("extension", "test-extension")}}, + collectionsCall: func(svc *mockscollections.Collections) { + svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp2", EnvID: environments.DefaultEnvironment, CollectionID: "2"}}, nil).Once() + svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata("extension", "test-extension")}).Return(nil).Once() + svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata("extension", "test-extension")).Return(nil).Once() + }, + envsCall: func(svc *envmocks.Environments) { + svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Once() + }, + wantErr: func(t *testing.T, err error) { + assert.NoError(t, err) + }, + co: SetSchemaMetadata("extension", "test-extension"), + }, + { + name: "Update view collection with the same id to new view collection", + collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp3", EnvID: environments.DefaultEnvironment, CollectionID: "3"}}}, + collectionsCall: func(svc *mockscollections.Collections) { + svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp2", EnvID: environments.DefaultEnvironment, CollectionID: "2"}}, nil).Once() + svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp3", EnvID: environments.DefaultEnvironment, CollectionID: "3"}}).Return(nil).Once() + }, + envsCall: func(svc *envmocks.Environments) { + svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Once() + }, + wantErr: func(t *testing.T, err error) { + assert.NoError(t, err) + }, + co: SetSchemaMetadata("extension", "test-extension"), + }, } for _, tt := range tests { @@ -105,7 +182,12 @@ func TestSetup_InstallCollections(t *testing.T) { } s := NewSetup(&content.Content{Collections: c, Environments: e}, "sp", "env", nil) - s.AddCollections(tt.collections) + s.force = tt.force + if tt.co != nil { + s.AddCollections(tt.collections, tt.co) + } else { + s.AddCollections(tt.collections) + } tt.wantErr(t, s.InstallCollections(context.Background())) }) }