From 72c78a0078f0afb20e16de1693dff881fa8f7ac5 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Wed, 4 Sep 2024 22:56:57 -0400 Subject: [PATCH 1/4] Add backend storage service for auto updates --- api/types/autoupdate/config.go | 60 ++++++++++ api/types/autoupdate/config_test.go | 94 +++++++++++++++ api/types/autoupdate/version.go | 67 +++++++++++ api/types/autoupdate/version_test.go | 93 +++++++++++++++ api/types/constants.go | 12 ++ lib/services/autoupdates.go | 63 ++++++++++ lib/services/local/autoupdate.go | 166 ++++++++++++++++++++++++++ lib/services/local/autoupdate_test.go | 146 ++++++++++++++++++++++ 8 files changed, 701 insertions(+) create mode 100644 api/types/autoupdate/config.go create mode 100644 api/types/autoupdate/config_test.go create mode 100644 api/types/autoupdate/version.go create mode 100644 api/types/autoupdate/version_test.go create mode 100644 lib/services/autoupdates.go create mode 100644 lib/services/local/autoupdate.go create mode 100644 lib/services/local/autoupdate_test.go diff --git a/api/types/autoupdate/config.go b/api/types/autoupdate/config.go new file mode 100644 index 0000000000000..0d15c5eeca6c8 --- /dev/null +++ b/api/types/autoupdate/config.go @@ -0,0 +1,60 @@ +/* + * 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.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..4bc5a3653c5b2 --- /dev/null +++ b/api/types/autoupdate/version.go @@ -0,0 +1,67 @@ +/* + * 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.Spec == nil { + return trace.BadParameter("Spec is nil") + } + + if v.Spec.ToolsVersion == "" { + return trace.BadParameter("ToolsVersion is unset") + } else 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 4e3e6a44c7de8..1e290b4b8706b 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..61a8744160a18 --- /dev/null +++ b/lib/services/local/autoupdate.go @@ -0,0 +1,166 @@ +/* + * 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( + backend, + types.KindAutoUpdateConfig, + autoUpdateConfigPrefix, + services.MarshalProtoResource[*autoupdate.AutoUpdateConfig], + services.UnmarshalProtoResource[*autoupdate.AutoUpdateConfig], + ) + if err != nil { + return nil, trace.Wrap(err) + } + version, err := generic.NewServiceWrapper( + backend, + types.KindAutoUpdateVersion, + autoUpdateVersionPrefix, + services.MarshalProtoResource[*autoupdate.AutoUpdateVersion], + services.UnmarshalProtoResource[*autoupdate.AutoUpdateVersion], + ) + 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) { + if err := update.ValidateAutoUpdateConfig(c); err != nil { + return nil, trace.Wrap(err) + } + 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) { + if err := update.ValidateAutoUpdateConfig(c); err != nil { + return nil, trace.Wrap(err) + } + 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) { + if err := update.ValidateAutoUpdateConfig(c); err != nil { + return nil, trace.Wrap(err) + } + 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) { + if err := update.ValidateAutoUpdateVersion(v); err != nil { + return nil, trace.Wrap(err) + } + 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) { + if err := update.ValidateAutoUpdateVersion(v); err != nil { + return nil, trace.Wrap(err) + } + 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) { + if err := update.ValidateAutoUpdateVersion(v); err != nil { + return nil, trace.Wrap(err) + } + 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..1574220ee43b1 --- /dev/null +++ b/lib/services/local/autoupdate_test.go @@ -0,0 +1,146 @@ +/* + * 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" + + "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/lib/backend/memory" +) + +// TestAutoupdateServiceConfigCRUD verifies get/create/update/upsert/delete methods of the backend service +// for autoupdate 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 := &autoupdate.AutoUpdateConfig{ + Kind: types.KindAutoUpdateConfig, + Version: types.V1, + Metadata: &headerv1.Metadata{Name: types.MetaNameAutoUpdateConfig}, + Spec: &autoupdate.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + } + + created, err := service.CreateAutoUpdateConfig(ctx, config) + require.NoError(t, err) + diff := cmp.Diff(config, created, + cmpopts.IgnoreFields(headerv1.Metadata{}, "Revision"), + cmpopts.IgnoreUnexported(autoupdate.AutoUpdateConfig{}, autoupdate.AutoUpdateConfigSpec{}, headerv1.Metadata{}), + ) + 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"), + cmpopts.IgnoreUnexported(autoupdate.AutoUpdateConfig{}, autoupdate.AutoUpdateConfigSpec{}, headerv1.Metadata{}), + ) + 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 autoupdate 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 := &autoupdate.AutoUpdateVersion{ + Kind: types.KindAutoUpdateVersion, + Version: types.V1, + Metadata: &headerv1.Metadata{Name: types.MetaNameAutoUpdateVersion}, + Spec: &autoupdate.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"), + cmpopts.IgnoreUnexported(autoupdate.AutoUpdateVersion{}, autoupdate.AutoUpdateVersionSpec{}, headerv1.Metadata{}), + ) + 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"), + cmpopts.IgnoreUnexported(autoupdate.AutoUpdateVersion{}, autoupdate.AutoUpdateVersionSpec{}, headerv1.Metadata{}), + ) + 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) +} From 004d5eadcd9aa5a280c38d473dd9a9e5eb5cd53f Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Thu, 5 Sep 2024 14:30:34 -0400 Subject: [PATCH 2/4] Add validation for metadata name --- api/types/autoupdate/config.go | 3 + api/types/autoupdate/version.go | 3 + lib/services/local/autoupdate_test.go | 103 ++++++++++++++++++++++---- 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/api/types/autoupdate/config.go b/api/types/autoupdate/config.go index 0d15c5eeca6c8..5be3db89fd0c5 100644 --- a/api/types/autoupdate/config.go +++ b/api/types/autoupdate/config.go @@ -52,6 +52,9 @@ func ValidateAutoUpdateConfig(c *autoupdate.AutoUpdateConfig) error { 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") } diff --git a/api/types/autoupdate/version.go b/api/types/autoupdate/version.go index 4bc5a3653c5b2..0264ab5b8ffc9 100644 --- a/api/types/autoupdate/version.go +++ b/api/types/autoupdate/version.go @@ -53,6 +53,9 @@ func ValidateAutoUpdateVersion(v *autoupdate.AutoUpdateVersion) error { 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") } diff --git a/lib/services/local/autoupdate_test.go b/lib/services/local/autoupdate_test.go index 1574220ee43b1..182f788cbcc51 100644 --- a/lib/services/local/autoupdate_test.go +++ b/lib/services/local/autoupdate_test.go @@ -26,16 +26,18 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/gravitational/trace" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" - "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + 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 +// TestAutoUpdateServiceConfigCRUD verifies get/create/update/upsert/delete methods of the backend service // for autoupdate config resource. -func TestAutoupdateServiceConfigCRUD(t *testing.T) { +func TestAutoUpdateServiceConfigCRUD(t *testing.T) { t.Parallel() bk, err := memory.New(memory.Config{}) @@ -45,18 +47,18 @@ func TestAutoupdateServiceConfigCRUD(t *testing.T) { require.NoError(t, err) ctx := context.Background() - config := &autoupdate.AutoUpdateConfig{ + config := &autoupdatepb.AutoUpdateConfig{ Kind: types.KindAutoUpdateConfig, Version: types.V1, Metadata: &headerv1.Metadata{Name: types.MetaNameAutoUpdateConfig}, - Spec: &autoupdate.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + 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"), - cmpopts.IgnoreUnexported(autoupdate.AutoUpdateConfig{}, autoupdate.AutoUpdateConfigSpec{}, headerv1.Metadata{}), + protocmp.Transform(), ) require.Empty(t, diff) require.NotEmpty(t, created.GetMetadata().GetRevision()) @@ -65,7 +67,7 @@ func TestAutoupdateServiceConfigCRUD(t *testing.T) { require.NoError(t, err) diff = cmp.Diff(config, got, cmpopts.IgnoreFields(headerv1.Metadata{}, "Revision"), - cmpopts.IgnoreUnexported(autoupdate.AutoUpdateConfig{}, autoupdate.AutoUpdateConfigSpec{}, headerv1.Metadata{}), + protocmp.Transform(), ) require.Empty(t, diff) require.Equal(t, created.GetMetadata().GetRevision(), got.GetMetadata().GetRevision()) @@ -89,9 +91,9 @@ func TestAutoupdateServiceConfigCRUD(t *testing.T) { require.ErrorAs(t, err, ¬FoundError) } -// TestAutoupdateServiceVersionCRUD verifies get/create/update/upsert/delete methods of the backend service +// TestAutoUpdateServiceVersionCRUD verifies get/create/update/upsert/delete methods of the backend service // for autoupdate version resource. -func TestAutoupdateServiceVersionCRUD(t *testing.T) { +func TestAutoUpdateServiceVersionCRUD(t *testing.T) { t.Parallel() bk, err := memory.New(memory.Config{}) @@ -101,18 +103,18 @@ func TestAutoupdateServiceVersionCRUD(t *testing.T) { require.NoError(t, err) ctx := context.Background() - version := &autoupdate.AutoUpdateVersion{ + version := &autoupdatepb.AutoUpdateVersion{ Kind: types.KindAutoUpdateVersion, Version: types.V1, Metadata: &headerv1.Metadata{Name: types.MetaNameAutoUpdateVersion}, - Spec: &autoupdate.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, + 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"), - cmpopts.IgnoreUnexported(autoupdate.AutoUpdateVersion{}, autoupdate.AutoUpdateVersionSpec{}, headerv1.Metadata{}), + protocmp.Transform(), ) require.Empty(t, diff) require.NotEmpty(t, created.GetMetadata().GetRevision()) @@ -121,7 +123,7 @@ func TestAutoupdateServiceVersionCRUD(t *testing.T) { require.NoError(t, err) diff = cmp.Diff(version, got, cmpopts.IgnoreFields(headerv1.Metadata{}, "Revision"), - cmpopts.IgnoreUnexported(autoupdate.AutoUpdateVersion{}, autoupdate.AutoUpdateVersionSpec{}, headerv1.Metadata{}), + protocmp.Transform(), ) require.Empty(t, diff) require.Equal(t, created.GetMetadata().GetRevision(), got.GetMetadata().GetRevision()) @@ -144,3 +146,78 @@ func TestAutoupdateServiceVersionCRUD(t *testing.T) { _, 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) +} From eee6cff48cb075f4619e27135db167bc762cca1b Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Thu, 5 Sep 2024 16:08:49 -0400 Subject: [PATCH 3/4] CR change --- api/types/autoupdate/version.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/types/autoupdate/version.go b/api/types/autoupdate/version.go index 0264ab5b8ffc9..088171a072ae3 100644 --- a/api/types/autoupdate/version.go +++ b/api/types/autoupdate/version.go @@ -62,7 +62,8 @@ func ValidateAutoUpdateVersion(v *autoupdate.AutoUpdateVersion) error { if v.Spec.ToolsVersion == "" { return trace.BadParameter("ToolsVersion is unset") - } else if _, err := semver.NewVersion(v.Spec.ToolsVersion); err != nil { + } + if _, err := semver.NewVersion(v.Spec.ToolsVersion); err != nil { return trace.BadParameter("ToolsVersion is not a valid semantic version") } From bc1bcbc2b3cd0e801754efc30f1e2b840de1a234 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Wed, 11 Sep 2024 14:31:36 -0400 Subject: [PATCH 4/4] Use KeyFunc and ValidateFunc --- lib/services/local/autoupdate.go | 52 ++++++++++++--------------- lib/services/local/autoupdate_test.go | 4 +-- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/lib/services/local/autoupdate.go b/lib/services/local/autoupdate.go index 61a8744160a18..21b4172767cc3 100644 --- a/lib/services/local/autoupdate.go +++ b/lib/services/local/autoupdate.go @@ -45,22 +45,32 @@ type AutoupdateService struct { // NewAutoupdateService returns a new AutoupdateService. func NewAutoupdateService(backend backend.Backend) (*AutoupdateService, error) { config, err := generic.NewServiceWrapper( - backend, - types.KindAutoUpdateConfig, - autoUpdateConfigPrefix, - services.MarshalProtoResource[*autoupdate.AutoUpdateConfig], - services.UnmarshalProtoResource[*autoupdate.AutoUpdateConfig], - ) + 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( - backend, - types.KindAutoUpdateVersion, - autoUpdateVersionPrefix, - services.MarshalProtoResource[*autoupdate.AutoUpdateVersion], - services.UnmarshalProtoResource[*autoupdate.AutoUpdateVersion], - ) + 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) } @@ -76,9 +86,6 @@ func (s *AutoupdateService) CreateAutoUpdateConfig( ctx context.Context, c *autoupdate.AutoUpdateConfig, ) (*autoupdate.AutoUpdateConfig, error) { - if err := update.ValidateAutoUpdateConfig(c); err != nil { - return nil, trace.Wrap(err) - } config, err := s.config.CreateResource(ctx, c) return config, trace.Wrap(err) } @@ -88,9 +95,6 @@ func (s *AutoupdateService) UpdateAutoUpdateConfig( ctx context.Context, c *autoupdate.AutoUpdateConfig, ) (*autoupdate.AutoUpdateConfig, error) { - if err := update.ValidateAutoUpdateConfig(c); err != nil { - return nil, trace.Wrap(err) - } config, err := s.config.UpdateResource(ctx, c) return config, trace.Wrap(err) } @@ -100,9 +104,6 @@ func (s *AutoupdateService) UpsertAutoUpdateConfig( ctx context.Context, c *autoupdate.AutoUpdateConfig, ) (*autoupdate.AutoUpdateConfig, error) { - if err := update.ValidateAutoUpdateConfig(c); err != nil { - return nil, trace.Wrap(err) - } config, err := s.config.UpsertResource(ctx, c) return config, trace.Wrap(err) } @@ -123,9 +124,6 @@ func (s *AutoupdateService) CreateAutoUpdateVersion( ctx context.Context, v *autoupdate.AutoUpdateVersion, ) (*autoupdate.AutoUpdateVersion, error) { - if err := update.ValidateAutoUpdateVersion(v); err != nil { - return nil, trace.Wrap(err) - } version, err := s.version.CreateResource(ctx, v) return version, trace.Wrap(err) } @@ -135,9 +133,6 @@ func (s *AutoupdateService) UpdateAutoUpdateVersion( ctx context.Context, v *autoupdate.AutoUpdateVersion, ) (*autoupdate.AutoUpdateVersion, error) { - if err := update.ValidateAutoUpdateVersion(v); err != nil { - return nil, trace.Wrap(err) - } version, err := s.version.UpdateResource(ctx, v) return version, trace.Wrap(err) } @@ -147,9 +142,6 @@ func (s *AutoupdateService) UpsertAutoUpdateVersion( ctx context.Context, v *autoupdate.AutoUpdateVersion, ) (*autoupdate.AutoUpdateVersion, error) { - if err := update.ValidateAutoUpdateVersion(v); err != nil { - return nil, trace.Wrap(err) - } version, err := s.version.UpsertResource(ctx, v) return version, trace.Wrap(err) } diff --git a/lib/services/local/autoupdate_test.go b/lib/services/local/autoupdate_test.go index 182f788cbcc51..a27af676589d2 100644 --- a/lib/services/local/autoupdate_test.go +++ b/lib/services/local/autoupdate_test.go @@ -36,7 +36,7 @@ import ( ) // TestAutoUpdateServiceConfigCRUD verifies get/create/update/upsert/delete methods of the backend service -// for autoupdate config resource. +// for auto update config resource. func TestAutoUpdateServiceConfigCRUD(t *testing.T) { t.Parallel() @@ -92,7 +92,7 @@ func TestAutoUpdateServiceConfigCRUD(t *testing.T) { } // TestAutoUpdateServiceVersionCRUD verifies get/create/update/upsert/delete methods of the backend service -// for autoupdate version resource. +// for auto update version resource. func TestAutoUpdateServiceVersionCRUD(t *testing.T) { t.Parallel()