Repository: elastic/elastic-agent
Pull request #6638: [RPM/DEB] Changes to binary distribution 
Author: login: michalpristas
Labels: name: bug
description: Something isn't working
name: Team:Elastic-Agent-Control-Plane
description: Label for the Agent Control Plane team
name: skip-changelog
name: backport-active-9
description: Automated backport with mergify to all the active 9.[0-9]+ branches
Created: 2025-01-30T08:54:15Z | Merged: 2025-03-19T16:21:18Z

## What does this PR do?

Fixes behavior for RPM

 - RPM/DEB installs basic agent by default
 - installation respects value of ELATIC_AGENT_FLAVOR
 - on upgrade
   - in case previous installlation without flavor -> copy all
   - in case previous installation with flavor -> respect flavor

This takes a bit different approach as we cannot control what's being copied over during installations.
Before install we mark desired flavor into flavor file.
After install we execute new undocumented command `apply-flavor` that removes all unnecessary component from current version home.

## Why is it important?

Reducing space on disk by omitting not needed components.

## Checklist

- [x] I have read and understood the [pull request guidelines](https://github.com/elastic/elastic-agent/blob/main/CONTRIBUTING.md#pull-request-guidelines) of this project.
- [x] My code follows the style guidelines of this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] I have made corresponding change to the default configuration files
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have added an entry in `./changelog/fragments` using the [changelog tool](https://github.com/elastic/elastic-agent#changelog)
- [ ] I have added an integration test or an E2E test

### How to test

#### DEB
##### install scenario
- compile deb
- run `dpkg -i elastic-agent-{rest of filename}.deb`
- `cat /var/lib/elastic-agent/.flavor` (expected basic)
- `ls -la /var/lib/elastic-agent/data/elastic-agent-*/componentes` expected limited set
- uninstall and purge
- run `ELASTIC_AGENT_FLAVOR=servers dpkg -i elastic-agent-{rest of filename}.deb`
- `cat /var/lib/elastic-agent/.flavor` (expected servers)
- `ls -la /var/lib/elastic-agent/data/elastic-agent-*/componentes` expected apm fleet-server present
- uninstall and purge

##### upgrade scenario
- compile deb
- install 8.17.2 snapshot `dpkg -i elastic-agent-{rest of filename}.deb`
- `cat /var/lib/elastic-agent/.flavor` (expected file not found)
- `ls -la /var/lib/elastic-agent/data/elastic-agent-*/componentes` expected whole set
- install 9.0.0 snapshot `dpkg -i elastic-agent-{rest of filename}.deb`
- `cat /var/lib/elastic-agent/.flavor` expected not found
- `ls -la /var/lib/elastic-agent/data/elastic-agent-*/componentes` expected whole set

- uninstall and purge
- compile additional deb with different hash
- install additional deb `ELASTIC_AGENT_FLAVOR=servers dpkg -i elastic-agent-{rest of filename}.deb`
- `cat /var/lib/elastic-agent/.flavor` (expected servers)
- `ls -la /var/lib/elastic-agent/data/elastic-agent-*/componentes` expected servers set
- install original build `dpkg -i elastic-agent-{rest of filename}.deb`
- `cat /var/lib/elastic-agent/.flavor` expected servers
- `ls -la /var/lib/elastic-agent/data/elastic-agent-*/componentes` expected servers set

same steps for rpm but use yum

Fixes: #6637 


Diff:
diff --git a/dev-tools/packaging/templates/linux/postinstall.sh.tmpl b/dev-tools/packaging/templates/linux/postinstall.sh.tmpl
index 9b2c5de8893..c2eb9937226 100644
--- a/dev-tools/packaging/templates/linux/postinstall.sh.tmpl
+++ b/dev-tools/packaging/templates/linux/postinstall.sh.tmpl
@@ -17,6 +17,8 @@ fi
 echo "create symlink "$symlink" to "$new_agent_dir/elastic-agent""
 ln -s "$new_agent_dir/elastic-agent" "$symlink"
 
+$new_agent_dir/elastic-agent apply-flavor
+
 # reload systemctl and then restart service
 echo "systemd enable/restart elastic-agent"
 systemctl daemon-reload 2> /dev/null
diff --git a/dev-tools/packaging/templates/linux/preinstall.sh.tmpl b/dev-tools/packaging/templates/linux/preinstall.sh.tmpl
index b396c76476c..88f13fca03b 100644
--- a/dev-tools/packaging/templates/linux/preinstall.sh.tmpl
+++ b/dev-tools/packaging/templates/linux/preinstall.sh.tmpl
@@ -5,6 +5,7 @@ set -e
 commit_hash="{{ commit_short }}"
 version_dir="{{agent_package_version}}{{snapshot_suffix}}"
 symlink="/usr/share/elastic-agent/bin/elastic-agent"
+flavor_file="/var/lib/elastic-agent/.flavor"
 new_agent_dir="/var/lib/elastic-agent/data/elastic-agent-$version_dir-$commit_hash"
 old_agent_dir=""
 
@@ -43,4 +44,19 @@ if test -L "$symlink"; then
     fi
 else
     echo "no previous installation found"
+
+    # create dir in case it does not exist
+    mkdir -p "$new_agent_dir" 
+    
+    # 2 is upgrade for Fedora, do not upgrade file when upgrading and file exists
+    if [[ "$1" != "2" ]]; then
+        if [[ -n "${ELASTIC_AGENT_FLAVOR}" ]]; then
+            # Do not modify the file if it already exists
+            echo "${ELASTIC_AGENT_FLAVOR}" > "$flavor_file"
+        else
+            # Defaults to basic installation
+            echo "basic" > "$flavor_file"
+        fi
+    fi
 fi
+
diff --git a/internal/pkg/agent/cmd/apply_flavor.go b/internal/pkg/agent/cmd/apply_flavor.go
new file mode 100644
index 00000000000..f184df21185
--- /dev/null
+++ b/internal/pkg/agent/cmd/apply_flavor.go
@@ -0,0 +1,59 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package cmd
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/spf13/cobra"
+
+	"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
+	"github.com/elastic/elastic-agent/internal/pkg/agent/install"
+	"github.com/elastic/elastic-agent/internal/pkg/cli"
+	v1 "github.com/elastic/elastic-agent/pkg/api/v1"
+)
+
+func newApplyFlavorCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "apply-flavor",
+		Short: "Apply Flavor cleans up unnecessary components from agent installation directory",
+		Run: func(c *cobra.Command, _ []string) {
+			if err := applyCmd(); err != nil {
+				fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
+				logExternal(fmt.Sprintf("%s apply flavor failed: %s", paths.BinaryName, err))
+				os.Exit(1)
+			}
+		},
+		Hidden: true,
+	}
+
+	return cmd
+}
+
+func applyCmd() error {
+	topPath := paths.Top()
+	detectedFlavor, err := install.UsedFlavor(topPath, "")
+	if err != nil {
+		if errors.Is(err, os.ErrNotExist) {
+			return nil
+		}
+		return err
+	}
+	if detectedFlavor == "" {
+		return nil
+	}
+
+	versionedHome := paths.VersionedHome(topPath)
+	manifestFilePath := filepath.Join(versionedHome, v1.ManifestFileName)
+	flavor, err := install.Flavor(detectedFlavor, manifestFilePath, nil)
+	if err != nil {
+		return err
+	}
+
+	return install.ApplyFlavor(versionedHome, flavor)
+}
diff --git a/internal/pkg/agent/cmd/common.go b/internal/pkg/agent/cmd/common.go
index 059c144399d..0bec7dc3540 100644
--- a/internal/pkg/agent/cmd/common.go
+++ b/internal/pkg/agent/cmd/common.go
@@ -94,6 +94,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command {
 	addCommandIfNotNil(cmd, newComponentCommandWithArgs(args, streams))
 	addCommandIfNotNil(cmd, newLogsCommandWithArgs(args, streams))
 	addCommandIfNotNil(cmd, newOtelCommandWithArgs(args, streams))
+	addCommandIfNotNil(cmd, newApplyFlavorCommandWithArgs(args, streams))
 
 	// windows special hidden sub-command (only added on Windows)
 	reexec := newReExecWindowsCommand(args, streams)
diff --git a/internal/pkg/agent/install/flavors.go b/internal/pkg/agent/install/flavors.go
index e666fea19c5..f856726fd05 100644
--- a/internal/pkg/agent/install/flavors.go
+++ b/internal/pkg/agent/install/flavors.go
@@ -48,7 +48,7 @@ func UsedFlavor(topPath, defaultFlavor string) (string, error) {
 		return "", fmt.Errorf("failed reading flavor marker file: %w", err)
 	}
 
-	return string(content), nil
+	return strings.TrimSpace(string(content)), nil
 }
 
 func Flavor(detectedFlavor string, registryPath string, flavorsRegistry map[string][]string) (FlavorDefinition, error) {
@@ -118,7 +118,7 @@ func ApplyFlavor(versionedHome string, flavor FlavorDefinition) error {
 	}
 
 	for _, ftr := range filesToRemove {
-		if removeErr := os.RemoveAll(ftr); !os.IsNotExist(removeErr) {
+		if removeErr := os.RemoveAll(ftr); removeErr != nil && !os.IsNotExist(removeErr) {
 			err = fmt.Errorf("failed cleaning components: %w", removeErr)
 		}
 	}
diff --git a/internal/pkg/agent/install/flavors_test.go b/internal/pkg/agent/install/flavors_test.go
index 82542ec32b9..e268e290205 100644
--- a/internal/pkg/agent/install/flavors_test.go
+++ b/internal/pkg/agent/install/flavors_test.go
@@ -13,6 +13,9 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v3"
+
+	v1 "github.com/elastic/elastic-agent/pkg/api/v1"
 )
 
 var flavorsRegistry = map[string][]string{
@@ -20,6 +23,12 @@ var flavorsRegistry = map[string][]string{
 	"servers": {"agentbeat", "endpoint-security", "pf-host-agent", "cloudbeat", "apm-server", "fleet-server", "pf-elastic-symbolizer", "pf-elastic-collector"},
 }
 
+var manifest = v1.PackageManifest{
+	Package: v1.PackageDesc{
+		Flavors: flavorsRegistry,
+	},
+}
+
 func TestSubpathsForComponent(t *testing.T) {
 	binarySuffix := ""
 	if runtime.GOOS == "windows" {
@@ -190,6 +199,42 @@ func TestAllowedSubpathsForFlavor(t *testing.T) {
 			sort.Strings(subpaths)
 			assert.Equal(t, tt.wantSubpaths, subpaths)
 		})
+
+		t.Run(tt.name+"-file", func(t *testing.T) {
+			tmpDir := t.TempDir()
+			manifestFile := filepath.Join(tmpDir, "manifest.yaml")
+			mb, err := yaml.Marshal(manifest)
+			require.NoError(t, err)
+			require.NoError(t, os.WriteFile(manifestFile, mb, 0o644))
+			defer os.Remove(manifestFile)
+
+			// Create temp dir with spec files
+			componentsDir := filepath.Join(versionedHome, "components")
+			require.NoError(t, os.MkdirAll(componentsDir, 0755))
+
+			// Write spec files
+			for component, content := range tt.specFiles {
+				specPath := filepath.Join(componentsDir, component+".spec.yml")
+				require.NoError(t, os.WriteFile(specPath, []byte(content), 0644))
+				defer os.Remove(specPath)
+			}
+
+			// Test function
+			flavor, err := Flavor(tt.flavor, manifestFile, nil)
+			if tt.wantError {
+				require.Error(t, err)
+				assert.Contains(t, err.Error(), tt.errorContains)
+				return
+			}
+
+			subpaths, err := allowedSubpathsForFlavor(versionedHome, flavor)
+			assert.NoError(t, err)
+
+			require.NoError(t, err)
+			sort.Strings(tt.wantSubpaths)
+			sort.Strings(subpaths)
+			assert.Equal(t, tt.wantSubpaths, subpaths)
+		})
 	}
 }
 
diff --git a/pkg/testing/fixture_install.go b/pkg/testing/fixture_install.go
index 1c39d592deb..4ac87897276 100644
--- a/pkg/testing/fixture_install.go
+++ b/pkg/testing/fixture_install.go
@@ -461,7 +461,11 @@ func (f *Fixture) installDeb(ctx context.Context, installOpts *InstallOpts, shou
 	}
 
 	// sudo apt-get install the deb
-	out, err := exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", f.srcPackage).CombinedOutput() // #nosec G204 -- Need to pass in name of package
+	cmd := exec.CommandContext(ctx, "sudo", "-E", "apt-get", "install", "-y", f.srcPackage) // #nosec G204 -- Need to pass in name of package
+	if installOpts.InstallServers {
+		cmd.Env = append(cmd.Env, "ELASTIC_AGENT_FLAVOR=servers")
+	}
+	out, err := cmd.CombinedOutput() // #nosec G204 -- Need to pass in name of package
 	if err != nil {
 		return out, fmt.Errorf("apt install failed: %w output:%s", err, string(out))
 	}
@@ -546,7 +550,11 @@ func (f *Fixture) installRpm(ctx context.Context, installOpts *InstallOpts, shou
 	}
 
 	// sudo rpm -iv elastic-agent rpm
-	out, err := exec.CommandContext(ctx, "sudo", "rpm", "-i", "-v", f.srcPackage).CombinedOutput() // #nosec G204 -- Need to pass in name of package
+	cmd := exec.CommandContext(ctx, "sudo", "-E", "rpm", "-i", "-v", f.srcPackage) // #nosec G204 -- Need to pass in name of package
+	if installOpts.InstallServers {
+		cmd.Env = append(cmd.Env, "ELASTIC_AGENT_FLAVOR=servers")
+	}
+	out, err := cmd.CombinedOutput()
 	if err != nil {
 		return out, fmt.Errorf("rpm install failed: %w output:%s", err, string(out))
 	}
diff --git a/testing/integration/install_test.go b/testing/integration/install_test.go
index 7de5668f715..1b6833e63c8 100644
--- a/testing/integration/install_test.go
+++ b/testing/integration/install_test.go
@@ -438,6 +438,10 @@ func testInstallWithoutBasePathWithCustomUser(ctx context.Context, t *testing.T,
 func testComponentsPresence(ctx context.Context, fixture *atesting.Fixture, requiredComponents []componentPresenceDefinition, unwantedComponents []componentPresenceDefinition) func(*testing.T) {
 	return func(t *testing.T) {
 		agentWorkDir := fixture.WorkDir()
+		if pf := fixture.PackageFormat(); pf == "deb" || pf == "rpm" {
+			// these are hardcoded paths in packages.yml
+			agentWorkDir = "/var/lib/elastic-agent"
+		}
 		componentsDir, err := aTesting.FindComponentsDir(agentWorkDir)
 		require.NoError(t, err)
 
diff --git a/testing/integration/linux_deb_test.go b/testing/integration/linux_deb_test.go
index 1c36b35c428..40cf9d96400 100644
--- a/testing/integration/linux_deb_test.go
+++ b/testing/integration/linux_deb_test.go
@@ -9,6 +9,7 @@ package integration
 import (
 	"context"
 	"fmt"
+	"os"
 	"os/exec"
 	"strings"
 	"testing"
@@ -49,6 +50,75 @@ func TestDebLogIngestFleetManaged(t *testing.T) {
 	agentFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("deb"))
 	require.NoError(t, err)
 
+	installOpts := atesting.InstallOpts{
+		NonInteractive: true,
+		Force:          true,
+		InstallServers: false,
+	}
+
+	testDebLogIngestFleetManagedWithCheck(ctx, t, agentFixture, info, installOpts,
+		testComponentsPresence(ctx, agentFixture,
+			[]componentPresenceDefinition{
+				{"agentbeat", []string{"windows", "linux", "darwin"}},
+				{"endpoint-security", []string{"windows", "linux", "darwin"}},
+				{"pf-host-agent", []string{"linux"}},
+			},
+			[]componentPresenceDefinition{
+				{"cloudbeat", []string{"linux"}},
+				{"apm-server", []string{"windows", "linux", "darwin"}},
+				{"fleet-server", []string{"windows", "linux", "darwin"}},
+				{"pf-elastic-symbolizer", []string{"linux"}},
+				{"pf-elastic-collector", []string{"linux"}},
+			},
+		),
+	)
+}
+
+func TestDebInstallsServers(t *testing.T) {
+	info := define.Require(t, define.Requirements{
+		Group: Deb,
+		Stack: &define.Stack{},
+		OS: []define.OS{
+			{
+				Type:   define.Linux,
+				Distro: "ubuntu",
+			},
+		},
+		Local: false,
+		Sudo:  true,
+	})
+
+	ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute))
+	defer cancel()
+
+	agentFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("deb"))
+	require.NoError(t, err)
+
+	installOpts := atesting.InstallOpts{
+		NonInteractive: true,
+		Force:          true,
+		InstallServers: true,
+	}
+
+	testDebLogIngestFleetManagedWithCheck(ctx, t, agentFixture, info, installOpts,
+		testComponentsPresence(ctx, agentFixture,
+			[]componentPresenceDefinition{
+				{"agentbeat", []string{"windows", "linux", "darwin"}},
+				{"endpoint-security", []string{"windows", "linux", "darwin"}},
+				{"pf-host-agent", []string{"linux"}},
+				{"cloudbeat", []string{"linux"}},
+				{"apm-server", []string{"windows", "linux", "darwin"}},
+				{"fleet-server", []string{"windows", "linux", "darwin"}},
+				{"pf-elastic-symbolizer", []string{"linux"}},
+				{"pf-elastic-collector", []string{"linux"}},
+			},
+			[]componentPresenceDefinition{},
+		),
+	)
+}
+
+func testDebLogIngestFleetManagedWithCheck(ctx context.Context, t *testing.T, agentFixture *atesting.Fixture, info *define.Info, installOpts atesting.InstallOpts, componentCheck func(t *testing.T)) {
+
 	// 1. Create a policy in Fleet with monitoring enabled.
 	// To ensure there are no conflicts with previous test runs against
 	// the same ESS stack, we add the current time at the end of the policy
@@ -70,10 +140,12 @@ func TestDebLogIngestFleetManaged(t *testing.T) {
 		},
 	}
 
-	installOpts := atesting.InstallOpts{
-		NonInteractive: true,
-		Force:          true,
-	}
+	// remove flavor to start fresh, in case there is some leftover.
+	_ = os.Remove("/var/lib/elastic-agent/.flavor")
+	t.Cleanup(func() {
+		// cleanup after ourselves
+		_ = os.Remove("/var/lib/elastic-agent/.flavor")
+	})
 
 	// 2. Install the Elastic-Agent with the policy that
 	// was just created.
@@ -88,6 +160,10 @@ func TestDebLogIngestFleetManaged(t *testing.T) {
 	t.Logf("created policy: %s", policy.ID)
 	check.ConnectedToFleet(ctx, t, agentFixture, 5*time.Minute)
 
+	if componentCheck != nil {
+		t.Run("check components set", componentCheck)
+	}
+
 	t.Run("Monitoring logs are shipped", func(t *testing.T) {
 		testMonitoringLogsAreShipped(t, ctx, info, agentFixture, policy)
 	})
diff --git a/testing/integration/linux_rpm_test.go b/testing/integration/linux_rpm_test.go
index 1a8d5dfcc32..504ca9e9056 100644
--- a/testing/integration/linux_rpm_test.go
+++ b/testing/integration/linux_rpm_test.go
@@ -9,6 +9,7 @@ package integration
 import (
 	"context"
 	"fmt"
+	"os"
 	"os/exec"
 	"strings"
 	"testing"
@@ -50,6 +51,74 @@ func TestRpmLogIngestFleetManaged(t *testing.T) {
 	agentFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("rpm"))
 	require.NoError(t, err)
 
+	installOpts := atesting.InstallOpts{
+		NonInteractive: true,
+		Force:          true,
+		InstallServers: false,
+	}
+
+	testRpmLogIngestFleetManagedWithCheck(ctx, t, agentFixture, info, installOpts,
+		testComponentsPresence(ctx, agentFixture,
+			[]componentPresenceDefinition{
+				{"agentbeat", []string{"windows", "linux", "darwin"}},
+				{"endpoint-security", []string{"windows", "linux", "darwin"}},
+				{"pf-host-agent", []string{"linux"}},
+			},
+			[]componentPresenceDefinition{
+				{"cloudbeat", []string{"linux"}},
+				{"apm-server", []string{"windows", "linux", "darwin"}},
+				{"fleet-server", []string{"windows", "linux", "darwin"}},
+				{"pf-elastic-symbolizer", []string{"linux"}},
+				{"pf-elastic-collector", []string{"linux"}},
+			},
+		),
+	)
+}
+
+func TestRpmInstallsServers(t *testing.T) {
+	info := define.Require(t, define.Requirements{
+		Group: Deb,
+		Stack: &define.Stack{},
+		OS: []define.OS{
+			{
+				Type:   define.Linux,
+				Distro: "rhel",
+			},
+		},
+		Local: false,
+		Sudo:  true,
+	})
+
+	ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute))
+	defer cancel()
+
+	agentFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("rpm"))
+	require.NoError(t, err)
+
+	installOpts := atesting.InstallOpts{
+		NonInteractive: true,
+		Force:          true,
+		InstallServers: true,
+	}
+
+	testRpmLogIngestFleetManagedWithCheck(ctx, t, agentFixture, info, installOpts,
+		testComponentsPresence(ctx, agentFixture,
+			[]componentPresenceDefinition{
+				{"agentbeat", []string{"windows", "linux", "darwin"}},
+				{"endpoint-security", []string{"windows", "linux", "darwin"}},
+				{"pf-host-agent", []string{"linux"}},
+				{"cloudbeat", []string{"linux"}},
+				{"apm-server", []string{"windows", "linux", "darwin"}},
+				{"fleet-server", []string{"windows", "linux", "darwin"}},
+				{"pf-elastic-symbolizer", []string{"linux"}},
+				{"pf-elastic-collector", []string{"linux"}},
+			},
+			[]componentPresenceDefinition{},
+		),
+	)
+}
+
+func testRpmLogIngestFleetManagedWithCheck(ctx context.Context, t *testing.T, agentFixture *atesting.Fixture, info *define.Info, installOpts atesting.InstallOpts, componentCheck func(t *testing.T)) {
 	// 1. Create a policy in Fleet with monitoring enabled.
 	// To ensure there are no conflicts with previous test runs against
 	// the same ESS stack, we add the current time at the end of the policy
@@ -71,10 +140,12 @@ func TestRpmLogIngestFleetManaged(t *testing.T) {
 		},
 	}
 
