-
Notifications
You must be signed in to change notification settings - Fork 144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: Agent failed to upgrade from 8.4.2 to 8.5.0 BC1 for MAC 12 agent using agent binary. #1392
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
APPL???? |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleExecutable</key> | ||
<string>elastic-agent</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>co.elastic.elastic-agent</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>elastic-agent</string> | ||
<key>CFBundlePackageType</key> | ||
<string>APPL</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>{{ beat_version }}</string> | ||
<key>CFBundleVersion</key> | ||
<string>{{ beat_version }}</string> | ||
</dict> | ||
</plist> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#!/bin/sh | ||
# Fix up the symlink and exit | ||
|
||
set -e | ||
|
||
symlink="/Library/Elastic/Agent/elastic-agent" | ||
|
||
if test -L "$symlink"; then | ||
ln -sfn "data/elastic-agent-{{ commit_short }}/elastic-agent.app/Contents/MacOS/elastic-agent" "$symlink" | ||
fi | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
// or more contributor license agreements. Licensed under the Elastic License; | ||
// you may not use this file except in compliance with the Elastic License. | ||
|
||
package info | ||
|
||
import ( | ||
"fmt" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" | ||
"github.com/elastic/elastic-agent/internal/pkg/release" | ||
"github.com/google/go-cmp/cmp" | ||
) | ||
|
||
func TestIsInsideData(t *testing.T) { | ||
validExePath := paths.BinaryDir(filepath.Join("data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit()))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should you explicitly include a case that contains the MacOS bundle path? |
||
|
||
tests := []struct { | ||
name string | ||
exePath string | ||
res bool | ||
}{ | ||
{ | ||
name: "empty", | ||
}, | ||
{ | ||
name: "invalid", | ||
exePath: "data/elastic-agent", | ||
}, | ||
{ | ||
name: "valid", | ||
exePath: validExePath, | ||
res: true, | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
t.Run(tc.name, func(t *testing.T) { | ||
res := IsInsideData(tc.exePath) | ||
diff := cmp.Diff(tc.res, res) | ||
if diff != "" { | ||
t.Error(diff) | ||
} | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -9,6 +9,7 @@ import ( | |||||
"fmt" | ||||||
"os" | ||||||
"path/filepath" | ||||||
"runtime" | ||||||
"strings" | ||||||
"sync" | ||||||
|
||||||
|
@@ -21,6 +22,8 @@ const ( | |||||
// AgentLockFileName is the name of the overall Elastic Agent file lock. | ||||||
AgentLockFileName = "agent.lock" | ||||||
tempSubdir = "tmp" | ||||||
|
||||||
darwin = "darwin" | ||||||
) | ||||||
|
||||||
var ( | ||||||
|
@@ -68,7 +71,9 @@ func SetTop(path string) { | |||||
func TempDir() string { | ||||||
tmpDir := filepath.Join(Data(), tempSubdir) | ||||||
tmpCreator.Do(func() { | ||||||
// create tempdir as it probably don't exists | ||||||
// Create tempdir as it probably don't exists. | ||||||
// The error was not checked here before and the linter is not happy about it. | ||||||
// Changing this now would lead to the wide change scope that intended at the moment, so just making the linter happy for now. | ||||||
_ = os.MkdirAll(tmpDir, 0750) | ||||||
}) | ||||||
return tmpDir | ||||||
|
@@ -172,10 +177,15 @@ func SetInstall(path string) { | |||||
// initialTop returns the initial top-level path for the binary | ||||||
// | ||||||
// When nested in top-level/data/elastic-agent-${hash}/ the result is top-level/. | ||||||
// The agent fexecutable for MacOS is wrappend in the bundle, so the path to the binary is | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
// top-level/data/elastic-agent-${hash}/elastic-agent.app/Contents/MacOS | ||||||
func initialTop() string { | ||||||
exePath := retrieveExecutablePath() | ||||||
if insideData(exePath) { | ||||||
return filepath.Dir(filepath.Dir(exePath)) | ||||||
exePath = filepath.Dir(filepath.Dir(exePath)) | ||||||
if runtime.GOOS == darwin { | ||||||
exePath = filepath.Dir(filepath.Dir(filepath.Dir(exePath))) | ||||||
} | ||||||
} | ||||||
return exePath | ||||||
} | ||||||
|
@@ -195,6 +205,21 @@ func retrieveExecutablePath() string { | |||||
|
||||||
// insideData returns true when the exePath is inside of the current Agents data path. | ||||||
func insideData(exePath string) bool { | ||||||
expectedPath := filepath.Join("data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit())) | ||||||
expectedPath := BinaryDir(filepath.Join("data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit()))) | ||||||
return strings.HasSuffix(exePath, expectedPath) | ||||||
} | ||||||
|
||||||
// BinaryDir returns the application binary directory | ||||||
// For macOS it appends the path inside of the app bundle | ||||||
// For other platforms it returns the same dir | ||||||
func BinaryDir(baseDir string) string { | ||||||
if runtime.GOOS == darwin { | ||||||
baseDir = filepath.Join(baseDir, "elastic-agent.app", "Contents", "MacOS") | ||||||
} | ||||||
return baseDir | ||||||
} | ||||||
|
||||||
// BinaryPath returns the application binary path that is concatenation of the directory and the agentName | ||||||
func BinaryPath(baseDir, agentName string) string { | ||||||
return filepath.Join(BinaryDir(baseDir), agentName) | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,8 @@ func ChangeSymlink(ctx context.Context, log *logger.Logger, targetHash string) e | |
hashedDir := fmt.Sprintf("%s-%s", agentName, targetHash) | ||
|
||
symlinkPath := filepath.Join(paths.Top(), agentName) | ||
newPath := filepath.Join(paths.Top(), "data", hashedDir, agentName) | ||
|
||
newPath := paths.BinaryPath(filepath.Join(paths.Top(), "data", hashedDir), agentName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we forget I am concerned the need for this won't be obvious to the next engineer to touch this code, and it could be forgotten or removed in the future. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the same thing in service_darwin.go, so this seems to be necessary in lots of places now but I am not sure if anything enforces the need for it. func invokeCmd(topPath string) *exec.Cmd {
homeExePath := paths.BinaryPath(topPath, agentName) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah currently the logic is scattered in different places, so we call these wrapper func in few different places. Relying on e2e tests to catch any issues more or less. |
||
|
||
// handle windows suffixes | ||
if runtime.GOOS == "windows" { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import ( | |
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
|
||
"github.com/otiai10/copy" | ||
|
||
|
@@ -17,6 +18,10 @@ import ( | |
"github.com/elastic/elastic-agent/internal/pkg/agent/errors" | ||
) | ||
|
||
const ( | ||
darwin = "darwin" | ||
) | ||
|
||
// Install installs Elastic Agent persistently on the system including creating and starting its service. | ||
func Install(cfgFile string) error { | ||
dir, err := findDirectory() | ||
|
@@ -53,15 +58,36 @@ func Install(cfgFile string) error { | |
|
||
// place shell wrapper, if present on platform | ||
if paths.ShellWrapperPath != "" { | ||
err = os.MkdirAll(filepath.Dir(paths.ShellWrapperPath), 0755) | ||
if err == nil { | ||
err = ioutil.WriteFile(paths.ShellWrapperPath, []byte(paths.ShellWrapper), 0755) // nolint:gosec,G306 // this is fine. | ||
} | ||
if err != nil { | ||
return errors.New( | ||
err, | ||
fmt.Sprintf("failed to write shell wrapper (%s)", paths.ShellWrapperPath), | ||
errors.M("destination", paths.ShellWrapperPath)) | ||
// Install symlink for darwin instead | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we explain why we need to do this? Let's document the reason we need this symlink in the code or in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before this change the agent used the shell script wrapper to start the agent, this posed an issue with FDA cause the root process of the launchd service was |
||
if runtime.GOOS == darwin { | ||
// Check if previous shell wrapper or symlink exists and remove it so it can be overwritten | ||
if _, err := os.Lstat(paths.ShellWrapperPath); err == nil { | ||
if err := os.Remove(paths.ShellWrapperPath); err != nil { | ||
return errors.New( | ||
err, | ||
fmt.Sprintf("failed to remove (%s)", paths.ShellWrapperPath), | ||
errors.M("destination", paths.ShellWrapperPath)) | ||
} | ||
} | ||
err = os.Symlink("/Library/Elastic/Agent/elastic-agent", paths.ShellWrapperPath) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. replace with: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will leave as is for now, even though the change is trivial, because:
can change (and would have to retest) when merging to the main branch, not for the urgent fix |
||
if err != nil { | ||
return errors.New( | ||
err, | ||
fmt.Sprintf("failed to create elastic-agent symlink (%s)", paths.ShellWrapperPath), | ||
errors.M("destination", paths.ShellWrapperPath)) | ||
} | ||
} else { | ||
err = os.MkdirAll(filepath.Dir(paths.ShellWrapperPath), 0755) | ||
if err == nil { | ||
//nolint: gosec // this is intended to be an executable shell script, not chaning the permissions for the linter | ||
err = ioutil.WriteFile(paths.ShellWrapperPath, []byte(paths.ShellWrapper), 0755) | ||
} | ||
if err != nil { | ||
return errors.New( | ||
err, | ||
fmt.Sprintf("failed to write shell wrapper (%s)", paths.ShellWrapperPath), | ||
errors.M("destination", paths.ShellWrapperPath)) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -151,6 +177,9 @@ func findDirectory() (string, error) { | |
// executable path is being reported as being down inside of data path | ||
// move up to directories to perform the copy | ||
sourceDir = filepath.Dir(filepath.Dir(sourceDir)) | ||
if runtime.GOOS == darwin { | ||
sourceDir = filepath.Dir(filepath.Dir(filepath.Dir(sourceDir))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we check we're inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why? the agent binary will always be inside of the app bundle with this change, it's not going to be installed outside of the app bundle. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah right if it's inside data it cannot be root executable. |
||
} | ||
} | ||
err = verifyDirectory(sourceDir) | ||
if err != nil { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment here documenting that you are stripping off the
elastic-agent.app/Contents/MacOS
paths? Either that or make a small helper function for this, I see the same logic in paths/common.go.Those who haven't memorized the application bundle structure on Mac might not recognize what this is doing.