Skip to content

Commit 2a3710c

Browse files
[RPM/DEB] Changes to binary distribution (#6638) (#7473)
(cherry picked from commit 5d4b00e) Co-authored-by: Michal Pristas <[email protected]>
1 parent 0c99b52 commit 2a3710c

File tree

10 files changed

+298
-12
lines changed

10 files changed

+298
-12
lines changed

dev-tools/packaging/templates/linux/postinstall.sh.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ fi
1717
echo "create symlink "$symlink" to "$new_agent_dir/elastic-agent""
1818
ln -s "$new_agent_dir/elastic-agent" "$symlink"
1919

20+
$new_agent_dir/elastic-agent apply-flavor
21+
2022
# reload systemctl and then restart service
2123
echo "systemd enable/restart elastic-agent"
2224
systemctl daemon-reload 2> /dev/null

dev-tools/packaging/templates/linux/preinstall.sh.tmpl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set -e
55
commit_hash="{{ commit_short }}"
66
version_dir="{{agent_package_version}}{{snapshot_suffix}}"
77
symlink="/usr/share/elastic-agent/bin/elastic-agent"
8+
flavor_file="/var/lib/elastic-agent/.flavor"
89
new_agent_dir="/var/lib/elastic-agent/data/elastic-agent-$version_dir-$commit_hash"
910
old_agent_dir=""
1011

@@ -43,4 +44,19 @@ if test -L "$symlink"; then
4344
fi
4445
else
4546
echo "no previous installation found"
47+
48+
# create dir in case it does not exist
49+
mkdir -p "$new_agent_dir"
50+
51+
# 2 is upgrade for Fedora, do not upgrade file when upgrading and file exists
52+
if [[ "$1" != "2" ]]; then
53+
if [[ -n "${ELASTIC_AGENT_FLAVOR}" ]]; then
54+
# Do not modify the file if it already exists
55+
echo "${ELASTIC_AGENT_FLAVOR}" > "$flavor_file"
56+
else
57+
# Defaults to basic installation
58+
echo "basic" > "$flavor_file"
59+
fi
60+
fi
4661
fi
62+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License 2.0;
3+
// you may not use this file except in compliance with the Elastic License 2.0.
4+
5+
package cmd
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"os"
11+
"path/filepath"
12+
13+
"github.com/spf13/cobra"
14+
15+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
16+
"github.com/elastic/elastic-agent/internal/pkg/agent/install"
17+
"github.com/elastic/elastic-agent/internal/pkg/cli"
18+
v1 "github.com/elastic/elastic-agent/pkg/api/v1"
19+
)
20+
21+
func newApplyFlavorCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command {
22+
cmd := &cobra.Command{
23+
Use: "apply-flavor",
24+
Short: "Apply Flavor cleans up unnecessary components from agent installation directory",
25+
Run: func(c *cobra.Command, _ []string) {
26+
if err := applyCmd(); err != nil {
27+
fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
28+
logExternal(fmt.Sprintf("%s apply flavor failed: %s", paths.BinaryName, err))
29+
os.Exit(1)
30+
}
31+
},
32+
Hidden: true,
33+
}
34+
35+
return cmd
36+
}
37+
38+
func applyCmd() error {
39+
topPath := paths.Top()
40+
detectedFlavor, err := install.UsedFlavor(topPath, "")
41+
if err != nil {
42+
if errors.Is(err, os.ErrNotExist) {
43+
return nil
44+
}
45+
return err
46+
}
47+
if detectedFlavor == "" {
48+
return nil
49+
}
50+
51+
versionedHome := paths.VersionedHome(topPath)
52+
manifestFilePath := filepath.Join(versionedHome, v1.ManifestFileName)
53+
flavor, err := install.Flavor(detectedFlavor, manifestFilePath, nil)
54+
if err != nil {
55+
return err
56+
}
57+
58+
return install.ApplyFlavor(versionedHome, flavor)
59+
}

internal/pkg/agent/cmd/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command {
9494
addCommandIfNotNil(cmd, newComponentCommandWithArgs(args, streams))
9595
addCommandIfNotNil(cmd, newLogsCommandWithArgs(args, streams))
9696
addCommandIfNotNil(cmd, newOtelCommandWithArgs(args, streams))
97+
addCommandIfNotNil(cmd, newApplyFlavorCommandWithArgs(args, streams))
9798

9899
// windows special hidden sub-command (only added on Windows)
99100
reexec := newReExecWindowsCommand(args, streams)

internal/pkg/agent/install/flavors.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func UsedFlavor(topPath, defaultFlavor string) (string, error) {
4848
return "", fmt.Errorf("failed reading flavor marker file: %w", err)
4949
}
5050

51-
return string(content), nil
51+
return strings.TrimSpace(string(content)), nil
5252
}
5353

