Skip to content

Commit 705ea4e

Browse files
authored
Release/v11.2.7 (#2033)
* fix(agent): poll Windows service state instead of fixed sleep during updates sc stop/start are async on Windows - they return immediately without waiting for the service to actually change state. Replace fixed 10s sleep with active polling (500ms interval, 60s timeout) to guarantee the service has stopped before replacing binaries and started before health checking. * fix(aws): correct typo in log.userIdentityAccessrequestParametersBucketNamesKeyId * fix(updater): enhance version.json promotion and rollback handling during updates * feat(dependency): implement pre-download hooks for updater service management
1 parent da9520b commit 705ea4e

10 files changed

Lines changed: 253 additions & 57 deletions

File tree

agent/dependency/dependency.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,29 @@ const (
2121
MacosCollectorVersion = "11.2.3"
2222
)
2323

24+
// getUpdaterVersion reads the desired updater version from version.json
25+
// (the updater_version field). Falls back to UpdaterVersion if the
26+
// file is missing, unreadable, or the field is empty.
27+
//
28+
// The source of truth for this value is version.json shipped by the server,
29+
// which the updater promotes on each successful agent update.
30+
func getUpdaterVersion() string {
31+
var v struct {
32+
UpdaterVersion string `json:"updater_version"`
33+
}
34+
versionPath := filepath.Join(fs.GetExecutablePath(), "version.json")
35+
if !fs.Exists(versionPath) {
36+
return UpdaterVersion
37+
}
38+
if err := fs.ReadJSON(versionPath, &v); err != nil {
39+
return UpdaterVersion
40+
}
41+
if v.UpdaterVersion == "" {
42+
return UpdaterVersion
43+
}
44+
return v.UpdaterVersion
45+
}
46+
2447
// UpdaterFile returns the updater binary name with OS and architecture suffix.
2548
// Format: utmstack_updater_service_<os>_<arch>[.exe]
2649
// Examples:
@@ -43,6 +66,7 @@ type Dependency struct {
4366
DownloadURL func(server string) string // URL template to download from
4467
DownloadName string // Filename to save as (if different from BinaryPath basename)
4568
Critical bool // If true, failure blocks agent startup
69+
PreDownload func() (cleanup func(), err error) // Called before download, returns cleanup for rollback
4670
PostDownload func() error // Run after download (e.g., unzip). Can be nil.
4771
Configure func() error // Run on first install (can be nil)
4872
Update func() error // Run on version change (can be nil, uses Configure)
@@ -188,13 +212,34 @@ func Reconcile(server string, skipCertValidation bool) error {
188212
} else if inst.Version != dep.Version {
189213
// VERSION CHANGED: Download (if needed) and update
190214
utils.Logger.Info("Updating dependency: %s (%s -> %s)", dep.Name, inst.Version, dep.Version)
215+
216+
// Call PreDownload hook if defined
217+
var cleanup func()
218+
if dep.PreDownload != nil {
219+
var err error
220+
cleanup, err = dep.PreDownload()
221+
if err != nil {
222+
errMsg := fmt.Errorf("failed to run PreDownload for %s: %v", dep.Name, err)
223+
utils.Logger.ErrorF("%v", errMsg)
224+
if dep.Critical {
225+
criticalErrors = append(criticalErrors, errMsg)
226+
}
227+
continue
228+
}
229+
}
230+
191231
if dep.DownloadURL != nil {
192232
if err := downloadDependency(dep, server, skipCertValidation); err != nil {
193233
errMsg := fmt.Errorf("failed to download dependency update %s: %v", dep.Name, err)
194234
utils.Logger.ErrorF("%v", errMsg)
195235
if dep.Critical {
196236
criticalErrors = append(criticalErrors, errMsg)
197237
}
238+
// Rollback: call cleanup if PreDownload succeeded
239+
if cleanup != nil {
240+
utils.Logger.Info("Rolling back PreDownload changes for %s", dep.Name)
241+
cleanup()
242+
}
198243
continue
199244
}
200245
}
@@ -210,6 +255,11 @@ func Reconcile(server string, skipCertValidation bool) error {
210255
if dep.Critical {
211256
criticalErrors = append(criticalErrors, errMsg)
212257
}
258+
// Rollback: call cleanup if PreDownload succeeded
259+
if cleanup != nil {
260+
utils.Logger.Info("Rolling back PreDownload changes for %s", dep.Name)
261+
cleanup()
262+
}
213263
continue
214264
}
215265
}

agent/dependency/deps_darwin.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/utmstack/UTMStack/agent/config"
1111
"github.com/utmstack/UTMStack/shared/exec"
1212
"github.com/utmstack/UTMStack/shared/fs"
13+
"github.com/utmstack/UTMStack/shared/svc"
1314
)
1415

1516
const macosCollectorBinary = "utmstack-collector-mac"
@@ -20,15 +21,16 @@ func GetDependencies() []Dependency {
2021

2122
return []Dependency{
2223
{
23-
Name: "updater",
24-
Version: UpdaterVersion,
25-
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
24+
Name: "updater",
25+
Version: getUpdaterVersion(),
26+
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
2627
DownloadURL: func(server string) string {
2728
return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile(""))
2829
},
29-
Critical: false,
30-
Configure: configureUpdater,
31-
Uninstall: uninstallUpdater,
30+
Critical: false,
31+
PreDownload: preDownloadUpdater,
32+
Configure: configureUpdater,
33+
Uninstall: uninstallUpdater,
3234
},
3335
{
3436
Name: "macos-collector",
@@ -66,3 +68,19 @@ func uninstallUpdater() error {
6668
}
6769
return exec.Run(updaterPath, fs.GetExecutablePath(), "uninstall")
6870
}
71+
72+
func preDownloadUpdater() (func(), error) {
73+
// Stop the updater service before download
74+
if err := svc.Stop(config.SERVICE_UPDATER_NAME); err != nil {
75+
// Service might not be running or installed yet - that's OK
76+
// Return cleanup function anyway (safe to start)
77+
return func() {
78+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
79+
}, nil
80+
}
81+
82+
// Return cleanup function that restarts the service
83+
return func() {
84+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
85+
}, nil
86+
}

agent/dependency/deps_linux_amd64.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/utmstack/UTMStack/agent/config"
1212
"github.com/utmstack/UTMStack/shared/exec"
1313
"github.com/utmstack/UTMStack/shared/fs"
14+
"github.com/utmstack/UTMStack/shared/svc"
1415
)
1516

1617
// GetDependencies returns the list of dependencies for Linux amd64.
@@ -19,15 +20,16 @@ func GetDependencies() []Dependency {
1920

2021
return []Dependency{
2122
{
22-
Name: "updater",
23-
Version: UpdaterVersion,
24-
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
23+
Name: "updater",
24+
Version: getUpdaterVersion(),
25+
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
2526
DownloadURL: func(server string) string {
2627
return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile(""))
2728
},
28-
Critical: false,
29-
Configure: configureUpdater,
30-
Uninstall: uninstallUpdater,
29+
Critical: false,
30+
PreDownload: preDownloadUpdater,
31+
Configure: configureUpdater,
32+
Uninstall: uninstallUpdater,
3133
},
3234

3335
// New beats dependency - only for uninstalling existing filebeat/winlogbeat
@@ -75,3 +77,19 @@ func uninstallUpdater() error {
7577
func uninstallBeats() error {
7678
return collector.UninstallAll()
7779
}
80+
81+
func preDownloadUpdater() (func(), error) {
82+
// Stop the updater service before download
83+
if err := svc.Stop(config.SERVICE_UPDATER_NAME); err != nil {
84+
// Service might not be running or installed yet - that's OK
85+
// Return cleanup function anyway (safe to start)
86+
return func() {
87+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
88+
}, nil
89+
}
90+
91+
// Return cleanup function that restarts the service
92+
return func() {
93+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
94+
}, nil
95+
}

agent/dependency/deps_linux_arm64.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/utmstack/UTMStack/agent/config"
1111
"github.com/utmstack/UTMStack/shared/exec"
1212
"github.com/utmstack/UTMStack/shared/fs"
13+
"github.com/utmstack/UTMStack/shared/svc"
1314
)
1415

1516
// GetDependencies returns the list of dependencies for Linux arm64.
@@ -19,15 +20,16 @@ func GetDependencies() []Dependency {
1920

2021
return []Dependency{
2122
{
22-
Name: "updater",
23-
Version: UpdaterVersion,
24-
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
23+
Name: "updater",
24+
Version: getUpdaterVersion(),
25+
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
2526
DownloadURL: func(server string) string {
2627
return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile(""))
2728
},
28-
Critical: false,
29-
Configure: configureUpdater,
30-
Uninstall: uninstallUpdater,
29+
Critical: false,
30+
PreDownload: preDownloadUpdater,
31+
Configure: configureUpdater,
32+
Uninstall: uninstallUpdater,
3133
},
3234

3335
// Auditd dependency - auto-configures Linux audit daemon
@@ -61,3 +63,19 @@ func uninstallUpdater() error {
6163
}
6264
return exec.Run(updaterPath, fs.GetExecutablePath(), "uninstall")
6365
}
66+
67+
func preDownloadUpdater() (func(), error) {
68+
// Stop the updater service before download
69+
if err := svc.Stop(config.SERVICE_UPDATER_NAME); err != nil {
70+
// Service might not be running or installed yet - that's OK
71+
// Return cleanup function anyway (safe to start)
72+
return func() {
73+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
74+
}, nil
75+
}
76+
77+
// Return cleanup function that restarts the service
78+
return func() {
79+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
80+
}, nil
81+
}

agent/dependency/deps_windows_amd64.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/utmstack/UTMStack/agent/config"
1212
"github.com/utmstack/UTMStack/shared/exec"
1313
"github.com/utmstack/UTMStack/shared/fs"
14+
"github.com/utmstack/UTMStack/shared/svc"
1415
)
1516

1617
// GetDependencies returns the list of dependencies for Windows amd64.
@@ -19,15 +20,16 @@ func GetDependencies() []Dependency {
1920

2021
return []Dependency{
2122
{
22-
Name: "updater",
23-
Version: UpdaterVersion,
24-
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
23+
Name: "updater",
24+
Version: getUpdaterVersion(),
25+
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
2526
DownloadURL: func(server string) string {
2627
return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile(""))
2728
},
28-
Critical: false, // Agent can run without updater
29-
Configure: configureUpdater,
30-
Uninstall: uninstallUpdater,
29+
Critical: false, // Agent can run without updater
30+
PreDownload: preDownloadUpdater,
31+
Configure: configureUpdater,
32+
Uninstall: uninstallUpdater,
3133
},
3234

3335
// New beats dependency - only for uninstalling existing filebeat/winlogbeat
@@ -57,3 +59,19 @@ func uninstallUpdater() error {
5759
func uninstallBeats() error {
5860
return collector.UninstallAll()
5961
}
62+
63+
func preDownloadUpdater() (func(), error) {
64+
// Stop the updater service before download
65+
if err := svc.Stop(config.SERVICE_UPDATER_NAME); err != nil {
66+
// Service might not be running or installed yet - that's OK
67+
// Return cleanup function anyway (safe to start)
68+
return func() {
69+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
70+
}, nil
71+
}
72+
73+
// Return cleanup function that restarts the service
74+
return func() {
75+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
76+
}, nil
77+
}

agent/dependency/deps_windows_arm64.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/utmstack/UTMStack/agent/config"
1111
"github.com/utmstack/UTMStack/shared/exec"
1212
"github.com/utmstack/UTMStack/shared/fs"
13+
"github.com/utmstack/UTMStack/shared/svc"
1314
)
1415

1516
// GetDependencies returns the list of dependencies for Windows arm64.
@@ -19,15 +20,16 @@ func GetDependencies() []Dependency {
1920

2021
return []Dependency{
2122
{
22-
Name: "updater",
23-
Version: UpdaterVersion,
24-
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
23+
Name: "updater",
24+
Version: getUpdaterVersion(),
25+
BinaryPath: filepath.Join(basePath, UpdaterFile("")),
2526
DownloadURL: func(server string) string {
2627
return fmt.Sprintf(config.DependUrl, server, config.DependenciesPort, UpdaterFile(""))
2728
},
28-
Critical: false,
29-
Configure: configureUpdater,
30-
Uninstall: uninstallUpdater,
29+
Critical: false,
30+
PreDownload: preDownloadUpdater,
31+
Configure: configureUpdater,
32+
Uninstall: uninstallUpdater,
3133
},
3234
}
3335
}
@@ -44,3 +46,19 @@ func uninstallUpdater() error {
4446
}
4547
return exec.Run(updaterPath, fs.GetExecutablePath(), "uninstall")
4648
}
49+
50+
func preDownloadUpdater() (func(), error) {
51+
// Stop the updater service before download
52+
if err := svc.Stop(config.SERVICE_UPDATER_NAME); err != nil {
53+
// Service might not be running or installed yet - that's OK
54+
// Return cleanup function anyway (safe to start)
55+
return func() {
56+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
57+
}, nil
58+
}
59+
60+
// Return cleanup function that restarts the service
61+
return func() {
62+
_ = svc.Start(config.SERVICE_UPDATER_NAME)
63+
}, nil
64+
}

0 commit comments

Comments
 (0)