Skip to content

Commit d48c89c

Browse files
Refactor binary downloads in agent packaging targets (#9218) (#9828)
* Clean up artifact download code in packaging * Drop some unused functions * Fix linter warnings * Move http body closing to the inner download function (cherry picked from commit 05f62c2) Co-authored-by: Mikołaj Świątek <mail@mikolajswiatek.com>
1 parent d2be084 commit d48c89c

File tree

3 files changed

+38
-105
lines changed

3 files changed

+38
-105
lines changed

dev-tools/mage/downloads/utils.go

Lines changed: 23 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
package downloads
66

77
import (
8+
"context"
89
"fmt"
910
"io"
11+
"net/http"
1012
"os"
1113
"path/filepath"
1214
"regexp"
@@ -15,67 +17,51 @@ import (
1517
devtools "github.com/elastic/elastic-agent/dev-tools/mage"
1618

1719
"github.com/cenkalti/backoff/v4"
18-
"github.com/gofrs/uuid/v5"
1920
)
2021

2122
var checksumFileRegex = regexp.MustCompile(`^([0-9a-f]{128})\s+(\w.*)$`)
2223

2324
// downloadRequest struct contains download details ad path and URL
2425
type downloadRequest struct {
25-
URL string
26-
DownloadPath string
27-
UnsanitizedFilePath string
26+
URL string
27+
TargetPath string
2828
}
2929

3030
// downloadFile will download a url and store it in a temporary path.
3131
// It writes to the destination file as it downloads it, without
3232
// loading the entire file into memory.
3333
func downloadFile(downloadRequest *downloadRequest) error {
34-
var filePath string
35-
if downloadRequest.DownloadPath == "" {
36-
u, err := uuid.NewV4()
37-
if err != nil {
38-
return fmt.Errorf("failed to create UUID: %w", err)
39-
}
40-
tempParentDir := filepath.Join(os.TempDir(), u.String())
41-
err = mkdirAll(tempParentDir)
42-
if err != nil {
43-
return fmt.Errorf("creating directory: %w", err)
44-
}
45-
u, err = uuid.NewV4()
46-
if err != nil {
47-
return fmt.Errorf("failed to create UUID: %w", err)
48-
}
49-
filePath = filepath.Join(tempParentDir, u.String())
50-
downloadRequest.DownloadPath = filePath
51-
} else {
52-
u, err := uuid.NewV4()
53-
if err != nil {
54-
return fmt.Errorf("failed to create UUID: %w", err)
55-
}
56-
filePath = filepath.Join(downloadRequest.DownloadPath, u.String())
57-
}
58-
59-
tempFile, err := os.Create(filePath)
34+
targetFile, err := os.Create(downloadRequest.TargetPath)
6035
if err != nil {
6136
return fmt.Errorf("creating file: %w", err)
6237
}
63-
defer tempFile.Close()
38+
defer func() {
39+
_ = targetFile.Close()
40+
}()
6441

65-
downloadRequest.UnsanitizedFilePath = tempFile.Name()
6642
exp := getExponentialBackoff(3)
6743

6844
retryCount := 1
69-
var fileReader io.Reader
7045
download := func() error {
71-
r := httpRequest{URL: downloadRequest.URL}
72-
bodyStr, err := get(r)
46+
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, downloadRequest.URL, nil)
47+
if err != nil {
48+
return fmt.Errorf("creating request: %w", err)
49+
}
50+
resp, err := http.DefaultClient.Do(req)
7351
if err != nil {
7452
retryCount++
7553
return fmt.Errorf("downloading file %s: %w", downloadRequest.URL, err)
7654
}
55+
defer func() {
56+
_ = resp.Body.Close()
57+
}()
58+
_, err = io.Copy(targetFile, resp.Body)
59+
if err != nil {
60+
// try to drain the body before returning to ensure the connection can be reused
61+
_, _ = io.Copy(io.Discard, resp.Body)
62+
return fmt.Errorf("writing file %s: %w", targetFile.Name(), err)
63+
}
7764

78-
fileReader = strings.NewReader(bodyStr)
7965
return nil
8066
}
8167

@@ -84,12 +70,7 @@ func downloadFile(downloadRequest *downloadRequest) error {
8470
return err
8571
}
8672

87-
_, err = io.Copy(tempFile, fileReader)
88-
if err != nil {
89-
return fmt.Errorf("writing file %s: %w", tempFile.Name(), err)
90-
}
91-
92-
_ = os.Chmod(tempFile.Name(), 0666)
73+
_ = os.Chmod(targetFile.Name(), 0666)
9374

9475
return nil
9576
}

dev-tools/mage/downloads/utils_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,12 @@ func TestDownloadFile(t *testing.T) {
2626
var dRequest = downloadRequest{
2727
URL: fmt.Sprintf("http://%s/some-file.txt",
2828
s.Listener.Addr().String()),
29-
DownloadPath: "",
29+
TargetPath: filepath.Join(t.TempDir(), "some-file.txt"),
3030
}
3131

3232
err := downloadFile(&dRequest)
3333
assert.Nil(t, err)
34-
assert.NotEmpty(t, dRequest.UnsanitizedFilePath)
35-
defer os.Remove(filepath.Dir(dRequest.UnsanitizedFilePath))
34+
assert.FileExistsf(t, dRequest.TargetPath, "file should exist")
3635
}
3736

3837
func TestVerifyChecksum(t *testing.T) {

dev-tools/mage/downloads/versions.go

Lines changed: 13 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"fmt"
1111
"log/slog"
1212
"os"
13-
"path"
1413
"path/filepath"
1514
"regexp"
1615
"strconv"
@@ -107,51 +106,11 @@ func CheckPRVersion(version string, fallbackVersion string) string {
107106
return version
108107
}
109108

110-
// FetchElasticArtifact fetches an artifact from the right repository, returning binary name, path and error
111-
func FetchElasticArtifact(ctx context.Context, artifact string, version string, os string, arch string, extension string, isDocker bool, xpack bool) (string, string, error) {
112-
useCISnapshots := GithubCommitSha1 != ""
113-
114-
return FetchElasticArtifactForSnapshots(ctx, useCISnapshots, artifact, version, os, arch, extension, isDocker, xpack)
115-
}
116-
117-
// FetchElasticArtifactForSnapshots fetches an artifact from the right repository, returning binary name, path and error
118-
func FetchElasticArtifactForSnapshots(ctx context.Context, useCISnapshots bool, artifact string, version string, os string, arch string, extension string, isDocker bool, xpack bool) (string, string, error) {
119-
binaryName := buildArtifactName(artifact, version, os, arch, extension, isDocker)
120-
binaryPath, err := FetchProjectBinaryForSnapshots(ctx, useCISnapshots, artifact, binaryName, artifact, version, timeoutFactor, xpack, "", false)
121-
if err != nil {
122-
logger.Error("Could not download the binary for the Elastic artifact",
123-
slog.String("artifact", artifact),
124-
slog.String("version", version),
125-
slog.String("os", os),
126-
slog.String("arch", arch),
127-
slog.String("extension", extension),
128-
slog.String("error", err.Error()),
129-
)
130-
return "", "", err
131-
}
132-
133-
return binaryName, binaryPath, nil
134-
}
135-
136109
// GetCommitVersion returns a version including the version and the git commit, if it exists
137110
func GetCommitVersion(version string) string {
138111
return newElasticVersion(version).HashedVersion
139112
}
140113

141-
// GetElasticArtifactURL returns the URL of a released artifact, which its full name is defined in the first argument,
142-
// from Elastic's artifact repository, building the JSON path query based on the full name
143-
// It also returns the URL of the sha512 file of the released artifact.
144-
// i.e. GetElasticArtifactURL("elastic-agent-$VERSION-$ARCH.deb", "elastic-agent", "$VERSION")
145-
// i.e. GetElasticArtifactURL("elastic-agent-$VERSION-x86_64.rpm", "elastic-agent","$VERSION")
146-
// i.e. GetElasticArtifactURL("elastic-agent-$VERSION-linux-$ARCH.tar.gz", "elastic-agent","$VERSION")
147-
func GetElasticArtifactURL(artifactName string, artifact string, version string) (string, string, error) {
148-
resolver := NewArtifactURLResolver(artifactName, artifact, version)
149-
if resolver == nil {
150-
return "", "", errors.New("nil resolver returned")
151-
}
152-
return resolver.Resolve()
153-
}
154-
155114
// GetElasticArtifactVersion returns the current version:
156115
// 1. Elastic's artifact repository, building the JSON path query based
157116
// If the version is a SNAPSHOT including a commit, then it will directly use the version without checking the artifacts API
@@ -354,11 +313,19 @@ func FetchProjectBinaryForSnapshots(ctx context.Context, useCISnapshots bool, pr
354313
return "", fmt.Errorf("⚠️ Beats local path usage is deprecated and not used to fetch the binaries. Please use the packaging job to generate the artifacts to be consumed by these tests")
355314
}
356315

316+
if downloadPath == "" {
317+
return "", errors.New("downloadPath cannot be empty")
318+
}
319+
357320
handleDownload := func(URL string) (string, error) {
358321
name := artifactName
322+
if strings.HasSuffix(URL, ".sha512") {
323+
name = fmt.Sprintf("%s.sha512", name)
324+
}
325+
downloadFilePath := filepath.Join(downloadPath, name)
359326
downloadRequest := downloadRequest{
360-
DownloadPath: downloadPath,
361-
URL: URL,
327+
TargetPath: downloadFilePath,
328+
URL: URL,
362329
}
363330
span, _ := apm.StartSpanOptions(ctx, "Fetching Project binary", "project.url.fetch-binary", apm.SpanOptions{
364331
Parent: apm.SpanFromContext(ctx).TraceContext(),
@@ -379,28 +346,14 @@ func FetchProjectBinaryForSnapshots(ctx context.Context, useCISnapshots bool, pr
379346

380347
err := downloadFile(&downloadRequest)
381348
if err != nil {
382-
return downloadRequest.UnsanitizedFilePath, err
383-
}
384-
385-
if strings.HasSuffix(URL, ".sha512") {
386-
name = fmt.Sprintf("%s.sha512", name)
387-
}
388-
// use artifact name as file name to avoid having URL params in the name
389-
sanitizedFilePath := filepath.Join(path.Dir(downloadRequest.UnsanitizedFilePath), name)
390-
err = os.Rename(downloadRequest.UnsanitizedFilePath, sanitizedFilePath)
391-
if err != nil {
392-
logger.Warn("Could not sanitize downloaded file name. Keeping old name",
393-
slog.String("fileName", downloadRequest.UnsanitizedFilePath),
394-
slog.String("sanitizedFileName", sanitizedFilePath),
395-
)
396-
sanitizedFilePath = downloadRequest.UnsanitizedFilePath
349+
return "", err
397350
}
398351

399352
binariesMutex.Lock()
400-
binariesCache[URL] = sanitizedFilePath
353+
binariesCache[URL] = downloadFilePath
401354
binariesMutex.Unlock()
402355

403-
return sanitizedFilePath, nil
356+
return downloadFilePath, nil
404357
}
405358

406359
var downloadURL, downloadShaURL string

0 commit comments

Comments
 (0)