5454
func Flavor(detectedFlavor string, registryPath string, flavorsRegistry map[string][]string) (FlavorDefinition, error) {
@@ -118,7 +118,7 @@ func ApplyFlavor(versionedHome string, flavor FlavorDefinition) error {
118118
}
119119

120120
for _, ftr := range filesToRemove {
121-
if removeErr := os.RemoveAll(ftr); !os.IsNotExist(removeErr) {
121+
if removeErr := os.RemoveAll(ftr); removeErr != nil && !os.IsNotExist(removeErr) {
122122
err = fmt.Errorf("failed cleaning components: %w", removeErr)
123123
}
124124
}

internal/pkg/agent/install/flavors_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,22 @@ import (
1313

1414
"github.com/stretchr/testify/assert"
1515
"github.com/stretchr/testify/require"
16+
"gopkg.in/yaml.v3"
17+
18+
v1 "github.com/elastic/elastic-agent/pkg/api/v1"
1619
)
1720

1821
var flavorsRegistry = map[string][]string{
1922
"basic": {"agentbeat", "endpoint-security", "pf-host-agent"},
2023
"servers": {"agentbeat", "endpoint-security", "pf-host-agent", "cloudbeat", "apm-server", "fleet-server", "pf-elastic-symbolizer", "pf-elastic-collector"},
2124
}
2225

26+
var manifest = v1.PackageManifest{
27+
Package: v1.PackageDesc{
28+
Flavors: flavorsRegistry,
29+
},
30+
}
31+
2332
func TestSubpathsForComponent(t *testing.T) {
2433
binarySuffix := ""
2534
if runtime.GOOS == "windows" {
@@ -190,6 +199,42 @@ func TestAllowedSubpathsForFlavor(t *testing.T) {
190199
sort.Strings(subpaths)
191200
assert.Equal(t, tt.wantSubpaths, subpaths)
192201
})
202+
203+
t.Run(tt.name+"-file", func(t *testing.T) {
204+
tmpDir := t.TempDir()
205+
manifestFile := filepath.Join(tmpDir, "manifest.yaml")
206+
mb, err := yaml.Marshal(manifest)
207+
require.NoError(t, err)
208+
require.NoError(t, os.WriteFile(manifestFile, mb, 0o644))
209+
defer os.Remove(manifestFile)
210+
211+
// Create temp dir with spec files
212+
componentsDir := filepath.Join(versionedHome, "components")
213+
require.NoError(t, os.MkdirAll(componentsDir, 0755))
214+
215+
// Write spec files
216+
for component, content := range tt.specFiles {
217+
specPath := filepath.Join(componentsDir, component+".spec.yml")
218+
require.NoError(t, os.WriteFile(specPath, []byte(content), 0644))
219+
defer os.Remove(specPath)
220+
}
221+
222+
// Test function
223+
flavor, err := Flavor(tt.flavor, manifestFile, nil)
224+
if tt.wantError {
225+
require.Error(t, err)
226+
assert.Contains(t, err.Error(), tt.errorContains)
227+
return
228+
}
229+
230+
subpaths, err := allowedSubpathsForFlavor(versionedHome, flavor)
231+
assert.NoError(t, err)
232+
233+
require.NoError(t, err)
234+
sort.Strings(tt.wantSubpaths)
235+
sort.Strings(subpaths)
236+
assert.Equal(t, tt.wantSubpaths, subpaths)
237+
})
193238
}
194239
}
195240

pkg/testing/fixture_install.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,11 @@ func (f *Fixture) installDeb(ctx context.Context, installOpts *InstallOpts, shou
461461
}
462462

463463
// sudo apt-get install the deb
464-
out, err := exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", f.srcPackage).CombinedOutput() // #nosec G204 -- Need to pass in name of package
464+
cmd := exec.CommandContext(ctx, "sudo", "-E", "apt-get", "install", "-y", f.srcPackage) // #nosec G204 -- Need to pass in name of package
465+
if installOpts.InstallServers {
466+
cmd.Env = append(cmd.Env, "ELASTIC_AGENT_FLAVOR=servers")
467+
}
468+
out, err := cmd.CombinedOutput() // #nosec G204 -- Need to pass in name of package
465469
if err != nil {
466470
return out, fmt.Errorf("apt install failed: %w output:%s", err, string(out))
467471
}
@@ -546,7 +550,11 @@ func (f *Fixture) installRpm(ctx context.Context, installOpts *InstallOpts, shou
546550
}
547551

548552
// sudo rpm -iv elastic-agent rpm
549-
out, err := exec.CommandContext(ctx, "sudo", "rpm", "-i", "-v", f.srcPackage).CombinedOutput() // #nosec G204 -- Need to pass in name of package
553+
cmd := exec.CommandContext(ctx, "sudo", "-E", "rpm", "-i", "-v", f.srcPackage) // #nosec G204 -- Need to pass in name of package
554+
if installOpts.InstallServers {
555+
cmd.Env = append(cmd.Env, "ELASTIC_AGENT_FLAVOR=servers")
556+
}
557+
out, err := cmd.CombinedOutput()
550558
if err != nil {
551559
return out, fmt.Errorf("rpm install failed: %w output:%s", err, string(out))
552560
}

testing/integration/install_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,10 @@ func testInstallWithoutBasePathWithCustomUser(ctx context.Context, t *testing.T,
438438
func testComponentsPresence(ctx context.Context, fixture *atesting.Fixture, requiredComponents []componentPresenceDefinition, unwantedComponents []componentPresenceDefinition) func(*testing.T) {
439439
return func(t *testing.T) {
440440
agentWorkDir := fixture.WorkDir()
441+
if pf := fixture.PackageFormat(); pf == "deb" || pf == "rpm" {
442+
// these are hardcoded paths in packages.yml
443+
agentWorkDir = "/var/lib/elastic-agent"
444+
}
441445
componentsDir, err := aTesting.FindComponentsDir(agentWorkDir)
442446
require.NoError(t, err)
443447

testing/integration/linux_deb_test.go

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package integration
99
import (
1010
"context"
1111
"fmt"
12+
"os"
1213
"os/exec"
1314
"strings"
1415
"testing"
@@ -49,6 +50,75 @@ func TestDebLogIngestFleetManaged(t *testing.T) {
4950
agentFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("deb"))
5051
require.NoError(t, err)
5152

53+
installOpts := atesting.InstallOpts{
54+
NonInteractive: true,
55+
Force: true,
56+
InstallServers: false,
57+
}
58+
59+
testDebLogIngestFleetManagedWithCheck(ctx, t, agentFixture, info, installOpts,
60+
testComponentsPresence(ctx, agentFixture,
61+
[]componentPresenceDefinition{
62+
{"agentbeat", []string{"windows", "linux", "darwin"}},
63+
{"endpoint-security", []string{"windows", "linux", "darwin"}},
64+
{"pf-host-agent", []string{"linux"}},
65+
},
66+
[]componentPresenceDefinition{
67+
{"cloudbeat", []string{"linux"}},
68+
{"apm-server", []string{"windows", "linux", "darwin"}},
69+
{"fleet-server", []string{"windows", "linux", "darwin"}},
70+
{"pf-elastic-symbolizer", []string{"linux"}},
71+
{"pf-elastic-collector", []string{"linux"}},
72+
},
73+
),
74+
)
75+
}
76+
77+
func TestDebInstallsServers(t *testing.T) {
78+
info := define.Require(t, define.Requirements{
79+
Group: Deb,
80+
Stack: &define.Stack{},
81+
OS: []define.OS{
82+
{
83+
Type: define.Linux,
84+
Distro: "ubuntu",
85+
},
86+
},
87+
Local: false,
88+
Sudo: true,
89+
})
90+
91+
ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute))
92+
defer cancel()
93+
94+
agentFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("deb"))
95+
require.NoError(t, err)
96+
97+
installOpts := atesting.InstallOpts{
98+
NonInteractive: true,
99+
Force: true,
100+
InstallServers: true,
101+
}
102+
103+
testDebLogIngestFleetManagedWithCheck(ctx, t, agentFixture, info, installOpts,
104+
testComponentsPresence(ctx, agentFixture,
105+
[]componentPresenceDefinition{
106+
{"agentbeat", []string{"windows", "linux", "darwin"}},
107+
{"endpoint-security", []string{"windows", "linux", "darwin"}},
108+
{"pf-host-agent", []string{"linux"}},
109+
{"cloudbeat", []string{"linux"}},
110+
{"apm-server", []string{"windows", "linux", "darwin"}},
111+
{"fleet-server", []string{"windows", "linux", "darwin"}},
112+
{"pf-elastic-symbolizer", []string{"linux"}},
113+
{"pf-elastic-collector", []string{"linux"}},
114+
},
115+
[]componentPresenceDefinition{},
116+
),
117+
)
118+
}
119+
120+
func testDebLogIngestFleetManagedWithCheck(ctx context.Context, t *testing.T, agentFixture *atesting.Fixture, info *define.Info, installOpts atesting.InstallOpts, componentCheck func(t *testing.T)) {
121+
52122
// 1. Create a policy in Fleet with monitoring enabled.
53123
// To ensure there are no conflicts with previous test runs against
54124
// the same ESS stack, we add the current time at the end of the policy
@@ -70,10 +140,12 @@ func TestDebLogIngestFleetManaged(t *testing.T) {
70140
},
71141
}
72142

73-
installOpts := atesting.InstallOpts{
74-
NonInteractive: true,
75-
Force: true,
76-
}
143+
// remove flavor to start fresh, in case there is some leftover.
144+
_ = os.Remove("/var/lib/elastic-agent/.flavor")
145+
t.Cleanup(func() {
146+
// cleanup after ourselves
147+
_ = os.Remove("/var/lib/elastic-agent/.flavor")
148+
})
77149

78150
// 2. Install the Elastic-Agent with the policy that
79151
// was just created.
@@ -88,6 +160,10 @@ func TestDebLogIngestFleetManaged(t *testing.T) {
88160
t.Logf("created policy: %s", policy.ID)
89161
check.ConnectedToFleet(ctx, t, agentFixture, 5*time.Minute)
90162

163+
if componentCheck != nil {
164+
t.Run("check components set", componentCheck)
165+
}
166+
91167
t.Run("Monitoring logs are shipped", func(t *testing.T) {
92168
testMonitoringLogsAreShipped(t, ctx, info, agentFixture, policy)
93169
})

0 commit comments

Comments
 (0)