Skip to content

Commit

Permalink
Squash implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
iaguis committed Jan 29, 2015
1 parent 7f4a1b1 commit 0cb48b8
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 9 deletions.
179 changes: 175 additions & 4 deletions lib/docker2aci.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package docker2aci

import (
"archive/tar"
"bytes"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -46,6 +47,12 @@ type DockerURL struct {
Tag string
}

type SquashAcc struct {
OutWriter *tar.Writer
Manifests []schema.ImageManifest
Filelist []string
}

const (
defaultIndex = "index.docker.io"
defaultTag = "latest"
Expand All @@ -58,11 +65,14 @@ const (
{docker registry URL}/{image name}:{tag}
It then gets all the layers of the requested image, converts each of them
to ACI and places the resulting files in outputDir.
It then gets all the layers of the requested image and converts each of
them to ACI.
If the squash flag is true, it squashes all the layers in one file and
places this file in outputDir; if it is false, it places every layer in its
own ACI in outputDir.
It returns the list of generated ACI paths.
*/
func Convert(dockerURL string, outputDir string) ([]string, error) {
func Convert(dockerURL string, squash bool, outputDir string) ([]string, error) {
parsedURL := parseDockerURL(dockerURL)

repoData, err := getRepoData(parsedURL.IndexURL, parsedURL.ImageName)
Expand All @@ -84,9 +94,18 @@ func Convert(dockerURL string, outputDir string) ([]string, error) {
return nil, err
}

layersOutputDir := "."
if squash {
layersOutputDir, err = ioutil.TempDir("", "docker2aci-")
if err != nil {
return nil, fmt.Errorf("Error creating dir: %v", err)
}
defer os.RemoveAll(layersOutputDir)
}

var aciLayerPaths []string
for _, layerID := range ancestry {
aciPath, err := buildACI(layerID, repoData, parsedURL, outputDir)
aciPath, err := buildACI(layerID, repoData, parsedURL, layersOutputDir)
if err != nil {
fmt.Fprintf(os.Stderr, "Error building layer: %v\n", err)
return nil, err
Expand All @@ -95,6 +114,20 @@ func Convert(dockerURL string, outputDir string) ([]string, error) {
aciLayerPaths = append(aciLayerPaths, aciPath)
}

if squash {
squashedFilename := strings.Replace(parsedURL.ImageName, "/", "-", -1)
if parsedURL.Tag != "" {
squashedFilename += "-" + parsedURL.Tag
}
squashedFilename += ".aci"
squashedImagePath := path.Join(outputDir, squashedFilename)

if err := squashLayers(aciLayerPaths, squashedImagePath); err != nil {
fmt.Fprintf(os.Stderr, "Error squashing image: %v\n", err)
}
aciLayerPaths = []string{squashedImagePath}
}

return aciLayerPaths, nil
}

Expand Down Expand Up @@ -513,6 +546,144 @@ func writeACI(layer io.Reader, manifest *schema.ImageManifest, output string) er
return nil
}

func squashLayers(layers []string, squashedImagePath string) error {
squashedImageFile, err := os.Create(squashedImagePath)
if err != nil {
return err
}
defer squashedImageFile.Close()

squashAcc := new(SquashAcc)
squashAcc.OutWriter = tar.NewWriter(squashedImageFile)
for _, aciPath := range layers {
squashAcc, err = reduceACIs(squashAcc, aciPath)
if err != nil {
return err
}
}

finalManifest := mergeManifests(squashAcc.Manifests)

finalManifestBytes, err := json.Marshal(finalManifest)
if err != nil {
return err
}

// Write manifest
hdr := &tar.Header{
Name: "manifest",
Mode: 0600,
Size: int64(len(finalManifestBytes)),
}
if err := squashAcc.OutWriter.WriteHeader(hdr); err != nil {
return err
}
if _, err := squashAcc.OutWriter.Write(finalManifestBytes); err != nil {
return err
}

squashAcc.OutWriter.Close()

return nil
}

func reduceACIs(squashAcc *SquashAcc, currentPath string) (*SquashAcc, error) {
currentFile, err := os.Open(currentPath)
if err != nil {
return nil, err
}
defer currentFile.Close()

_, manCurBuf, err := getFileInTar("manifest", currentFile)
if err != nil {
return nil, err
}

manCurBytes := manCurBuf.Bytes()

manifestCur := schema.ImageManifest{}
err = json.Unmarshal(manCurBytes, &manifestCur)
if err != nil {
return nil, err
}

squashAcc.Manifests = append(squashAcc.Manifests, manifestCur)

tr := tar.NewReader(currentFile)
for {
hdr, err := tr.Next()
if err == io.EOF {
// end of tar archive
break
}
if err != nil {
return nil, fmt.Errorf("Error reading layer tar entry: %v", err)
}

if hdr.Name == "manifest" {
continue
}

if !in(squashAcc.Filelist, hdr.Name) {
squashAcc.Filelist = append(squashAcc.Filelist, hdr.Name)

if err := squashAcc.OutWriter.WriteHeader(hdr); err != nil {
return nil, fmt.Errorf("Error writing header: %v", err)
}
if _, err := io.Copy(squashAcc.OutWriter, tr); err != nil {
return nil, fmt.Errorf("Error copying file into the tar out: %v", err)
}
}
}

return squashAcc, nil
}

func getFileInTar(fileName string, tarFile *os.File) (*tar.Header, *bytes.Buffer, error) {
defer func() {
tarFile.Seek(0, 0)
}()

tr := tar.NewReader(tarFile)

for {
hdr, err := tr.Next()
if err == io.EOF {
// end of tar archive
break
}
if err != nil {
return nil, nil, err
}

if hdr.Name == fileName {
var buffer = new(bytes.Buffer)
_, err := buffer.ReadFrom(tr)
if err != nil {
return nil, nil, err
}

return hdr, buffer, nil
}
}

return nil, nil, nil
}

func in(list []string, el string) bool {
for _, x := range list {
if el == x {
return true
}
}
return false
}

func mergeManifests(manifests []schema.ImageManifest) *schema.ImageManifest {
// TODO implement me
return &manifests[0]
}

func setAuthToken(req *http.Request, token []string) {
if req.Header.Get("Authorization") == "" {
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
Expand Down
15 changes: 10 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ const (
rocketDir = "/var/lib/rkt"
)

var flagImport = flag.Bool("import", false, "Import ACI images to the rocket store")
var (
flagImport = flag.Bool("import", false, "Import ACI images to the rocket store")
flagNoSquash = flag.Bool("nosquash", false, "Don't Squash layers and output every layer as ACI")
)

func runDocker2ACI(arg string, flagImport bool, flagNoSquash bool) error {
squash := !flagNoSquash

func runDocker2ACI(arg string, flagImport bool) error {
aciLayerPaths, err := docker2aci.Convert(arg, ".")
aciLayerPaths, err := docker2aci.Convert(arg, squash, ".")
if err != nil {
fmt.Fprintf(os.Stderr, "Conversion error: %v\n", err)
return err
Expand Down Expand Up @@ -78,11 +83,11 @@ func main() {
args := flag.Args()

if len(args) != 1 {
fmt.Println("Usage: docker2aci [--import] [REGISTRYURL/]IMAGE_NAME[:TAG]")
fmt.Println("Usage: docker2aci [--import] [--nosquash] [REGISTRYURL/]IMAGE_NAME[:TAG]")
return
}

if err := runDocker2ACI(args[0], *flagImport); err != nil {
if err := runDocker2ACI(args[0], *flagImport, *flagNoSquash); err != nil {
os.Exit(1)
}
}

0 comments on commit 0cb48b8

Please sign in to comment.