blob: f9b85dd51ea228219331e6c2c4f4e06e5bb8a40c [file] [log] [blame]
package e2e
import (
"archive/tar"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
func extractBinary(archivePath, destinationDir, expectedName string) (string, error) {
f, err := os.Open(archivePath)
if err != nil {
return "", err
}
defer f.Close()
gz, err := gzip.NewReader(f)
if err != nil {
return "", fmt.Errorf("open gzip archive: %w", err)
}
defer gz.Close()
if err := os.MkdirAll(destinationDir, 0o755); err != nil {
return "", err
}
var found []byte
tr := tar.NewReader(gz)
for {
h, err := tr.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return "", fmt.Errorf("read tar archive: %w", err)
}
clean := filepath.Clean(h.Name)
if filepath.IsAbs(h.Name) || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
return "", fmt.Errorf("unsafe archive path %q", h.Name)
}
if filepath.Base(clean) != expectedName {
continue
}
if h.Typeflag != tar.TypeReg && h.Typeflag != tar.TypeRegA {
return "", fmt.Errorf("expected executable %q is not a regular file", h.Name)
}
if found != nil {
return "", fmt.Errorf("archive contains multiple %q executables", expectedName)
}
if h.Size < 1 || h.Size > 512<<20 {
return "", fmt.Errorf("invalid executable size %d", h.Size)
}
found, err = io.ReadAll(io.LimitReader(tr, h.Size+1))
if err != nil {
return "", err
}
if int64(len(found)) != h.Size {
return "", fmt.Errorf("truncated executable %q", h.Name)
}
}
if found == nil {
return "", fmt.Errorf("archive does not contain expected executable %q", expectedName)
}
destination := filepath.Join(destinationDir, expectedName)
if err := os.WriteFile(destination, found, 0o755); err != nil {
return "", err
}
if err := os.Chmod(destination, 0o755); err != nil {
return "", err
}
return destination, nil
}
func verifyBinaryVersion(path, expected string) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, path, "version").CombinedOutput()
if ctx.Err() != nil {
return fmt.Errorf("verify %s version: %w", filepath.Base(path), ctx.Err())
}
if err != nil {
return fmt.Errorf("verify %s version: %w (%s)", filepath.Base(path), err, boundedText(out, 2048))
}
if !versionOutputMatches(out, expected) {
return fmt.Errorf("unexpected %s version: expected %s, got %s", filepath.Base(path), expected, boundedText(out, 2048))
}
return nil
}
func versionOutputMatches(output []byte, expected string) bool {
withoutV := strings.TrimPrefix(expected, "v")
for _, field := range strings.Fields(string(output)) {
field = strings.Trim(field, " \t\r\n,;:()[]{}")
if field == expected || field == withoutV {
return true
}
}
return false
}