Skip to content

Commit

Permalink
Adds Darwin Support for Vault and Consul e2e Deps (#10156)
Browse files Browse the repository at this point in the history
Signed-off-by: Daneyon Hansen <[email protected]>
  • Loading branch information
danehans authored Nov 12, 2024
1 parent a93178d commit dcd09c4
Show file tree
Hide file tree
Showing 6 changed files with 338 additions and 41 deletions.
7 changes: 7 additions & 0 deletions changelog/v1.18.0-beta34/issue_10152.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
changelog:
- type: NON_USER_FACING
issueLink: https://github.com/solo-io/gloo/issues/10152
resolvesIssue: true
description: >-
Adds `darwin` operating system support for the Consul and Vault dependencies
used by Kubernetes e2e testing.
9 changes: 2 additions & 7 deletions test/services/consul.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ import (
"github.com/solo-io/gloo/test/testutils"
)

const (
consulDockerImage = "hashicorp/consul:1.15.3"
consulBinaryName = "consul"
)

type ConsulFactory struct {
consulPath string
tmpdir string
Expand All @@ -49,8 +44,8 @@ func NewConsulFactory() (*ConsulFactory, error) {
return nil, err
}
binaryPath, err := utils.GetBinary(utils.GetBinaryParams{
Filename: consulBinaryName,
DockerImage: consulDockerImage,
Filename: testutils.ConsulBinaryName,
DockerImage: testutils.ConsulDockerImage,
DockerPath: "/bin/consul",
EnvKey: testutils.ConsulBinary,
TmpDir: tmpdir,
Expand Down
179 changes: 150 additions & 29 deletions test/services/utils/download.go
Original file line number Diff line number Diff line change
@@ -1,59 +1,93 @@
package utils

import (
"archive/zip"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"

"github.com/onsi/ginkgo/v2"

"github.com/solo-io/gloo/test/testutils"
)

type GetBinaryParams struct {
Filename string // the name of the binary on the $PATH or in the docker container
DockerImage string // the docker image to use if Env or Local are not present
DockerPath string // the location of the binary in the docker container, including the filename
EnvKey string // the environment var to look at for a user-specified service binary
TmpDir string // the temp directory to store a downloaded binary if needed
}
// ExecLookPathWrapper is a wrapper around exec.LookPath so it can be mocked in tests.
var ExecLookPathWrapper = exec.LookPath

// GetBinary uses the passed params structure to get a binary for the service in the first found of 3 locations:
//
// 1. specified via environment variable
//
// 2. matching binary already on path
//
// 3. download the hard-coded version via docker and extract the binary from that
func GetBinary(params GetBinaryParams) (string, error) {
// first check if an environment variable was specified for the binary location
envPath := os.Getenv(params.EnvKey)
if envPath != "" {
log.Printf("Using %s specified in environment variable %s: %s", params.Filename, params.EnvKey, envPath)
return envPath, nil
// DownloadAndExtractBinary downloads and extracts the Vault or Consul binary for darwin OS
var DownloadAndExtractBinary = func(tmpDir, filename, version string) (string, error) {
goos := runtime.GOOS
goarch := runtime.GOARCH

url := fmt.Sprintf("https://releases.hashicorp.com/%s/%s/%s_%s_%s_%s.zip", filename, version, filename, version, goos, goarch)
log.Printf("Downloading %s binary from: %s", filename, url)

// Create temp zip file
zipPath := filepath.Join(tmpDir, fmt.Sprintf("%s.zip", filename))
out, err := os.Create(zipPath)
if err != nil {
return "", fmt.Errorf("failed to create temp zip file: %w", err)
}
defer out.Close()

// next check if we have a matching binary on $PATH
localPath, err := exec.LookPath(params.Filename)
if err == nil {
log.Printf("Using %s from PATH: %s", params.Filename, localPath)
return localPath, nil
// Download the zip file
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("failed to download %s binary: %w", filename, err)
}
defer resp.Body.Close()

// finally, try to grab one from docker
return dockerDownload(params.TmpDir, params)
_, err = out.ReadFrom(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to download %s binary: %w", filename, err)
}

// Extract the zip file
err = Unzip(zipPath, tmpDir)
if err != nil {
return "", fmt.Errorf("failed to unzip %s binary: %w", filename, err)
}

// Add extracted binary to PATH
binaryPath := filepath.Join(tmpDir, filename)
if err := os.Chmod(binaryPath, 0755); err != nil {
return "", fmt.Errorf("failed to make %s binary executable: %w", filename, err)
}

log.Printf("%s binary extracted to: %s", filename, binaryPath)
return binaryPath, nil
}

func dockerDownload(tmpdir string, params GetBinaryParams) (string, error) {
// DockerDownload extracts a binary from a Docker image by running a temporary
// Docker container, copying the binary from the container's filesystem, and
// saving it to a local temporary directory. This function is primarily used
// when a binary is not available via environment variables or on the system's PATH.
//
// tmpdir: The temporary directory where the binary will be saved.
// params: A struct containing parameters such as the filename, Docker image,
//
// and Docker path to locate the binary in the container.
//
// Returns the path to the saved binary on success or an error if the operation fails.
var DockerDownload = func(tmpdir string, params GetBinaryParams) (string, error) {
goos := runtime.GOOS

if goos == "darwin" {
return "", fmt.Errorf("unsupported operating system: %s", goos)
}

log.Printf("Using %s from docker image: %s", params.Filename, params.DockerImage)

// use bash to run a docker container and extract the binary file from the running container
bash := fmt.Sprintf(`
set -ex
CID=$(docker run -d %s /bin/sh -c exit)
# just print the image sha for repoducibility
# just print the image sha for reproducibility
echo "Using %s Image:"
docker inspect %s -f "{{.RepoDigests}}"
Expand All @@ -78,5 +112,92 @@ docker rm -f $CID
}

return filepath.Join(tmpdir, params.Filename), nil
}

type GetBinaryParams struct {
Filename string // the name of the binary on the $PATH or in the docker container
DockerImage string // the docker image to use if Env or Local are not present
DockerPath string // the location of the binary in the docker container, including the filename
EnvKey string // the environment var to look at for a user-specified service binary
TmpDir string // the temp directory to store a downloaded binary if needed
}

// GetBinary checks for a binary in the following order:
// 1. From the environment variable if specified
// 2. Locally available on the $PATH
// 3. If `darwin` is the OS, download the Vault or Consul binary from the HashiCorp release page
// 4. As a last resort, pull the binary from a Docker image
func GetBinary(params GetBinaryParams) (string, error) {
// first check if an environment variable was specified for the binary location
envPath := os.Getenv(params.EnvKey)
if envPath != "" {
log.Printf("Using %s specified in environment variable %s: %s", params.Filename, params.EnvKey, envPath)
return envPath, nil
}

// next check if we have a matching binary on $PATH
localPath, err := ExecLookPathWrapper(params.Filename)
if err == nil {
log.Printf("Using %s from PATH: %s", params.Filename, localPath)
return localPath, nil
}

// if GOOS is darwin and the Filename is either vault or consul, download from HashiCorp releases
if runtime.GOOS == "darwin" {
switch params.Filename {
case "vault":
log.Printf("Downloading %s for darwin", params.Filename)
return DownloadAndExtractBinary(params.TmpDir, testutils.VaultBinaryName, testutils.VaultBinaryVersion)
case "consul":
log.Printf("Downloading %s for darwin", params.Filename)
return DownloadAndExtractBinary(params.TmpDir, testutils.ConsulBinaryName, testutils.ConsulBinaryVersion)
}
}

// finally, try to grab one from docker
return DockerDownload(params.TmpDir, params)
}

// Unzip unzips the given archive to the specified destination
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()

for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)

// Create directories or files
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}

// Ensure the directory exists
if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}

// Extract the file
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}

rc, err := f.Open()
if err != nil {
return err
}

_, err = outFile.ReadFrom(rc)
outFile.Close()
rc.Close()

if err != nil {
return err
}
}
return nil
}
Loading

0 comments on commit dcd09c4

Please sign in to comment.