Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/content-service/pkg/initializer/download.go
2499 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package initializer
6
7
import (
8
"context"
9
"io"
10
"net/http"
11
"os"
12
"path/filepath"
13
"time"
14
15
"github.com/gitpod-io/gitpod/common-go/log"
16
"github.com/gitpod-io/gitpod/common-go/tracing"
17
csapi "github.com/gitpod-io/gitpod/content-service/api"
18
"github.com/gitpod-io/gitpod/content-service/pkg/archive"
19
"github.com/opencontainers/go-digest"
20
"github.com/opentracing/opentracing-go"
21
"golang.org/x/sync/errgroup"
22
"golang.org/x/xerrors"
23
)
24
25
type fileInfo struct {
26
URL string
27
28
// Path is relative to the FileDownloadInitializer's TargetLocation, e.g. if TargetLocation is in `/workspace/myrepo`
29
// a Path of `foobar/file` would produce a file in `/workspace/myrepo/foobar/file`.
30
// Path must include the filename. The FileDownloadInitializer will create any parent directories
31
// necessary to place the file.
32
Path string
33
34
// Digest is a hash of the file content in the OCI Digest format (see https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests).
35
// This information is used to compute subsequent
36
// content versions, and to validate the file content was downloaded correctly.
37
Digest digest.Digest
38
}
39
40
type fileDownloadInitializer struct {
41
FilesInfos []fileInfo
42
TargetLocation string
43
HTTPClient *http.Client
44
RetryTimeout time.Duration
45
}
46
47
// Run initializes the workspace
48
func (ws *fileDownloadInitializer) Run(ctx context.Context, mappings []archive.IDMapping) (src csapi.WorkspaceInitSource, metrics csapi.InitializerMetrics, err error) {
49
span, ctx := opentracing.StartSpanFromContext(ctx, "FileDownloadInitializer.Run")
50
defer tracing.FinishSpan(span, &err)
51
start := time.Now()
52
initialSize, fsErr := getFsUsage()
53
if fsErr != nil {
54
log.WithError(fsErr).Error("could not get disk usage")
55
}
56
57
for _, info := range ws.FilesInfos {
58
err := ws.downloadFile(ctx, info)
59
if err != nil {
60
tracing.LogError(span, xerrors.Errorf("cannot download file '%s' from '%s': %w", info.Path, info.URL, err))
61
return src, nil, err
62
}
63
}
64
65
if fsErr == nil {
66
currentSize, fsErr := getFsUsage()
67
if fsErr != nil {
68
log.WithError(fsErr).Error("could not get disk usage")
69
}
70
71
metrics = csapi.InitializerMetrics{csapi.InitializerMetric{
72
Type: "fileDownload",
73
Duration: time.Since(start),
74
Size: currentSize - initialSize,
75
}}
76
}
77
78
src = csapi.WorkspaceInitFromOther
79
return
80
}
81
82
func (ws *fileDownloadInitializer) downloadFile(ctx context.Context, info fileInfo) (err error) {
83
//nolint:ineffassign
84
span, ctx := opentracing.StartSpanFromContext(ctx, "downloadFile")
85
defer tracing.FinishSpan(span, &err)
86
span.LogKV("url", info.URL)
87
88
fn := filepath.Join(ws.TargetLocation, info.Path)
89
err = os.MkdirAll(filepath.Dir(fn), 0755)
90
if err != nil {
91
tracing.LogError(span, xerrors.Errorf("cannot mkdir %s: %w", filepath.Dir(fn), err))
92
}
93
94
fd, err := os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
95
if err != nil {
96
return err
97
}
98
99
dl := func() (err error) {
100
req, err := http.NewRequestWithContext(ctx, "GET", info.URL, nil)
101
if err != nil {
102
return err
103
}
104
_ = opentracing.GlobalTracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
105
106
resp, err := ws.HTTPClient.Do(req)
107
if err != nil {
108
return err
109
}
110
defer resp.Body.Close()
111
if resp.StatusCode != http.StatusOK {
112
return xerrors.Errorf("non-OK download response: %s", resp.Status)
113
}
114
115
pr, pw := io.Pipe()
116
body := io.TeeReader(resp.Body, pw)
117
118
eg, _ := errgroup.WithContext(ctx)
119
eg.Go(func() error {
120
_, err = io.Copy(fd, body)
121
pw.Close()
122
return err
123
})
124
eg.Go(func() error {
125
dgst, err := digest.FromReader(pr)
126
if err != nil {
127
return err
128
}
129
if dgst != info.Digest {
130
return xerrors.Errorf("digest mismatch")
131
}
132
return nil
133
})
134
135
return eg.Wait()
136
}
137
for i := 0; i < otsDownloadAttempts; i++ {
138
span.LogKV("attempt", i)
139
if i > 0 {
140
time.Sleep(ws.RetryTimeout)
141
}
142
143
err = dl()
144
if err == context.Canceled || err == context.DeadlineExceeded {
145
return
146
}
147
if err == nil {
148
break
149
}
150
log.WithError(err).WithField("attempt", i).Warn("cannot download additional content files")
151
}
152
if err != nil {
153
return err
154
}
155
156
return nil
157
}
158
159