Skip to content

Commit 44528c4

Browse files
authored
Fix debian packaging for upgrades (#5260)
* Fix upgrade to not delete symlink and to restart daemon. * Fix upgrade. * Add changelog. * Add deb/rpm upgrade tests. * Fix build tags. * Fix typo in service name for systemctl enable. * Fix issue in binaryPath() to work before install. * Don't use extract for deb/rpm in tests. * RHEL for RPM. * Fix test issues. * Fix deb/rpm upgrade tests. * Fix imports.
1 parent a1fd2c9 commit 44528c4

8 files changed

+475
-170
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: bug-fix
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Fix loss of state.enc on upgrade with debian packaging
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
19+
#description:
20+
21+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
22+
component:
23+
24+
# PR URL; optional; the PR number that added the changeset.
25+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
26+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
27+
# Please provide it if you are adding a fragment for a different PR.
28+
pr: https://github.com/elastic/elastic-agent/pull/5260
29+
30+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
31+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
32+
issue: https://github.com/elastic/elastic-agent/issues/5101

dev-tools/packaging/packages.yml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ shared:
2020
# Deb/RPM spec for community beats.
2121
- &deb_rpm_agent_spec
2222
<<: *common
23+
pre_install_script: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/linux/preinstall.sh.tmpl'
2324
post_install_script: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/linux/postinstall.sh.tmpl'
2425
post_rm_script: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/linux/postrm.sh.tmpl'
2526
files:

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

+6-32
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,12 @@
22

33
set -e
44

5-
symlink="/usr/share/elastic-agent/bin/elastic-agent"
6-
old_agent_dir=""
7-
8-
# check if $symlink exists for the previous install
9-
# and derive the old agent directory
10-
if test -L "$symlink"; then
11-
resolved_symlink="$(readlink -f -- "$symlink")"
12-
# check if it is resolved to non empty string
13-
if ! [ -z "$resolved_symlink" ]; then
14-
old_agent_dir="$( dirname "$resolved_symlink" )"
15-
fi
16-
fi
17-
185
commit_hash="{{ commit_short }}"
196
version_dir="{{agent_package_version}}{{snapshot_suffix}}"
20-
7+
symlink="/usr/share/elastic-agent/bin/elastic-agent"
218
new_agent_dir="/var/lib/elastic-agent/data/elastic-agent-$version_dir-$commit_hash"
229

23-
# copy the state files if there was a previous agent install
24-
if ! [ -z "$old_agent_dir" ] && ! [ "$old_agent_dir" -ef "$new_agent_dir" ]; then
25-
yml_path="$old_agent_dir/state.yml"
26-
enc_path="$old_agent_dir/state.enc"
27-
echo "migrate state from $old_agent_dir to $new_agent_dir"
28-
29-
if test -f "$yml_path"; then
30-
echo "found "$yml_path", copy to "$new_agent_dir"."
31-
cp "$yml_path" "$new_agent_dir"
32-
fi
33-
34-
if test -f "$enc_path"; then
35-
echo "found "$enc_path", copy to "$new_agent_dir"."
36-
cp "$enc_path" "$new_agent_dir"
37-
fi
38-
fi
39-
40-
# delete symlink if exists
10+
# delete $symlink if exists
4111
if test -L "$symlink"; then
4212
echo "found symlink $symlink, unlink"
4313
unlink "$symlink"
@@ -47,5 +17,9 @@ fi
4717
echo "create symlink "$symlink" to "$new_agent_dir/elastic-agent""
4818
ln -s "$new_agent_dir/elastic-agent" "$symlink"
4919

20+
# reload systemctl and then restart service
21+
echo "systemd enable/restart elastic-agent"
5022
systemctl daemon-reload 2> /dev/null
23+
systemctl enable elastic-agent 2> /dev/null || true
24+
systemctl restart elastic-agent 2> /dev/null || true
5125
exit 0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
commit_hash="{{ commit_short }}"
6+
version_dir="{{agent_package_version}}{{snapshot_suffix}}"
7+
symlink="/usr/share/elastic-agent/bin/elastic-agent"
8+
new_agent_dir="/var/lib/elastic-agent/data/elastic-agent-$version_dir-$commit_hash"
9+
old_agent_dir=""
10+
11+
# upon upgrade we migrate the current symlink to an upgrade symlink as the previous
12+
# installed version will remove the symlink
13+
if test -L "$symlink"; then
14+
resolved_symlink="$(readlink -- "$symlink")"
15+
if ! [ -z "$resolved_symlink" ]; then
16+
old_agent_dir="$( dirname "$resolved_symlink" )"
17+
echo "previous installation directory $old_agent_dir"
18+
else
19+
echo "unable to read existing symlink"
20+
fi
21+
22+
# copy the state files if there was a previous agent install
23+
if ! [ -z "$old_agent_dir" ] && ! [ "$old_agent_dir" -ef "$new_agent_dir" ]; then
24+
yml_path="$old_agent_dir/state.yml"
25+
enc_path="$old_agent_dir/state.enc"
26+
echo "migrate state from $old_agent_dir to $new_agent_dir"
27+
28+
if test -f "$yml_path"; then
29+
echo "found "$yml_path", copy to "$new_agent_dir"."
30+
mkdir -p "$new_agent_dir"
31+
cp "$yml_path" "$new_agent_dir"
32+
else
33+
echo "didn't find $yml_path"
34+
fi
35+
36+
if test -f "$enc_path"; then
37+
echo "found "$enc_path", copy to "$new_agent_dir"."
38+
mkdir -p "$new_agent_dir"
39+
cp "$enc_path" "$new_agent_dir"
40+
else
41+
echo "didn't find $enc_path"
42+
fi
43+
fi
44+
else
45+
echo "no previous installation found"
46+
fi

pkg/testing/fixture_install.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,8 @@ func (f *Fixture) installDeb(ctx context.Context, installOpts *InstallOpts, opts
408408
return nil, fmt.Errorf("failed to prepare: %w", err)
409409
}
410410

411-
// sudo apt install the deb
412-
out, err := exec.CommandContext(ctx, "sudo", "apt", "install", f.srcPackage).CombinedOutput() // #nosec G204 -- Need to pass in name of package
411+
// sudo apt-get install the deb
412+
out, err := exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", f.srcPackage).CombinedOutput() // #nosec G204 -- Need to pass in name of package
413413
if err != nil {
414414
return out, fmt.Errorf("apt install failed: %w output:%s", err, string(out))
415415
}

testing/integration/linux_deb_test.go

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
//go:build integration
6+
7+
package integration
8+
9+
import (
10+
"context"
11+
"fmt"
12+
"os/exec"
13+
"strings"
14+
"testing"
15+
"time"
16+
17+
"github.com/gofrs/uuid/v5"
18+
19+
"github.com/elastic/elastic-agent-libs/kibana"
20+
21+
atesting "github.com/elastic/elastic-agent/pkg/testing"
22+
"github.com/elastic/elastic-agent/pkg/testing/define"
23+
"github.com/elastic/elastic-agent/pkg/testing/tools"
24+
"github.com/elastic/elastic-agent/pkg/testing/tools/check"
25+
"github.com/elastic/elastic-agent/pkg/testing/tools/fleettools"
26+
"github.com/elastic/elastic-agent/pkg/testing/tools/testcontext"
27+
"github.com/elastic/elastic-agent/testing/upgradetest"
28+
29+
"github.com/stretchr/testify/require"
30+
)
31+
32+
func TestDebLogIngestFleetManaged(t *testing.T) {
33+
info := define.Require(t, define.Requirements{
34+
Group: Deb,
35+
Stack: &define.Stack{},
36+
OS: []define.OS{
37+
{
38+
Type: define.Linux,
39+
Distro: "ubuntu",
40+
},
41+
},
42+
Local: false,
43+
Sudo: true,
44+
})
45+
46+
ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute))
47+
defer cancel()
48+
49+
agentFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("deb"))
50+
require.NoError(t, err)
51+
52+
// 1. Create a policy in Fleet with monitoring enabled.
53+
// To ensure there are no conflicts with previous test runs against
54+
// the same ESS stack, we add the current time at the end of the policy
55+
// name. This policy does not contain any integration.
56+
t.Log("Enrolling agent in Fleet with a test policy")
57+
createPolicyReq := kibana.AgentPolicy{
58+
Name: fmt.Sprintf("test-policy-enroll-%s", uuid.Must(uuid.NewV4()).String()),
59+
Namespace: info.Namespace,
60+
Description: "test policy for agent enrollment",
61+
MonitoringEnabled: []kibana.MonitoringEnabledOption{
62+
kibana.MonitoringEnabledLogs,
63+
kibana.MonitoringEnabledMetrics,
64+
},
65+
AgentFeatures: []map[string]interface{}{
66+
{
67+
"name": "test_enroll",
68+
"enabled": true,
69+
},
70+
},
71+
}
72+
73+
installOpts := atesting.InstallOpts{
74+
NonInteractive: true,
75+
Force: true,
76+
}
77+
78+
// 2. Install the Elastic-Agent with the policy that
79+
// was just created.
80+
policy, err := tools.InstallAgentWithPolicy(
81+
ctx,
82+
t,
83+
installOpts,
84+
agentFixture,
85+
info.KibanaClient,
86+
createPolicyReq)
87+
require.NoError(t, err)
88+
t.Logf("created policy: %s", policy.ID)
89+
check.ConnectedToFleet(ctx, t, agentFixture, 5*time.Minute)
90+
91+
t.Run("Monitoring logs are shipped", func(t *testing.T) {
92+
testMonitoringLogsAreShipped(t, ctx, info, agentFixture, policy)
93+
})
94+
95+
t.Run("Normal logs with flattened data_stream are shipped", func(t *testing.T) {
96+
testFlattenedDatastreamFleetPolicy(t, ctx, info, policy)
97+
})
98+
}
99+
100+
func TestDebFleetUpgrade(t *testing.T) {
101+
info := define.Require(t, define.Requirements{
102+
Group: Deb,
103+
Stack: &define.Stack{},
104+
OS: []define.OS{
105+
{
106+
Type: define.Linux,
107+
Distro: "ubuntu",
108+
},
109+
},
110+
Local: false,
111+
Sudo: true,
112+
})
113+
114+
ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute))
115+
defer cancel()
116+
117+
// start from previous minor
118+
upgradeFromVersion, err := upgradetest.PreviousMinor()
119+
require.NoError(t, err)
120+
startFixture, err := atesting.NewFixture(
121+
t,
122+
upgradeFromVersion.String(),
123+
atesting.WithFetcher(atesting.ArtifactFetcher()),
124+
atesting.WithPackageFormat("deb"),
125+
)
126+
require.NoError(t, err)
127+
128+
// end on the current build with deb
129+
endFixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("deb"))
130+
require.NoError(t, err)
131+
132+
// 1. Create a policy in Fleet with monitoring enabled.
133+
// To ensure there are no conflicts with previous test runs against
134+
// the same ESS stack, we add the current time at the end of the policy
135+
// name. This policy does not contain any integration.
136+
t.Log("Enrolling agent in Fleet with a test policy")
137+
createPolicyReq := kibana.AgentPolicy{
138+
Name: fmt.Sprintf("test-policy-enroll-%s", uuid.Must(uuid.NewV4()).String()),
139+
Namespace: info.Namespace,
140+
Description: "test policy for agent enrollment",
141+
MonitoringEnabled: []kibana.MonitoringEnabledOption{
142+
kibana.MonitoringEnabledLogs,
143+
kibana.MonitoringEnabledMetrics,
144+
},
145+
AgentFeatures: []map[string]interface{}{
146+
{
147+
"name": "test_enroll",
148+
"enabled": true,
149+
},
150+
},
151+
}
152+
153+
installOpts := atesting.InstallOpts{
154+
NonInteractive: true,
155+
Force: true,
156+
}
157+
158+
// 2. Install the Elastic-Agent with the policy that
159+
// was just created.
160+
policy, err := tools.InstallAgentWithPolicy(
161+
ctx,
162+
t,
163+
installOpts,
164+
startFixture,
165+
info.KibanaClient,
166+
createPolicyReq)
167+
require.NoError(t, err)
168+
t.Logf("created policy: %s", policy.ID)
169+
check.ConnectedToFleet(ctx, t, startFixture, 5*time.Minute)
170+
171+
// 3. Upgrade deb to the build version
172+
srcPackage, err := endFixture.SrcPackage(ctx)
173+
require.NoError(t, err)
174+
cmd := exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "-qq", "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold", srcPackage)
175+
cmd.Env = append(cmd.Env, "DEBIAN_FRONTEND=noninteractive")
176+
out, err := cmd.CombinedOutput() // #nosec G204 -- Need to pass in name of package
177+
require.NoError(t, err, string(out))
178+
179+
// 4. Wait for version in Fleet to match
180+
// Fleet will not include the `-SNAPSHOT` in the `GetAgentVersion` result
181+
noSnapshotVersion := strings.TrimSuffix(define.Version(), "-SNAPSHOT")
182+
require.Eventually(t, func() bool {
183+
newVersion, err := fleettools.GetAgentVersion(ctx, info.KibanaClient, policy.ID)
184+
if err != nil {
185+
t.Logf("error getting agent version: %v", err)
186+
return false
187+
}
188+
if noSnapshotVersion == newVersion {
189+
return true
190+
}
191+
t.Logf("Got Agent version %s != %s", newVersion, noSnapshotVersion)
192+
return false
193+
}, 5*time.Minute, time.Second)
194+
}

0 commit comments

Comments
 (0)