Skip to content
This repository has been archived by the owner on Sep 21, 2023. It is now read-only.

Packaging for elastic agent #126

Merged
merged 5 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ by running `mage -l` from the root of the repository. The most commonly used com
* `mage build` to build the data shipper binary.
* `mage check` to check license files and dependencies.
* `mage lint` to lint the source code using [golangci-lint](https://golangci-lint.run/).
* `mage package` to produce release artifacts.
* `go test ./...` to run all tests.

Run mage commands with the `-v` flag for more detailed output, for example `mage -v check`.
14 changes: 14 additions & 0 deletions elastic-agent-shipper.spec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
outputs:
- name: elasticsearch
description: Elasticsearch
platforms: &platforms
- linux/amd64
- linux/arm64
- darwin/amd64
- darwin/arm64
- windows/amd64
- container/amd64
- container/arm64
command: {}

245 changes: 237 additions & 8 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@
package main

import (
"archive/tar"
"archive/zip"
"compress/gzip"
"context"
"crypto/sha512"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
Expand All @@ -29,7 +35,10 @@ import (
)

const (
GoreleaserRepo = "github.com/goreleaser/[email protected]"
GoreleaserRepo = "github.com/goreleaser/[email protected]"
platformsEnvName = "PLATFORMS"
specFileName = devtools.ProjectName + ".spec.yml"
configFileName = devtools.ProjectName + ".yml"
)

// Aliases are shortcuts to long target names.
Expand All @@ -52,7 +61,7 @@ func (Build) All() {
}

// Clean removes the build directory.
func (Build) Clean(test string) {
func (Build) Clean() {
os.RemoveAll("build") // nolint:errcheck //not required
}

Expand Down Expand Up @@ -95,7 +104,7 @@ func (Build) Binary() error {
}
}

platform := os.Getenv("PLATFORMS")
platform := os.Getenv(platformsEnvName)
if platform != "" && devtools.PlatformFiles[platform] == nil {
return fmt.Errorf("Platform %s not recognized, only supported options: all, darwin, linux, windows, darwin/amd64, darwin/arm64, linux/386, linux/amd64, linux/arm64, windows/386, windows/amd64", platform)
}
Expand Down Expand Up @@ -177,11 +186,7 @@ func CheckBinaries(platform string, version string) error {
return errors.New("No selected platform files found")
}
for _, platform := range selectedPlatformFiles {
var execName = devtools.ProjectName
if strings.Contains(platform, "windows") {
execName += ".exe"
}
binary := filepath.Join(path, fmt.Sprintf("%s-%s-%s", devtools.ProjectName, version, platform), execName)
binary := binaryName(path, version, platform)
if _, err := os.Stat(binary); err != nil {
return fmt.Errorf("Build: binary check failed: %w", err)
}
Expand Down Expand Up @@ -215,3 +220,227 @@ func Notice() error {
// See https://github.com/golang/go/issues/43994#issuecomment-770053099
return gotool.Mod.Tidy()
}

// PACKAGE

// Package runs all the checks including licence, notice, gomod, git changes, followed by binary build.
func Package() {
// these are not allowed in parallel
mg.SerialDeps(
Build.Binary,
)

version, err := fullVersion()
if err != nil {
panic(err)
}

platforms := devtools.PlatformFiles[os.Getenv(platformsEnvName)]
// current by default
if platforms == nil {
platforms = devtools.PlatformFiles[runtime.GOOS+"/"+runtime.GOARCH]
}

archivePath := filepath.Join("build", "distributions")
if err := os.MkdirAll(archivePath, 0755); err != nil {
panic(err)
}

for _, platform := range platforms {
isWindows := strings.Contains(platform, "windows")

// grab binary
binaryPath := binaryFilePath(version, platform)

// prepare package
var err error
var archiveFileName string
targetDir := fmt.Sprintf("%s-%s", devtools.ProjectName, platform)
if isWindows {
archiveFileName = fmt.Sprintf("%s-%s.zip", devtools.ProjectName, platform)
err = prepareZipArchive(
archivePath,
targetDir,
archiveFileName,
map[string]string{
specFileName: specFileName,
configFileName: configFileName,
execName(platform): binaryPath,
},
)
} else {
archiveFileName = fmt.Sprintf("%s-%s.tar.gz", devtools.ProjectName, platform)
err = prepareTarArchive(
archivePath,
targetDir,
archiveFileName,
map[string]string{
specFileName: specFileName,
configFileName: configFileName,
execName(platform): binaryPath,
},
)
}

if err != nil {
panic(err)
}

// generate sha sum
archiveFullPath := filepath.Join(archivePath, archiveFileName)
archive, err := os.Open(archiveFullPath)
if err != nil {
panic(fmt.Errorf("failed opening archive %q: %w", archiveFileName, err))
}

h := sha512.New()
if _, err := io.Copy(h, archive); err != nil {
panic(fmt.Errorf("failed computing hash for archive %q: %w", archiveFileName, err))
}

shaFile := archiveFullPath + ".sha512"
content := fmt.Sprintf("%x %s\n", h.Sum(nil), archiveFileName)
if err := ioutil.WriteFile(shaFile, []byte(content), 0644); err != nil {
panic(fmt.Errorf("failed writing hash for archive %q: %w", archiveFileName, err))
}

archive.Close()
}
}

func prepareZipArchive(path, targetDir, archiveFileName string, files map[string]string) error {
archiveFullName := filepath.Join(path, archiveFileName)
archiveFile, err := os.Create(archiveFullName)
if err != nil {
return fmt.Errorf("failed to create %q: %w", archiveFullName, err)
}
defer archiveFile.Close()

zipWriter := zip.NewWriter(archiveFile)
defer zipWriter.Close()

addFile := func(fileName, filePath string, zipWriter *zip.Writer) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed opening a file: %w", err)
}
defer file.Close()

stat, err := file.Stat()
if err != nil {
return fmt.Errorf("failed retrieving stat info for a file: %w", err)
}

header, err := zip.FileInfoHeader(stat)
header.Method = zip.Deflate
header.Name = filepath.Join(targetDir, fileName)

hWriter, err := zipWriter.CreateHeader(header)
if err != nil {
return fmt.Errorf("failed writing zip header: %w", err)
}

if _, err := io.Copy(hWriter, file); err != nil {
return fmt.Errorf("failed adding a file into an archive: %w", err)
}
return nil
}

for fileName, filePath := range files {
if err := addFile(fileName, filePath, zipWriter); err != nil {
return fmt.Errorf("failed adding file %q into zip archive: %w", filePath, err)
}
}

return nil
}

func prepareTarArchive(path, targetDir, archiveFileName string, files map[string]string) error {
archiveFullName := filepath.Join(path, archiveFileName)
archiveFile, err := os.Create(archiveFullName)
if err != nil {
return fmt.Errorf("failed to create %q: %w", archiveFullName, err)
}
defer archiveFile.Close()

gzipWriter := gzip.NewWriter(archiveFile)
defer gzipWriter.Close()

tarWriter := tar.NewWriter(gzipWriter)
defer tarWriter.Close()

addFile := func(fileName, filePath string, tarWriter *tar.Writer) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed opening a file: %w", err)
}
defer file.Close()

stat, err := file.Stat()
if err != nil {
return fmt.Errorf("failed retrieving stat info for a file: %w", err)
}

header := &tar.Header{
Name: filepath.Join(targetDir, fileName),
Size: stat.Size(),
Mode: int64(stat.Mode()),
ModTime: stat.ModTime(),
}

if err := tarWriter.WriteHeader(header); err != nil {
return fmt.Errorf("failed writing tar header: %w", err)
}

if _, err := io.Copy(tarWriter, file); err != nil {
return fmt.Errorf("failed adding a file into an archive: %w", err)
}

return nil
}

for fileName, filePath := range files {
if err := addFile(fileName, filePath, tarWriter); err != nil {
return fmt.Errorf("failed adding file %q into tar archive: %w", filePath, err)
}
}

return nil
}

func fullVersion() (string, error) {
version := tools.DefaultBeatVersion
if versionQualifier, versionQualified := os.LookupEnv("VERSION_QUALIFIER"); versionQualified {
version += fmt.Sprintf("-%s", versionQualifier)
}

if snapshotEnv := os.Getenv("SNAPSHOT"); snapshotEnv != "" {
isSnapshot, err := strconv.ParseBool(snapshotEnv)
if err != nil {
return "", err
}
if isSnapshot {
version += fmt.Sprintf("-%s", "SNAPSHOT")
}
}

return version, nil
}

func binaryFilePath(version, platform string) string {
path := filepath.Join("build", "binaries")
return binaryName(path, version, platform)
}

func binaryName(path, version, platform string) string {
return filepath.Join(path, fmt.Sprintf("%s-%s-%s", devtools.ProjectName, version, platform), execName(platform))
}

func execName(platform string) string {
var execName = devtools.ProjectName
if strings.Contains(platform, "windows") {
execName += ".exe"
}

return execName
}