@@ -7,7 +7,6 @@ package initializer
7
7
import (
8
8
"context"
9
9
"io"
10
- "io/ioutil"
11
10
"net/http"
12
11
"os"
13
12
"path/filepath"
@@ -17,83 +16,111 @@ import (
17
16
"github.com/gitpod-io/gitpod/common-go/tracing"
18
17
csapi "github.com/gitpod-io/gitpod/content-service/api"
19
18
"github.com/gitpod-io/gitpod/content-service/pkg/archive"
19
+ "github.com/opencontainers/go-digest"
20
20
"github.com/opentracing/opentracing-go"
21
+ "golang.org/x/sync/errgroup"
21
22
"golang.org/x/xerrors"
22
23
)
23
24
24
- type FileInfo struct {
25
- url string
26
- // filePath is relative to the FileDownloadInitializer's TargetLocation, e.g. if TargetLocation is in `/workspace/myrepo`
27
- // a filePath of `foobar/file` would produce a file in `/workspace/myrepo/foobar/file`.
28
- // filePath must include the filename. The FileDownloadInitializer will create any parent directories
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
29
31
// necessary to place the file.
30
- filePath string
31
- // 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).
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).
32
35
// This information is used to compute subsequent
33
36
// content versions, and to validate the file content was downloaded correctly.
34
- digest string
37
+ Digest digest. Digest
35
38
}
36
39
37
- type FileDownloadInitializer struct {
38
- FilesInfos []FileInfo
40
+ type fileDownloadInitializer struct {
41
+ FilesInfos []fileInfo
39
42
TargetLocation string
43
+ HTTPClient * http.Client
44
+ RetryTimeout time.Duration
40
45
}
41
46
42
47
// Run initializes the workspace
43
- func (ws * FileDownloadInitializer ) Run (ctx context.Context , mappings []archive.IDMapping ) (src csapi.WorkspaceInitSource , err error ) {
48
+ func (ws * fileDownloadInitializer ) Run (ctx context.Context , mappings []archive.IDMapping ) (src csapi.WorkspaceInitSource , err error ) {
44
49
span , ctx := opentracing .StartSpanFromContext (ctx , "FileDownloadInitializer.Run" )
45
50
defer tracing .FinishSpan (span , & err )
46
51
47
52
for _ , info := range ws .FilesInfos {
48
- contents , err := downloadFile (ctx , info .url )
49
- if err != nil {
50
- tracing .LogError (span , xerrors .Errorf ("cannot download file '%s' from '%s': %w" , info .filePath , info .url , err ))
51
- }
52
-
53
- fullPath := filepath .Join (ws .TargetLocation , info .filePath )
54
- err = os .MkdirAll (filepath .Dir (fullPath ), 0755 )
55
- if err != nil {
56
- tracing .LogError (span , xerrors .Errorf ("cannot mkdir %s: %w" , filepath .Dir (fullPath ), err ))
57
- }
58
- err = ioutil .WriteFile (fullPath , contents , 0755 )
53
+ err := ws .downloadFile (ctx , info )
59
54
if err != nil {
60
- tracing .LogError (span , xerrors .Errorf ("cannot write %s: %w" , fullPath , err ))
55
+ tracing .LogError (span , xerrors .Errorf ("cannot download file '%s' from '%s': %w" , info .Path , info .URL , err ))
56
+ return src , err
61
57
}
62
58
}
63
- return src , nil
59
+ return csapi . WorkspaceInitFromOther , nil
64
60
}
65
61
66
- func downloadFile (ctx context.Context , url string ) (content [] byte , err error ) {
62
+ func ( ws * fileDownloadInitializer ) downloadFile (ctx context.Context , info fileInfo ) (err error ) {
67
63
//nolint:ineffassign
68
64
span , ctx := opentracing .StartSpanFromContext (ctx , "downloadFile" )
69
65
defer tracing .FinishSpan (span , & err )
70
- span .LogKV ("url" , url )
66
+ span .LogKV ("url" , info . URL )
71
67
72
- dl := func () (content []byte , err error ) {
73
- req , err := http .NewRequestWithContext (ctx , "GET" , url , nil )
68
+ fn := filepath .Join (ws .TargetLocation , info .Path )
69
+ err = os .MkdirAll (filepath .Dir (fn ), 0755 )
70
+ if err != nil {
71
+ tracing .LogError (span , xerrors .Errorf ("cannot mkdir %s: %w" , filepath .Dir (fn ), err ))
72
+ }
73
+
74
+ fd , err := os .OpenFile (fn , os .O_CREATE | os .O_TRUNC | os .O_WRONLY , 0644 )
75
+ if err != nil {
76
+ return err
77
+ }
78
+
79
+ dl := func () (err error ) {
80
+ req , err := http .NewRequestWithContext (ctx , "GET" , info .URL , nil )
74
81
if err != nil {
75
- return nil , err
82
+ return err
76
83
}
77
84
_ = opentracing .GlobalTracer ().Inject (span .Context (), opentracing .HTTPHeaders , opentracing .HTTPHeadersCarrier (req .Header ))
78
85
79
- resp , err := http . DefaultClient .Do (req )
86
+ resp , err := ws . HTTPClient .Do (req )
80
87
if err != nil {
81
- return nil , err
88
+ return err
82
89
}
83
90
defer resp .Body .Close ()
84
91
if resp .StatusCode != http .StatusOK {
85
- return nil , xerrors .Errorf ("non-OK OTS response: %s" , resp .Status )
92
+ return xerrors .Errorf ("non-OK download response: %s" , resp .Status )
86
93
}
87
94
88
- return io .ReadAll (resp .Body )
95
+ pr , pw := io .Pipe ()
96
+ body := io .TeeReader (resp .Body , pw )
97
+
98
+ eg , _ := errgroup .WithContext (ctx )
99
+ eg .Go (func () error {
100
+ _ , err = io .Copy (fd , body )
101
+ pw .Close ()
102
+ return err
103
+ })
104
+ eg .Go (func () error {
105
+ dgst , err := digest .FromReader (pr )
106
+ if err != nil {
107
+ return err
108
+ }
109
+ if dgst != info .Digest {
110
+ return xerrors .Errorf ("digest mismatch" )
111
+ }
112
+ return nil
113
+ })
114
+
115
+ return eg .Wait ()
89
116
}
90
117
for i := 0 ; i < otsDownloadAttempts ; i ++ {
91
118
span .LogKV ("attempt" , i )
92
119
if i > 0 {
93
- time .Sleep (time . Second )
120
+ time .Sleep (ws . RetryTimeout )
94
121
}
95
122
96
- content , err = dl ()
123
+ err = dl ()
97
124
if err == context .Canceled || err == context .DeadlineExceeded {
98
125
return
99
126
}
@@ -103,8 +130,8 @@ func downloadFile(ctx context.Context, url string) (content []byte, err error) {
103
130
log .WithError (err ).WithField ("attempt" , i ).Warn ("cannot download additional content files" )
104
131
}
105
132
if err != nil {
106
- return nil , err
133
+ return err
107
134
}
108
135
109
- return content , nil
136
+ return nil
110
137
}
0 commit comments