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/service/servicecfg/config.go b/lib/service/servicecfg/config.go
index 33a89c6223fbc..8a769ace38223 100644
--- a/lib/service/servicecfg/config.go
+++ b/lib/service/servicecfg/config.go
@@ -169,6 +169,9 @@ type Config struct {
// ClusterConfiguration is a service that provides cluster configuration
ClusterConfiguration services.ClusterConfiguration
+ // AutoUpdateService is a service that provides auto update configuration and version.
+ AutoUpdateService services.AutoUpdateService
+
// CipherSuites is a list of TLS ciphersuites that Teleport supports. If
// omitted, a Teleport selected list of defaults will be used.
CipherSuites []uint16
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)
+}