Skip to content
Open
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
12 changes: 0 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,6 @@ jobs:
- name: Run fast tests
run: go test -v -race ./internal/...

- name: Download Remoteproc Simulator
uses: robinraju/release-downloader@daf26c55d821e836577a15f77d86ddc078948b05 # v1.12
with:
repository: arm/remoteproc-simulator
tag: 'v0.0.8'
fileName: remoteproc-simulator_*_linux_amd64.tar.gz
out-file-path: remoteproc-simulator
extract: true

- name: Install Remoteproc Simulator
run: echo "$PWD/remoteproc-simulator" >> $GITHUB_PATH

- name: Set up Lima
uses: lima-vm/lima-actions/setup@03b96d61959e83b2c737e44162c3088e81de0886 # v1.0.1
id: lima-actions-setup
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ coverage.out
# binaries
/containerd-shim-remoteproc-v1
/remoteproc-runtime

# test downloads
.downloads/
290 changes: 290 additions & 0 deletions e2e/download/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
package download

import (
"archive/tar"
"archive/zip"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"

"github.com/arm/remoteproc-runtime/e2e/repo"
)

var downloadLocks sync.Map

func cacheDir() string {
return filepath.Join(repo.MustFindRootDir(), ".downloads")
}

func GithubRelease(ctx context.Context, owner, repoName, version, goos, goarch string) (string, error) {
executableName := repoName
if goos == "windows" {
executableName += ".exe"
}

extractDir := filepath.Join(cacheDir(), repoName, goos, goarch, version)
executablePath := filepath.Join(extractDir, executableName)

if fileExists(executablePath) {
return executablePath, nil
}

lockKey := executablePath
mu, _ := downloadLocks.LoadOrStore(lockKey, &sync.Mutex{})
lock := mu.(*sync.Mutex)

lock.Lock()
defer lock.Unlock()

if fileExists(executablePath) {
return executablePath, nil
}

fmt.Printf("Downloading %s/%s %s for %s/%s...\n", owner, repoName, version, goos, goarch)

assetURL, assetName, err := getReleaseAssetURL(ctx, owner, repoName, version, goos, goarch)
if err != nil {
return "", err
}

if err := os.MkdirAll(extractDir, 0o755); err != nil {
return "", fmt.Errorf("failed to create cache directory: %w", err)
}

archivePath := filepath.Join(extractDir, assetName)
if err := downloadFile(ctx, assetURL, archivePath); err != nil {
return "", fmt.Errorf("failed to download file: %w", err)
}

if err := extractArchive(archivePath, extractDir, goos); err != nil {
return "", fmt.Errorf("failed to extract archive: %w", err)
}

_ = os.Remove(archivePath)

if err := os.Chmod(executablePath, 0o755); err != nil {
return "", fmt.Errorf("failed to make file executable: %w", err)
}

return executablePath, nil
}

func getReleaseAssetURL(ctx context.Context, owner, repoName, version, goos, goarch string) (string, string, error) {
versionWithoutV := strings.TrimPrefix(version, "v")

var assetName string
if goos == "windows" {
assetName = fmt.Sprintf("%s_%s_%s_%s.zip", repoName, versionWithoutV, goos, goarch)
} else {
assetName = fmt.Sprintf("%s_%s_%s_%s.tar.gz", repoName, versionWithoutV, goos, goarch)
}

releaseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", owner, repoName, version)
req, err := http.NewRequestWithContext(ctx, "GET", releaseURL, nil)
if err != nil {
return "", "", err
}
req.Header.Set("Accept", "application/vnd.github.v3+json")

if token := os.Getenv("GITHUB_TOKEN"); token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}

client := &http.Client{Timeout: 5 * time.Minute}
resp, err := client.Do(req)
if err != nil {
return "", "", err
}
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", "", fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, string(body))
}

var release struct {
Assets []struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
} `json:"assets"`
}

if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return "", "", err
}

for _, asset := range release.Assets {
if asset.Name == assetName {
return asset.BrowserDownloadURL, assetName, nil
}
}

availableAssets := make([]string, len(release.Assets))
for i, asset := range release.Assets {
availableAssets[i] = asset.Name
}

return "", "", fmt.Errorf("asset %s not found in release %s, available assets: %v", assetName, version, availableAssets)
}

func downloadFile(ctx context.Context, url, targetPath string) error {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return err
}

if token := os.Getenv("GITHUB_TOKEN"); token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}

client := &http.Client{Timeout: 5 * time.Minute}
resp, err := client.Do(req)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("download returned status %d: %s", resp.StatusCode, string(body))
}

tmpPath := targetPath + ".tmp"
out, err := os.Create(tmpPath)
if err != nil {
return err
}
defer func() { _ = out.Close() }()

if _, err := io.Copy(out, resp.Body); err != nil {
_ = os.Remove(tmpPath)
return err
}

if err := out.Close(); err != nil {
_ = os.Remove(tmpPath)
return err
}

return os.Rename(tmpPath, targetPath)
}

func extractArchive(archivePath, extractDir, goos string) error {
if goos == "windows" {
return extractZip(archivePath, extractDir)
}
return extractTarGz(archivePath, extractDir)
}

func extractTarGz(archivePath, extractDir string) error {
f, err := os.Open(archivePath)
if err != nil {
return err
}
defer func() { _ = f.Close() }()

gzr, err := gzip.NewReader(f)
if err != nil {
return err
}
defer func() { _ = gzr.Close() }()

tr := tar.NewReader(gzr)

for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}

target := filepath.Join(extractDir, header.Name)

switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(target, 0o755); err != nil {
return err
}
case tar.TypeReg:
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return err
}

outFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode))
if err != nil {
return err
}

if _, err := io.Copy(outFile, tr); err != nil {
_ = outFile.Close()
return err
}
if err := outFile.Close(); err != nil {
return err
}
}
}

return nil
}

func extractZip(archivePath, extractDir string) error {
r, err := zip.OpenReader(archivePath)
if err != nil {
return err
}
defer func() { _ = r.Close() }()

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

if f.FileInfo().IsDir() {
if err := os.MkdirAll(target, 0o755); err != nil {
return err
}
continue
}

if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return err
}

outFile, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}

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

_, err = io.Copy(outFile, rc)
_ = rc.Close()
if closeErr := outFile.Close(); closeErr != nil && err == nil {
err = closeErr
}

if err != nil {
return err
}
}

return nil
}

func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
14 changes: 12 additions & 2 deletions e2e/remoteproc/simulator.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
package remoteproc

import (
"context"
"fmt"
"os/exec"
"path/filepath"

"github.com/arm/remoteproc-runtime/e2e/download"
"github.com/arm/remoteproc-runtime/e2e/runner"
)

const Version = "v0.0.8"

func DownloadSimulator(ctx context.Context, version, goos, goarch string) (string, error) {
return download.GithubRelease(ctx, "arm", "remoteproc-simulator", version, goos, goarch)
}

type Simulator struct {
cmd *runner.StreamingCmd
binary string
name string
index uint
rootDir string
}

func NewSimulator(rootDir string) *Simulator {
func NewSimulator(binary, rootDir string) *Simulator {
return &Simulator{
binary: binary,
rootDir: rootDir,
index: 0,
name: "some-cpu",
Expand All @@ -30,7 +40,7 @@ func (r *Simulator) WithName(name string) *Simulator {

func (r *Simulator) Start() error {
cmd := exec.Command(
"remoteproc-simulator",
r.binary,
"--root-dir", r.rootDir,
"--index", fmt.Sprintf("%d", r.index),
"--name", r.name,
Expand Down
Loading
Loading