diff --git a/api/types/autoupdate/config.go b/api/types/autoupdate/config.go
index d61c35eccf0c2..32ae056195b64 100644
--- a/api/types/autoupdate/config.go
+++ b/api/types/autoupdate/config.go
@@ -1,24 +1,24 @@
/*
- * 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 .
- */
+Copyright 2024 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
package autoupdate
import (
+ "time"
+
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1"
@@ -26,13 +26,6 @@ import (
"github.com/gravitational/teleport/api/types"
)
-const (
- // ToolsUpdateModeEnabled enables client tools automatic updates.
- ToolsUpdateModeEnabled = "enabled"
- // ToolsUpdateModeDisabled disables client tools automatic updates.
- ToolsUpdateModeDisabled = "disabled"
-)
-
// NewAutoUpdateConfig creates a new auto update configuration resource.
func NewAutoUpdateConfig(spec *autoupdate.AutoUpdateConfigSpec) (*autoupdate.AutoUpdateConfig, error) {
config := &autoupdate.AutoUpdateConfig{
@@ -66,10 +59,41 @@ func ValidateAutoUpdateConfig(c *autoupdate.AutoUpdateConfig) error {
return trace.BadParameter("Spec is nil")
}
if c.Spec.Tools != nil {
- if c.Spec.Tools.Mode != ToolsUpdateModeDisabled && c.Spec.Tools.Mode != ToolsUpdateModeEnabled {
- return trace.BadParameter("ToolsMode is not valid")
+ if err := checkToolsMode(c.Spec.Tools.Mode); err != nil {
+ return trace.Wrap(err, "validating spec.tools.mode")
+ }
+ }
+ if c.Spec.Agents != nil {
+ if err := checkAgentsMode(c.Spec.Agents.Mode); err != nil {
+ return trace.Wrap(err, "validating spec.agents.mode")
+ }
+ if err := checkAgentsStrategy(c.Spec.Agents.Strategy); err != nil {
+ return trace.Wrap(err, "validating spec.agents.strategy")
}
+
+ windowDuration := c.Spec.Agents.MaintenanceWindowDuration.AsDuration()
+ if c.Spec.Agents.Strategy == AgentsStrategyHaltOnError && windowDuration != 0 {
+ return trace.BadParameter("spec.agents.maintenance_window_duration must be zero when the strategy is %q", c.Spec.Agents.Strategy)
+ }
+ if c.Spec.Agents.Strategy == AgentsStrategyTimeBased && windowDuration < 10*time.Minute {
+ return trace.BadParameter("spec.agents.maintenance_window_duration must be greater than 10 minutes when the strategy is %q", c.Spec.Agents.Strategy)
+ }
+
+ if err := checkAgentSchedules(c.Spec.Agents.Schedules); err != nil {
+ return trace.Wrap(err, "validating spec.agents.schedules")
+ }
+
}
return nil
}
+
+func checkAgentSchedules(schedules *autoupdate.AgentAutoUpdateSchedules) error {
+ // TODO: change this logic when we implement group support.
+ // Currently we reject any non-nil schedule
+ // When we'll implement schedule support, we'll treat an empty schedule as the default schedule.
+ if schedules == nil {
+ return nil
+ }
+ return trace.NotImplemented("agent schedules are not implemented yet")
+}
diff --git a/api/types/autoupdate/config_test.go b/api/types/autoupdate/config_test.go
index 443d6f246fa56..f6b6a87aa6bd8 100644
--- a/api/types/autoupdate/config_test.go
+++ b/api/types/autoupdate/config_test.go
@@ -1,29 +1,29 @@
/*
- * 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 .
- */
+Copyright 2024 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
package autoupdate
import (
"testing"
+ "time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
+ "google.golang.org/protobuf/types/known/durationpb"
"github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1"
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
@@ -99,7 +99,121 @@ func TestNewAutoUpdateConfig(t *testing.T) {
},
},
assertErr: func(t *testing.T, err error, a ...any) {
- require.ErrorContains(t, err, "ToolsMode is not valid")
+ require.ErrorContains(t, err, "unsupported tools mode: \"invalid-mode\"")
+ },
+ },
+ {
+ name: "invalid agents mode",
+ spec: &autoupdate.AutoUpdateConfigSpec{
+ Agents: &autoupdate.AutoUpdateConfigSpecAgents{
+ Mode: "invalid-mode",
+ Strategy: AgentsStrategyHaltOnError,
+ },
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "unsupported agents mode: \"invalid-mode\"")
+ },
+ },
+ {
+ name: "invalid agents strategy",
+ spec: &autoupdate.AutoUpdateConfigSpec{
+ Agents: &autoupdate.AutoUpdateConfigSpecAgents{
+ Mode: AgentsUpdateModeEnabled,
+ Strategy: "invalid-strategy",
+ },
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "unsupported agents strategy: \"invalid-strategy\"")
+ },
+ },
+ {
+ name: "invalid agents non-nil maintenance window with halt-on-error",
+ spec: &autoupdate.AutoUpdateConfigSpec{
+ Agents: &autoupdate.AutoUpdateConfigSpecAgents{
+ Mode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyHaltOnError,
+ MaintenanceWindowDuration: durationpb.New(time.Hour),
+ },
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "maintenance_window_duration must be zero")
+ },
+ },
+ {
+ name: "invalid agents nil maintenance window with time-based strategy",
+ spec: &autoupdate.AutoUpdateConfigSpec{
+ Agents: &autoupdate.AutoUpdateConfigSpecAgents{
+ Mode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyTimeBased,
+ },
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "maintenance_window_duration must be greater than 10 minutes")
+ },
+ },
+ {
+ name: "invalid agents short maintenance window",
+ spec: &autoupdate.AutoUpdateConfigSpec{
+ Agents: &autoupdate.AutoUpdateConfigSpecAgents{
+ Mode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyTimeBased,
+ MaintenanceWindowDuration: durationpb.New(time.Minute),
+ },
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "maintenance_window_duration must be greater than 10 minutes")
+ },
+ },
+ {
+ name: "success agents autoupdate halt-on-failure",
+ spec: &autoupdate.AutoUpdateConfigSpec{
+ Agents: &autoupdate.AutoUpdateConfigSpecAgents{
+ Mode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyHaltOnError,
+ },
+ },
+ 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{
+ Agents: &autoupdate.AutoUpdateConfigSpecAgents{
+ Mode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyHaltOnError,
+ },
+ },
+ },
+ },
+ {
+ name: "success agents autoupdate time-based",
+ spec: &autoupdate.AutoUpdateConfigSpec{
+ Agents: &autoupdate.AutoUpdateConfigSpecAgents{
+ Mode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyTimeBased,
+ MaintenanceWindowDuration: durationpb.New(time.Hour),
+ },
+ },
+ 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{
+ Agents: &autoupdate.AutoUpdateConfigSpecAgents{
+ Mode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyTimeBased,
+ MaintenanceWindowDuration: durationpb.New(time.Hour),
+ },
+ },
},
},
}
diff --git a/api/types/autoupdate/constants.go b/api/types/autoupdate/constants.go
new file mode 100644
index 0000000000000..deed5168fb21f
--- /dev/null
+++ b/api/types/autoupdate/constants.go
@@ -0,0 +1,46 @@
+/*
+Copyright 2024 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package autoupdate
+
+const (
+ // ToolsUpdateModeEnabled enables client tools automatic updates.
+ ToolsUpdateModeEnabled = "enabled"
+ // ToolsUpdateModeDisabled disables client tools automatic updates.
+ ToolsUpdateModeDisabled = "disabled"
+
+ // AgentsUpdateModeEnabled enabled agent automatic updates.
+ AgentsUpdateModeEnabled = "enabled"
+ // AgentsUpdateModeDisabled disables agent automatic updates.
+ AgentsUpdateModeDisabled = "disabled"
+ // AgentsUpdateModeSuspended temporarily suspends agent automatic updates.
+ AgentsUpdateModeSuspended = "suspended"
+
+ // AgentsScheduleRegular is the regular agent update schedule.
+ AgentsScheduleRegular = "regular"
+ // AgentsScheduleImmediate is the immediate agent update schedule.
+ // Every agent must update immediately if it's not already running the target version.
+ // This can be used to recover agents in case of major incident or actively exploited vulnerability.
+ AgentsScheduleImmediate = "immediate"
+
+ // AgentsStrategyHaltOnError is the agent update strategy that updates groups sequentially
+ // according to their order in the schedule. The previous groups must succeed.
+ AgentsStrategyHaltOnError = "halt-on-error"
+ // AgentsStrategyTimeBased is the agent update strategy that updates groups solely based on their
+ // maintenance window. There is no dependency between groups. Agents won't be instructed to update
+ // if the window is over.
+ AgentsStrategyTimeBased = "time-based"
+)
diff --git a/api/types/autoupdate/rollout.go b/api/types/autoupdate/rollout.go
new file mode 100644
index 0000000000000..814d71313d3ed
--- /dev/null
+++ b/api/types/autoupdate/rollout.go
@@ -0,0 +1,76 @@
+/*
+Copyright 2024 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+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"
+)
+
+// NewAutoUpdateAgentRollout creates a new auto update version resource.
+func NewAutoUpdateAgentRollout(spec *autoupdate.AutoUpdateAgentRolloutSpec) (*autoupdate.AutoUpdateAgentRollout, error) {
+ version := &autoupdate.AutoUpdateAgentRollout{
+ Kind: types.KindAutoUpdateAgentRollout,
+ Version: types.V1,
+ Metadata: &headerv1.Metadata{
+ Name: types.MetaNameAutoUpdateAgentRollout,
+ },
+ Spec: spec,
+ }
+ if err := ValidateAutoUpdateAgentRollout(version); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ return version, nil
+}
+
+// ValidateAutoUpdateAgentRollout checks that required parameters are set
+// for the specified AutoUpdateAgentRollout.
+func ValidateAutoUpdateAgentRollout(v *autoupdate.AutoUpdateAgentRollout) error {
+ if v == nil {
+ return trace.BadParameter("AutoUpdateAgentRollout is nil")
+ }
+ if v.Metadata == nil {
+ return trace.BadParameter("Metadata is nil")
+ }
+ if v.Metadata.Name != types.MetaNameAutoUpdateAgentRollout {
+ return trace.BadParameter("Name is not valid")
+ }
+ if v.Spec == nil {
+ return trace.BadParameter("Spec is nil")
+ }
+ if err := checkVersion(v.Spec.StartVersion); err != nil {
+ return trace.Wrap(err, "validating spec.start_version")
+ }
+ if err := checkVersion(v.Spec.TargetVersion); err != nil {
+ return trace.Wrap(err, "validating spec.target_version")
+ }
+ if err := checkAgentsMode(v.Spec.AutoupdateMode); err != nil {
+ return trace.Wrap(err, "validating spec.autoupdate_mode")
+ }
+ if err := checkScheduleName(v.Spec.Schedule); err != nil {
+ return trace.Wrap(err, "validating spec.schedule")
+ }
+ if err := checkAgentsStrategy(v.Spec.Strategy); err != nil {
+ return trace.Wrap(err, "validating spec.strategy")
+ }
+
+ return nil
+}
diff --git a/api/types/autoupdate/rollout_test.go b/api/types/autoupdate/rollout_test.go
new file mode 100644
index 0000000000000..cce4dc8495d83
--- /dev/null
+++ b/api/types/autoupdate/rollout_test.go
@@ -0,0 +1,145 @@
+/*
+Copyright 2024 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+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 AutoUpdateConfig resource.
+func TestNewAutoUpdateAgentRollout(t *testing.T) {
+ tests := []struct {
+ name string
+ spec *autoupdate.AutoUpdateAgentRolloutSpec
+ want *autoupdate.AutoUpdateAgentRollout
+ assertErr func(*testing.T, error, ...any)
+ }{
+ {
+ name: "success valid rollout",
+ spec: &autoupdate.AutoUpdateAgentRolloutSpec{
+ StartVersion: "1.2.3",
+ TargetVersion: "2.3.4-dev",
+ Schedule: AgentsScheduleRegular,
+ AutoupdateMode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyHaltOnError,
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.NoError(t, err)
+ },
+ want: &autoupdate.AutoUpdateAgentRollout{
+ Kind: types.KindAutoUpdateAgentRollout,
+ Version: types.V1,
+ Metadata: &headerv1.Metadata{
+ Name: types.MetaNameAutoUpdateAgentRollout,
+ },
+ Spec: &autoupdate.AutoUpdateAgentRolloutSpec{
+ StartVersion: "1.2.3",
+ TargetVersion: "2.3.4-dev",
+ Schedule: AgentsScheduleRegular,
+ AutoupdateMode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyHaltOnError,
+ },
+ },
+ },
+ {
+ name: "missing spec",
+ spec: nil,
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "Spec is nil")
+ },
+ },
+ {
+ name: "missing start version",
+ spec: &autoupdate.AutoUpdateAgentRolloutSpec{
+ TargetVersion: "2.3.4-dev",
+ Schedule: AgentsScheduleRegular,
+ AutoupdateMode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyHaltOnError,
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "start_version\n\tversion is unset")
+ },
+ },
+ {
+ name: "invalid target version",
+ spec: &autoupdate.AutoUpdateAgentRolloutSpec{
+ StartVersion: "1.2.3",
+ TargetVersion: "2-3-4",
+ Schedule: AgentsScheduleRegular,
+ AutoupdateMode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyHaltOnError,
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "target_version\n\tversion \"2-3-4\" is not a valid semantic version")
+ },
+ },
+ {
+ name: "invalid autoupdate mode",
+ spec: &autoupdate.AutoUpdateAgentRolloutSpec{
+ StartVersion: "1.2.3",
+ TargetVersion: "2.3.4-dev",
+ Schedule: AgentsScheduleRegular,
+ AutoupdateMode: "invalid-mode",
+ Strategy: AgentsStrategyHaltOnError,
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "unsupported agents mode: \"invalid-mode\"")
+ },
+ },
+ {
+ name: "invalid schedule name",
+ spec: &autoupdate.AutoUpdateAgentRolloutSpec{
+ StartVersion: "1.2.3",
+ TargetVersion: "2.3.4-dev",
+ Schedule: "invalid-schedule",
+ AutoupdateMode: AgentsUpdateModeEnabled,
+ Strategy: AgentsStrategyHaltOnError,
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "unsupported schedule type: \"invalid-schedule\"")
+ },
+ },
+ {
+ name: "invalid strategy",
+ spec: &autoupdate.AutoUpdateAgentRolloutSpec{
+ StartVersion: "1.2.3",
+ TargetVersion: "2.3.4-dev",
+ Schedule: AgentsScheduleRegular,
+ AutoupdateMode: AgentsUpdateModeEnabled,
+ Strategy: "invalid-strategy",
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "unsupported agents strategy: \"invalid-strategy\"")
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := NewAutoUpdateAgentRollout(tt.spec)
+ tt.assertErr(t, err)
+ require.Empty(t, cmp.Diff(got, tt.want, protocmp.Transform()))
+ })
+ }
+}
diff --git a/api/types/autoupdate/utils.go b/api/types/autoupdate/utils.go
new file mode 100644
index 0000000000000..4772ff8a94411
--- /dev/null
+++ b/api/types/autoupdate/utils.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2024 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package autoupdate
+
+import (
+ "github.com/coreos/go-semver/semver"
+ "github.com/gravitational/trace"
+)
+
+func checkVersion(version string) error {
+ if version == "" {
+ return trace.BadParameter("version is unset")
+ }
+ if _, err := semver.NewVersion(version); err != nil {
+ return trace.BadParameter("version %q is not a valid semantic version", version)
+ }
+ return nil
+}
+
+func checkAgentsMode(mode string) error {
+ switch mode {
+ case AgentsUpdateModeEnabled, AgentsUpdateModeDisabled, AgentsUpdateModeSuspended:
+ return nil
+ default:
+ return trace.BadParameter("unsupported agents mode: %q", mode)
+ }
+}
+
+func checkToolsMode(mode string) error {
+ switch mode {
+ case ToolsUpdateModeEnabled, ToolsUpdateModeDisabled:
+ return nil
+ default:
+ return trace.BadParameter("unsupported tools mode: %q", mode)
+ }
+}
+
+func checkScheduleName(schedule string) error {
+ switch schedule {
+ case AgentsScheduleRegular, AgentsScheduleImmediate:
+ return nil
+ default:
+ return trace.BadParameter("unsupported schedule type: %q", schedule)
+ }
+}
+
+func checkAgentsStrategy(strategy string) error {
+ switch strategy {
+ case AgentsStrategyHaltOnError, AgentsStrategyTimeBased:
+ return nil
+ default:
+ return trace.BadParameter("unsupported agents strategy: %q", strategy)
+ }
+}
diff --git a/api/types/autoupdate/version.go b/api/types/autoupdate/version.go
index ad2d12f265949..4bfe14c53fc1f 100644
--- a/api/types/autoupdate/version.go
+++ b/api/types/autoupdate/version.go
@@ -1,25 +1,22 @@
/*
- * 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 .
- */
+Copyright 2024 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
package autoupdate
import (
- "github.com/coreos/go-semver/semver"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1"
@@ -61,11 +58,22 @@ func ValidateAutoUpdateVersion(v *autoupdate.AutoUpdateVersion) error {
}
if v.Spec.Tools != nil {
- if v.Spec.Tools.TargetVersion == "" {
- return trace.BadParameter("TargetVersion is unset")
+ if err := checkVersion(v.Spec.Tools.TargetVersion); err != nil {
+ return trace.Wrap(err, "validating spec.tools.target_version")
+ }
+ }
+ if v.Spec.Agents != nil {
+ if err := checkVersion(v.Spec.Agents.StartVersion); err != nil {
+ return trace.Wrap(err, "validating spec.agents.start_version")
+ }
+ if err := checkVersion(v.Spec.Agents.TargetVersion); err != nil {
+ return trace.Wrap(err, "validating spec.agents.target_version")
+ }
+ if err := checkAgentsMode(v.Spec.Agents.Mode); err != nil {
+ return trace.Wrap(err, "validating spec.agents.mode")
}
- if _, err := semver.NewVersion(v.Spec.Tools.TargetVersion); err != nil {
- return trace.BadParameter("TargetVersion is not a valid semantic version")
+ if err := checkScheduleName(v.Spec.Agents.Schedule); err != nil {
+ return trace.Wrap(err, "validating spec.agents.schedule")
}
}
diff --git a/api/types/autoupdate/version_test.go b/api/types/autoupdate/version_test.go
index 70790a204b219..a59a4f6fe6c22 100644
--- a/api/types/autoupdate/version_test.go
+++ b/api/types/autoupdate/version_test.go
@@ -1,20 +1,18 @@
/*
- * 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 .
- */
+Copyright 2024 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
package autoupdate
@@ -69,7 +67,7 @@ func TestNewAutoUpdateVersion(t *testing.T) {
},
},
assertErr: func(t *testing.T, err error, a ...any) {
- require.ErrorContains(t, err, "TargetVersion is unset")
+ require.ErrorContains(t, err, "target_version\n\tversion is unset")
},
},
{
@@ -80,7 +78,7 @@ func TestNewAutoUpdateVersion(t *testing.T) {
},
},
assertErr: func(t *testing.T, err error, a ...any) {
- require.ErrorContains(t, err, "TargetVersion is not a valid semantic version")
+ require.ErrorContains(t, err, "target_version\n\tversion \"17-0-0\" is not a valid semantic version")
},
},
{
@@ -90,6 +88,91 @@ func TestNewAutoUpdateVersion(t *testing.T) {
require.ErrorContains(t, err, "Spec is nil")
},
},
+ {
+ name: "success agents autoupdate version",
+ spec: &autoupdate.AutoUpdateVersionSpec{
+ Agents: &autoupdate.AutoUpdateVersionSpecAgents{
+ StartVersion: "1.2.3-dev.1",
+ TargetVersion: "1.2.3-dev.2",
+ Schedule: AgentsScheduleRegular,
+ Mode: AgentsUpdateModeEnabled,
+ },
+ },
+ 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{
+ Agents: &autoupdate.AutoUpdateVersionSpecAgents{
+ StartVersion: "1.2.3-dev.1",
+ TargetVersion: "1.2.3-dev.2",
+ Schedule: AgentsScheduleRegular,
+ Mode: AgentsUpdateModeEnabled,
+ },
+ },
+ },
+ },
+ {
+ name: "invalid empty agents start version",
+ spec: &autoupdate.AutoUpdateVersionSpec{
+ Agents: &autoupdate.AutoUpdateVersionSpecAgents{
+ StartVersion: "",
+ TargetVersion: "1.2.3",
+ Mode: AgentsUpdateModeEnabled,
+ Schedule: AgentsScheduleRegular,
+ },
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "start_version\n\tversion is unset")
+ },
+ },
+ {
+ name: "invalid empty agents target version",
+ spec: &autoupdate.AutoUpdateVersionSpec{
+ Agents: &autoupdate.AutoUpdateVersionSpecAgents{
+ StartVersion: "1.2.3-dev",
+ TargetVersion: "",
+ Mode: AgentsUpdateModeEnabled,
+ Schedule: AgentsScheduleRegular,
+ },
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "target_version\n\tversion is unset")
+ },
+ },
+ {
+ name: "invalid semantic agents start version",
+ spec: &autoupdate.AutoUpdateVersionSpec{
+ Agents: &autoupdate.AutoUpdateVersionSpecAgents{
+ StartVersion: "17-0-0",
+ TargetVersion: "1.2.3",
+ Mode: AgentsUpdateModeEnabled,
+ Schedule: AgentsScheduleRegular,
+ },
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "start_version\n\tversion \"17-0-0\" is not a valid semantic version")
+ },
+ },
+ {
+ name: "invalid semantic agents target version",
+ spec: &autoupdate.AutoUpdateVersionSpec{
+ Agents: &autoupdate.AutoUpdateVersionSpecAgents{
+ StartVersion: "1.2.3",
+ TargetVersion: "17-0-0",
+ Mode: AgentsUpdateModeEnabled,
+ Schedule: AgentsScheduleRegular,
+ },
+ },
+ assertErr: func(t *testing.T, err error, a ...any) {
+ require.ErrorContains(t, err, "target_version\n\tversion \"17-0-0\" is not a valid semantic version")
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/api/types/constants.go b/api/types/constants.go
index 87c0335586bf6..b50b91c480aad 100644
--- a/api/types/constants.go
+++ b/api/types/constants.go
@@ -331,12 +331,18 @@ const (
// KindAutoUpdateVersion is the resource with autoupdate versions.
KindAutoUpdateVersion = "autoupdate_version"
+ // KindAutoUpdateAgentRollout is the resource that controls and tracks agent rollouts.
+ KindAutoUpdateAgentRollout = "autoupdate_agent_rollout"
+
// 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"
+ // MetaNameAutoUpdateAgentRollout is the name of the autoupdate agent rollout resource.
+ MetaNameAutoUpdateAgentRollout = "autoupdate-agent-rollout"
+
// KindClusterAuditConfig is the resource that holds cluster audit configuration.
KindClusterAuditConfig = "cluster_audit_config"