Skip to content

Commit 3db4865

Browse files
pchilacmacknz
andauthored
Feature/add agent version to installation directory name (#4193)
* Use package manifest for install (#4173) * use version in path for rpm and deb * install elastic-agent remapping paths * Use package manifests for upgrade (#4174) * map paths when upgrading using .tar.gz packages * use structured output from unpack to identify old and new agents directories * copy state directories and rotate symlink correctly * Support version in watcher cleanup rollback (#4176) * extract directory paths to allow unit testing (instead of nested paths.* calls) * introduce tests for watcher cleanup and rollback * close data directory after cleanup * Reintroduce upgrade version check (#4177) * Fix shutdownCallback() directories * reintroduce version check in upgrade * Use watcher that supports paths in update marker (#4178) * Invoke agent watcher capable of handling new paths * Adapt TestShutdownCallback to run multiple testcases * Fix shutdownCallback to work with version in path * Fix MacOS relink step * Add commit hash to manifest * document manifest-aware upgrade --------- Co-authored-by: Craig MacKenzie <craig.mackenzie@elastic.co> * small fixes based on PR feedback * Add wait for watcher (#4229) * Fix rollbackInstall order of execution and handle errors * Add wait for watcher up and running --------- Co-authored-by: Craig MacKenzie <craig.mackenzie@elastic.co>
1 parent 1b89d57 commit 3db4865

27 files changed

+2311
-336
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: feature
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Add the full version number to the installation directory name
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; a word indicating the component this changeset affects.
22+
component: agent
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/owner/repo/1234
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/owner/repo/1234

dev-tools/mage/settings.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"gopkg.in/yaml.v3"
2424

2525
"github.com/magefile/mage/sh"
26-
"golang.org/x/tools/go/vcs"
26+
"golang.org/x/tools/go/vcs" //nolint:staticcheck // this deprecation will be handled in https://github.com/elastic/elastic-agent/issues/4138
2727

2828
"github.com/elastic/elastic-agent/dev-tools/mage/gotool"
2929
v1 "github.com/elastic/elastic-agent/pkg/api/v1"
@@ -48,6 +48,7 @@ const (
4848
// Mapped functions
4949
agentPackageVersionMappedFunc = "agent_package_version"
5050
agentManifestGeneratorMappedFunc = "manifest"
51+
snapshotSuffix = "snapshot_suffix"
5152
)
5253

5354
// Common settings with defaults derived from files, CWD, and environment.
@@ -108,6 +109,7 @@ var (
108109
"contains": strings.Contains,
109110
agentPackageVersionMappedFunc: AgentPackageVersion,
110111
agentManifestGeneratorMappedFunc: PackageManifest,
112+
snapshotSuffix: SnapshotSuffix,
111113
}
112114
)
113115

@@ -253,6 +255,7 @@ repo.RootDir = {{ repo.RootDir }}
253255
repo.ImportPath = {{ repo.ImportPath }}
254256
repo.SubDir = {{ repo.SubDir }}
255257
agent_package_version = {{ agent_package_version}}
258+
snapshot_suffix = {{ snapshot_suffix }}
256259
`
257260

258261
return Expand(dumpTemplate)
@@ -309,6 +312,13 @@ func PackageManifest() (string, error) {
309312
return "", fmt.Errorf("retrieving agent package version: %w", err)
310313
}
311314
m.Package.Version = packageVersion
315+
316+
hash, err := CommitHash()
317+
if err != nil {
318+
return "", fmt.Errorf("retrieving agent commit hash: %w", err)
319+
}
320+
m.Package.Hash = hash
321+
312322
commitHashShort, err := CommitHashShort()
313323
if err != nil {
314324
return "", fmt.Errorf("retrieving agent commit hash: %w", err)
@@ -318,7 +328,7 @@ func PackageManifest() (string, error) {
318328
m.Package.VersionedHome = versionedHomePath
319329
m.Package.PathMappings = []map[string]string{{}}
320330
m.Package.PathMappings[0][versionedHomePath] = fmt.Sprintf("data/elastic-agent-%s%s-%s", m.Package.Version, SnapshotSuffix(), commitHashShort)
321-
m.Package.PathMappings[0]["manifest.yaml"] = fmt.Sprintf("data/elastic-agent-%s%s-%s/manifest.yaml", m.Package.Version, SnapshotSuffix(), commitHashShort)
331+
m.Package.PathMappings[0][v1.ManifestFileName] = fmt.Sprintf("data/elastic-agent-%s%s-%s/%s", m.Package.Version, SnapshotSuffix(), commitHashShort, v1.ManifestFileName)
322332
yamlBytes, err := yaml.Marshal(m)
323333
if err != nil {
324334
return "", fmt.Errorf("marshaling manifest: %w", err)

dev-tools/packaging/package_test.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,9 @@ func checkZip(t *testing.T, file string) {
201201

202202
func checkManifestFileContents(t *testing.T, extractedPackageDir string) {
203203
t.Log("Checking file manifest.yaml")
204-
manifestReadCloser, err := os.Open(filepath.Join(extractedPackageDir, "manifest.yaml"))
204+
manifestReadCloser, err := os.Open(filepath.Join(extractedPackageDir, v1.ManifestFileName))
205205
if err != nil {
206-
t.Errorf("opening manifest %s : %v", "manifest.yaml", err)
206+
t.Errorf("opening manifest %s : %v", v1.ManifestFileName, err)
207207
}
208208
defer func(closer io.ReadCloser) {
209209
err := closer.Close()
@@ -219,6 +219,9 @@ func checkManifestFileContents(t *testing.T, extractedPackageDir string) {
219219
assert.Equal(t, v1.ManifestKind, m.Kind, "manifest specifies wrong kind")
220220
assert.Equal(t, v1.VERSION, m.Version, "manifest specifies wrong api version")
221221

222+
assert.NotEmpty(t, m.Package.Version, "manifest version must not be empty")
223+
assert.NotEmpty(t, m.Package.Hash, "manifest hash must not be empty")
224+
222225
if assert.NotEmpty(t, m.Package.PathMappings, "path mappings in manifest are empty") {
223226
versionedHome := m.Package.VersionedHome
224227
assert.DirExistsf(t, filepath.Join(extractedPackageDir, versionedHome), "versionedHome directory %q not found in %q", versionedHome, extractedPackageDir)

dev-tools/packaging/packages.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,19 @@ shared:
6262
/etc/init.d/{{.BeatServiceName}}:
6363
template: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/{{.PackageType}}/elastic-agent.init.sh.tmpl'
6464
mode: 0755
65-
/var/lib/{{.BeatName}}/data/{{.BeatName}}-{{ commit_short }}/{{.BeatName}}{{.BinaryExt}}:
65+
/var/lib/{{.BeatName}}/data/{{.BeatName}}-{{agent_package_version}}{{snapshot_suffix}}-{{ commit_short }}/{{.BeatName}}{{.BinaryExt}}:
6666
source: build/golang-crossbuild/{{.BeatName}}-{{.GOOS}}-{{.Platform.Arch}}{{.BinaryExt}}
6767
mode: 0755
68-
/var/lib/{{.BeatName}}/data/{{.BeatName}}-{{ commit_short }}/package.version:
68+
/var/lib/{{.BeatName}}/data/{{.BeatName}}-{{agent_package_version}}{{snapshot_suffix}}-{{ commit_short }}/package.version:
6969
content: >
7070
{{ agent_package_version }}
7171
mode: 0644
72-
/var/lib/{{.BeatName}}/data/{{.BeatName}}-{{ commit_short }}/components:
72+
/var/lib/{{.BeatName}}/data/{{.BeatName}}-{{agent_package_version}}{{snapshot_suffix}}-{{ commit_short }}/components:
7373
source: '{{.AgentDropPath}}/{{.GOOS}}-{{.AgentArchName}}.tar.gz/'
7474
mode: 0755
7575
config_mode: 0644
7676
skip_on_missing: true
77-
/var/lib/{{.BeatName}}/data/{{.BeatName}}-{{ commit_short }}/manifest.yaml:
77+
/var/lib/{{.BeatName}}/data/{{.BeatName}}-{{agent_package_version}}{{snapshot_suffix}}-{{ commit_short }}/manifest.yaml:
7878
mode: 0644
7979
content: >
8080
{{ manifest }}

dev-tools/packaging/templates/darwin/elastic-agent.tmpl

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ set -e
66
symlink="/Library/Elastic/Agent/elastic-agent"
77

88
if test -L "$symlink"; then
9-
ln -sfn "data/elastic-agent-{{ commit_short }}/elastic-agent.app/Contents/MacOS/elastic-agent" "$symlink"
9+
symlinkTarget="data/elastic-agent-{{ commit_short }}/elastic-agent.app/Contents/MacOS/elastic-agent"
10+
if test -f "data/elastic-agent-{{ agent_package_version }}{{ snapshot_suffix }}-{{ commit_short }}/elastic-agent.app/Contents/MacOS/elastic-agent"; then
11+
symlinkTarget="data/elastic-agent-{{ agent_package_version }}{{ snapshot_suffix }}-{{ commit_short }}/elastic-agent.app/Contents/MacOS/elastic-agent"
12+
ln -sfn "$symlinkTarget" "$symlink"
1013
fi
1114

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ if test -L "$symlink"; then
1616
fi
1717

1818
commit_hash="{{ commit_short }}"
19+
version_dir="{{agent_package_version}}{{snapshot_suffix}}"
1920

20-
new_agent_dir="/var/lib/elastic-agent/data/elastic-agent-$commit_hash"
21+
new_agent_dir="/var/lib/elastic-agent/data/elastic-agent-$version_dir-$commit_hash"
2122

2223
# copy the state files if there was a previous agent install
2324
if ! [ -z "$old_agent_dir" ] && ! [ "$old_agent_dir" -ef "$new_agent_dir" ]; then

docs/upgrades.md

+71
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,74 @@ sequenceDiagram
6161
end
6262
end
6363
```
64+
65+
### Introducing package manifest
66+
67+
Starting from version 8.13.0 an additional file `manifest.yaml` is present in elastic-agent packages.
68+
The purpose of this file is to present some metadata and package information to be used during install/upgrade operations.
69+
70+
The first enhancement that makes use of this package manifest is [#2579](https://github.com/elastic/elastic-agent/issues/2579)
71+
as we use the manifest to map the package directory structure (based on agent commit hash) into one that takes also the
72+
agent version into account. This allows releasing versions of the agent package where only the component versions change,
73+
with the agent commit unchanged.
74+
75+
76+
The [structure](../pkg/api/v1/manifest.go) of such manifest is defined in the [api/v1 package](../pkg/api/v1/).
77+
The manifest data is generated during packaging and the file is added to the package files. This is an example of a
78+
complete manifest:
79+
80+
```yaml
81+
version: co.elastic.agent/v1
82+
kind: PackageManifest
83+
package:
84+
version: 8.13.0
85+
snapshot: true
86+
hash: 15658b38b48ba4487afadc5563b1576b85ce0264
87+
versioned-home: data/elastic-agent-15658b
88+
path-mappings:
89+
- data/elastic-agent-15658b: data/elastic-agent-8.13.0-SNAPSHOT-15658b
90+
manifest.yaml: data/elastic-agent-8.13.0-SNAPSHOT-15658b/manifest.yaml
91+
```
92+
93+
The package information describes the package version, whether it's a snapshot build, the elastic-agent commit hash it
94+
has been built from and where to find the versioned home of the elastic agent within the package.
95+
96+
Another section lists the path mappings that must be applied by an elastic-agent that is aware of the package manifest
97+
(version >8.13.0): these path mappings allow the incoming agent version to have some control over where the files in
98+
package will be stored on disk.
99+
100+
#### Upgrading without the manifest
101+
102+
Legacy elastic-agent upgrade is a pretty straightforward affair:
103+
- Download the agent package to use for upgrade
104+
- Open the .zip or .tar.gz archive and iterate over the files
105+
- Look for the elastic-agent commit file to retrieve the actual hash of the agent version we want to install
106+
- Extract any package file under `/data` under the installed agent `/data` directory
107+
- After extraction check if the hash we read from the package matches with the one from the current agent:
108+
- if it's the same hash the upgrade fails because we are trying to upgrade to the same version
109+
- if we extracted a package with a different hash, the upgrade keeps going
110+
- Copy the elastic agent action store and components run directory into the new agent directories `elastic-agent-<hash>`
111+
- Rotate the symlink in the top directory to point to the new agent executable `data/elastic-agent-<hash>/elastic-agent`
112+
- Write the update marker containing the information about the new and old agent versions/hashes in `data` directory
113+
- Invoke the watcher `elastic-agent watch` command to ensure that the new version of agent works correctly after restart
114+
- Shutdown current agent and its command components, copy components state once again and restart
115+
116+
#### Upgrading using the manifest
117+
118+
Upgrading using the manifest allows for the new version to pass along some information about the package to the upgrading agent.
119+
The new process looks like this:
120+
- Download the elastic-agent package to use for upgrade
121+
- Extract package metadata from the new elastic-agent package (`version`, `snapshot` and `hash`):
122+
- if the package has a manifest we extract `version` and `snapshot` flag as declared by the package manifest
123+
- if there is no manifest for the package we extract `version` and `snapshot` from the version string passed to the upgrader
124+
- the `hash` is always retrieved from the agent commit file (this is always present in the package)
125+
- compare the tuple of new `(version, snapshot, hash)` to the current `(version, snapshot, hash)`: if they are the same
126+
the upgrade fails because we are trying to upgrade to the same version as current
127+
- Extract any package file (after mapping it using file mappings in manifest if present) that should go under `/data`.
128+
Return the new versionedHome (where the new version of agent has its files, returned as path relative to the top directory)
129+
- Copy the elastic agent action store and components run directory into the new agent in `<versionedHome>/run`
130+
- Write the update marker containing the information about the new and old agent version, hash and home in `data` directory
131+
- Invoke the watcher `elastic-agent watch` command to ensure that the new version of agent works correctly after restart:
132+
- we invoke the current agent binary if the new version < 8.13.0 (needed to make sure it supports the paths written in the update marker)
133+
- we invoke the new agent binary if the new version > 8.13.0
134+
- Shutdown current agent and its command components, copy components state once again and restart

internal/pkg/agent/application/paths/common.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,16 @@ func ExternalInputs() string {
180180

181181
// Data returns the data directory for Agent
182182
func Data() string {
183+
return DataFrom(Top())
184+
}
185+
186+
// DataFrom returns the data directory for Agent using the passed directory as top path
187+
func DataFrom(topDirPath string) string {
183188
if unversionedHome {
184189
// unversioned means the topPath is the data path
185-
return topPath
190+
return topDirPath
186191
}
187-
return filepath.Join(Top(), "data")
192+
return filepath.Join(topDirPath, "data")
188193
}
189194

190195
// Run returns the run directory for Agent

0 commit comments

Comments
 (0)