// Execute the scratch-n-push func (s *DockerScratchPushStep) Execute(ctx context.Context, sess *core.Session) (int, error) { // This is clearly only relevant to docker so we're going to dig into the // transport internals a little bit to get the container ID dt := sess.Transport().(*DockerTransport) containerID := dt.containerID _, err := s.CollectArtifact(containerID) if err != nil { return -1, err } // layer.tar has an extra folder in it so we have to strip it :/ artifactReader, err := os.Open(s.options.HostPath("layer.tar")) if err != nil { return -1, err } defer artifactReader.Close() layerFile, err := os.OpenFile(s.options.HostPath("real_layer.tar"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return -1, err } defer layerFile.Close() dgst := digest.Canonical.New() mwriter := io.MultiWriter(layerFile, dgst.Hash()) tr := tar.NewReader(artifactReader) tw := tar.NewWriter(mwriter) for { hdr, err := tr.Next() if err == io.EOF { // finished the tarball break } if err != nil { return -1, err } // Skip the base dir if hdr.Name == "./" { continue } if strings.HasPrefix(hdr.Name, "output/") { hdr.Name = hdr.Name[len("output/"):] } else if strings.HasPrefix(hdr.Name, "source/") { hdr.Name = hdr.Name[len("source/"):] } if len(hdr.Name) == 0 { continue } tw.WriteHeader(hdr) _, err = io.Copy(tw, tr) if err != nil { return -1, err } } digest := dgst.Digest() config := &container.Config{ Cmd: s.cmd, Entrypoint: s.entrypoint, Hostname: containerID[:16], WorkingDir: s.workingDir, Volumes: s.volumes, ExposedPorts: tranformPorts(s.ports), } // Make the JSON file we need t := time.Now() base := image.V1Image{ Architecture: "amd64", Container: containerID, ContainerConfig: container.Config{ Hostname: containerID[:16], }, DockerVersion: "1.10", Created: t, OS: "linux", Config: config, } imageJSON := image.Image{ V1Image: base, History: []image.History{image.History{Created: t}}, RootFS: &image.RootFS{ Type: "layers", DiffIDs: []layer.DiffID{layer.DiffID(digest)}, }, } js, err := imageJSON.MarshalJSON() if err != nil { return -1, err } hash := sha256.New() hash.Write(js) layerID := hex.EncodeToString(hash.Sum(nil)) err = os.MkdirAll(s.options.HostPath("scratch", layerID), 0755) if err != nil { return -1, err } layerFile.Close() err = os.Rename(layerFile.Name(), s.options.HostPath("scratch", layerID, "layer.tar")) if err != nil { return -1, err } defer os.RemoveAll(s.options.HostPath("scratch")) // VERSION file versionFile, err := os.OpenFile(s.options.HostPath("scratch", layerID, "VERSION"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return -1, err } defer versionFile.Close() _, err = versionFile.Write([]byte("1.0")) if err != nil { return -1, err } err = versionFile.Sync() if err != nil { return -1, err } // json file jsonFile, err := os.OpenFile(s.options.HostPath("scratch", layerID, "json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return -1, err } defer jsonFile.Close() _, err = jsonFile.Write(js) if err != nil { return -1, err } err = jsonFile.Sync() if err != nil { return -1, err } // repositories file repositoriesFile, err := os.OpenFile(s.options.HostPath("scratch", "repositories"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return -1, err } defer repositoriesFile.Close() _, err = repositoriesFile.Write([]byte(fmt.Sprintf(`{"%s":{`, s.authenticator.Repository(s.repository)))) if err != nil { return -1, err } if len(s.tags) == 0 { s.tags = []string{"latest"} } for i, tag := range s.tags { _, err = repositoriesFile.Write([]byte(fmt.Sprintf(`"%s":"%s"`, tag, layerID))) if err != nil { return -1, err } if i != len(s.tags)-1 { _, err = repositoriesFile.Write([]byte{','}) if err != nil { return -1, err } } } _, err = repositoriesFile.Write([]byte{'}', '}'}) err = repositoriesFile.Sync() if err != nil { return -1, err } // Build our output tarball and start writing to it imageFile, err := os.Create(s.options.HostPath("scratch.tar")) if err != nil { return -1, err } defer imageFile.Close() err = util.TarPath(imageFile, s.options.HostPath("scratch")) if err != nil { return -1, err } imageFile.Close() client, err := NewDockerClient(s.dockerOptions) if err != nil { return 1, err } // Check the auth if !s.dockerOptions.DockerLocal { check, err := s.authenticator.CheckAccess(s.repository, auth.Push) if !check || err != nil { s.logger.Errorln("Not allowed to interact with this repository:", s.repository) return -1, fmt.Errorf("Not allowed to interact with this repository: %s", s.repository) } } s.repository = s.authenticator.Repository(s.repository) s.logger.WithFields(util.LogFields{ "Repository": s.repository, "Tags": s.tags, "Message": s.message, }).Debug("Scratch push to registry") // Okay, we can access it, do a docker load to import the image then push it loadFile, err := os.Open(s.options.HostPath("scratch.tar")) if err != nil { return -1, err } defer loadFile.Close() e, err := core.EmitterFromContext(ctx) if err != nil { return 1, err } err = client.LoadImage(docker.LoadImageOptions{InputStream: loadFile}) if err != nil { return 1, err } return s.tagAndPush(layerID, e, client) }
// CreateImageConfig constructs the image metadata from layers that compose the image func CreateImageConfig(images []*ImageWithMeta, manifest *Manifest) error { if len(images) == 0 { return nil } imageLayer := images[0] // the layer that represents the actual image image := docker.V1Image{} rootFS := docker.NewRootFS() history := make([]docker.History, 0, len(images)) diffIDs := make(map[string]string) var size int64 // step through layers to get command history and diffID from oldest to newest for i := len(images) - 1; i >= 0; i-- { layer := images[i] if err := json.Unmarshal([]byte(layer.meta), &image); err != nil { return fmt.Errorf("Failed to unmarshall layer history: %s", err) } h := docker.History{ Created: image.Created, Author: image.Author, CreatedBy: strings.Join(image.ContainerConfig.Cmd, " "), Comment: image.Comment, } history = append(history, h) rootFS.DiffIDs = append(rootFS.DiffIDs, dockerLayer.DiffID(layer.diffID)) diffIDs[layer.diffID] = layer.ID size += layer.size } // result is constructed without unused fields result := docker.Image{ V1Image: docker.V1Image{ Comment: image.Comment, Created: image.Created, Container: image.Container, ContainerConfig: image.ContainerConfig, DockerVersion: image.DockerVersion, Author: image.Author, Config: image.Config, Architecture: image.Architecture, OS: image.OS, }, RootFS: rootFS, History: history, } bytes, err := result.MarshalJSON() if err != nil { return fmt.Errorf("Failed to marshall image metadata: %s", err) } // calculate image ID sum := fmt.Sprintf("%x", sha256.Sum256(bytes)) log.Infof("Image ID: sha256:%s", sum) // prepare metadata result.V1Image.Parent = image.Parent result.Size = size result.V1Image.ID = imageLayer.ID metaData := metadata.ImageConfig{ V1Image: result.V1Image, ImageID: sum, // TODO: this will change when issue 1186 is // implemented -- only populate the digests when pulled by digest Digests: []string{manifest.Digest}, Tags: []string{options.tag}, Name: manifest.Name, DiffIDs: diffIDs, History: history, } blob, err := json.Marshal(metaData) if err != nil { return fmt.Errorf("Failed to marshal image metadata: %s", err) } // store metadata imageLayer.meta = string(blob) return nil }
// CreateImageConfig constructs the image metadata from layers that compose the image func (ic *ImageC) CreateImageConfig(images []*ImageWithMeta) (metadata.ImageConfig, error) { imageLayer := images[0] // the layer that represents the actual image // if we already have an imageID associated with this layerID, we don't need // to calculate imageID and can just grab the image config from the cache id := cache.RepositoryCache().GetImageID(imageLayer.ID) if image, err := cache.ImageCache().Get(id); err == nil { return *image, nil } manifest := ic.ImageManifest image := docker.V1Image{} rootFS := docker.NewRootFS() history := make([]docker.History, 0, len(images)) diffIDs := make(map[string]string) var size int64 // step through layers to get command history and diffID from oldest to newest for i := len(images) - 1; i >= 0; i-- { layer := images[i] if err := json.Unmarshal([]byte(layer.Meta), &image); err != nil { return metadata.ImageConfig{}, fmt.Errorf("Failed to unmarshall layer history: %s", err) } h := docker.History{ Created: image.Created, Author: image.Author, CreatedBy: strings.Join(image.ContainerConfig.Cmd, " "), Comment: image.Comment, } history = append(history, h) rootFS.DiffIDs = append(rootFS.DiffIDs, dockerLayer.DiffID(layer.DiffID)) diffIDs[layer.DiffID] = layer.ID size += layer.Size } // result is constructed without unused fields result := docker.Image{ V1Image: docker.V1Image{ Comment: image.Comment, Created: image.Created, Container: image.Container, ContainerConfig: image.ContainerConfig, DockerVersion: image.DockerVersion, Author: image.Author, Config: image.Config, Architecture: image.Architecture, OS: image.OS, }, RootFS: rootFS, History: history, } bytes, err := result.MarshalJSON() if err != nil { return metadata.ImageConfig{}, fmt.Errorf("Failed to marshall image metadata: %s", err) } // calculate image ID sum := fmt.Sprintf("%x", sha256.Sum256(bytes)) log.Infof("Image ID: sha256:%s", sum) // prepare metadata result.V1Image.Parent = image.Parent result.Size = size result.V1Image.ID = imageLayer.ID imageConfig := metadata.ImageConfig{ V1Image: result.V1Image, ImageID: sum, // TODO: this will change when issue 1186 is // implemented -- only populate the digests when pulled by digest Digests: []string{manifest.Digest}, Tags: []string{ic.Tag}, Name: manifest.Name, DiffIDs: diffIDs, History: history, Reference: ic.Reference, } return imageConfig, nil }