Skip to content

Commit

Permalink
[backport main] Fix: Agent failed to upgrade from 8.4.2 to 8.5.0 BC1 …
Browse files Browse the repository at this point in the history
…for MAC 12 agent using agent binary. (#1401)

[backport main] Fix: Agent failed to upgrade from 8.4.2 to 8.5.0 BC1 for MAC 12 agent using agent binary. (#1401)
  • Loading branch information
aleksmaus authored Oct 6, 2022
1 parent 166e7f6 commit bd06f46
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 129 deletions.
3 changes: 3 additions & 0 deletions dev-tools/packaging/packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,9 @@ specs:
<<: *agent_darwin_binary_spec
<<: *elastic_license_for_binaries
files:
'data/{{.BeatName}}-{{ commit_short }}/elastic-agent':
template: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/darwin/elastic-agent.tmpl'
mode: 0755
'{{.BeatName}}{{.BinaryExt}}':
source: data/{{.BeatName}}-{{ commit_short }}/elastic-agent.app/Contents/MacOS/{{.BeatName}}{{.BinaryExt}}
symlink: true
Expand Down
11 changes: 11 additions & 0 deletions dev-tools/packaging/templates/darwin/elastic-agent.tmpl
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

25 changes: 2 additions & 23 deletions internal/pkg/agent/application/info/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@
package info

import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
"github.com/elastic/elastic-agent/internal/pkg/release"
)

const (
Expand All @@ -31,30 +28,12 @@ func RunningInstalled() bool {
}
execPath, _ := os.Executable()
execPath, _ = filepath.Abs(execPath)
execName := filepath.Base(execPath)
execDir := filepath.Dir(execPath)
if IsInsideData(execDir) {
// executable path is being reported as being down inside of data path
// move up to directories to perform the comparison
execDir = filepath.Dir(filepath.Dir(execDir))
if runtime.GOOS == darwin {
execDir = filepath.Dir(filepath.Dir(filepath.Dir(execDir)))
}
execPath = filepath.Join(execDir, execName)
}

execPath = filepath.Join(paths.ExecDir(filepath.Dir(execPath)), filepath.Base(execPath))
for _, expected := range expectedPaths {
if paths.ArePathsEqual(expected, execPath) {
return true
}
}
return false
}

// IsInsideData returns true when the exePath is inside of the current Agents data path.
func IsInsideData(exePath string) bool {
expectedPath := filepath.Join("data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit()))
if runtime.GOOS == darwin {
expectedPath = filepath.Join(expectedPath, "elastic-agent.app", "Contents", "MacOS")
}
return strings.HasSuffix(exePath, expectedPath)
}
53 changes: 0 additions & 53 deletions internal/pkg/agent/application/info/state_test.go

This file was deleted.

49 changes: 34 additions & 15 deletions internal/pkg/agent/application/paths/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,21 +177,14 @@ 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
// The agent executable for MacOS is wrapped in the app bundle, so the path to the binary is
// top-level/data/elastic-agent-${hash}/elastic-agent.app/Contents/MacOS
func initialTop() string {
exePath := retrieveExecutablePath()
if insideData(exePath) {
exePath = filepath.Dir(filepath.Dir(exePath))
if runtime.GOOS == darwin {
exePath = filepath.Dir(filepath.Dir(filepath.Dir(exePath)))
}
}
return exePath
return ExecDir(retrieveExecutableDir())
}

// retrieveExecutablePath returns the executing binary, even if the started binary was a symlink
func retrieveExecutablePath() string {
func retrieveExecutableDir() string {
execPath, err := os.Executable()
if err != nil {
panic(err)
Expand All @@ -203,11 +196,37 @@ func retrieveExecutablePath() string {
return filepath.Dir(evalPath)
}

// 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()))
// isInsideData returns true when the exePath is inside of the current Agents data path.
func isInsideData(exeDir string) bool {
expectedDir := binaryDir(filepath.Join("data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit())))
return strings.HasSuffix(exeDir, expectedDir)
}

// ExecDir returns the "executable" directory which is:
// 1. The same if the execDir is not inside of the data path
// 2. Two levels up if the execDir inside of the data path on non-macOS platforms
// 3. Five levels up if the execDir inside of the dataPath on macOS platform
func ExecDir(execDir string) string {
if isInsideData(execDir) {
execDir = filepath.Dir(filepath.Dir(execDir))
if runtime.GOOS == darwin {
execDir = filepath.Dir(filepath.Dir(filepath.Dir(execDir)))
}
}
return execDir
}

// 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 {
expectedPath = filepath.Join(expectedPath, "elastic-agent.app", "Contents", "MacOS")
baseDir = filepath.Join(baseDir, "elastic-agent.app", "Contents", "MacOS")
}
return strings.HasSuffix(exePath, expectedPath)
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)
}
92 changes: 92 additions & 0 deletions internal/pkg/agent/application/paths/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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 paths

import (
"fmt"
"path/filepath"
"runtime"
"testing"

"github.com/elastic/elastic-agent/internal/pkg/release"
"github.com/google/go-cmp/cmp"
)

func validTestPath() string {
validPath := filepath.Join("data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit()))
if runtime.GOOS == darwin {
validPath = filepath.Join(validPath, "elastic-agent.app", "Contents", "MacOS")
}
return validPath
}

func TestIsInsideData(t *testing.T) {
tests := []struct {
name string
exePath string
res bool
}{
{
name: "empty",
},
{
name: "invalid",
exePath: "data/elastic-agent",
},
{
name: "valid",
exePath: validTestPath(),
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)
}
})
}
}

func TestExecDir(t *testing.T) {
base := filepath.Join(string(filepath.Separator), "Library", "Elastic", "Agent")
tests := []struct {
name string
execDir string
resDir string
}{
{
name: "empty",
},
{
name: "non-data path",
execDir: "data/elastic-agent",
resDir: "data/elastic-agent",
},
{
name: "valid",
execDir: validTestPath(),
resDir: ".",
},
{
name: "valid abs",
execDir: filepath.Join(base, validTestPath()),
resDir: base,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
resDir := ExecDir(tc.execDir)
diff := cmp.Diff(tc.resDir, resDir)
if diff != "" {
t.Error(diff)
}
})
}
}
11 changes: 5 additions & 6 deletions internal/pkg/agent/application/upgrade/service_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -50,13 +49,13 @@ func (p *darwinPidProvider) Close() {}

func (p *darwinPidProvider) PID(ctx context.Context) (int, error) {
piders := []func(context.Context) (int, error){
p.piderFromCmd(ctx, "launchctl", "list", paths.ServiceName),
p.piderFromCmd("launchctl", "list", paths.ServiceName),
}

// if release is specifically built to be upgradeable (using DEV flag)
// we dont require to run as a service and will need sudo fallback
if release.Upgradeable() {
piders = append(piders, p.piderFromCmd(ctx, "sudo", "launchctl", "list", paths.ServiceName))
piders = append(piders, p.piderFromCmd("sudo", "launchctl", "list", paths.ServiceName))
}

var pidErrors error
Expand All @@ -72,7 +71,7 @@ func (p *darwinPidProvider) PID(ctx context.Context) (int, error) {
return 0, pidErrors
}

func (p *darwinPidProvider) piderFromCmd(ctx context.Context, name string, args ...string) func(context.Context) (int, error) {
func (p *darwinPidProvider) piderFromCmd(name string, args ...string) func(context.Context) (int, error) {
return func(context.Context) (int, error) {
listCmd := exec.Command(name, args...)
listCmd.SysProcAttr = &syscall.SysProcAttr{
Expand Down Expand Up @@ -115,8 +114,8 @@ func (p *darwinPidProvider) piderFromCmd(ctx context.Context, name string, args
}

func invokeCmd(topPath string) *exec.Cmd {
homeExePath := filepath.Join(topPath, agentName)

// paths.BinaryPath properly derives the newPath depending on the platform. The path to the binary for macOS is inside of the app bundle.
homeExePath := paths.BinaryPath(topPath, agentName)
cmd := exec.Command(homeExePath, watcherSubcommand,
"--path.config", paths.Config(),
"--path.home", paths.Top(),
Expand Down
17 changes: 12 additions & 5 deletions internal/pkg/agent/application/upgrade/step_relink.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,25 @@ import (
"github.com/elastic/elastic-agent/pkg/core/logger"
)

const (
windows = "windows"
exe = ".exe"
)

// ChangeSymlink updates symlink paths to match current version.
func ChangeSymlink(ctx context.Context, log *logger.Logger, targetHash string) error {
// create symlink to elastic-agent-{hash}
hashedDir := fmt.Sprintf("%s-%s", agentName, targetHash)

symlinkPath := filepath.Join(paths.Top(), agentName)
newPath := filepath.Join(paths.Top(), "data", hashedDir, agentName)

// paths.BinaryPath properly derives the binary directory depending on the platform. The path to the binary for macOS is inside of the app bundle.
newPath := paths.BinaryPath(filepath.Join(paths.Top(), "data", hashedDir), agentName)

// handle windows suffixes
if runtime.GOOS == "windows" {
symlinkPath += ".exe"
newPath += ".exe"
if runtime.GOOS == windows {
symlinkPath += exe
newPath += exe
}

prevNewPath := prevSymlinkPath()
Expand All @@ -51,7 +58,7 @@ func prevSymlinkPath() string {
agentPrevName := agentName + ".prev"

// handle windows suffixes
if runtime.GOOS == "windows" {
if runtime.GOOS == windows {
agentPrevName = agentName + ".exe.prev"
}

Expand Down
Loading

0 comments on commit bd06f46

Please sign in to comment.