From 63838a3e12ddac8b9b024d990aa780941eb8f26c Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Thu, 21 Nov 2024 16:08:23 -0500 Subject: [PATCH] [teleport-update] Add support for version pinning (#49307) * pinning * cleanup * unskip * cleanup * unpin * typo --- .../already_disabled.golden | 2 + .../TestUpdater_Disable/enabled.golden | 2 + .../FIPS_and_Enterprise_flags.golden | 4 +- .../backup_version_kept_for_validation.golden | 4 +- .../backup_version_removed_on_install.golden | 4 +- .../config_does_not_exist.golden | 4 +- .../config_from_file.golden | 2 + .../config_from_user.golden | 2 + .../defaults.golden} | 4 +- .../insecure_URL.golden | 2 + .../install_error.golden | 2 + .../invalid_metadata.golden | 2 + .../TestUpdater_Install/override_skip.golden | 12 + .../version_already_installed.golden | 4 +- .../TestUpdater_Unpin/not_pinned.golden | 12 + .../testdata/TestUpdater_Unpin/pinned.golden | 12 + .../FIPS_and_Enterprise_flags.golden | 2 + .../backup_version_kept_when_no_change.golden | 2 + .../backup_version_removed_on_install.golden | 2 + .../TestUpdater_Update/insecure_URL.golden | 2 + .../TestUpdater_Update/install_error.golden | 2 + .../invalid_metadata.golden | 2 + .../TestUpdater_Update/pinned_version.golden | 12 + .../TestUpdater_Update/reload_fails.golden | 2 + .../TestUpdater_Update/setup_fails.golden | 2 + .../TestUpdater_Update/skip_version.golden | 12 + .../updates_disabled_during_window.golden | 2 + .../updates_disabled_outside_of_window.golden | 2 + .../updates_enabled_during_window.golden | 2 + .../updates_enabled_outside_of_window.golden | 2 + ...version_already_installed_in_window.golden | 2 + ...already_installed_outside_of_window.golden | 2 + lib/autoupdate/agent/updater.go | 222 ++++++++++-------- lib/autoupdate/agent/updater_test.go | 153 +++++++++++- tool/teleport-update/main.go | 56 ++++- 35 files changed, 446 insertions(+), 111 deletions(-) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/FIPS_and_Enterprise_flags.golden (72%) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/backup_version_kept_for_validation.golden (73%) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/backup_version_removed_on_install.golden (73%) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/config_does_not_exist.golden (72%) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/config_from_file.golden (83%) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/config_from_user.golden (84%) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable/already_enabled.golden => TestUpdater_Install/defaults.golden} (73%) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/insecure_URL.golden (81%) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/install_error.golden (80%) rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/invalid_metadata.golden (79%) create mode 100644 lib/autoupdate/agent/testdata/TestUpdater_Install/override_skip.golden rename lib/autoupdate/agent/testdata/{TestUpdater_Enable => TestUpdater_Install}/version_already_installed.golden (72%) create mode 100644 lib/autoupdate/agent/testdata/TestUpdater_Unpin/not_pinned.golden create mode 100644 lib/autoupdate/agent/testdata/TestUpdater_Unpin/pinned.golden create mode 100644 lib/autoupdate/agent/testdata/TestUpdater_Update/pinned_version.golden create mode 100644 lib/autoupdate/agent/testdata/TestUpdater_Update/skip_version.golden diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Disable/already_disabled.golden b/lib/autoupdate/agent/testdata/TestUpdater_Disable/already_disabled.golden index 2ddb840b01794..3f31750f4fe2e 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Disable/already_disabled.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Disable/already_disabled.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: "" enabled: false + pinned: false status: active_version: "" backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Disable/enabled.golden b/lib/autoupdate/agent/testdata/TestUpdater_Disable/enabled.golden index 2ddb840b01794..3f31750f4fe2e 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Disable/enabled.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Disable/enabled.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: "" enabled: false + pinned: false status: active_version: "" backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/FIPS_and_Enterprise_flags.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/FIPS_and_Enterprise_flags.golden similarity index 72% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/FIPS_and_Enterprise_flags.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/FIPS_and_Enterprise_flags.golden index d9e09a2c95d71..01e93f92914bb 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/FIPS_and_Enterprise_flags.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/FIPS_and_Enterprise_flags.golden @@ -4,7 +4,9 @@ spec: proxy: localhost group: "" url_template: "" - enabled: true + enabled: false + pinned: false status: active_version: 16.3.0 backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/backup_version_kept_for_validation.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/backup_version_kept_for_validation.golden similarity index 73% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/backup_version_kept_for_validation.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/backup_version_kept_for_validation.golden index 77829fb91706a..9d7a8ddc539c2 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/backup_version_kept_for_validation.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/backup_version_kept_for_validation.golden @@ -4,7 +4,9 @@ spec: proxy: localhost group: "" url_template: "" - enabled: true + enabled: false + pinned: false status: active_version: 16.3.0 backup_version: backup-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/backup_version_removed_on_install.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/backup_version_removed_on_install.golden similarity index 73% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/backup_version_removed_on_install.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/backup_version_removed_on_install.golden index 9dd03b7888da4..55aa3076c66cc 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/backup_version_removed_on_install.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/backup_version_removed_on_install.golden @@ -4,7 +4,9 @@ spec: proxy: localhost group: "" url_template: "" - enabled: true + enabled: false + pinned: false status: active_version: 16.3.0 backup_version: old-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/config_does_not_exist.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/config_does_not_exist.golden similarity index 72% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/config_does_not_exist.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/config_does_not_exist.golden index d9e09a2c95d71..01e93f92914bb 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/config_does_not_exist.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/config_does_not_exist.golden @@ -4,7 +4,9 @@ spec: proxy: localhost group: "" url_template: "" - enabled: true + enabled: false + pinned: false status: active_version: 16.3.0 backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/config_from_file.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/config_from_file.golden similarity index 83% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/config_from_file.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/config_from_file.golden index 61e41f76ca234..95cf51146d769 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/config_from_file.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/config_from_file.golden @@ -5,6 +5,8 @@ spec: group: group url_template: https://example.com enabled: true + pinned: false status: active_version: 16.3.0 backup_version: old-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/config_from_user.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/config_from_user.golden similarity index 84% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/config_from_user.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/config_from_user.golden index c1f0fd166b497..02157c7f6751a 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/config_from_user.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/config_from_user.golden @@ -5,6 +5,8 @@ spec: group: new-group url_template: https://example.com/new enabled: true + pinned: false status: active_version: new-version backup_version: old-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/already_enabled.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/defaults.golden similarity index 73% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/already_enabled.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/defaults.golden index 9dd03b7888da4..55aa3076c66cc 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/already_enabled.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/defaults.golden @@ -4,7 +4,9 @@ spec: proxy: localhost group: "" url_template: "" - enabled: true + enabled: false + pinned: false status: active_version: 16.3.0 backup_version: old-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/insecure_URL.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/insecure_URL.golden similarity index 81% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/insecure_URL.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/insecure_URL.golden index d3da980a1afde..fcfc5f9ee2085 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/insecure_URL.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/insecure_URL.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: http://example.com enabled: false + pinned: false status: active_version: "" backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/install_error.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/install_error.golden similarity index 80% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/install_error.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/install_error.golden index 2ddb840b01794..3f31750f4fe2e 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/install_error.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/install_error.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: "" enabled: false + pinned: false status: active_version: "" backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/invalid_metadata.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/invalid_metadata.golden similarity index 79% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/invalid_metadata.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/invalid_metadata.golden index df0c99fe5fe7e..6da0a8480b84e 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/invalid_metadata.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/invalid_metadata.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: "" enabled: false + pinned: false status: active_version: "" backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Install/override_skip.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/override_skip.golden new file mode 100644 index 0000000000000..55aa3076c66cc --- /dev/null +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/override_skip.golden @@ -0,0 +1,12 @@ +version: v1 +kind: update_config +spec: + proxy: localhost + group: "" + url_template: "" + enabled: false + pinned: false +status: + active_version: 16.3.0 + backup_version: old-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Enable/version_already_installed.golden b/lib/autoupdate/agent/testdata/TestUpdater_Install/version_already_installed.golden similarity index 72% rename from lib/autoupdate/agent/testdata/TestUpdater_Enable/version_already_installed.golden rename to lib/autoupdate/agent/testdata/TestUpdater_Install/version_already_installed.golden index d9e09a2c95d71..01e93f92914bb 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Enable/version_already_installed.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Install/version_already_installed.golden @@ -4,7 +4,9 @@ spec: proxy: localhost group: "" url_template: "" - enabled: true + enabled: false + pinned: false status: active_version: 16.3.0 backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Unpin/not_pinned.golden b/lib/autoupdate/agent/testdata/TestUpdater_Unpin/not_pinned.golden new file mode 100644 index 0000000000000..3f31750f4fe2e --- /dev/null +++ b/lib/autoupdate/agent/testdata/TestUpdater_Unpin/not_pinned.golden @@ -0,0 +1,12 @@ +version: v1 +kind: update_config +spec: + proxy: "" + group: "" + url_template: "" + enabled: false + pinned: false +status: + active_version: "" + backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Unpin/pinned.golden b/lib/autoupdate/agent/testdata/TestUpdater_Unpin/pinned.golden new file mode 100644 index 0000000000000..3f31750f4fe2e --- /dev/null +++ b/lib/autoupdate/agent/testdata/TestUpdater_Unpin/pinned.golden @@ -0,0 +1,12 @@ +version: v1 +kind: update_config +spec: + proxy: "" + group: "" + url_template: "" + enabled: false + pinned: false +status: + active_version: "" + backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/FIPS_and_Enterprise_flags.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/FIPS_and_Enterprise_flags.golden index 066926264d28e..7a1a7032fb885 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/FIPS_and_Enterprise_flags.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/FIPS_and_Enterprise_flags.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: https://example.com enabled: true + pinned: false status: active_version: 16.3.0 backup_version: old-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/backup_version_kept_when_no_change.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/backup_version_kept_when_no_change.golden index 646397b4713d7..757a156bbfa96 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/backup_version_kept_when_no_change.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/backup_version_kept_when_no_change.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: https://example.com enabled: true + pinned: false status: active_version: 16.3.0 backup_version: backup-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/backup_version_removed_on_install.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/backup_version_removed_on_install.golden index 066926264d28e..7a1a7032fb885 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/backup_version_removed_on_install.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/backup_version_removed_on_install.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: https://example.com enabled: true + pinned: false status: active_version: 16.3.0 backup_version: old-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/insecure_URL.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/insecure_URL.golden index 6ff42e075b57a..c98efffd903f6 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/insecure_URL.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/insecure_URL.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: http://example.com enabled: true + pinned: false status: active_version: "" backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/install_error.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/install_error.golden index 3b9e19637eef5..fd57902d7d177 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/install_error.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/install_error.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: "" enabled: true + pinned: false status: active_version: "" backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/invalid_metadata.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/invalid_metadata.golden index e47fe44a13da0..a9f316edbbe7c 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/invalid_metadata.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/invalid_metadata.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: "" enabled: false + pinned: false status: active_version: "" backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/pinned_version.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/pinned_version.golden new file mode 100644 index 0000000000000..2d14ba4eb6504 --- /dev/null +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/pinned_version.golden @@ -0,0 +1,12 @@ +version: v1 +kind: update_config +spec: + proxy: localhost + group: "" + url_template: https://example.com + enabled: true + pinned: true +status: + active_version: old-version + backup_version: backup-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/reload_fails.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/reload_fails.golden index 3628297dd9443..bbf9d429ef96e 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/reload_fails.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/reload_fails.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: https://example.com enabled: true + pinned: false status: active_version: old-version backup_version: backup-version + skip_version: 16.3.0 diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/setup_fails.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/setup_fails.golden index 3628297dd9443..bbf9d429ef96e 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/setup_fails.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/setup_fails.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: https://example.com enabled: true + pinned: false status: active_version: old-version backup_version: backup-version + skip_version: 16.3.0 diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/skip_version.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/skip_version.golden new file mode 100644 index 0000000000000..bbf9d429ef96e --- /dev/null +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/skip_version.golden @@ -0,0 +1,12 @@ +version: v1 +kind: update_config +spec: + proxy: localhost + group: "" + url_template: https://example.com + enabled: true + pinned: false +status: + active_version: old-version + backup_version: backup-version + skip_version: 16.3.0 diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_disabled_during_window.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_disabled_during_window.golden index dc449aab8503e..3f50f3d6cc3fe 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_disabled_during_window.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_disabled_during_window.golden @@ -5,6 +5,8 @@ spec: group: group url_template: https://example.com enabled: false + pinned: false status: active_version: old-version backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_disabled_outside_of_window.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_disabled_outside_of_window.golden index dc449aab8503e..3f50f3d6cc3fe 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_disabled_outside_of_window.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_disabled_outside_of_window.golden @@ -5,6 +5,8 @@ spec: group: group url_template: https://example.com enabled: false + pinned: false status: active_version: old-version backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_enabled_during_window.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_enabled_during_window.golden index 61e41f76ca234..95cf51146d769 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_enabled_during_window.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_enabled_during_window.golden @@ -5,6 +5,8 @@ spec: group: group url_template: https://example.com enabled: true + pinned: false status: active_version: 16.3.0 backup_version: old-version + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_enabled_outside_of_window.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_enabled_outside_of_window.golden index 802c475ba90f4..e63a0dc9363d5 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_enabled_outside_of_window.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/updates_enabled_outside_of_window.golden @@ -5,6 +5,8 @@ spec: group: group url_template: https://example.com enabled: true + pinned: false status: active_version: old-version backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/version_already_installed_in_window.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/version_already_installed_in_window.golden index 6e16d193a8eb0..5c33b18377c84 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/version_already_installed_in_window.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/version_already_installed_in_window.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: https://example.com enabled: true + pinned: false status: active_version: 16.3.0 backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/testdata/TestUpdater_Update/version_already_installed_outside_of_window.golden b/lib/autoupdate/agent/testdata/TestUpdater_Update/version_already_installed_outside_of_window.golden index 6e16d193a8eb0..5c33b18377c84 100644 --- a/lib/autoupdate/agent/testdata/TestUpdater_Update/version_already_installed_outside_of_window.golden +++ b/lib/autoupdate/agent/testdata/TestUpdater_Update/version_already_installed_outside_of_window.golden @@ -5,6 +5,8 @@ spec: group: "" url_template: https://example.com enabled: true + pinned: false status: active_version: 16.3.0 backup_version: "" + skip_version: "" diff --git a/lib/autoupdate/agent/updater.go b/lib/autoupdate/agent/updater.go index 58ec5b0d98a25..4c968a0dd0f91 100644 --- a/lib/autoupdate/agent/updater.go +++ b/lib/autoupdate/agent/updater.go @@ -103,6 +103,8 @@ type UpdateSpec struct { URLTemplate string `yaml:"url_template"` // Enabled controls whether auto-updates are enabled. Enabled bool `yaml:"enabled"` + // Pinned controls whether the active_version is pinned. + Pinned bool `yaml:"pinned"` } // UpdateStatus describes the status field in update.yaml. @@ -111,6 +113,8 @@ type UpdateStatus struct { ActiveVersion string `yaml:"active_version"` // BackupVersion is the last working version of Teleport. BackupVersion string `yaml:"backup_version"` + // SkipVersion is the last reverted version of Teleport. + SkipVersion string `yaml:"skip_version"` } // NewLocalUpdater returns a new Updater that auto-updates local @@ -315,26 +319,18 @@ const ( // OverrideConfig contains overrides for individual update operations. // If validated, these overrides may be persisted to disk. type OverrideConfig struct { - // Proxy address, scheme and port optional. - // Overrides existing value if specified. - Proxy string - // Group identifier for updates (e.g., staging) - // Overrides existing value if specified. - Group string - // URLTemplate for the Teleport tgz download URL - // Overrides existing value if specified. - URLTemplate string + UpdateSpec // ForceVersion to the specified version. ForceVersion string // ForceFlags in installed Teleport. ForceFlags InstallFlags } -// Enable enables agent updates and attempts an initial update. -// If the initial update succeeds, auto-updates are enabled and the configuration is persisted. -// Otherwise, the auto-updates configuration is not changed. +// Install attempts an initial installation of Teleport. +// If the initial installation succeeds, the override configuration is persisted. +// Otherwise, the configuration is not changed. // This function is idempotent. -func (u *Updater) Enable(ctx context.Context, override OverrideConfig) error { +func (u *Updater) Install(ctx context.Context, override OverrideConfig) error { // Read configuration from update.yaml and override any new values passed as flags. cfg, err := readConfig(u.ConfigPath) if err != nil { @@ -343,56 +339,42 @@ func (u *Updater) Enable(ctx context.Context, override OverrideConfig) error { if err := validateConfigSpec(&cfg.Spec, override); err != nil { return trace.Wrap(err) } - if cfg.Spec.Proxy == "" { - return trace.Errorf("Teleport proxy URL must be specified with --proxy or present in %s", updateConfigName) - } + + activeVersion := cfg.Status.ActiveVersion + skipVersion := cfg.Status.SkipVersion // Lookup target version from the proxy. - addr, err := libutils.ParseAddr(cfg.Spec.Proxy) + resp, err := u.find(ctx, cfg) if err != nil { - return trace.Errorf("failed to parse proxy server address: %w", err) - } - targetVersion := override.ForceVersion - flags := override.ForceFlags - if targetVersion == "" { - resp, err := webclient.Find(&webclient.Config{ - Context: ctx, - ProxyAddr: addr.Addr, - Insecure: u.InsecureSkipVerify, - Timeout: 30 * time.Second, - UpdateGroup: cfg.Spec.Group, - Pool: u.Pool, - }) - if err != nil { - return trace.Errorf("failed to request version from proxy: %w", err) - } - targetVersion = resp.AutoUpdate.AgentVersion - switch resp.Edition { - case modules.BuildEnterprise: - flags |= FlagEnterprise - case modules.BuildOSS, modules.BuildCommunity: - default: - u.Log.WarnContext(ctx, "Unknown edition detected, defaulting to community.", "edition", resp.Edition) - } - if resp.FIPS { - flags |= FlagFIPS - } + return trace.Wrap(err) + } + targetVersion := resp.version + flags := resp.flags + flags |= override.ForceFlags + if override.ForceVersion != "" { + targetVersion = override.ForceVersion } - if targetVersion == "" { + switch targetVersion { + case "": return trace.Errorf("agent version not available from Teleport cluster") + case skipVersion: + u.Log.WarnContext(ctx, "Target version was previously marked as broken. Retrying update.", targetVersionKey, targetVersion, activeVersionKey, activeVersion) + default: + u.Log.InfoContext(ctx, "Initiating installation.", targetVersionKey, targetVersion, activeVersionKey, activeVersion) } - u.Log.InfoContext(ctx, "Initiating initial update.", targetVersionKey, targetVersion, activeVersionKey, cfg.Status.ActiveVersion) - if err := u.update(ctx, cfg, targetVersion, flags); err != nil { return trace.Wrap(err) } + if targetVersion == cfg.Status.SkipVersion { + cfg.Status.SkipVersion = "" + } - // Always write the configuration file if enable succeeds. + // Only write the configuration file if the initial update succeeds. + // Note: skip_version is never set on failed enable, only failed update. - cfg.Spec.Enabled = true if err := writeConfig(u.ConfigPath, cfg); err != nil { return trace.Errorf("failed to write %s: %w", updateConfigName, err) } @@ -418,6 +400,24 @@ func (u *Updater) Disable(ctx context.Context) error { return nil } +// Unpin allows the current version to be changed by Update. +// This function is idempotent. +func (u *Updater) Unpin(ctx context.Context) error { + cfg, err := readConfig(u.ConfigPath) + if err != nil { + return trace.Errorf("failed to read %s: %w", updateConfigName, err) + } + if !cfg.Spec.Pinned { + u.Log.InfoContext(ctx, "Current version not pinned.", activeVersionKey, cfg.Status.ActiveVersion) + return nil + } + cfg.Spec.Pinned = false + if err := writeConfig(u.ConfigPath, cfg); err != nil { + return trace.Errorf("failed to write %s: %w", updateConfigName, err) + } + return nil +} + // Update initiates an agent update. // If the update succeeds, the new installed version is marked as active. // Otherwise, the auto-updates configuration is not changed. @@ -433,50 +433,36 @@ func (u *Updater) Update(ctx context.Context) error { return trace.Wrap(err) } activeVersion := cfg.Status.ActiveVersion + skipVersion := cfg.Status.SkipVersion if !cfg.Spec.Enabled { u.Log.InfoContext(ctx, "Automatic updates disabled.", activeVersionKey, activeVersion) return nil } - if cfg.Spec.Proxy == "" { - return trace.Errorf("Teleport proxy URL must be present in %s", updateConfigName) - } - - // Lookup target version from the proxy. - addr, err := libutils.ParseAddr(cfg.Spec.Proxy) + resp, err := u.find(ctx, cfg) if err != nil { - return trace.Errorf("failed to parse proxy server address: %w", err) - } - resp, err := webclient.Find(&webclient.Config{ - Context: ctx, - ProxyAddr: addr.Addr, - Insecure: u.InsecureSkipVerify, - Timeout: 30 * time.Second, - UpdateGroup: cfg.Spec.Group, - Pool: u.Pool, - }) - if err != nil { - return trace.Errorf("failed to request version from proxy: %w", err) - } - targetVersion := resp.AutoUpdate.AgentVersion - var flags InstallFlags - switch resp.Edition { - case modules.BuildEnterprise: - flags |= FlagEnterprise - case modules.BuildOSS, modules.BuildCommunity: - default: - u.Log.WarnContext(ctx, "Unknown edition detected, defaulting to community.", "edition", resp.Edition) + return trace.Wrap(err) } - if resp.FIPS { - flags |= FlagFIPS + targetVersion := resp.version + + if cfg.Spec.Pinned { + switch targetVersion { + case activeVersion: + u.Log.InfoContext(ctx, "Teleport is up-to-date. Installation is pinned to prevent future updates.", activeVersionKey, activeVersion) + default: + u.Log.InfoContext(ctx, "Teleport version is pinned. Skipping update.", targetVersionKey, targetVersion, activeVersionKey, activeVersion) + } + return nil } - if !resp.AutoUpdate.AgentAutoUpdate { + if !resp.active { switch targetVersion { case "": u.Log.WarnContext(ctx, "Cannot determine target agent version. Waiting for both version and update window.") case activeVersion: u.Log.InfoContext(ctx, "Teleport is up-to-date. Update window is not active.", activeVersionKey, activeVersion) + case skipVersion: + u.Log.InfoContext(ctx, "Update available, but the new version is marked as broken. Update window is not active.", targetVersionKey, targetVersion, activeVersionKey, activeVersion) default: u.Log.InfoContext(ctx, "Update available, but update window is not active.", targetVersionKey, targetVersion, activeVersionKey, activeVersion) } @@ -490,24 +476,68 @@ func (u *Updater) Update(ctx context.Context) error { case activeVersion: u.Log.InfoContext(ctx, "Teleport is up-to-date. Update window is active, but no action is needed.", activeVersionKey, activeVersion) return nil + case skipVersion: + u.Log.InfoContext(ctx, "Update available, but the new version is marked as broken. Skipping update during the update window.", targetVersionKey, targetVersion, activeVersionKey, activeVersion) + return nil default: u.Log.InfoContext(ctx, "Update available. Initiating update.", targetVersionKey, targetVersion, activeVersionKey, activeVersion) } + time.Sleep(resp.jitter) - jitterSec := resp.AutoUpdate.AgentUpdateJitterSeconds - time.Sleep(time.Duration(jitterSec) * time.Second) - - if err := u.update(ctx, cfg, targetVersion, flags); err != nil { - return trace.Wrap(err) + updateErr := u.update(ctx, cfg, targetVersion, resp.flags) + writeErr := writeConfig(u.ConfigPath, cfg) + if writeErr != nil { + writeErr = trace.Errorf("failed to write %s: %w", updateConfigName, writeErr) + } else { + u.Log.InfoContext(ctx, "Configuration updated.") } + return trace.NewAggregate(updateErr, writeErr) +} - // Write the configuration file if update succeeds. - - if err := writeConfig(u.ConfigPath, cfg); err != nil { - return trace.Errorf("failed to write %s: %w", updateConfigName, err) +func (u *Updater) find(ctx context.Context, cfg *UpdateConfig) (findResp, error) { + if cfg.Spec.Proxy == "" { + return findResp{}, trace.Errorf("Teleport proxy URL must be specified with --proxy or present in %s", updateConfigName) } - u.Log.InfoContext(ctx, "Configuration updated.") - return nil + addr, err := libutils.ParseAddr(cfg.Spec.Proxy) + if err != nil { + return findResp{}, trace.Errorf("failed to parse proxy server address: %w", err) + } + resp, err := webclient.Find(&webclient.Config{ + Context: ctx, + ProxyAddr: addr.Addr, + Insecure: u.InsecureSkipVerify, + Timeout: 30 * time.Second, + UpdateGroup: cfg.Spec.Group, + Pool: u.Pool, + }) + if err != nil { + return findResp{}, trace.Errorf("failed to request version from proxy: %w", err) + } + var flags InstallFlags + switch resp.Edition { + case modules.BuildEnterprise: + flags |= FlagEnterprise + case modules.BuildOSS, modules.BuildCommunity: + default: + u.Log.WarnContext(ctx, "Unknown edition detected, defaulting to community.", "edition", resp.Edition) + } + if resp.FIPS { + flags |= FlagFIPS + } + jitterSec := resp.AutoUpdate.AgentUpdateJitterSeconds + return findResp{ + version: resp.AutoUpdate.AgentVersion, + flags: flags, + active: resp.AutoUpdate.AgentAutoUpdate, + jitter: time.Duration(jitterSec) * time.Second, + }, nil +} + +type findResp struct { + version string + flags InstallFlags + active bool + jitter time.Duration } func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion string, flags InstallFlags) error { @@ -551,6 +581,7 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion s // fix the link to restore the active version. revertConfig := func(ctx context.Context) bool { + cfg.Status.SkipVersion = targetVersion if ok := revert(ctx); !ok { u.Log.ErrorContext(ctx, "Failed to revert Teleport symlinks. Installation likely broken.") return false @@ -614,9 +645,8 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion s versions, err := u.Installer.List(ctx) if err != nil { - return trace.Errorf("failed to list installed versions: %w", err) - } - if n := len(versions); n > 2 { + u.Log.ErrorContext(ctx, "Failed to read installed versions.", errorKey, err) + } else if n := len(versions); n > 2 { u.Log.WarnContext(ctx, "More than 2 versions of Teleport installed. Version directory may need cleanup to save space.", "count", n) } return nil @@ -684,6 +714,12 @@ func validateConfigSpec(spec *UpdateSpec, override OverrideConfig) error { !strings.HasPrefix(strings.ToLower(spec.URLTemplate), "https://") { return trace.Errorf("Teleport download URL must use TLS (https://)") } + if override.Enabled { + spec.Enabled = true + } + if override.Pinned { + spec.Pinned = true + } return nil } diff --git a/lib/autoupdate/agent/updater_test.go b/lib/autoupdate/agent/updater_test.go index e553241981134..b67c808c6bbac 100644 --- a/lib/autoupdate/agent/updater_test.go +++ b/lib/autoupdate/agent/updater_test.go @@ -124,6 +124,92 @@ func TestUpdater_Disable(t *testing.T) { } } +func TestUpdater_Unpin(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cfg *UpdateConfig // nil -> file not present + errMatch string + }{ + { + name: "pinned", + cfg: &UpdateConfig{ + Version: updateConfigVersion, + Kind: updateConfigKind, + Spec: UpdateSpec{ + Pinned: true, + }, + }, + }, + { + name: "not pinned", + cfg: &UpdateConfig{ + Version: updateConfigVersion, + Kind: updateConfigKind, + Spec: UpdateSpec{ + Pinned: false, + }, + }, + }, + { + name: "config does not exist", + }, + { + name: "invalid metadata", + cfg: &UpdateConfig{ + Spec: UpdateSpec{ + Enabled: true, + }, + }, + errMatch: "invalid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + cfgPath := filepath.Join(dir, VersionsDirName, "update.yaml") + + updater, err := NewLocalUpdater(LocalUpdaterConfig{ + InsecureSkipVerify: true, + DataDir: dir, + }) + require.NoError(t, err) + + // Create config file only if provided in test case + if tt.cfg != nil { + b, err := yaml.Marshal(tt.cfg) + require.NoError(t, err) + err = os.WriteFile(cfgPath, b, 0600) + require.NoError(t, err) + } + + err = updater.Unpin(context.Background()) + if tt.errMatch != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errMatch) + return + } + require.NoError(t, err) + + data, err := os.ReadFile(cfgPath) + + // If no config is present, disable should not create it + if tt.cfg == nil { + require.ErrorIs(t, err, os.ErrNotExist) + return + } + require.NoError(t, err) + + if golden.ShouldSet() { + golden.Set(t, data) + } + require.Equal(t, string(golden.Get(t)), string(data)) + }) + } +} + func TestUpdater_Update(t *testing.T) { t.Parallel() @@ -398,6 +484,40 @@ func TestUpdater_Update(t *testing.T) { setupCalls: 1, errMatch: "reload error", }, + { + name: "skip version", + cfg: &UpdateConfig{ + Version: updateConfigVersion, + Kind: updateConfigKind, + Spec: UpdateSpec{ + URLTemplate: "https://example.com", + Enabled: true, + }, + Status: UpdateStatus{ + ActiveVersion: "old-version", + BackupVersion: "backup-version", + SkipVersion: "16.3.0", + }, + }, + inWindow: true, + }, + { + name: "pinned version", + cfg: &UpdateConfig{ + Version: updateConfigVersion, + Kind: updateConfigKind, + Spec: UpdateSpec{ + URLTemplate: "https://example.com", + Enabled: true, + Pinned: true, + }, + Status: UpdateStatus{ + ActiveVersion: "old-version", + BackupVersion: "backup-version", + }, + }, + inWindow: true, + }, } for _, tt := range tests { @@ -645,7 +765,7 @@ func TestUpdater_LinkPackage(t *testing.T) { } } -func TestUpdater_Enable(t *testing.T) { +func TestUpdater_Install(t *testing.T) { t.Parallel() tests := []struct { @@ -673,6 +793,7 @@ func TestUpdater_Enable(t *testing.T) { Version: updateConfigVersion, Kind: updateConfigKind, Spec: UpdateSpec{ + Enabled: true, Group: "group", URLTemplate: "https://example.com", }, @@ -702,27 +823,45 @@ func TestUpdater_Enable(t *testing.T) { }, }, userCfg: OverrideConfig{ - Group: "new-group", - URLTemplate: "https://example.com/new", + UpdateSpec: UpdateSpec{ + Enabled: true, + Group: "new-group", + URLTemplate: "https://example.com/new", + }, ForceVersion: "new-version", }, installedVersion: "new-version", installedTemplate: "https://example.com/new", linkedVersion: "new-version", + requestGroup: "new-group", reloadCalls: 1, setupCalls: 1, }, { - name: "already enabled", + name: "defaults", cfg: &UpdateConfig{ Version: updateConfigVersion, Kind: updateConfigKind, - Spec: UpdateSpec{ - Enabled: true, + Status: UpdateStatus{ + ActiveVersion: "old-version", }, + }, + + installedVersion: "16.3.0", + installedTemplate: cdnURITemplate, + linkedVersion: "16.3.0", + reloadCalls: 1, + setupCalls: 1, + }, + { + name: "override skip", + cfg: &UpdateConfig{ + Version: updateConfigVersion, + Kind: updateConfigKind, Status: UpdateStatus{ ActiveVersion: "old-version", + SkipVersion: "16.3.0", }, }, @@ -947,7 +1086,7 @@ func TestUpdater_Enable(t *testing.T) { } ctx := context.Background() - err = updater.Enable(ctx, tt.userCfg) + err = updater.Install(ctx, tt.userCfg) if tt.errMatch != "" { require.Error(t, err) assert.Contains(t, err.Error(), tt.errMatch) diff --git a/tool/teleport-update/main.go b/tool/teleport-update/main.go index 2e53e10c7b905..e8d52bb87e42b 100644 --- a/tool/teleport-update/main.go +++ b/tool/teleport-update/main.go @@ -106,7 +106,7 @@ func Run(args []string) error { versionCmd := app.Command("version", fmt.Sprintf("Print the version of your %s binary.", autoupdate.BinaryName)) - enableCmd := app.Command("enable", "Enable agent auto-updates and perform initial update.") + enableCmd := app.Command("enable", "Enable agent auto-updates and perform initial installation or update.") enableCmd.Flag("proxy", "Address of the Teleport Proxy."). Short('p').Envar(proxyServerEnvVar).StringVar(&ccfg.Proxy) enableCmd.Flag("group", "Update group for this agent installation."). @@ -119,7 +119,20 @@ func Run(args []string) error { Short('s').Hidden().BoolVar(&ccfg.SelfSetup) // TODO(sclevine): add force-fips and force-enterprise as hidden flags + pinCmd := app.Command("pin", "Install Teleport and lock the updater on the installed version.") + pinCmd.Flag("proxy", "Address of the Teleport Proxy."). + Short('p').Envar(proxyServerEnvVar).StringVar(&ccfg.Proxy) + pinCmd.Flag("group", "Update group for this agent installation."). + Short('g').Envar(updateGroupEnvVar).StringVar(&ccfg.Group) + pinCmd.Flag("template", "Go template used to override Teleport download URL."). + Short('t').Envar(templateEnvVar).StringVar(&ccfg.URLTemplate) + pinCmd.Flag("force-version", "Force the provided version instead of querying it from the Teleport cluster."). + Short('f').Envar(updateVersionEnvVar).StringVar(&ccfg.ForceVersion) + pinCmd.Flag("self-setup", "Use the current teleport-update binary to create systemd service config for auto-updates."). + Short('s').Hidden().BoolVar(&ccfg.SelfSetup) + disableCmd := app.Command("disable", "Disable agent auto-updates.") + unpinCmd := app.Command("unpin", "Unpin the current version, allowing it to be updated.") updateCmd := app.Command("update", "Update agent to the latest version, if a new version is available.") updateCmd.Flag("self-setup", "Use the current teleport-update binary to create systemd service config for auto-updates."). @@ -145,9 +158,15 @@ func Run(args []string) error { switch command { case enableCmd.FullCommand(): - err = cmdEnable(ctx, &ccfg) + ccfg.Enabled = true + err = cmdInstall(ctx, &ccfg) + case pinCmd.FullCommand(): + ccfg.Pinned = true + err = cmdInstall(ctx, &ccfg) case disableCmd.FullCommand(): err = cmdDisable(ctx, &ccfg) + case unpinCmd.FullCommand(): + err = cmdUnpin(ctx, &ccfg) case updateCmd.FullCommand(): err = cmdUpdate(ctx, &ccfg) case linkCmd.FullCommand(): @@ -210,8 +229,35 @@ func cmdDisable(ctx context.Context, ccfg *cliConfig) error { return nil } -// cmdEnable enables updates and triggers an initial update. -func cmdEnable(ctx context.Context, ccfg *cliConfig) error { +// cmdUnpin unpins the current version. +func cmdUnpin(ctx context.Context, ccfg *cliConfig) error { + updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{ + DataDir: ccfg.DataDir, + LinkDir: ccfg.LinkDir, + SystemDir: autoupdate.DefaultSystemDir, + SelfSetup: ccfg.SelfSetup, + Log: plog, + }) + if err != nil { + return trace.Errorf("failed to setup updater: %w", err) + } + unlock, err := libutils.FSWriteLock(filepath.Join(ccfg.DataDir, lockFileName)) + if err != nil { + return trace.Errorf("failed to grab concurrent execution lock: %w", err) + } + defer func() { + if err := unlock(); err != nil { + plog.DebugContext(ctx, "Failed to close lock file", "error", err) + } + }() + if err := updater.Unpin(ctx); err != nil { + return trace.Wrap(err) + } + return nil +} + +// cmdInstall installs Teleport and sets configuration. +func cmdInstall(ctx context.Context, ccfg *cliConfig) error { updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{ DataDir: ccfg.DataDir, LinkDir: ccfg.LinkDir, @@ -233,7 +279,7 @@ func cmdEnable(ctx context.Context, ccfg *cliConfig) error { plog.DebugContext(ctx, "Failed to close lock file", "error", err) } }() - if err := updater.Enable(ctx, ccfg.OverrideConfig); err != nil { + if err := updater.Install(ctx, ccfg.OverrideConfig); err != nil { return trace.Wrap(err) } return nil