// FIXME: Do more extensive tests (ex: create multiple, delete, recreate; // create multiple, check the amount of images and paths, etc..) func TestGraphCreate(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) archive, err := fakeTar() if err != nil { t.Fatal(err) } img, err := graph.Create(archive, "", "", "Testing", "", nil, nil) if err != nil { t.Fatal(err) } if err := utils.ValidateID(img.ID); err != nil { t.Fatal(err) } if img.Comment != "Testing" { t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", img.Comment) } if img.DockerVersion != dockerversion.VERSION { t.Fatalf("Wrong docker_version: should be '%s', not '%s'", dockerversion.VERSION, img.DockerVersion) } images, err := graph.Map() if err != nil { t.Fatal(err) } else if l := len(images); l != 1 { t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l) } if images[img.ID] == nil { t.Fatalf("Could not find image with id %s", img.ID) } }
func validateRemoteName(remoteName string) error { var ( namespace string name string ) nameParts := strings.SplitN(remoteName, "/", 2) if len(nameParts) < 2 { namespace = "library" name = nameParts[0] // the repository name must not be a valid image ID if err := utils.ValidateID(name); err == nil { return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name) } } else { namespace = nameParts[0] name = nameParts[1] } if !validNamespaceChars.MatchString(namespace) { return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace) } if len(namespace) < 4 || len(namespace) > 30 { return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace) } if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") { return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace) } if strings.Contains(namespace, "--") { return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace) } if !validRepo.MatchString(name) { return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name) } return nil }
func LoadImage(root string) (*Image, error) { // Load the json data jsonData, err := ioutil.ReadFile(jsonPath(root)) if err != nil { return nil, err } img := &Image{} if err := json.Unmarshal(jsonData, img); err != nil { return nil, err } if err := utils.ValidateID(img.ID); err != nil { return nil, err } if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil { if !os.IsNotExist(err) { return nil, err } // If the layersize file does not exist then set the size to a negative number // because a layer size of 0 (zero) is valid img.Size = -1 } else { size, err := strconv.Atoi(string(buf)) if err != nil { return nil, err } img.Size = int64(size) } return img, nil }
func validateRepositoryName(repositoryName string) error { var ( namespace string name string ) nameParts := strings.SplitN(repositoryName, "/", 2) if len(nameParts) < 2 { namespace = "library" name = nameParts[0] // the repository name must not be a valid image ID if err := utils.ValidateID(name); err == nil { return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name) } } else { namespace = nameParts[0] name = nameParts[1] } if !validNamespace.MatchString(namespace) { return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace) } if !validRepo.MatchString(name) { return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name) } return nil }
// Register imports a pre-existing image into the graph. func (graph *Graph) Register(img *image.Image, jsonData []byte, layerData archive.ArchiveReader) (err error) { defer func() { // If any error occurs, remove the new dir from the driver. // Don't check for errors since the dir might not have been created. // FIXME: this leaves a possible race condition. if err != nil { graph.driver.Remove(img.ID) } }() if err := utils.ValidateID(img.ID); err != nil { return err } // (This is a convenience to save time. Race conditions are taken care of by os.Rename) if graph.Exists(img.ID) { return fmt.Errorf("Image %s already exists", img.ID) } // Ensure that the image root does not exist on the filesystem // when it is not registered in the graph. // This is common when you switch from one graph driver to another if err := os.RemoveAll(graph.ImageRoot(img.ID)); err != nil && !os.IsNotExist(err) { return err } // If the driver has this ID but the graph doesn't, remove it from the driver to start fresh. // (the graph is the source of truth). // Ignore errors, since we don't know if the driver correctly returns ErrNotExist. // (FIXME: make that mandatory for drivers). graph.driver.Remove(img.ID) tmp, err := graph.Mktemp("") defer os.RemoveAll(tmp) if err != nil { return fmt.Errorf("Mktemp failed: %s", err) } // Create root filesystem in the driver if err := graph.driver.Create(img.ID, img.Parent); err != nil { return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err) } // Mount the root filesystem so we can apply the diff/layer rootfs, err := graph.driver.Get(img.ID, "") if err != nil { return fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err) } defer graph.driver.Put(img.ID) img.SetGraph(graph) if err := image.StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil { return err } // Commit if err := os.Rename(tmp, graph.ImageRoot(img.ID)); err != nil { return err } graph.idIndex.Add(img.ID) return nil }
func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error { if err := eng.Job("image_get", address).Run(); err != nil { logrus.Debugf("Loading %s", address) imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) if err != nil { logrus.Debugf("Error reading json", err) return err } layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar")) if err != nil { logrus.Debugf("Error reading embedded tar", err) return err } img, err := image.NewImgJSON(imageJson) if err != nil { logrus.Debugf("Error unmarshalling json", err) return err } if err := utils.ValidateID(img.ID); err != nil { logrus.Debugf("Error validating ID: %s", err) return err } // ensure no two downloads of the same layer happen at the same time if c, err := s.poolAdd("pull", "layer:"+img.ID); err != nil { if c != nil { logrus.Debugf("Image (id: %s) load is already running, waiting: %v", img.ID, err) <-c return nil } return err } defer s.poolRemove("pull", "layer:"+img.ID) if img.Parent != "" { if !s.graph.Exists(img.Parent) { if err := s.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil { return err } } } if err := s.graph.Register(img, layer); err != nil { return err } } logrus.Debugf("Completed processing %s", address) return nil }
func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error { if err := eng.Job("image_get", address).Run(); err != nil { log.Debugf("Loading %s", address) imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) if err != nil { log.Debugf("Error reading json", err) return err } layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar")) if err != nil { log.Debugf("Error reading embedded tar", err) return err } img, err := image.NewImgJSON(imageJson) if err != nil { log.Debugf("Error unmarshalling json", err) return err } if err := utils.ValidateID(img.ID); err != nil { log.Debugf("Error validating ID: %s", err) return err } if img.Parent != "" { if !s.graph.Exists(img.Parent) { if err := s.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil { return err } } } if err := s.graph.Register(img, layer); err != nil { return err } } log.Debugf("Completed processing %s", address) return nil }
func LoadImage(root string) (*Image, error) { // Open the JSON file to decode by streaming jsonSource, err := os.Open(jsonPath(root)) if err != nil { return nil, err } defer jsonSource.Close() img := &Image{} dec := json.NewDecoder(jsonSource) // Decode the JSON data if err := dec.Decode(img); err != nil { return nil, err } if err := utils.ValidateID(img.ID); err != nil { return nil, err } if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil { if !os.IsNotExist(err) { return nil, err } // If the layersize file does not exist then set the size to a negative number // because a layer size of 0 (zero) is valid img.Size = -1 } else { // Using Atoi here instead would temporarily convert the size to a machine // dependent integer type, which causes images larger than 2^31 bytes to // display negative sizes on 32-bit machines: size, err := strconv.ParseInt(string(buf), 10, 64) if err != nil { return nil, err } img.Size = int64(size) } return img, nil }