diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 6ee9674253e..cd89c2ff5a6 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -110,6 +110,7 @@ - Allow ':' characters in dynamic variables {issue}624[624] {pull}680[680] - Allow the - char to appear as part of variable names in eql expressions. {issue}709[709] {pull}710[710] - Allow the / char in variable names in eql and transpiler. {issue}715[715] {pull}718[718] +- Agent updates will clean up unneeded artifacts. {issue}693[693] {issue}694[694] {pull}752[752] ==== New features diff --git a/internal/pkg/agent/application/upgrade/cleanup.go b/internal/pkg/agent/application/upgrade/cleanup.go new file mode 100644 index 00000000000..f694447cc55 --- /dev/null +++ b/internal/pkg/agent/application/upgrade/cleanup.go @@ -0,0 +1,49 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package upgrade + +import ( + "os" + "path/filepath" + "strings" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" +) + +// preUpgradeCleanup will remove files that do not have the passed version number from the downloads directory. +func preUpgradeCleanup(version string) error { + files, err := os.ReadDir(paths.Downloads()) + if err != nil { + return err + } + for _, file := range files { + if file.IsDir() { + continue + } + if !strings.Contains(file.Name(), version) { + if err := os.Remove(filepath.Join(paths.Downloads(), file.Name())); err != nil { + return err + } + } + } + return nil +} + +// cleanAllDownloads will remove all files from the downloads directory +func cleanAllDownloads() error { + files, err := os.ReadDir(paths.Downloads()) + if err != nil { + return err + } + for _, file := range files { + if file.IsDir() { + continue + } + if err := os.Remove(filepath.Join(paths.Downloads(), file.Name())); err != nil { + return err + } + } + return nil +} diff --git a/internal/pkg/agent/application/upgrade/cleanup_test.go b/internal/pkg/agent/application/upgrade/cleanup_test.go new file mode 100644 index 00000000000..db3a4edbef0 --- /dev/null +++ b/internal/pkg/agent/application/upgrade/cleanup_test.go @@ -0,0 +1,61 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package upgrade + +import ( + "os" + "path/filepath" + "testing" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" + + "github.com/stretchr/testify/require" +) + +func setupDir(t *testing.T) { + t.Helper() + dir := t.TempDir() + paths.SetDownloads(dir) + + err := os.WriteFile(filepath.Join(dir, "test-8.3.0-file"), []byte("hello, world!"), 0640) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile(filepath.Join(dir, "test-8.4.0-file"), []byte("hello, world!"), 0640) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile(filepath.Join(dir, "test-8.5.0-file"), []byte("hello, world!"), 0640) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile(filepath.Join(dir, "test-hash-file"), []byte("hello, world!"), 0640) + if err != nil { + t.Fatal(err) + } +} + +func TestPreUpgradeCleanup(t *testing.T) { + setupDir(t) + err := preUpgradeCleanup("8.4.0") + require.NoError(t, err) + + files, err := os.ReadDir(paths.Downloads()) + require.NoError(t, err) + require.Len(t, files, 1) + require.Equal(t, "test-8.4.0-file", files[0].Name()) + fi, err := files[0].Info() + require.NoError(t, err) + require.Greater(t, fi.Size(), int64(0)) +} + +func TestCleanAllDownloads(t *testing.T) { + setupDir(t) + err := cleanAllDownloads() + require.NoError(t, err) + files, err := os.ReadDir(paths.Downloads()) + require.NoError(t, err) + require.Len(t, files, 0) +} diff --git a/internal/pkg/agent/application/upgrade/upgrade.go b/internal/pkg/agent/application/upgrade/upgrade.go index 1d370cb5301..b69057f0c3a 100644 --- a/internal/pkg/agent/application/upgrade/upgrade.go +++ b/internal/pkg/agent/application/upgrade/upgrade.go @@ -126,6 +126,11 @@ func (u *Upgrader) Upgrade(ctx context.Context, a Action, reexecNow bool) (_ ree "running under control of the systems supervisor") } + err = preUpgradeCleanup(a.Version()) + if err != nil { + u.log.Errorf("Unable to clean downloads dir %q before update: %v", paths.Downloads(), err) + } + if u.caps != nil { if _, err := u.caps.Apply(a); errors.Is(err, capabilities.ErrBlocked) { return nil, nil @@ -137,6 +142,11 @@ func (u *Upgrader) Upgrade(ctx context.Context, a Action, reexecNow bool) (_ ree sourceURI := u.sourceURI(a.SourceURI()) archivePath, err := u.downloadArtifact(ctx, a.Version(), sourceURI) if err != nil { + // Run the same preUpgradeCleanup task to get rid of any newly downloaded files + // This may have an issue if users are upgrading to the same version number. + if dErr := preUpgradeCleanup(a.Version()); dErr != nil { + u.log.Errorf("Unable to remove file after verification failure: %v", dErr) + } return nil, err } @@ -184,6 +194,13 @@ func (u *Upgrader) Upgrade(ctx context.Context, a Action, reexecNow bool) (_ ree return nil, nil } + // Clean everything from the downloads dir + // If we wanted to be a bit simpler we could call os.RemoveAll to remove the dir + children + err = cleanAllDownloads() + if err != nil { + u.log.Errorf("Unable to clean downloads dir %q after update: %v", paths.Downloads(), err) + } + return cb, nil }