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"