-
Notifications
You must be signed in to change notification settings - Fork 18
/
releases.go
104 lines (91 loc) · 2.65 KB
/
releases.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
package releases
import (
"context"
"errors"
"fmt"
"path"
"strings"
"time"
"github.com/circleci/ex/httpclient"
)
var ErrNotFound = errors.New("not found")
type Requirements struct {
Version string `json:"version"`
OS string `json:"os"`
Arch string `json:"arch"`
}
// Releases helps find the latest release and download URL for artifacts using the execution release structure.
type Releases struct {
baseURL string
client *httpclient.Client
}
func New(baseURL string) *Releases {
return &Releases{baseURL: baseURL, client: httpclient.New(httpclient.Config{
Name: "releases",
BaseURL: baseURL,
Timeout: 10 * time.Second,
})}
}
// Version gets the latest released version of an artifact.
func (d *Releases) Version(ctx context.Context) (string, error) {
version := ""
err := d.client.Call(ctx, httpclient.NewRequest("GET", "/release.txt",
httpclient.StringDecoder(&version),
))
if err != nil {
return "", err
}
version = strings.TrimSpace(version)
if version == "" {
return version, ErrNotFound
}
return version, nil
}
// ResolveURL gets the raw download URL for a release, based on the requirements (version, OS, arch)
func (d *Releases) ResolveURL(ctx context.Context, rq Requirements) (string, error) {
r, err := d.resolveURLs(ctx, rq)
if err != nil {
return "", err
}
return r[0], nil
}
// ResolveURLs gets the raw download URLs for all binaries of a release, based on the requirements (version, OS, arch)
func (d *Releases) ResolveURLs(ctx context.Context, rq Requirements) (map[string]string, error) {
r, err := d.resolveURLs(ctx, rq)
if err != nil {
return nil, err
}
result := make(map[string]string)
for _, p := range r {
_, file := path.Split(p)
file = strings.TrimSuffix(file, ".exe")
result[file] = p
}
return result, nil
}
func (d *Releases) resolveURLs(ctx context.Context, rq Requirements) ([]string, error) {
urls := ""
err := d.client.Call(ctx, httpclient.NewRequest("GET", "/"+rq.Version+"/checksums.txt",
httpclient.StringDecoder(&urls),
))
if err != nil {
return nil, err
}
return d.decodeDownload(rq, urls)
}
func (d *Releases) decodeDownload(rq Requirements, urls string) ([]string, error) {
result := make([]string, 0)
for _, txt := range strings.Split(urls, "\n") {
if strings.Contains(txt, rq.OS) && strings.Contains(txt, rq.Arch) {
parts := strings.Split(txt, " ")
// with some releases the file part is stored with a leading *./
filename := path.Clean(parts[1][1:])
filename = strings.TrimPrefix(filename, "/")
result = append(result, fmt.Sprintf("%s/%s/%s", d.baseURL, rq.Version, filename))
}
}
if len(result) == 0 {
return result, ErrNotFound
}
return result, nil
}