-
Notifications
You must be signed in to change notification settings - Fork 99
/
fetch.go
113 lines (93 loc) · 3.5 KB
/
fetch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package packaging
import (
"context"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/go-kit/kit/log/level"
"github.com/kolide/kit/fs"
"github.com/kolide/launcher/pkg/contexts/ctxlog"
"github.com/pkg/errors"
"go.opencensus.io/trace"
)
// FetchBinary will synchronously download a binary as per the
// supplied desired version and platform identifiers. The path to the
// downloaded binary is returned or an error if the operation did not
// succeed.
//
// You must specify a localCacheDir, to reuse downloads
func FetchBinary(ctx context.Context, localCacheDir, name, binaryName, version string, target Target) (string, error) {
ctx, span := trace.StartSpan(ctx, "packaging.fetchbinary")
defer span.End()
logger := ctxlog.FromContext(ctx)
// Create the cache directory if it doesn't already exist
if localCacheDir == "" {
return "", errors.New("Empty cache dir argument")
}
localBinaryPath := filepath.Join(localCacheDir, fmt.Sprintf("%s-%s-%s", name, target.Platform, version), binaryName)
localPackagePath := filepath.Join(localCacheDir, fmt.Sprintf("%s-%s-%s.tar.gz", name, target.Platform, version))
// See if a local package exists on disk already. If so, return the cached path
if _, err := os.Stat(localBinaryPath); err == nil {
return localBinaryPath, nil
}
// If not we have to download the package. First, create download
// URI. Notary stores things by name, sans extension. So just strip
// it off.
baseName := strings.TrimSuffix(name, filepath.Ext(name))
url := fmt.Sprintf("https://dl.kolide.co/%s", dlTarPath(baseName, version, string(target.Platform)))
level.Debug(logger).Log(
"msg", "starting download",
"url", url,
)
// Download the package
downloadReq, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", errors.Wrap(err, "new request")
}
downloadReq = downloadReq.WithContext(ctx)
httpClient := http.DefaultClient
response, err := httpClient.Do(downloadReq)
if err != nil {
return "", errors.Wrap(err, "couldn't download binary archive")
}
defer response.Body.Close()
if response.StatusCode != 200 {
return "", errors.Errorf("Failed download. Got http status %s", response.Status)
}
// Store it in cache
writeHandle, err := os.Create(localPackagePath)
if err != nil {
return "", errors.Wrap(err, "couldn't create file handle at local package download path")
}
defer writeHandle.Close()
_, err = io.Copy(writeHandle, response.Body)
if err != nil {
return "", errors.Wrap(err, "couldn't copy HTTP response body to file")
}
// explicitly close the write handle before untaring the archive
writeHandle.Close()
if err := os.MkdirAll(filepath.Dir(localBinaryPath), fs.DirMode); err != nil {
return "", errors.Wrap(err, "couldn't create directory for binary")
}
// UntarBundle is a bit misnamed. this untars unto the directory
// containing that file. It has a call to filepath.Dir(destination) there.
if err := fs.UntarBundle(localBinaryPath, localPackagePath); err != nil {
return "", errors.Wrap(err, "couldn't untar download")
}
// TODO / FIXME this fails for osquery-extension, since the binary name is inconsistent.
if _, err := os.Stat(localBinaryPath); err != nil {
level.Debug(logger).Log(
"msg", "Missing local binary",
"localBinaryPath", localBinaryPath,
)
return "", errors.Wrap(err, "local binary does not exist but it should")
}
return localBinaryPath, nil
}
func dlTarPath(name, version, platform string) string {
return path.Join("kolide", name, platform, fmt.Sprintf("%s-%s.tar.gz", name, version))
}