Skip to content
This repository was archived by the owner on Mar 27, 2024. It is now read-only.

Commit c255953

Browse files
authored
Merge pull request #251 from nkubala/error_handling
Handle error gracefully when we can't retrieve an image
2 parents 509496e + 9c47911 commit c255953

File tree

7 files changed

+117
-60
lines changed

7 files changed

+117
-60
lines changed

cmd/analyze.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ limitations under the License.
1717
package cmd
1818

1919
import (
20-
"errors"
2120
"fmt"
2221
"os"
2322

2423
"github.com/GoogleContainerTools/container-diff/cmd/util/output"
2524
"github.com/GoogleContainerTools/container-diff/differs"
2625
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
26+
"github.com/pkg/errors"
2727
"github.com/sirupsen/logrus"
2828
"github.com/spf13/cobra"
2929
)
@@ -56,27 +56,27 @@ func checkAnalyzeArgNum(args []string) error {
5656
func analyzeImage(imageName string, analyzerArgs []string) error {
5757
analyzeTypes, err := differs.GetAnalyzers(analyzerArgs)
5858
if err != nil {
59-
return err
59+
return errors.Wrap(err, "getting analyzers")
6060
}
6161

6262
image, err := getImageForName(imageName)
6363
if err != nil {
64-
return err
64+
return errors.Wrapf(err, "error retrieving image %s", imageName)
6565
}
6666

6767
if noCache && !save {
6868
defer pkgutil.CleanupImage(image)
6969
}
7070
if err != nil {
71-
return fmt.Errorf("Error processing image: %s", err)
71+
return fmt.Errorf("error processing image: %s", err)
7272
}
7373

7474
req := differs.SingleRequest{
7575
Image: image,
7676
AnalyzeTypes: analyzeTypes}
7777
analyses, err := req.GetAnalysis()
7878
if err != nil {
79-
return fmt.Errorf("Error performing image analysis: %s", err)
79+
return fmt.Errorf("error performing image analysis: %s", err)
8080
}
8181

8282
logrus.Info("retrieving analyses")

cmd/analyze_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ var analyzeArgNumTests = []testpair{
2929
func TestAnalyzeArgNum(t *testing.T) {
3030
for _, test := range analyzeArgNumTests {
3131
err := checkAnalyzeArgNum(test.input)
32-
if (err == nil) != test.expected_output {
33-
if test.expected_output {
34-
t.Errorf("Got unexpected error: %s", err)
35-
} else {
32+
if (err == nil) != test.shouldError {
33+
if test.shouldError {
3634
t.Errorf("Expected error but got none")
35+
} else {
36+
t.Errorf("Got unexpected error: %s", err)
3737
}
3838
}
3939
}

cmd/diff.go

+45-20
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ limitations under the License.
1717
package cmd
1818

1919
import (
20-
"errors"
2120
"fmt"
2221
"os"
22+
"strings"
2323
"sync"
2424

2525
"github.com/GoogleContainerTools/container-diff/cmd/util/output"
2626
"github.com/GoogleContainerTools/container-diff/differs"
2727
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
2828
"github.com/GoogleContainerTools/container-diff/util"
29+
"github.com/pkg/errors"
2930
"github.com/sirupsen/logrus"
3031
"github.com/spf13/cobra"
3132
)
@@ -66,50 +67,74 @@ func checkFilenameFlag(_ []string) error {
6667
return nil
6768
}
6869
}
69-
return errors.New("Please include --types=file with the --filename flag")
70+
return errors.New("please include --types=file with the --filename flag")
71+
}
72+
73+
// processImage is a concurrency-friendly wrapper around getImageForName
74+
func processImage(imageName string, imageMap map[string]*pkgutil.Image, wg *sync.WaitGroup, errChan chan<- error) {
75+
defer wg.Done()
76+
image, err := getImageForName(imageName)
77+
if err != nil {
78+
errChan <- fmt.Errorf("error retrieving image %s: %s", imageName, err)
79+
}
80+
imageMap[imageName] = &image
81+
}
82+
83+
// collects errors from a channel and combines them
84+
// assumes channel has already been closed
85+
func readErrorsFromChannel(c chan error) error {
86+
errs := []string{}
87+
for {
88+
err, ok := <-c
89+
if !ok {
90+
break
91+
}
92+
errs = append(errs, err.Error())
93+
}
94+
95+
if len(errs) > 0 {
96+
return errors.New(strings.Join(errs, "\n"))
97+
}
98+
return nil
7099
}
71100

72101
func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
73102
diffTypes, err := differs.GetAnalyzers(diffArgs)
74103
if err != nil {
75-
return err
104+
return errors.Wrap(err, "getting analyzers")
76105
}
77106

78107
var wg sync.WaitGroup
79108
wg.Add(2)
80109

81110
logrus.Infof("starting diff on images %s and %s, using differs: %s\n", image1Arg, image2Arg, diffArgs)
82111

83-
imageMap := map[string]*pkgutil.Image{
84-
image1Arg: {},
85-
image2Arg: {},
86-
}
87-
// TODO: fix error handling here
88-
for imageArg := range imageMap {
89-
go func(imageName string, imageMap map[string]*pkgutil.Image) {
90-
defer wg.Done()
91-
image, err := getImageForName(imageName)
92-
imageMap[imageName] = &image
93-
if err != nil {
94-
logrus.Warningf("Diff may be inaccurate: %s", err)
95-
}
96-
}(imageArg, imageMap)
97-
}
112+
imageMap := map[string]*pkgutil.Image{}
113+
errChan := make(chan error, 2)
114+
115+
go processImage(image1Arg, imageMap, &wg, errChan)
116+
go processImage(image2Arg, imageMap, &wg, errChan)
117+
98118
wg.Wait()
119+
close(errChan)
99120

100121
if noCache && !save {
101122
defer pkgutil.CleanupImage(*imageMap[image1Arg])
102123
defer pkgutil.CleanupImage(*imageMap[image2Arg])
103124
}
104125

126+
if err := readErrorsFromChannel(errChan); err != nil {
127+
return err
128+
}
129+
105130
logrus.Info("computing diffs")
106131
req := differs.DiffRequest{
107132
Image1: *imageMap[image1Arg],
108133
Image2: *imageMap[image2Arg],
109134
DiffTypes: diffTypes}
110135
diffs, err := req.GetDiff()
111136
if err != nil {
112-
return fmt.Errorf("Could not retrieve diff: %s", err)
137+
return fmt.Errorf("could not retrieve diff: %s", err)
113138
}
114139
outputResults(diffs)
115140

@@ -122,7 +147,7 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
122147
}
123148

124149
if noCache && save {
125-
logrus.Infof("Images were saved at %s and %s", imageMap[image1Arg].FSPath,
150+
logrus.Infof("images were saved at %s and %s", imageMap[image1Arg].FSPath,
126151
imageMap[image2Arg].FSPath)
127152
}
128153
return nil

cmd/diff_test.go

+35-10
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,46 @@ import (
2121
)
2222

2323
var diffArgNumTests = []testpair{
24-
{[]string{}, false},
25-
{[]string{"one"}, false},
26-
{[]string{"one", "two"}, true},
27-
{[]string{"one", "two", "three"}, false},
24+
{[]string{}, true},
25+
{[]string{"one"}, true},
26+
{[]string{"one", "two"}, false},
27+
{[]string{"one", "two", "three"}, true},
2828
}
2929

3030
func TestDiffArgNum(t *testing.T) {
3131
for _, test := range diffArgNumTests {
3232
err := checkDiffArgNum(test.input)
33-
if (err == nil) != test.expected_output {
34-
if test.expected_output {
35-
t.Errorf("Got unexpected error: %s", err)
36-
} else {
37-
t.Errorf("Expected error but got none")
38-
}
33+
checkError(t, err, test.shouldError)
34+
}
35+
}
36+
37+
type imageDiff struct {
38+
image1 string
39+
image2 string
40+
shouldError bool
41+
}
42+
43+
var imageDiffs = []imageDiff{
44+
{"", "", true},
45+
{"gcr.io/google-appengine/python", "gcr.io/google-appengine/debian9", false},
46+
{"gcr.io/google-appengine/python", "cats", true},
47+
}
48+
49+
func TestDiffImages(t *testing.T) {
50+
for _, test := range imageDiffs {
51+
err := diffImages(test.image1, test.image2, []string{"apt"})
52+
checkError(t, err, test.shouldError)
53+
err = diffImages(test.image1, test.image2, []string{"metadata"})
54+
checkError(t, err, test.shouldError)
55+
}
56+
}
57+
58+
func checkError(t *testing.T, err error, shouldError bool) {
59+
if (err == nil) == shouldError {
60+
if shouldError {
61+
t.Errorf("expected error but got none")
62+
} else {
63+
t.Errorf("got unexpected error: %s", err)
3964
}
4065
}
4166
}

cmd/root.go

+20-13
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/GoogleContainerTools/container-diff/util"
3939
"github.com/google/go-containerregistry/pkg/v1"
4040
homedir "github.com/mitchellh/go-homedir"
41+
"github.com/pkg/errors"
4142
"github.com/sirupsen/logrus"
4243
"github.com/spf13/cobra"
4344
"github.com/spf13/pflag"
@@ -131,6 +132,9 @@ func checkIfValidAnalyzer(_ []string) error {
131132
return nil
132133
}
133134

135+
// getImageForName infers the source of an image and retrieves a v1.Image reference to it.
136+
// Once a reference is obtained, it attempts to unpack the v1.Image's reader's contents
137+
// into a temp directory on the local filesystem.
134138
func getImageForName(imageName string) (pkgutil.Image, error) {
135139
logrus.Infof("retrieving image: %s", imageName)
136140
var img v1.Image
@@ -139,17 +143,17 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
139143
start := time.Now()
140144
img, err = tarball.ImageFromPath(imageName, nil)
141145
if err != nil {
142-
return pkgutil.Image{}, err
146+
return pkgutil.Image{}, errors.Wrap(err, "retrieving tar from path")
143147
}
144148
elapsed := time.Now().Sub(start)
145-
logrus.Infof("retrieving image from tar took %f seconds", elapsed.Seconds())
149+
logrus.Infof("retrieving image ref from tar took %f seconds", elapsed.Seconds())
146150
} else if strings.HasPrefix(imageName, DaemonPrefix) {
147151
// remove the daemon prefix
148152
imageName = strings.Replace(imageName, DaemonPrefix, "", -1)
149153

150154
ref, err := name.ParseReference(imageName, name.WeakValidation)
151155
if err != nil {
152-
return pkgutil.Image{}, err
156+
return pkgutil.Image{}, errors.Wrap(err, "parsing image reference")
153157
}
154158

155159
start := time.Now()
@@ -158,28 +162,28 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
158162
Buffer: true,
159163
})
160164
if err != nil {
161-
return pkgutil.Image{}, err
165+
return pkgutil.Image{}, errors.Wrap(err, "retrieving image from daemon")
162166
}
163167
elapsed := time.Now().Sub(start)
164-
logrus.Infof("retrieving image from daemon took %f seconds", elapsed.Seconds())
168+
logrus.Infof("retrieving local image ref took %f seconds", elapsed.Seconds())
165169
} else {
166170
// either has remote prefix or has no prefix, in which case we force remote
167171
imageName = strings.Replace(imageName, RemotePrefix, "", -1)
168172
ref, err := name.ParseReference(imageName, name.WeakValidation)
169173
if err != nil {
170-
return pkgutil.Image{}, err
174+
return pkgutil.Image{}, errors.Wrap(err, "parsing image reference")
171175
}
172176
auth, err := authn.DefaultKeychain.Resolve(ref.Context().Registry)
173177
if err != nil {
174-
return pkgutil.Image{}, err
178+
return pkgutil.Image{}, errors.Wrap(err, "resolving auth")
175179
}
176180
start := time.Now()
177181
img, err = remote.Image(ref, auth, http.DefaultTransport)
178182
if err != nil {
179-
return pkgutil.Image{}, err
183+
return pkgutil.Image{}, errors.Wrap(err, "retrieving remote image")
180184
}
181185
elapsed := time.Now().Sub(start)
182-
logrus.Infof("retrieving remote image took %f seconds", elapsed.Seconds())
186+
logrus.Infof("retrieving remote image ref took %f seconds", elapsed.Seconds())
183187
}
184188

185189
// create tempdir and extract fs into it
@@ -188,7 +192,7 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
188192
start := time.Now()
189193
imgLayers, err := img.Layers()
190194
if err != nil {
191-
return pkgutil.Image{}, err
195+
return pkgutil.Image{}, errors.Wrap(err, "getting image layers")
192196
}
193197
for _, layer := range imgLayers {
194198
layerStart := time.Now()
@@ -197,12 +201,12 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
197201
if err != nil {
198202
return pkgutil.Image{
199203
Layers: layers,
200-
}, err
204+
}, errors.Wrap(err, "getting extract path for layer")
201205
}
202206
if err := pkgutil.GetFileSystemForLayer(layer, path, nil); err != nil {
203207
return pkgutil.Image{
204208
Layers: layers,
205-
}, err
209+
}, errors.Wrap(err, "getting filesystem for layer")
206210
}
207211
layers = append(layers, pkgutil.Layer{
208212
FSPath: path,
@@ -215,12 +219,15 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
215219
}
216220

217221
path, err := getExtractPathForImage(imageName, img)
222+
if err != nil {
223+
return pkgutil.Image{}, err
224+
}
218225
// extract fs into provided dir
219226
if err := pkgutil.GetFileSystemForImage(img, path, nil); err != nil {
220227
return pkgutil.Image{
221228
FSPath: path,
222229
Layers: layers,
223-
}, err
230+
}, errors.Wrap(err, "getting filesystem for image")
224231
}
225232
return pkgutil.Image{
226233
Image: img,

cmd/root_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ limitations under the License.
1717
package cmd
1818

1919
type testpair struct {
20-
input []string
21-
expected_output bool
20+
input []string
21+
shouldError bool
2222
}

0 commit comments

Comments
 (0)