diff --git a/dev-tools/packaging/packages.yml b/dev-tools/packaging/packages.yml index 5254991306e..688342fe42f 100644 --- a/dev-tools/packaging/packages.yml +++ b/dev-tools/packaging/packages.yml @@ -333,7 +333,7 @@ shared: 'data/{{.BeatName}}-{{ commit_short }}/components/agentbeat.spec.yml': source: '{{.AgentDropPath}}/{{.GOOS}}-{{.AgentArchName}}.tar.gz/agentbeat.spec.yml' expand_spec: true - + - &agent_docker_edot_wolfi_spec <<: *agent_docker_edot_spec docker_variant: 'elastic-otel-collector-wolfi' @@ -370,7 +370,7 @@ shared: 'data/{{.BeatName}}-{{ commit_short }}/components/pf-host-agent.spec.yml': source: '{{.AgentDropPath}}/{{.GOOS}}-{{.AgentArchName}}.tar.gz/pf-host-agent.spec.yml' expand_spec: true - + - &agent_docker_slim_wolfi_spec <<: *agent_docker_slim_spec docker_variant: 'slim-wolfi' @@ -922,7 +922,7 @@ specs: <<: *elastic_license_for_binaries files: '{{.BeatName}}{{.BinaryExt}}': - source: ./build/golang-crossbuild/{{.BeatName}}-{{.GOOS}}-{{.Platform.Arch}}{{.BinaryExt}} + source: ./build/windows-archive-root-binary/elastic-agent-archive-root.exe 'package.version': content: > {{ agent_package_version }} diff --git a/magefile.go b/magefile.go index ef3910f8129..7937b81198d 100644 --- a/magefile.go +++ b/magefile.go @@ -294,6 +294,54 @@ func (Build) GenerateConfig() error { return sh.Copy(filepath.Join(buildDir, configFile), filepath.Join(metaDir, configFile)) } +// WindowsArchiveRootBinary compiles a binary to be placed at the root of the windows elastic-agent archive. This binary +// is a thin proxy to the actual elastic-agent binary that resides in the data/elastic-agent-{commit-short-sha} +// directory of the archive. +func (Build) WindowsArchiveRootBinary() error { + fmt.Println("--- Compiling root binary for windows archive") + hashShort, err := devtools.CommitHashShort() + if err != nil { + return fmt.Errorf("error getting commit hash: %w", err) + } + + outputName := "elastic-agent-archive-root" + if runtime.GOOS != "windows" { + // add the .exe extension on non-windows platforms + outputName += ".exe" + } + + args := devtools.BuildArgs{ + Name: outputName, + OutputDir: filepath.Join(buildDir, "windows-archive-root-binary"), + InputFiles: []string{"wrapper/windows/archive-proxy/main.go"}, + CGO: false, + WinMetadata: true, + ExtraFlags: []string{ + "-buildmode", "pie", // windows versions inside the support matrix do support position independent code + "-trimpath", // Remove all file system paths from the compiled executable, to improve build reproducibility + }, + Vars: map[string]string{ + "main.CommitSHA": hashShort, + }, + Env: map[string]string{ + "GOOS": "windows", + "GOARCH": "amd64", + }, + LDFlags: []string{ + "-s", // Strip all debug symbols from binary (does not affect Go stack traces). + }, + } + + if devtools.FIPSBuild { + // there is no actual FIPS relevance for this particular binary + // but better safe than sorry + args.ExtraFlags = append(args.ExtraFlags, "-tags=requirefips") + args.CGO = true + } + + return devtools.Build(args) +} + // GolangCrossBuildOSS build the Beat binary inside of the golang-builder. // Do not use directly, use crossBuild instead. func GolangCrossBuildOSS() error { @@ -1022,6 +1070,12 @@ func packageAgent(ctx context.Context, platforms []string, dependenciesVersion s log.Println("--- Running post packaging ") mg.Deps(Update) mg.Deps(agentBinaryTarget, CrossBuildGoDaemon) + + // compile the elastic-agent.exe proxy binary for the windows archive + if slices.Contains(platforms, "windows/amd64") { + mg.Deps(Build.WindowsArchiveRootBinary) + } + mg.SerialDeps(devtools.Package, TestPackages) return nil } diff --git a/wrapper/windows/archive-proxy/go.mod b/wrapper/windows/archive-proxy/go.mod new file mode 100644 index 00000000000..4c5e00e5f9e --- /dev/null +++ b/wrapper/windows/archive-proxy/go.mod @@ -0,0 +1,9 @@ +module github.com/elastic/elastic-agent/wrapper/windows/archive-proxy + +go 1.24.1 + +require github.com/elastic/elastic-agent v0.0.0 + +require golang.org/x/sys v0.31.0 // indirect + +replace github.com/elastic/elastic-agent => ../../../ diff --git a/wrapper/windows/archive-proxy/go.sum b/wrapper/windows/archive-proxy/go.sum new file mode 100644 index 00000000000..802d06db8a3 --- /dev/null +++ b/wrapper/windows/archive-proxy/go.sum @@ -0,0 +1 @@ +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/wrapper/windows/archive-proxy/main.go b/wrapper/windows/archive-proxy/main.go new file mode 100644 index 00000000000..dc892e04325 --- /dev/null +++ b/wrapper/windows/archive-proxy/main.go @@ -0,0 +1,93 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package main + +import ( + "errors" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + + "github.com/elastic/elastic-agent/pkg/core/process" +) + +// CommitSHA is set by the linker at build time +var CommitSHA string + +func main() { + log.SetFlags(0) + if CommitSHA == "" { + // this should never happen + log.Fatal("No commit SHA provided\n") + } + + exePath, err := os.Executable() + if err != nil { + log.Fatalf("Error getting executable path: %v\n", err) + } + + exeAbsPath, err := filepath.Abs(exePath) + if err != nil { + log.Fatalf("Error getting executable absolute path: %v\n", err) + } + + // Fabricate the elastic-agent.exe path that resides inside the data/elastic-agent-{commit-short-sha} directory + exeTopPath := filepath.Dir(exeAbsPath) + nestedAgentBinaryPath := filepath.Join(exeTopPath, "data", fmt.Sprintf("elastic-agent-%s", CommitSHA), "elastic-agent.exe") + + // Create the arguments + var args []string + if len(os.Args) > 1 { + args = os.Args[1:] + } + + g, err := process.CreateJobObject() + if err != nil { + log.Fatalf("Unable to create job object: %v\n", err) + } + defer func() { + _ = g.Close() + }() + + // Create the command + command := exec.Command(nestedAgentBinaryPath, args...) + + // Forward stdout, stderr, stdin + command.Stdout = os.Stdout + command.Stderr = os.Stderr + command.Stdin = os.Stdin + + // Pass the environment + command.Env = os.Environ() + + // Run the command + err = command.Start() + if err != nil { + log.Fatalf("Error running command: %v\n", err) + } + + // Add the process to the job object + if err := g.Assign(command.Process); err != nil { + log.Fatalf("Error adding job object: %v\n", err) + } + + err = command.Wait() + var exitError *exec.ExitError + switch { + case errors.As(err, &exitError): + exitCode := exitError.ExitCode() + if exitCode == 0 { + // Exit with non-zero exit code since we did get an error + os.Exit(1) + } + // Exit with the same exit code + os.Exit(exitCode) + case err != nil: + // Exit with a non-zero exit code + log.Fatalf("Command failed: %v\n", err) + } +}