diff --git a/api/types/autoupdate/config.go b/api/types/autoupdate/config.go new file mode 100644 index 0000000000000..5be3db89fd0c5 --- /dev/null +++ b/api/types/autoupdate/config.go @@ -0,0 +1,63 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package autoupdate + +import ( + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "github.com/gravitational/teleport/api/types" +) + +// NewAutoUpdateConfig creates a new auto update configuration resource. +func NewAutoUpdateConfig(spec *autoupdate.AutoUpdateConfigSpec) (*autoupdate.AutoUpdateConfig, error) { + config := &autoupdate.AutoUpdateConfig{ + Kind: types.KindAutoUpdateConfig, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateConfig, + }, + Spec: spec, + } + if err := ValidateAutoUpdateConfig(config); err != nil { + return nil, trace.Wrap(err) + } + + return config, nil +} + +// ValidateAutoUpdateConfig checks that required parameters are set +// for the specified AutoUpdateConfig. +func ValidateAutoUpdateConfig(c *autoupdate.AutoUpdateConfig) error { + if c == nil { + return trace.BadParameter("AutoUpdateConfig is nil") + } + if c.Metadata == nil { + return trace.BadParameter("Metadata is nil") + } + if c.Metadata.Name != types.MetaNameAutoUpdateConfig { + return trace.BadParameter("Name is not valid") + } + if c.Spec == nil { + return trace.BadParameter("Spec is nil") + } + + return nil +} diff --git a/api/types/autoupdate/config_test.go b/api/types/autoupdate/config_test.go new file mode 100644 index 0000000000000..4ebf29a536841 --- /dev/null +++ b/api/types/autoupdate/config_test.go @@ -0,0 +1,94 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package autoupdate + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "github.com/gravitational/teleport/api/types" +) + +// TestNewAutoUpdateConfig verifies validation for auto update config resource. +func TestNewAutoUpdateConfig(t *testing.T) { + tests := []struct { + name string + spec *autoupdate.AutoUpdateConfigSpec + want *autoupdate.AutoUpdateConfig + assertErr func(*testing.T, error, ...any) + }{ + { + name: "success tools autoupdate disabled", + spec: &autoupdate.AutoUpdateConfigSpec{ + ToolsAutoupdate: false, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.NoError(t, err) + }, + want: &autoupdate.AutoUpdateConfig{ + Kind: types.KindAutoUpdateConfig, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateConfig, + }, + Spec: &autoupdate.AutoUpdateConfigSpec{ + ToolsAutoupdate: false, + }, + }, + }, + { + name: "success tools autoupdate enabled", + spec: &autoupdate.AutoUpdateConfigSpec{ + ToolsAutoupdate: true, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.NoError(t, err) + }, + want: &autoupdate.AutoUpdateConfig{ + Kind: types.KindAutoUpdateConfig, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateConfig, + }, + Spec: &autoupdate.AutoUpdateConfigSpec{ + ToolsAutoupdate: true, + }, + }, + }, + { + name: "invalid spec", + spec: nil, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "Spec is nil") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewAutoUpdateConfig(tt.spec) + tt.assertErr(t, err) + require.Empty(t, cmp.Diff(got, tt.want, protocmp.Transform())) + }) + } +} diff --git a/api/types/autoupdate/version.go b/api/types/autoupdate/version.go new file mode 100644 index 0000000000000..088171a072ae3 --- /dev/null +++ b/api/types/autoupdate/version.go @@ -0,0 +1,71 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package autoupdate + +import ( + "github.com/coreos/go-semver/semver" + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "github.com/gravitational/teleport/api/types" +) + +// NewAutoUpdateVersion creates a new auto update version resource. +func NewAutoUpdateVersion(spec *autoupdate.AutoUpdateVersionSpec) (*autoupdate.AutoUpdateVersion, error) { + version := &autoupdate.AutoUpdateVersion{ + Kind: types.KindAutoUpdateVersion, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateVersion, + }, + Spec: spec, + } + if err := ValidateAutoUpdateVersion(version); err != nil { + return nil, trace.Wrap(err) + } + + return version, nil +} + +// ValidateAutoUpdateVersion checks that required parameters are set +// for the specified AutoUpdateVersion. +func ValidateAutoUpdateVersion(v *autoupdate.AutoUpdateVersion) error { + if v == nil { + return trace.BadParameter("AutoUpdateVersion is nil") + } + if v.Metadata == nil { + return trace.BadParameter("Metadata is nil") + } + if v.Metadata.Name != types.MetaNameAutoUpdateVersion { + return trace.BadParameter("Name is not valid") + } + if v.Spec == nil { + return trace.BadParameter("Spec is nil") + } + + if v.Spec.ToolsVersion == "" { + return trace.BadParameter("ToolsVersion is unset") + } + if _, err := semver.NewVersion(v.Spec.ToolsVersion); err != nil { + return trace.BadParameter("ToolsVersion is not a valid semantic version") + } + + return nil +} diff --git a/api/types/autoupdate/version_test.go b/api/types/autoupdate/version_test.go new file mode 100644 index 0000000000000..5f6729ec42f5b --- /dev/null +++ b/api/types/autoupdate/version_test.go @@ -0,0 +1,93 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package autoupdate + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "github.com/gravitational/teleport/api/types" +) + +// TestNewAutoUpdateVersion verifies validation for auto update version resource. +func TestNewAutoUpdateVersion(t *testing.T) { + tests := []struct { + name string + spec *autoupdate.AutoUpdateVersionSpec + want *autoupdate.AutoUpdateVersion + assertErr func(*testing.T, error, ...any) + }{ + { + name: "success tools autoupdate version", + spec: &autoupdate.AutoUpdateVersionSpec{ + ToolsVersion: "1.2.3-dev", + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.NoError(t, err) + }, + want: &autoupdate.AutoUpdateVersion{ + Kind: types.KindAutoUpdateVersion, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateVersion, + }, + Spec: &autoupdate.AutoUpdateVersionSpec{ + ToolsVersion: "1.2.3-dev", + }, + }, + }, + { + name: "invalid empty tools version", + spec: &autoupdate.AutoUpdateVersionSpec{ + ToolsVersion: "", + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "ToolsVersion is unset") + }, + }, + { + name: "invalid semantic tools version", + spec: &autoupdate.AutoUpdateVersionSpec{ + ToolsVersion: "17-0-0", + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "ToolsVersion is not a valid semantic version") + }, + }, + { + name: "invalid spec", + spec: nil, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "Spec is nil") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewAutoUpdateVersion(tt.spec) + tt.assertErr(t, err) + require.Empty(t, cmp.Diff(got, tt.want, protocmp.Transform())) + }) + } +} diff --git a/api/types/constants.go b/api/types/constants.go index 283401615171f..12bfe0c65a5f7 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -320,6 +320,18 @@ const ( // alternative to their individual resource kinds. KindClusterConfig = "cluster_config" + // KindAutoUpdateConfig is the resource with autoupdate configuration. + KindAutoUpdateConfig = "autoupdate_config" + + // KindAutoUpdateVersion is the resource with autoupdate versions. + KindAutoUpdateVersion = "autoupdate_version" + + // MetaNameAutoUpdateConfig is the name of a configuration resource for autoupdate config. + MetaNameAutoUpdateConfig = "autoupdate-config" + + // MetaNameAutoUpdateVersion is the name of a resource for autoupdate version. + MetaNameAutoUpdateVersion = "autoupdate-version" + // KindClusterAuditConfig is the resource that holds cluster audit configuration. KindClusterAuditConfig = "cluster_audit_config" diff --git a/lib/services/autoupdates.go b/lib/services/autoupdates.go new file mode 100644 index 0000000000000..3079f355d7ff8 --- /dev/null +++ b/lib/services/autoupdates.go @@ -0,0 +1,63 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package services + +import ( + "context" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" +) + +// AutoUpdateServiceGetter defines only read-only service methods. +type AutoUpdateServiceGetter interface { + // GetAutoUpdateConfig gets the autoupdate configuration from the backend. + GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) + + // GetAutoUpdateVersion gets the autoupdate version from the backend. + GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) +} + +// AutoUpdateService stores the autoupdate service. +type AutoUpdateService interface { + AutoUpdateServiceGetter + + // CreateAutoUpdateConfig creates an auto update configuration. + CreateAutoUpdateConfig(ctx context.Context, config *autoupdate.AutoUpdateConfig) (*autoupdate.AutoUpdateConfig, error) + + // UpdateAutoUpdateConfig updates an auto update configuration. + UpdateAutoUpdateConfig(ctx context.Context, config *autoupdate.AutoUpdateConfig) (*autoupdate.AutoUpdateConfig, error) + + // UpsertAutoUpdateConfig sets an auto update configuration. + UpsertAutoUpdateConfig(ctx context.Context, c *autoupdate.AutoUpdateConfig) (*autoupdate.AutoUpdateConfig, error) + + // DeleteAutoUpdateConfig deletes the auto update configuration from the backend. + DeleteAutoUpdateConfig(ctx context.Context) error + + // CreateAutoUpdateVersion creates an auto update version. + CreateAutoUpdateVersion(ctx context.Context, config *autoupdate.AutoUpdateVersion) (*autoupdate.AutoUpdateVersion, error) + + // UpdateAutoUpdateVersion updates an auto update version. + UpdateAutoUpdateVersion(ctx context.Context, config *autoupdate.AutoUpdateVersion) (*autoupdate.AutoUpdateVersion, error) + + // UpsertAutoUpdateVersion sets an auto update version. + UpsertAutoUpdateVersion(ctx context.Context, c *autoupdate.AutoUpdateVersion) (*autoupdate.AutoUpdateVersion, error) + + // DeleteAutoUpdateVersion deletes the auto update version from the backend. + DeleteAutoUpdateVersion(ctx context.Context) error +} diff --git a/lib/services/local/autoupdate.go b/lib/services/local/autoupdate.go new file mode 100644 index 0000000000000..21b4172767cc3 --- /dev/null +++ b/lib/services/local/autoupdate.go @@ -0,0 +1,158 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package local + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + "github.com/gravitational/teleport/api/types" + update "github.com/gravitational/teleport/api/types/autoupdate" + "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/services/local/generic" +) + +const ( + autoUpdateConfigPrefix = "auto_update_config" + autoUpdateVersionPrefix = "auto_update_version" +) + +// AutoupdateService is responsible for managing auto update configuration and version. +type AutoupdateService struct { + config *generic.ServiceWrapper[*autoupdate.AutoUpdateConfig] + version *generic.ServiceWrapper[*autoupdate.AutoUpdateVersion] +} + +// NewAutoupdateService returns a new AutoupdateService. +func NewAutoupdateService(backend backend.Backend) (*AutoupdateService, error) { + config, err := generic.NewServiceWrapper( + generic.ServiceWrapperConfig[*autoupdate.AutoUpdateConfig]{ + Backend: backend, + ResourceKind: types.KindAutoUpdateConfig, + BackendPrefix: autoUpdateConfigPrefix, + MarshalFunc: services.MarshalProtoResource[*autoupdate.AutoUpdateConfig], + UnmarshalFunc: services.UnmarshalProtoResource[*autoupdate.AutoUpdateConfig], + ValidateFunc: update.ValidateAutoUpdateConfig, + KeyFunc: func(*autoupdate.AutoUpdateConfig) string { + return types.MetaNameAutoUpdateConfig + }, + }) + if err != nil { + return nil, trace.Wrap(err) + } + version, err := generic.NewServiceWrapper( + generic.ServiceWrapperConfig[*autoupdate.AutoUpdateVersion]{ + Backend: backend, + ResourceKind: types.KindAutoUpdateVersion, + BackendPrefix: autoUpdateVersionPrefix, + MarshalFunc: services.MarshalProtoResource[*autoupdate.AutoUpdateVersion], + UnmarshalFunc: services.UnmarshalProtoResource[*autoupdate.AutoUpdateVersion], + ValidateFunc: update.ValidateAutoUpdateVersion, + KeyFunc: func(version *autoupdate.AutoUpdateVersion) string { + return types.MetaNameAutoUpdateVersion + }, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + return &AutoupdateService{ + config: config, + version: version, + }, nil +} + +// CreateAutoUpdateConfig creates an auto update configuration singleton. +func (s *AutoupdateService) CreateAutoUpdateConfig( + ctx context.Context, + c *autoupdate.AutoUpdateConfig, +) (*autoupdate.AutoUpdateConfig, error) { + config, err := s.config.CreateResource(ctx, c) + return config, trace.Wrap(err) +} + +// UpdateAutoUpdateConfig updates an auto update configuration singleton. +func (s *AutoupdateService) UpdateAutoUpdateConfig( + ctx context.Context, + c *autoupdate.AutoUpdateConfig, +) (*autoupdate.AutoUpdateConfig, error) { + config, err := s.config.UpdateResource(ctx, c) + return config, trace.Wrap(err) +} + +// UpsertAutoUpdateConfig sets an auto update configuration. +func (s *AutoupdateService) UpsertAutoUpdateConfig( + ctx context.Context, + c *autoupdate.AutoUpdateConfig, +) (*autoupdate.AutoUpdateConfig, error) { + config, err := s.config.UpsertResource(ctx, c) + return config, trace.Wrap(err) +} + +// GetAutoUpdateConfig gets the auto update configuration from the backend. +func (s *AutoupdateService) GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) { + config, err := s.config.GetResource(ctx, types.MetaNameAutoUpdateConfig) + return config, trace.Wrap(err) +} + +// DeleteAutoUpdateConfig deletes the auto update configuration from the backend. +func (s *AutoupdateService) DeleteAutoUpdateConfig(ctx context.Context) error { + return trace.Wrap(s.config.DeleteResource(ctx, types.MetaNameAutoUpdateConfig)) +} + +// CreateAutoUpdateVersion creates an autoupdate version resource. +func (s *AutoupdateService) CreateAutoUpdateVersion( + ctx context.Context, + v *autoupdate.AutoUpdateVersion, +) (*autoupdate.AutoUpdateVersion, error) { + version, err := s.version.CreateResource(ctx, v) + return version, trace.Wrap(err) +} + +// UpdateAutoUpdateVersion updates an autoupdate version resource. +func (s *AutoupdateService) UpdateAutoUpdateVersion( + ctx context.Context, + v *autoupdate.AutoUpdateVersion, +) (*autoupdate.AutoUpdateVersion, error) { + version, err := s.version.UpdateResource(ctx, v) + return version, trace.Wrap(err) +} + +// UpsertAutoUpdateVersion sets autoupdate version resource. +func (s *AutoupdateService) UpsertAutoUpdateVersion( + ctx context.Context, + v *autoupdate.AutoUpdateVersion, +) (*autoupdate.AutoUpdateVersion, error) { + version, err := s.version.UpsertResource(ctx, v) + return version, trace.Wrap(err) +} + +// GetAutoUpdateVersion gets the auto update version from the backend. +func (s *AutoupdateService) GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) { + version, err := s.version.GetResource(ctx, types.MetaNameAutoUpdateVersion) + return version, trace.Wrap(err) +} + +// DeleteAutoUpdateVersion deletes the auto update version from the backend. +func (s *AutoupdateService) DeleteAutoUpdateVersion(ctx context.Context) error { + return trace.Wrap(s.version.DeleteResource(ctx, types.MetaNameAutoUpdateVersion)) +} diff --git a/lib/services/local/autoupdate_test.go b/lib/services/local/autoupdate_test.go new file mode 100644 index 0000000000000..a27af676589d2 --- /dev/null +++ b/lib/services/local/autoupdate_test.go @@ -0,0 +1,223 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package local + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + + autoupdatepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/autoupdate" + "github.com/gravitational/teleport/lib/backend/memory" +) + +// TestAutoUpdateServiceConfigCRUD verifies get/create/update/upsert/delete methods of the backend service +// for auto update config resource. +func TestAutoUpdateServiceConfigCRUD(t *testing.T) { + t.Parallel() + + bk, err := memory.New(memory.Config{}) + require.NoError(t, err) + + service, err := NewAutoupdateService(bk) + require.NoError(t, err) + + ctx := context.Background() + config := &autoupdatepb.AutoUpdateConfig{ + Kind: types.KindAutoUpdateConfig, + Version: types.V1, + Metadata: &headerv1.Metadata{Name: types.MetaNameAutoUpdateConfig}, + Spec: &autoupdatepb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + } + + created, err := service.CreateAutoUpdateConfig(ctx, config) + require.NoError(t, err) + diff := cmp.Diff(config, created, + cmpopts.IgnoreFields(headerv1.Metadata{}, "Revision"), + protocmp.Transform(), + ) + require.Empty(t, diff) + require.NotEmpty(t, created.GetMetadata().GetRevision()) + + got, err := service.GetAutoUpdateConfig(ctx) + require.NoError(t, err) + diff = cmp.Diff(config, got, + cmpopts.IgnoreFields(headerv1.Metadata{}, "Revision"), + protocmp.Transform(), + ) + require.Empty(t, diff) + require.Equal(t, created.GetMetadata().GetRevision(), got.GetMetadata().GetRevision()) + + config.Spec.ToolsAutoupdate = false + updated, err := service.UpdateAutoUpdateConfig(ctx, config) + require.NoError(t, err) + require.NotEqual(t, got.GetSpec().GetToolsAutoupdate(), updated.GetSpec().GetToolsAutoupdate()) + + _, err = service.UpsertAutoUpdateConfig(ctx, config) + require.NoError(t, err) + + err = service.DeleteAutoUpdateConfig(ctx) + require.NoError(t, err) + + _, err = service.GetAutoUpdateConfig(ctx) + var notFoundError *trace.NotFoundError + require.ErrorAs(t, err, ¬FoundError) + + _, err = service.UpdateAutoUpdateConfig(ctx, config) + require.ErrorAs(t, err, ¬FoundError) +} + +// TestAutoUpdateServiceVersionCRUD verifies get/create/update/upsert/delete methods of the backend service +// for auto update version resource. +func TestAutoUpdateServiceVersionCRUD(t *testing.T) { + t.Parallel() + + bk, err := memory.New(memory.Config{}) + require.NoError(t, err) + + service, err := NewAutoupdateService(bk) + require.NoError(t, err) + + ctx := context.Background() + version := &autoupdatepb.AutoUpdateVersion{ + Kind: types.KindAutoUpdateVersion, + Version: types.V1, + Metadata: &headerv1.Metadata{Name: types.MetaNameAutoUpdateVersion}, + Spec: &autoupdatepb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, + } + + created, err := service.CreateAutoUpdateVersion(ctx, version) + require.NoError(t, err) + diff := cmp.Diff(version, created, + cmpopts.IgnoreFields(headerv1.Metadata{}, "Revision"), + protocmp.Transform(), + ) + require.Empty(t, diff) + require.NotEmpty(t, created.GetMetadata().GetRevision()) + + got, err := service.GetAutoUpdateVersion(ctx) + require.NoError(t, err) + diff = cmp.Diff(version, got, + cmpopts.IgnoreFields(headerv1.Metadata{}, "Revision"), + protocmp.Transform(), + ) + require.Empty(t, diff) + require.Equal(t, created.GetMetadata().GetRevision(), got.GetMetadata().GetRevision()) + + version.Spec.ToolsVersion = "3.2.1" + updated, err := service.UpdateAutoUpdateVersion(ctx, version) + require.NoError(t, err) + require.NotEqual(t, got.GetSpec().GetToolsVersion(), updated.GetSpec().GetToolsVersion()) + + _, err = service.UpsertAutoUpdateVersion(ctx, version) + require.NoError(t, err) + + err = service.DeleteAutoUpdateVersion(ctx) + require.NoError(t, err) + + _, err = service.GetAutoUpdateVersion(ctx) + var notFoundError *trace.NotFoundError + require.ErrorAs(t, err, ¬FoundError) + + _, err = service.UpdateAutoUpdateVersion(ctx, version) + require.ErrorAs(t, err, ¬FoundError) +} + +// TestAutoUpdateServiceInvalidNameCreate verifies that configuration and version +// with not constant name is rejected to be created. +func TestAutoUpdateServiceInvalidNameCreate(t *testing.T) { + t.Parallel() + + bk, err := memory.New(memory.Config{}) + require.NoError(t, err) + + service, err := NewAutoupdateService(bk) + require.NoError(t, err) + + ctx := context.Background() + config := &autoupdatepb.AutoUpdateConfig{ + Kind: types.KindAutoUpdateConfig, + Version: types.V1, + Metadata: &headerv1.Metadata{Name: "invalid-auto-update-config-name"}, + Spec: &autoupdatepb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + } + + createdConfig, err := service.CreateAutoUpdateConfig(ctx, config) + require.Error(t, err) + require.Nil(t, createdConfig) + + version := &autoupdatepb.AutoUpdateVersion{ + Kind: types.KindAutoUpdateVersion, + Version: types.V1, + Metadata: &headerv1.Metadata{Name: "invalid-auto-update-version-name"}, + Spec: &autoupdatepb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, + } + + createdVersion, err := service.CreateAutoUpdateVersion(ctx, version) + require.Error(t, err) + require.Nil(t, createdVersion) +} + +// TestAutoUpdateServiceInvalidNameUpdate verifies that configuration and version +// with not constant name is rejected to be updated. +func TestAutoUpdateServiceInvalidNameUpdate(t *testing.T) { + t.Parallel() + + bk, err := memory.New(memory.Config{}) + require.NoError(t, err) + + service, err := NewAutoupdateService(bk) + require.NoError(t, err) + + ctx := context.Background() + + // Validate the config update restriction. + config, err := autoupdate.NewAutoUpdateConfig(&autoupdatepb.AutoUpdateConfigSpec{ToolsAutoupdate: true}) + require.NoError(t, err) + + createdConfig, err := service.UpsertAutoUpdateConfig(ctx, config) + require.NoError(t, err) + + createdConfig.GetMetadata().Name = "invalid-auto-update-config-name" + + createdConfig, err = service.UpdateAutoUpdateConfig(ctx, createdConfig) + require.Error(t, err) + require.Nil(t, createdConfig) + + // Validate the version update restriction. + version, err := autoupdate.NewAutoUpdateVersion(&autoupdatepb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}) + require.NoError(t, err) + + createdVersion, err := service.UpsertAutoUpdateVersion(ctx, version) + require.NoError(t, err) + + createdVersion.GetMetadata().Name = "invalid-auto-update-version-name" + + createdVersion, err = service.UpdateAutoUpdateVersion(ctx, createdVersion) + require.Error(t, err) + require.Nil(t, createdVersion) +}