-	installOpts := atesting.InstallOpts{
-		NonInteractive: true,
-		Force:          true,
-	}
+	// remove flavor to start fresh, in case there is some leftover.
+	_ = os.Remove("/var/lib/elastic-agent/.flavor")
+	t.Cleanup(func() {
+		// cleanup after ourselves
+		_ = os.Remove("/var/lib/elastic-agent/.flavor")
+	})
 
 	// 2. Install the Elastic-Agent with the policy that
 	// was just created.
@@ -89,6 +160,10 @@ func TestRpmLogIngestFleetManaged(t *testing.T) {
 	t.Logf("created policy: %s", policy.ID)
 	check.ConnectedToFleet(ctx, t, agentFixture, 5*time.Minute)
 
+	if componentCheck != nil {
+		t.Run("check components set", componentCheck)
+	}
+
 	t.Run("Monitoring logs are shipped", func(t *testing.T) {
 		testMonitoringLogsAreShipped(t, ctx, info, agentFixture, policy)
 	})


Reviews:
user: login: cmacknz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T18:27:58Z
user: login: cmacknz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T18:47:28Z
user: login: cmacknz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T18:50:17Z
user: login: cmacknz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T18:50:24Z
user: login: cmacknz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T18:53:40Z
user: login: michalpristas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T11:39:55Z
user: login: michalpristas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T11:40:21Z
user: login: michalpristas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T11:46:09Z
user: login: cmacknz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T13:32:41Z
user: login: cmacknz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T14:12:20Z
user: login: michalpristas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T12:25:26Z
user: login: cmacknz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T15:24:25Z
user: login: blakerouse
body: Code looks good, install tests looks good. Please create a follow up to add more to the upgrade test to verify that the flavor is used on upgrade (that is what is missing).
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T16:18:56Z
user: login: michalpristas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T16:20:44Z
user: login: michalpristas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T16:20:49Z
user: login: michalpristas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T16:21:02Z

Review comments:
diff_hunk: @@ -0,0 +1,13 @@
+basic:
path: internal/pkg/agent/install/.flavors
user: login: cmacknz
body: This is duplicating https://github.com/elastic/elastic-agent/blob/main/_meta/.flavors
created_at: 2025-03-17T18:27:58Z
author_association: MEMBER
diff_hunk: @@ -43,4 +44,19 @@ if test -L "$symlink"; then
     fi
 else
     echo "no previous installation found"
+
+    # create dir in case it does not exist
+    mkdir -p "$new_agent_dir" 
+    
+    # 2 is upgrade for Fedora, do not upgrade file when upgrading and file exists
+    if [[ "$1" != "2" ]]; then
+        if [[ -n "${ELASTIC_AGENT_FLAVOR}" ]]; then
path: dev-tools/packaging/templates/linux/preinstall.sh.tmpl
user: login: cmacknz
body: There should be a test proving this does what it is supposed to do.

We already have RPM and DEB tests:
- https://github.com/elastic/elastic-agent/blob/main/testing/integration/linux_rpm_test.go
- https://github.com/elastic/elastic-agent/blob/main/testing/integration/linux_deb_test.go

You should be able to modify those to prove the default installation type changed and is maintained as expected. 

If you pushed understanding the ELASTIC_AGENT_FLAVOR variable into the `apply-flavor` command you could test a lot of this independent of the package type.

One way is having a `set-flavor` command and passing the env var value to it. Alternatively, just having an argument to read it from the environment in apply-flavor or a new `set-flavor` command that you unconditionally set here. 

Then you'd just need to test that `set-flavor` and `apply-flavor` chain together properly, and basic smoke testing that the behavior of the RPM and DEB changed flavor appropriately as a result.
created_at: 2025-03-17T18:47:28Z
author_association: MEMBER
diff_hunk: @@ -48,7 +48,7 @@ func UsedFlavor(topPath, defaultFlavor string) (string, error) {
 		return "", fmt.Errorf("failed reading flavor marker file: %w", err)
 	}
 
-	return string(content), nil
+	return strings.TrimSpace(string(content)), nil
path: internal/pkg/agent/install/flavors.go
user: login: cmacknz
body: What bug is this fixing? It's not clear there's a test for it.
created_at: 2025-03-17T18:50:17Z
author_association: MEMBER
diff_hunk: @@ -118,7 +118,7 @@ func ApplyFlavor(versionedHome string, flavor FlavorDefinition) error {
 	}
 
 	for _, ftr := range filesToRemove {
-		if removeErr := os.RemoveAll(ftr); !os.IsNotExist(removeErr) {
+		if removeErr := os.RemoveAll(ftr); removeErr != nil && !os.IsNotExist(removeErr) {
path: internal/pkg/agent/install/flavors.go
user: login: cmacknz
body: Same as above.

What bug is this fixing? It's not clear there's a test for it.
created_at: 2025-03-17T18:50:24Z
author_association: MEMBER
diff_hunk: @@ -43,4 +44,19 @@ if test -L "$symlink"; then
     fi
 else
     echo "no previous installation found"
+
+    # create dir in case it does not exist
+    mkdir -p "$new_agent_dir" 
+    
+    # 2 is upgrade for Fedora, do not upgrade file when upgrading and file exists
+    if [[ "$1" != "2" ]]; then
+        if [[ -n "${ELASTIC_AGENT_FLAVOR}" ]]; then
path: dev-tools/packaging/templates/linux/preinstall.sh.tmpl
user: login: cmacknz
body: We would also want to make sure that an RPM and DEB upgrade do not reset this accidentally.
created_at: 2025-03-17T18:53:40Z
author_association: MEMBER
diff_hunk: @@ -118,7 +118,7 @@ func ApplyFlavor(versionedHome string, flavor FlavorDefinition) error {
 	}
 
 	for _, ftr := range filesToRemove {
-		if removeErr := os.RemoveAll(ftr); !os.IsNotExist(removeErr) {
+		if removeErr := os.RemoveAll(ftr); removeErr != nil && !os.IsNotExist(removeErr) {
path: internal/pkg/agent/install/flavors.go
user: login: michalpristas
body: this is path that was not hit before. in case removeErr is nil it will still go inside this condition
created_at: 2025-03-18T11:39:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,7 +48,7 @@ func UsedFlavor(topPath, defaultFlavor string) (string, error) {
 		return "", fmt.Errorf("failed reading flavor marker file: %w", err)
 	}
 
-	return string(content), nil
+	return strings.TrimSpace(string(content)), nil
path: internal/pkg/agent/install/flavors.go
user: login: michalpristas
body: i've seen weird formatting in some test, i do not recall which one
created_at: 2025-03-18T11:40:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -43,4 +44,19 @@ if test -L "$symlink"; then
     fi
 else
     echo "no previous installation found"
+
+    # create dir in case it does not exist
+    mkdir -p "$new_agent_dir" 
+    
+    # 2 is upgrade for Fedora, do not upgrade file when upgrading and file exists
+    if [[ "$1" != "2" ]]; then
+        if [[ -n "${ELASTIC_AGENT_FLAVOR}" ]]; then
path: dev-tools/packaging/templates/linux/preinstall.sh.tmpl
user: login: michalpristas
body: what kind of commands do you have in mind, elastic agent commands?

created_at: 2025-03-18T11:46:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -43,4 +44,19 @@ if test -L "$symlink"; then
     fi
 else
     echo "no previous installation found"
+
+    # create dir in case it does not exist
+    mkdir -p "$new_agent_dir" 
+    
+    # 2 is upgrade for Fedora, do not upgrade file when upgrading and file exists
+    if [[ "$1" != "2" ]]; then
+        if [[ -n "${ELASTIC_AGENT_FLAVOR}" ]]; then
path: dev-tools/packaging/templates/linux/preinstall.sh.tmpl
user: login: cmacknz
body: Yes, `apply-flavor` already is a command. Thinking more, these commands only make sense if you start from an unflavored agent as it can't add binaries back.

So my suggestion would be to have `apply-flavor` take the flavor to apply as an argument and encapsulate updating the file into it (or you could add a `--env` argument into it to push the env var there as well).
created_at: 2025-03-18T13:32:41Z
author_association: MEMBER
diff_hunk: @@ -43,4 +44,19 @@ if test -L "$symlink"; then
     fi
 else
     echo "no previous installation found"
+
+    # create dir in case it does not exist
+    mkdir -p "$new_agent_dir" 
+    
+    # 2 is upgrade for Fedora, do not upgrade file when upgrading and file exists
+    if [[ "$1" != "2" ]]; then
+        if [[ -n "${ELASTIC_AGENT_FLAVOR}" ]]; then
path: dev-tools/packaging/templates/linux/preinstall.sh.tmpl
user: login: cmacknz
body: I am much more interested in getting correct test coverage than I am about the exact design that leads to that happening at this point.

Doing more in apply-flavor would let you test outside of the RPM and DEB package but if it's easier to just test those directly do that instead.
created_at: 2025-03-18T14:12:20Z
author_association: MEMBER
diff_hunk: @@ -43,4 +44,19 @@ if test -L "$symlink"; then
     fi
 else
     echo "no previous installation found"
+
+    # create dir in case it does not exist
+    mkdir -p "$new_agent_dir" 
+    
+    # 2 is upgrade for Fedora, do not upgrade file when upgrading and file exists
+    if [[ "$1" != "2" ]]; then
+        if [[ -n "${ELASTIC_AGENT_FLAVOR}" ]]; then
path: dev-tools/packaging/templates/linux/preinstall.sh.tmpl
user: login: michalpristas
body: added tests
created_at: 2025-03-19T12:25:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package cmd
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/spf13/cobra"
+
+	"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
+	"github.com/elastic/elastic-agent/internal/pkg/agent/install"
+	"github.com/elastic/elastic-agent/internal/pkg/cli"
+	v1 "github.com/elastic/elastic-agent/pkg/api/v1"
+)
+
+func newApplyFlavorCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "apply-flavor",
+		Short: "Apply Flavor cleans up unnecessary components from agent installation directory",
+		Run: func(c *cobra.Command, _ []string) {
+			if err := applyCmd(); err != nil {
+				fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
+				logExternal(fmt.Sprintf("%s apply flavor failed: %s", paths.BinaryName, err))
+				os.Exit(1)
+			}
+		},
+		Hidden: true,
+	}
+
+	return cmd
+}
+
+func applyCmd() error {
+	topPath := paths.Top()
+	detectedFlavor, err := install.UsedFlavor(topPath, "")
+	if err != nil {
+		if errors.Is(err, os.ErrNotExist) {
+			return nil
path: internal/pkg/agent/cmd/apply_flavor.go
user: login: cmacknz
body: It is worth logging that we did nothing because there is no flavor file so we or the caller know this has happened.
created_at: 2025-03-19T15:11:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,59 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package cmd
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/spf13/cobra"
+
+	"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
+	"github.com/elastic/elastic-agent/internal/pkg/agent/install"
+	"github.com/elastic/elastic-agent/internal/pkg/cli"
+	v1 "github.com/elastic/elastic-agent/pkg/api/v1"
+)
+
+func newApplyFlavorCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "apply-flavor",
+		Short: "Apply Flavor cleans up unnecessary components from agent installation directory",
+		Run: func(c *cobra.Command, _ []string) {
+			if err := applyCmd(); err != nil {
+				fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
+				logExternal(fmt.Sprintf("%s apply flavor failed: %s", paths.BinaryName, err))
+				os.Exit(1)
+			}
+		},
+		Hidden: true,
+	}
+
+	return cmd
+}
+
+func applyCmd() error {
+	topPath := paths.Top()
+	detectedFlavor, err := install.UsedFlavor(topPath, "")
+	if err != nil {
+		if errors.Is(err, os.ErrNotExist) {
+			return nil
+		}
+		return err
+	}
+	if detectedFlavor == "" {
path: internal/pkg/agent/cmd/apply_flavor.go
user: login: cmacknz
body: Same here, we should log that we detected no flavor and did nothing.

In general this function should log what it did, neither install.Flavor or install.ApplyFlavor seem to log anything either. We can't tell "successfully changed flavor" from "did nothing because the flavor was already applied" for example.
created_at: 2025-03-19T15:12:58Z
author_association: MEMBER
diff_hunk: @@ -49,6 +50,75 @@ func TestDebLogIngestFleetManaged(t *testing.T) {
 	agentFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("deb"))
 	require.NoError(t, err)
 
+	installOpts := atesting.InstallOpts{
+		NonInteractive: true,
+		Force:          true,
+		InstallServers: false,
+	}
+
+	testDebLogIngestFleetManagedWithCheck(ctx, t, agentFixture, info, installOpts,
path: testing/integration/linux_deb_test.go
user: login: cmacknz
body: This check should go at the start and end of the upgrade test as well, perhaps just the end since we know from this test that installation behaves properly.

The same should apply to the RPM upgrade variant.
created_at: 2025-03-19T15:24:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,59 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package cmd
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/spf13/cobra"
+
+	"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
+	"github.com/elastic/elastic-agent/internal/pkg/agent/install"
+	"github.com/elastic/elastic-agent/internal/pkg/cli"
+	v1 "github.com/elastic/elastic-agent/pkg/api/v1"
+)
+
+func newApplyFlavorCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "apply-flavor",
+		Short: "Apply Flavor cleans up unnecessary components from agent installation directory",
+		Run: func(c *cobra.Command, _ []string) {
+			if err := applyCmd(); err != nil {
+				fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
+				logExternal(fmt.Sprintf("%s apply flavor failed: %s", paths.BinaryName, err))
+				os.Exit(1)
+			}
+		},
+		Hidden: true,
+	}
+
+	return cmd
+}
+
+func applyCmd() error {
+	topPath := paths.Top()
+	detectedFlavor, err := install.UsedFlavor(topPath, "")
+	if err != nil {
+		if errors.Is(err, os.ErrNotExist) {
+			return nil
path: internal/pkg/agent/cmd/apply_flavor.go
user: login: michalpristas
body: followup
created_at: 2025-03-19T16:20:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,6 +50,75 @@ func TestDebLogIngestFleetManaged(t *testing.T) {
 	agentFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("deb"))
 	require.NoError(t, err)
 
+	installOpts := atesting.InstallOpts{
+		NonInteractive: true,
+		Force:          true,
+		InstallServers: false,
+	}
+
+	testDebLogIngestFleetManagedWithCheck(ctx, t, agentFixture, info, installOpts,
path: testing/integration/linux_deb_test.go
user: login: michalpristas
body: followup
created_at: 2025-03-19T16:20:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package cmd
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/spf13/cobra"
+
+	"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
+	"github.com/elastic/elastic-agent/internal/pkg/agent/install"
+	"github.com/elastic/elastic-agent/internal/pkg/cli"
+	v1 "github.com/elastic/elastic-agent/pkg/api/v1"
+)
+
+func newApplyFlavorCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "apply-flavor",
+		Short: "Apply Flavor cleans up unnecessary components from agent installation directory",
+		Run: func(c *cobra.Command, _ []string) {
+			if err := applyCmd(); err != nil {
+				fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
+				logExternal(fmt.Sprintf("%s apply flavor failed: %s", paths.BinaryName, err))
+				os.Exit(1)
+			}
+		},
+		Hidden: true,
+	}
+
+	return cmd
+}
+
+func applyCmd() error {
+	topPath := paths.Top()
+	detectedFlavor, err := install.UsedFlavor(topPath, "")
+	if err != nil {
+		if errors.Is(err, os.ErrNotExist) {
+			return nil
+		}
+		return err
+	}
+	if detectedFlavor == "" {
path: internal/pkg/agent/cmd/apply_flavor.go
user: login: michalpristas
body: followup
created_at: 2025-03-19T16:21:02Z
author_association: CONTRIBUTOR

PR comments:
user: login: elasticmachine
created_at: 2025-01-30T08:54:17Z
body: Pinging @elastic/elastic-agent-control-plane (Team:Elastic-Agent-Control-Plane)
author_association: CONTRIBUTOR
user: login: elastic-sonarqube[bot]
created_at: 2025-01-31T08:21:50Z
body: ## [![Quality Gate failed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonar.elastic.dev/dashboard?id=elastic_elastic-agent_AYluowg0xMq8P7b4moiZ&pullRequest=6638) **Quality Gate failed**  
Failed conditions  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/failed-16px.png '')](https://sonar.elastic.dev/component_measures?id=elastic_elastic-agent_AYluowg0xMq8P7b4moiZ&pullRequest=6638&metric=new_coverage&view=list) [4.0% Coverage on New Code](https://sonar.elastic.dev/component_measures?id=elastic_elastic-agent_AYluowg0xMq8P7b4moiZ&pullRequest=6638&metric=new_coverage&view=list) (required ≥ 40%)  
  
[See analysis details on SonarQube](https://sonar.elastic.dev/dashboard?id=elastic_elastic-agent_AYluowg0xMq8P7b4moiZ&pullRequest=6638)


author_association: NONE
user: login: cmacknz
created_at: 2025-01-31T21:26:14Z
body: We have integration tests for these packages that should be updated to verify this works properly:

- https://github.com/elastic/elastic-agent/blob/main/testing/integration/linux_rpm_test.go
- https://github.com/elastic/elastic-agent/blob/main/testing/integration/linux_deb_test.go

Is an environment variable the canonical way to provide options to these package types? If we do something that doesn't follow the system convention we'll be explaining it forever.
author_association: MEMBER
user: login: leehinman
created_at: 2025-01-31T22:14:20Z
body: > Is an environment variable the canonical way to provide options to these package types? If we do something that doesn't follow the system convention we'll be explaining it forever.

An env var is **NOT** what you would expect.  For both RPM & DEB you would expect multiple RPM & DEB files that are built from the same source.  I would expect 2 RPM & DEB files, one that installs the minimal, and one that installs the extra components.  The extra components one would depend on the minimal.  Any upgrade scenario would upgrade both.


vim is a good example.

## RPM 

You can install `vim-minimal`, and have a working `vi`, here are the package contents.

```shell
$ rpm -ql vim-minimal
/etc/virc
/usr/bin/ex
/usr/bin/rvi
/usr/bin/rview
/usr/bin/vi
/usr/bin/view
/usr/lib/.build-id
/usr/lib/.build-id/5c
/usr/lib/.build-id/5c/ca9c420c7d69ce13f7cfe7ea0e7fdc7c18bc3f
/usr/libexec/vi
/usr/share/man/man1/ex.1.gz
/usr/share/man/man1/rvi.1.gz
/usr/share/man/man1/rview.1.gz
/usr/share/man/man1/vi.1.gz
/usr/share/man/man1/view.1.gz
/usr/share/man/man5/virc.5.gz
```

If you then want a more "full" vim experience you install `vim-enhanced` along side `vim-minimal` and you have:

``` shell
$ rpm -ql vim-enhanced
/usr/bin/rvim
/usr/bin/vim
/usr/bin/vimdiff
/usr/bin/vimtutor
/usr/lib/.build-id
/usr/lib/.build-id/cc
/usr/lib/.build-id/cc/b0b26d3af0feb447e3f786a6b7be4f79f854bd
```

But both of these are built from the same RPM source:

vim-minimal

``` shell
$ dnf info vim-minimal
Last metadata expiration check: 0:00:40 ago on Fri 31 Jan 2025 03:49:34 PM CST.
Installed Packages
Name         : vim-minimal
Epoch        : 2
Version      : 8.2.2637
Release      : 21.el9
Architecture : aarch64
Size         : 1.4 M
Source       : vim-8.2.2637-21.el9.src.rpm
Repository   : @System
From repo    : baseos
Summary      : A minimal version of the VIM editor
URL          : http://www.vim.org/
License      : Vim and MIT
Description  : VIM (VIsual editor iMproved) is an updated and improved version of the
             : vi editor.  Vi was the first real screen-based editor for UNIX, and is
             : still very popular.  VIM improves on vi by adding new features:
             : multiple windows, multi-level undo, block highlighting and more. The
             : vim-minimal package includes a minimal version of VIM, providing
             : the commands vi, view, ex, rvi, and rview. NOTE: The online help is
             : only available when the vim-common package is installed.
```			 

vim-enhanced

``` shell
$ dnf info vim-enhanced
Last metadata expiration check: 0:00:29 ago on Fri 31 Jan 2025 03:49:34 PM CST.
Available Packages
Name         : vim-enhanced
Epoch        : 2
Version      : 8.2.2637
Release      : 21.el9
Architecture : aarch64
Size         : 1.7 M
Source       : vim-8.2.2637-21.el9.src.rpm
Repository   : appstream
Summary      : A version of the VIM editor which includes recent enhancements
URL          : http://www.vim.org/
License      : Vim and MIT
Description  : VIM (VIsual editor iMproved) is an updated and improved version of the
             : vi editor.  Vi was the first real screen-based editor for UNIX, and is
             : still very popular.  VIM improves on vi by adding new features:
             : multiple windows, multi-level undo, block highlighting and more.  The
             : vim-enhanced package contains a version of VIM with extra, recently
             : introduced features like Python and Perl interpreters.
             :
             : Install the vim-enhanced package if you'd like to use a version of the
             : VIM editor which includes recently added enhancements like
             : interpreters for the Python and Perl scripting languages.  You'll also
             : need to install the vim-common package.
```

## Deb

You can install `vim-tiny` to get a minimal vi clone.

```shell
 dpkg -L vim-tiny
/.
/etc
/etc/vim
/etc/vim/vimrc.tiny
/usr
/usr/bin
/usr/bin/vim.tiny
/usr/share
/usr/share/bug
/usr/share/bug/vim-tiny
/usr/share/bug/vim-tiny/presubj
/usr/share/bug/vim-tiny/script
/usr/share/doc
/usr/share/doc/vim-tiny
/usr/share/doc/vim-tiny/changelog.Debian.gz
/usr/share/doc/vim-tiny/copyright
/usr/share/lintian
/usr/share/lintian/overrides
/usr/share/lintian/overrides/vim-tiny
/usr/share/vim
/usr/share/vim/vim91
/usr/share/vim/vim91/doc
/usr/share/vim/vim91/doc/README.Debian
/usr/share/vim/vim91/doc/help.txt
diverted by vim-runtime to: /usr/share/vim/vim91/doc/help.txt.vim-tiny
/usr/share/vim/vim91/doc/tags
diverted by vim-runtime to: /usr/share/vim/vim91/doc/tags.vim-tiny
```

If you want the full vim experience you would install `vim-nox` along side `vim-tiny`

```shell
$ dpkg -L vim-nox
/.
/usr
/usr/bin
/usr/bin/vim.nox
/usr/share
/usr/share/bug
/usr/share/bug/vim-nox
/usr/share/bug/vim-nox/presubj
/usr/share/bug/vim-nox/script
/usr/share/doc
/usr/share/doc/vim-nox
/usr/share/doc/vim-nox/changelog.Debian.gz
/usr/share/doc/vim-nox/copyright
/usr/share/lintian
/usr/share/lintian/overrides
/usr/share/lintian/overrides/vim-nox
```

Deb packages use the alternates system to set symlink so `vi` could point to either `/usr/bin/vim.tiny` or `/usr/bin/vim-nox`

But as you can see from the `apt info` for each, they are built from the same source.

vim-tiny

```shell
$ apt info vim-tiny
Package: vim-tiny
Version: 2:9.1.0016-1ubuntu7.6
Priority: important
Section: editors
Source: vim
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Debian Vim Maintainers <team+vim@tracker.debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 1,850 kB
Provides: editor
Depends: vim-common (= 2:9.1.0016-1ubuntu7.6), libacl1 (>= 2.2.23), libc6 (>= 2.34), libselinux1 (>= 3.1~), libtinfo6 (>= 6)
Suggests: indent
Homepage: https://www.vim.org/
Task: minimal
Download-Size: 770 kB
APT-Manual-Installed: no
APT-Sources: http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 Packages
Description: Vi IMproved - enhanced vi editor - compact version
 Vim is an almost compatible version of the UNIX editor Vi.
 .
 This package contains a minimal version of Vim compiled with no GUI and
 a small subset of features. This package's sole purpose is to provide
 the vi binary for base installations.
 .
 If a vim binary is wanted, try one of the following more featureful
 packages: vim, vim-nox, vim-motif, or vim-gtk3.
```

vim-nox

```shell
$ apt info vim-nox
Package: vim-nox
Version: 2:9.1.0016-1ubuntu7.6
Priority: extra
Section: universe/editors
Source: vim
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Debian Vim Maintainers <team+vim@tracker.debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 4,950 kB
Provides: editor, vim (= 2:9.1.0016-1ubuntu7.6), vim-lua (= 2:9.1.0016-1ubuntu7.6), vim-perl (= 2:9.1.0016-1ubuntu7.6), vim-python3 (= 2:9.1.0016-1ubuntu7.6), vim-ruby (= 2:9.1.0016-1ubuntu7.6), vim-tcl (= 2:9.1.0016-1ubuntu7.6)
Depends: vim-common (= 2:9.1.0016-1ubuntu7.6), vim-runtime (= 2:9.1.0016-1ubuntu7.6), libacl1 (>= 2.2.23), libc6 (>= 2.38), libgpm2 (>= 1.20.7), liblua5.1-0, libperl5.38t64 (>= 5.38.2), libpython3.12t64 (>= 3.12.1), libruby3.2 (>= 3.2.2), libselinux1 (>= 3.1~), libsodium23 (>= 1.0.14), libtcl8.6 (>= 8.6.0), libtinfo6 (>= 6)
Suggests: cscope, vim-doc
Homepage: https://www.vim.org/
Download-Size: 2,040 kB
APT-Manual-Installed: yes
APT-Sources: http://ports.ubuntu.com/ubuntu-ports noble-updates/universe arm64 Packages
Description: Vi IMproved - enhanced vi editor - with scripting languages support
 Vim is an almost compatible version of the UNIX editor Vi.
 .
 Many new features have been added: multi level undo, syntax
 highlighting, command line history, on-line help, filename
 completion, block operations, folding, Unicode support, etc.
 .
 This package contains a version of vim compiled with support for
 scripting with Lua, Perl, Python 3, Ruby, and Tcl but no GUI.
```
author_association: CONTRIBUTOR
user: login: blakerouse
created_at: 2025-02-03T16:26:08Z
body: I agree with @leehinman that for linux distributions it is done with multiple deps/rpms. I normally see things like:

- `elastic-agent-base` - only the Elastic Agent (no components)
- `elastic-agent-components-basic` - all basic components
- `elastic-agent-components-server` - all server components
- `elastic-agent` - (meta package) that depends on `elastic-agent-base` and `elastic-agent-component-basic`
- `elastic-agent-server` - (meta package) that depends on `elastic-agent-base`, `elastic-agent-components-basic`, and `elastic-agent-components-server`

The issue with this type of design, even if there is two packages and not as many as I said above, is that it no longer becomes a single package to download and install. A better install path for that is to add it to a APT source or a DPK source then allow it easy to install and have it install the dependencies.

I think the question here is, if this design (even if more correct) is how we want to require our users to install Elastic Agent from DEB/RPM packages.
author_association: CONTRIBUTOR
user: login: leehinman
created_at: 2025-02-03T19:46:59Z
body: Might just be me but when I did `ELASTIC_AGENT_FLAVOR=servers dpkg -i elastic-agent-9.0.0-arm64.deb` the `.flavor` file did indeed have "servers", but none of the extra components were installed.

One thing I don't like about this approach is that the DEB and RPM file databases will be out of sync with what is on disk.  Running `elastic-agent apply-flavor` will change what is on disk, but it doesn't update the DEB or RPM databases.  This means if anyone is using those databases for things like "making sure the binaries on disk are the ones RPM/DEB installed" it will fail.
author_association: CONTRIBUTOR
user: login: michalpristas
created_at: 2025-02-06T08:56:40Z
body: thanks for your insights. 
if that's ok i will then create base images like we discussed with docker images that represent either full or base version of agent. to avoid breaking changes and staying consistent in between types i'd rather add additional base to current all. behavior would stay the same except flavor file is now part of the rpm/deb package

cc @cmacknz 
author_association: CONTRIBUTOR
user: login: cmacknz
created_at: 2025-03-13T16:25:19Z
body: Considering the timing, and the potential inconsistency between RPM/DEB/MSI and plain `elastic-agent install` if we don't change this before 9.0 FF, the decision is to keep the approach of `ELATIC_AGENT_FLAVOR`. It isn't the canonical way to do this, but we'd rather drift from the system convention than end up with our installation methods not being in a consistent state in 9.0.

We could still introduce meta packages later if we think they result in a better experience here.
author_association: MEMBER
user: login: elastic-sonarqube[bot]
created_at: 2025-03-19T13:41:09Z
body: ## [![Quality Gate failed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonar.elastic.dev/dashboard?id=elastic_elastic-agent_AYluowg0xMq8P7b4moiZ&pullRequest=6638) **Quality Gate failed**  
Failed conditions  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/failed-16px.png '')](https://sonar.elastic.dev/component_measures?id=elastic_elastic-agent_AYluowg0xMq8P7b4moiZ&pullRequest=6638&metric=new_coverage&view=list) [3.8% Coverage on New Code](https://sonar.elastic.dev/component_measures?id=elastic_elastic-agent_AYluowg0xMq8P7b4moiZ&pullRequest=6638&metric=new_coverage&view=list) (required ≥ 40%)  
  
[See analysis details on SonarQube](https://sonar.elastic.dev/dashboard?id=elastic_elastic-agent_AYluowg0xMq8P7b4moiZ&pullRequest=6638)


author_association: NONE
user: login: elasticmachine
created_at: 2025-03-19T15:17:21Z
body: ## :yellow_heart: Build succeeded, but was flaky
* [Buildkite Build](https://buildkite.com/elastic/elastic-agent/builds/18957)
* Commit: 3fb4fe31bd974da7376049921b722361d12138bb

### Failed CI Steps
* [Extended runtime leak tests](https://buildkite.com/elastic/elastic-agent/builds/18957#0195ae70-a748-4364-92f7-b6c89aa7ab58)

### History
* :green_heart: [Build #18901](https://buildkite.com/elastic/elastic-agent/builds/18901) succeeded 44486938b421d2f909ada30afdce55e02264e242
* :green_heart: [Build #18761](https://buildkite.com/elastic/elastic-agent/builds/18761) succeeded 9863ca3463efc150540a87fc92c25bb79c49c0d1
* :broken_heart: [Build #18758](https://buildkite.com/elastic/elastic-agent/builds/18758) failed cf4ac3809d12ee74dabe0634349e03f36eb8fef2
* :broken_heart: [Build #18757](https://buildkite.com/elastic/elastic-agent/builds/18757) failed a51f46bf9adac2e6ab2517947558ce1ef224981a
* :green_heart: [Build #18755](https://buildkite.com/elastic/elastic-agent/builds/18755) succeeded 8344d23645966e8e53ffb0392d3665ef7054f39d
* :broken_heart: [Build #18716](https://buildkite.com/elastic/elastic-agent/builds/18716) failed 4bcacb68539e8b042bbcc1215766c05793f0c419

cc @michalpristas

<!--buildkite-pr-comment-elastic-agent
{"builds":[{"buildStatus":{"state":"passed","success":true,"hasRetries":true,"hasNonPreemptionRetries":true},"url":"https://buildkite.com/elastic/elastic-agent/builds/18957","number":18957,"commit":"3fb4fe31bd974da7376049921b722361d12138bb"},{"buildStatus":{"state":"passed","success":true,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/elastic-agent/builds/18901","number":18901,"commit":"44486938b421d2f909ada30afdce55e02264e242"},{"buildStatus":{"state":"passed","success":true,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/elastic-agent/builds/18761","number":18761,"commit":"9863ca3463efc150540a87fc92c25bb79c49c0d1"},{"buildStatus":{"state":"failing","success":false,"hasRetries":true,"hasNonPreemptionRetries":true},"url":"https://buildkite.com/elastic/elastic-agent/builds/18758","number":18758,"commit":"cf4ac3809d12ee74dabe0634349e03f36eb8fef2"},{"buildStatus":{"state":"failing","success":false,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/elastic-agent/builds/18757","number":18757,"commit":"a51f46bf9adac2e6ab2517947558ce1ef224981a"},{"buildStatus":{"state":"passed","success":true,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/elastic-agent/builds/18755","number":18755,"commit":"8344d23645966e8e53ffb0392d3665ef7054f39d"}],"number":18957}
buildkite-pr-comment-->
author_association: CONTRIBUTOR
user: login: cmacknz
created_at: 2025-03-19T15:46:04Z
body: Edit: this works without issue, was a typo where sudo was discarding the env var.

~I am testing the deb manually and it can't seem to get it to install the servers variant.~

```sh
ELASTIC_AGENT_FLAVOR=servers sudo dpkg -i /tmp/elastic-agent-9.1.0-SNAPSHOT-arm64.deb
Selecting previously unselected package elastic-agent.
(Reading database ... 77355 files and directories currently installed.)
Preparing to unpack .../elastic-agent-9.1.0-SNAPSHOT-arm64.deb ...
no previous installation found
Unpacking elastic-agent (9.1.0) ...
Setting up elastic-agent (9.1.0) ...
create symlink /usr/share/elastic-agent/bin/elastic-agent to /var/lib/elastic-agent/data/elastic-agent-9.1.0-SNAPSHOT-3fb4fe/elastic-agent
systemd enable/restart elastic-agent

ubuntu@feminine-oarfish:~$ ls /var/lib/elastic-agent/data/elastic-agent-9.1.0-SNAPSHOT-3fb4fe/components/
agentbeat           endpoint-security                lenses                 osqueryd
agentbeat.spec.yml  endpoint-security-resources.zip  module                 pf-host-agent
certs               endpoint-security.spec.yml       osquery-extension.ext  pf-host-agent.spec.yml

ubuntu@feminine-oarfish:~$ cat /var/lib/elastic-agent/.flavor
basic
```


Switching to `export ELASTIC_AGENT_FLAVOR=servers` doesn't change anything either.

There is no logging about why it is doing what it is doing. The first time I ran this, I had a typo in the env var and it installed the basic version with no indication that's what happened. This is a bit of a limitation for env vars but we can at least log what env var value we found.

I've also noticed that you need `dpkg -P` to remove the flavors file because `dpkg -r` leaves it there. This will have to be clearly documented.
author_association: MEMBER
user: login: cmacknz
created_at: 2025-03-19T15:50:04Z
body: OK another documentation update, `sudo ELASTIC_AGENT_FLAVOR=servers` instead of `ELASTIC_AGENT_FLAVOR=servers sudo` otherwise `sudo` discards env vars which is a mistake I have had in the past.  This is a suggestion we could put right in the logging.
author_association: MEMBER
user: login: cmacknz
created_at: 2025-03-19T15:58:04Z
body: The original changelog in https://github.com/elastic/elastic-agent/blob/main/changelog/fragments/1737131552-Changes-to-binary-distribution.yaml seems like it would work for this, though it doesn't actually specify how to choose flavors so we should edit it to specify this.
author_association: MEMBER
user: login: cmacknz
created_at: 2025-03-19T16:01:44Z
body: DEB upgrade works:

```
sudo apt-get install -y -qq -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold ./elastic-agent-9.2.0-SNAPSHOT-arm64.deb
(Reading database ... 77795 files and directories currently installed.)
Preparing to unpack .../elastic-agent-9.2.0-SNAPSHOT-arm64.deb ...
previous installation directory /var/lib/elastic-agent/data/elastic-agent-9.1.0-SNAPSHOT-3fb4fe
migrate state from /var/lib/elastic-agent/data/elastic-agent-9.1.0-SNAPSHOT-3fb4fe to /var/lib/elastic-agent/data/elastic-agent-9.2.0-SNAPSHOT-3fb4fe
didn't find /var/lib/elastic-agent/data/elastic-agent-9.1.0-SNAPSHOT-3fb4fe/state.yml
didn't find /var/lib/elastic-agent/data/elastic-agent-9.1.0-SNAPSHOT-3fb4fe/state.enc
Unpacking elastic-agent (9.2.0) over (9.1.0) ...
stopping elastic-agent
Warning: The unit file, source configuration file or drop-ins of elastic-agent.service changed on disk. Run 'systemctl daemon-reload' to reload units.
found symlink /usr/share/elastic-agent/bin/elastic-agent, unlink
systemd daemon-reload
dpkg: warning: unable to delete old directory '/var/lib/elastic-agent/data/elastic-agent-9.1.0-SNAPSHOT-3fb4fe': Directory not empty
Setting up elastic-agent (9.2.0) ...
create symlink /usr/share/elastic-agent/bin/elastic-agent to /var/lib/elastic-agent/data/elastic-agent-9.2.0-SNAPSHOT-3fb4fe/elastic-agent
systemd enable/restart elastic-agent
Scanning processes...
Scanning linux images...

Running kernel seems to be up-to-date.

No services need to be restarted.

No containers need to be restarted.

No user sessions are running outdated binaries.

No VM guests are running outdated hypervisor (qemu) binaries on this host.
```
author_association: MEMBER
user: login: michalpristas
created_at: 2025-03-19T16:16:53Z
body: > OK another documentation update, `sudo ELASTIC_AGENT_FLAVOR=servers` instead of `ELASTIC_AGENT_FLAVOR=servers sudo` otherwise `sudo` discards env vars which is a mistake I have had in the past. This is a suggestion we could put right in the logging.

you can also use `ELASTIC_AGENT_FLAVOR=servers sudo -E` as used in tests. this will preserve env variables. 
author_association: CONTRIBUTOR