diff --git a/.travis.yml b/.travis.yml index df8f4e05..0842f365 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,5 +8,4 @@ go_import_path: github.com/GoogleCloudPlatform/container-diff script: - ./.gofmt.sh - - sudo rm -rf /etc/docker - travis_wait ./.container-diff-tests.sh diff --git a/README.md b/README.md index 936bcee4..66693c03 100644 --- a/README.md +++ b/README.md @@ -226,8 +226,6 @@ type MultiVersionInfo struct { To run container-diff on image IDs, docker must be installed. -If encountering this error ```open /etc/docker/certs.d/gcr.io: permission -denied```, run ```sudo rm -rf /etc/docker```. ## Example Run diff --git a/utils/docker_utils.go b/utils/docker_utils.go index aec90a7b..56157465 100644 --- a/utils/docker_utils.go +++ b/utils/docker_utils.go @@ -12,11 +12,9 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "strings" "syscall" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" img "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" @@ -41,74 +39,6 @@ func ValidDockerVersion() (bool, error) { return false, nil } -func getImagePullResponse(image string, response []Event) (string, error) { - var imageDigest string - for _, event := range response { - if event.Error != "" { - err := fmt.Errorf("Error pulling image %s: %s", image, event.Error) - return "", err - } - digestPattern := regexp.MustCompile("^Digest: (sha256:[a-z|0-9]{64})$") - digestMatch := digestPattern.FindStringSubmatch(event.Status) - if len(digestMatch) != 0 { - imageDigest = digestMatch[1] - return imageDigest, nil - } - } - err := fmt.Errorf("Could not pull image %s", image) - return "", err -} - -func processImagePullEvents(image string, events []Event) (string, string, error) { - imageDigest, err := getImagePullResponse(image, events) - if err != nil { - return "", "", err - } - - URLPattern := regexp.MustCompile("^.+/(.+(:.+){0,1})$") - URLMatch := URLPattern.FindStringSubmatch(image) - imageName := strings.Replace(URLMatch[1], ":", "", -1) - imageURL := strings.TrimSuffix(image, URLMatch[2]) - imageID := imageURL + "@" + imageDigest - - return imageID, imageName, nil -} - -type Event struct { - Status string `json:"status"` - Error string `json:"error"` - Progress string `json:"progress"` - ProgressDetail struct { - Current int `json:"current"` - Total int `json:"total"` - } `json:"progressDetail"` -} - -func pullImageFromRepo(image string) (string, string, error) { - glog.Info("Pulling image") - cli, err := client.NewEnvClient() - response, err := cli.ImagePull(context.Background(), image, types.ImagePullOptions{}) - if err != nil { - return "", "", err - } - defer response.Close() - - d := json.NewDecoder(response) - - var events []Event - for { - var event Event - if err := d.Decode(&event); err != nil { - if err == io.EOF { - break - } - return "", "", err - } - events = append(events, event) - } - return processImagePullEvents(image, events) -} - type HistDiff struct { Adds []string Dels []string @@ -169,42 +99,29 @@ func processHistOutput(response bytes.Buffer) ([]img.HistoryResponseItem, error) return history, nil } -func processPullCmdOutput(image string, response bytes.Buffer) (string, string, error) { - respReader := bytes.NewReader(response.Bytes()) - reader := bufio.NewReader(respReader) +func imageToTar(image, dest string) (string, error) { + cli, err := client.NewEnvClient() + if err != nil { + return "", err + } - var events []Event - for { - var event Event - text, _, err := reader.ReadLine() - if err != nil { - if err == io.EOF { - break - } - return "", "", err - } - event.Status = string(text) - events = append(events, event) + imageTarPath, err := saveImageToTar(cli, image, dest) + if err != nil { + return "", err } - return processImagePullEvents(image, events) + return imageTarPath, nil } -func pullImageCmd(image string) (string, string, error) { - glog.Info("Pulling image") - pullArgs := []string{"pull", image} - dockerPullCmd := exec.Command("docker", pullArgs...) - var response bytes.Buffer - dockerPullCmd.Stdout = &response - if err := dockerPullCmd.Run(); err != nil { - if exiterr, ok := err.(*exec.ExitError); ok { - if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - glog.Error("Docker Pull Command Exit Status: ", status.ExitStatus()) - } - } else { - return "", "", err - } +// ImageToTar writes an image to a .tar file +func saveImageToTar(cli client.APIClient, image, tarName string) (string, error) { + glog.Info("Saving image") + imgBytes, err := cli.ImageSave(context.Background(), []string{image}) + if err != nil { + return "", err } - return processPullCmdOutput(image, response) + defer imgBytes.Close() + newpath := tarName + ".tar" + return newpath, copyToFile(newpath, imgBytes) } func imageToTarCmd(imageID, imageName string) (string, error) { diff --git a/utils/docker_utils_test.go b/utils/docker_utils_test.go deleted file mode 100644 index fc13ec69..00000000 --- a/utils/docker_utils_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package utils - -import ( - "testing" -) - -func TestGetImagePullResponse(t *testing.T) { - for _, test := range []struct { - image string - response []Event - expectedOutput string - expectedError bool - }{ - { - image: "noimage", - response: []Event{}, - expectedOutput: "Could not pull image noimage", - expectedError: true, - }, - { - image: "gcr.io/google_containers/nonexistentimage", - response: []Event{{Error: "Non-existing image"}}, - expectedOutput: "Error pulling image gcr.io/google_containers/nonexistentimage: Non-existing image", - expectedError: true, - }, - { - image: "gcr.io/google_containers/existentimage", - response: []Event{{Status: "Digest: sha256:c34ce3c1fcc0c7431e1392cc3abd0dfe2192ffea1898d5250f199d3ac8d8720f"}}, - expectedOutput: "sha256:c34ce3c1fcc0c7431e1392cc3abd0dfe2192ffea1898d5250f199d3ac8d8720f", - expectedError: false, - }, - } { - output, err := getImagePullResponse(test.image, test.response) - if err != nil && !test.expectedError { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil && test.expectedError { - t.Error("Expected error but got none") - } else if err != nil && (test.expectedOutput != err.Error()) { - t.Error("Had trouble getting error") - } else if err == nil && test.expectedOutput != output { - t.Errorf("Expected %s but got %s", test.expectedOutput, output) - } - } -} diff --git a/utils/image_prep_utils.go b/utils/image_prep_utils.go index 47f29576..53231151 100644 --- a/utils/image_prep_utils.go +++ b/utils/image_prep_utils.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/containers/image/docker" + "github.com/containers/image/types" "github.com/docker/docker/api/types/container" "github.com/golang/glog" ) @@ -116,14 +117,23 @@ func (p CloudPrepper) getFileSystem() (string, error) { panic(err) } - img, err := ref.NewImage(nil) + // By default, the image library will try to look at /etc/docker/certs.d + // As a non-root user, this would result in a permissions error, so we avoid this + // by looking in a temporary directory we create in the container-diff home directory + cwd, _ := os.Getwd() + tmpCerts, _ := ioutil.TempDir(cwd, "certs") + defer os.RemoveAll(tmpCerts) + ctx := &types.SystemContext{ + DockerCertPath: tmpCerts, + } + img, err := ref.NewImage(ctx) if err != nil { glog.Error(err) return "", err } defer img.Close() - imgSrc, err := ref.NewImageSource(nil, nil) + imgSrc, err := ref.NewImageSource(ctx, nil) if err != nil { glog.Error(err) return "", err @@ -157,7 +167,16 @@ func (p CloudPrepper) getConfig() (ConfigSchema, error) { return ConfigSchema{}, err } - img, err := ref.NewImage(nil) + // By default, the image library will try to look at /etc/docker/certs.d + // As a non-root user, this would result in a permissions error, so we avoid this + // by looking in a temporary directory we create in the container-diff home directory + cwd, _ := os.Getwd() + tmpCerts, _ := ioutil.TempDir(cwd, "certs") + defer os.RemoveAll(tmpCerts) + ctx := &types.SystemContext{ + DockerCertPath: tmpCerts, + } + img, err := ref.NewImage(ctx) if err != nil { glog.Errorf("Error referencing image %s from registry: %s", p.Source, err) return ConfigSchema{}, errors.New("Could not obtain image config") @@ -194,7 +213,7 @@ func (p IDPrepper) getFileSystem() (string, error) { glog.Info("Docker version incompatible with api, shelling out to local Docker client.") tarPath, err = imageToTarCmd(p.Source, p.Source) } else { - tarPath, err = saveImageToTar(p.Source, p.Source) + tarPath, err = imageToTar(p.Source, p.Source) } if err != nil { return "", err diff --git a/utils/image_utils.go b/utils/image_utils.go index 34442540..91b99932 100644 --- a/utils/image_utils.go +++ b/utils/image_utils.go @@ -1,14 +1,12 @@ package utils import ( - "context" "io" "io/ioutil" "os" "path/filepath" "regexp" - "github.com/docker/docker/client" "github.com/docker/docker/pkg/system" "github.com/golang/glog" ) @@ -28,31 +26,6 @@ func GetImageLayers(pathToImage string) []string { return layers } -func saveImageToTar(image, dest string) (string, error) { - cli, err := client.NewEnvClient() - if err != nil { - return "", err - } - - imageTarPath, err := ImageToTar(cli, image, dest) - if err != nil { - return "", err - } - return imageTarPath, nil -} - -// ImageToTar writes an image to a .tar file -func ImageToTar(cli client.APIClient, image, tarName string) (string, error) { - glog.Info("Saving image") - imgBytes, err := cli.ImageSave(context.Background(), []string{image}) - if err != nil { - return "", err - } - defer imgBytes.Close() - newpath := tarName + ".tar" - return newpath, copyToFile(newpath, imgBytes) -} - func CheckImageID(image string) bool { pattern := regexp.MustCompile("[a-z|0-9]{12}") if exp := pattern.FindString(image); exp != image {