@@ -24,14 +24,15 @@ import (
24
24
25
25
"github.com/atomist-skills/go-skill"
26
26
"github.com/docker/docker/client"
27
+ "github.com/dustin/go-humanize"
27
28
"github.com/google/go-containerregistry/pkg/authn"
28
29
"github.com/google/go-containerregistry/pkg/name"
29
30
v1 "github.com/google/go-containerregistry/pkg/v1"
30
31
"github.com/google/go-containerregistry/pkg/v1/daemon"
31
- "github.com/google/go-containerregistry/pkg/v1/empty"
32
- "github.com/google/go-containerregistry/pkg/v1/layout"
33
32
"github.com/google/go-containerregistry/pkg/v1/remote"
33
+ "github.com/google/go-containerregistry/pkg/v1/tarball"
34
34
"github.com/pkg/errors"
35
+ "github.com/sirupsen/logrus"
35
36
)
36
37
37
38
type ImageId struct {
@@ -58,11 +59,13 @@ func (i ImageId) String() string {
58
59
return i .name
59
60
}
60
61
62
+ type Cleanup = func ()
63
+
61
64
// SaveImage stores the v1.Image at path returned in OCI format
62
- func SaveImage (image string , client client.APIClient ) (v1.Image , string , error ) {
65
+ func SaveImage (image string , client client.APIClient ) (v1.Image , string , Cleanup , error ) {
63
66
ref , err := name .ParseReference (image )
64
67
if err != nil {
65
- return nil , "" , errors .Wrapf (err , "failed to parse reference: %s" , image )
68
+ return nil , "" , nil , errors .Wrapf (err , "failed to parse reference: %s" , image )
66
69
}
67
70
68
71
var path string
@@ -76,22 +79,22 @@ func SaveImage(image string, client client.APIClient) (v1.Image, string, error)
76
79
if err != nil {
77
80
img , err := daemon .Image (ImageId {name : image }, daemon .WithClient (client ))
78
81
if err != nil {
79
- return nil , "" , errors .Wrapf (err , "failed to pull image: %s" , image )
82
+ return nil , "" , nil , errors .Wrapf (err , "failed to pull image: %s" , image )
80
83
} else {
81
84
im , _ , err := client .ImageInspectWithRaw (context .Background (), image )
82
85
if err != nil {
83
- return nil , "" , errors .Wrapf (err , "failed to get local image: %s" , image )
86
+ return nil , "" , nil , errors .Wrapf (err , "failed to get local image: %s" , image )
84
87
}
85
- path , err = saveOci (im .ID , img , ref , path )
88
+ path , cleanup , err := saveTar (im .ID , img , ref , path )
86
89
if err != nil {
87
- return nil , "" , errors .Wrapf (err , "failed to save image: %s" , image )
90
+ return nil , "" , nil , errors .Wrapf (err , "failed to save image: %s" , image )
88
91
}
92
+ return img , path , cleanup , nil
89
93
}
90
- return img , path , nil
91
94
} else {
92
95
img , err := desc .Image ()
93
96
if err != nil {
94
- return nil , "" , errors .Wrapf (err , "failed to pull image: %s" , image )
97
+ return nil , "" , nil , errors .Wrapf (err , "failed to pull image: %s" , image )
95
98
}
96
99
var digest string
97
100
identifier := ref .Identifier ()
@@ -101,38 +104,70 @@ func SaveImage(image string, client client.APIClient) (v1.Image, string, error)
101
104
digestHash , _ := img .Digest ()
102
105
digest = digestHash .String ()
103
106
}
104
- path , err = saveOci (digest , img , ref , path )
107
+ path , cleanup , err := saveTar (digest , img , ref , path )
105
108
if err != nil {
106
- return nil , "" , errors .Wrapf (err , "failed to save image: %s" , image )
109
+ return nil , "" , nil , errors .Wrapf (err , "failed to save image: %s" , image )
107
110
}
108
- return img , path , nil
111
+ return img , path , cleanup , nil
109
112
}
110
113
}
111
114
112
- // saveOci writes the v1.Image img as an OCI Image Layout at path. If a layout
113
- // already exists at that path, it will add the image to the index.
114
- func saveOci (digest string , img v1.Image , ref name.Reference , path string ) (string , error ) {
115
+ func saveTar (digest string , img v1.Image , ref name.Reference , path string ) (string , Cleanup , error ) {
115
116
finalPath := strings .Replace (filepath .Join (path , digest ), ":" , string (os .PathSeparator ), 1 )
117
+ tarPath := filepath .Join (finalPath , "archive.tar" )
116
118
skill .Log .Debugf ("Copying image to %s" , finalPath )
117
119
118
- if _ , err := os .Stat (finalPath ); ! os .IsNotExist (err ) {
119
- return finalPath , nil
120
+ if _ , err := os .Stat (tarPath ); ! os .IsNotExist (err ) {
121
+ return finalPath , nil , nil
120
122
}
121
123
err := os .MkdirAll (finalPath , os .ModePerm )
122
124
if err != nil {
123
- return "" , err
125
+ return "" , nil , err
124
126
}
125
- p , err := layout .FromPath (finalPath )
126
- if err != nil {
127
- p , err = layout .Write (finalPath , empty .Index )
128
- if err != nil {
129
- return "" , err
127
+
128
+ c := make (chan v1.Update , 200 )
129
+ errchan := make (chan error )
130
+ go func () {
131
+ if err := tarball .WriteToFile (tarPath , ref , img , tarball .WithProgress (c )); err != nil {
132
+ errchan <- errors .Wrapf (err , "failed to write image to tar" )
133
+ }
134
+ errchan <- nil
135
+ }()
136
+
137
+ cleanup := func () {
138
+ e := os .Remove (tarPath )
139
+ if e != nil {
140
+ skill .Log .Warnf ("Failed to delete tmp image archive %s" , tarPath )
130
141
}
131
142
}
132
- if err = p .AppendImage (img ); err != nil {
133
- return "" , err
143
+
144
+ var update v1.Update
145
+ var pp int64
146
+ for {
147
+ select {
148
+ case update = <- c :
149
+ p := 100 * update .Complete / update .Total
150
+ if p % 10 == 0 && pp != p {
151
+ skill .Log .WithFields (logrus.Fields {
152
+ "event" : "copy" ,
153
+ "total" : update .Total ,
154
+ "complete" : update .Complete ,
155
+ }).Debugf ("Copying image %3d%% %s/%s" , p , humanize .Bytes (uint64 (update .Complete )), humanize .Bytes (uint64 (update .Total )))
156
+ pp = p
157
+ }
158
+ case err = <- errchan :
159
+ if err != nil {
160
+ return "" , cleanup , err
161
+ } else {
162
+ skill .Log .WithFields (logrus.Fields {
163
+ "event" : "copy" ,
164
+ "total" : update .Total ,
165
+ "complete" : update .Complete ,
166
+ }).Debugf ("Copying image completed" )
167
+ return finalPath , cleanup , nil
168
+ }
169
+ }
134
170
}
135
- return finalPath , nil
136
171
}
137
172
138
173
func withAuth () remote.Option {
0 commit comments