@@ -24,14 +24,15 @@ import (
2424
2525 "github.com/atomist-skills/go-skill"
2626 "github.com/docker/docker/client"
27+ "github.com/dustin/go-humanize"
2728 "github.com/google/go-containerregistry/pkg/authn"
2829 "github.com/google/go-containerregistry/pkg/name"
2930 v1 "github.com/google/go-containerregistry/pkg/v1"
3031 "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"
3332 "github.com/google/go-containerregistry/pkg/v1/remote"
33+ "github.com/google/go-containerregistry/pkg/v1/tarball"
3434 "github.com/pkg/errors"
35+ "github.com/sirupsen/logrus"
3536)
3637
3738type ImageId struct {
@@ -58,11 +59,13 @@ func (i ImageId) String() string {
5859 return i .name
5960}
6061
62+ type Cleanup = func ()
63+
6164// 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 ) {
6366 ref , err := name .ParseReference (image )
6467 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 )
6669 }
6770
6871 var path string
@@ -76,22 +79,22 @@ func SaveImage(image string, client client.APIClient) (v1.Image, string, error)
7679 if err != nil {
7780 img , err := daemon .Image (ImageId {name : image }, daemon .WithClient (client ))
7881 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 )
8083 } else {
8184 im , _ , err := client .ImageInspectWithRaw (context .Background (), image )
8285 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 )
8487 }
85- path , err = saveOci (im .ID , img , ref , path )
88+ path , cleanup , err := saveTar (im .ID , img , ref , path )
8689 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 )
8891 }
92+ return img , path , cleanup , nil
8993 }
90- return img , path , nil
9194 } else {
9295 img , err := desc .Image ()
9396 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 )
9598 }
9699 var digest string
97100 identifier := ref .Identifier ()
@@ -101,38 +104,70 @@ func SaveImage(image string, client client.APIClient) (v1.Image, string, error)
101104 digestHash , _ := img .Digest ()
102105 digest = digestHash .String ()
103106 }
104- path , err = saveOci (digest , img , ref , path )
107+ path , cleanup , err := saveTar (digest , img , ref , path )
105108 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 )
107110 }
108- return img , path , nil
111+ return img , path , cleanup , nil
109112 }
110113}
111114
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 ) {
115116 finalPath := strings .Replace (filepath .Join (path , digest ), ":" , string (os .PathSeparator ), 1 )
117+ tarPath := filepath .Join (finalPath , "archive.tar" )
116118 skill .Log .Debugf ("Copying image to %s" , finalPath )
117119
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
120122 }
121123 err := os .MkdirAll (finalPath , os .ModePerm )
122124 if err != nil {
123- return "" , err
125+ return "" , nil , err
124126 }
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 )
130141 }
131142 }
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+ }
134170 }
135- return finalPath , nil
136171}
137172
138173func withAuth () remote.Option {
0 commit comments