-
-
Notifications
You must be signed in to change notification settings - Fork 6
/
docker.go
173 lines (159 loc) · 4.12 KB
/
docker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package main
import (
"archive/tar"
"encoding/json"
"errors"
"io/ioutil"
slashpath "path"
"regexp"
)
// An item in the json array in the docker image's manifest.json.
type DockerManifestItem struct {
Config string
RepoTags []string
Layers []string
}
// Information we need about a docker image.
type DockerImage struct {
// The decoded layers of the docker image. The keys are the paths to
// the layers' tarballs within the image.
Layers map[string]Tree
// The contents of the docker image's manifest.json
Manifest []DockerManifestItem
}
// regular expression matching paths to layers inside the docker image.
var layerRegexp = regexp.MustCompile("^[0-9a-f]{64}/layer\\.tar$")
// Convert a tarball into a map from (full) paths to Files. Skips any file
// that is not a symlink, directory, or regular file.
//
// Note that the result is *not* a valid Tree; Trees are hierarchical,
// this is just a flat map from full paths to Files. Files which are
// directories do not have their contents populated.
func buildAbsFileMap(r *tar.Reader) (map[string]*File, error) {
it := iterTar(r)
ret := map[string]*File{}
for it.Next() {
hdr := it.Cur()
name := slashpath.Clean(hdr.Name)
switch hdr.Typeflag {
case tar.TypeSymlink:
ret[name] = &File{
target: hdr.Linkname,
}
case tar.TypeDir:
ret[name] = &File{
kids: Tree{},
}
case tar.TypeReg:
data, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
ret[name] = &File{
data: data,
// We treat an executable bit for anyone as an
// executable.
isExe: hdr.FileInfo().Mode().Perm()&0111 != 0,
}
}
}
return ret, it.Err()
}
// Insert the file at absPath into the .kids attribute of its parent directory.
// Adds the parent directory to abs if it does not already exist. An error is
// returned if abs already contains a file at absPath's parent that is not a
// directory.
//
// `abs` should be the return value from buildAbsFIleMap
func addRelFile(abs map[string]*File, absPath string) error {
if absPath == "." {
// The root of the file system (see the documentation
// for path.Clean). There is no parent directory, so
// just return.
return nil
}
file := abs[absPath]
if file == nil {
// empty directory
file = &File{
kids: Tree{},
}
abs[absPath] = file
}
dirPath, relPath := slashpath.Split(absPath)
dirPath = slashpath.Clean(dirPath)
relPath = slashpath.Clean(relPath)
if dirPath != "." {
// make sure all our ancestors are present.
if err := addRelFile(abs, dirPath); err != nil {
return err
}
}
dir := abs[dirPath]
if dir.kids == nil {
return errors.New("Conflict: non-directory has child nodes")
}
dir.kids[relPath] = file
return nil
}
// Build a tree of files based on abs, which should be the return value from
// buildAbsFileMap.
func buildTree(abs map[string]*File) (Tree, error) {
root := &File{
kids: Tree{},
}
abs["."] = root
for absPath := range abs {
err := addRelFile(abs, absPath)
if err != nil {
return nil, err
}
}
return root.kids, nil
}
// Unmarshal a layer tarball from within a docker image into a Tree.
func readLayer(r *tar.Reader) (Tree, error) {
absMap, err := buildAbsFileMap(r)
if err != nil {
return nil, err
}
return buildTree(absMap)
}
// Unmarshal a docker image from a tarball.
func readDockerImage(r *tar.Reader) (*DockerImage, error) {
ret := &DockerImage{
Layers: map[string]Tree{},
Manifest: []DockerManifestItem{},
}
it := iterTar(r)
for it.Next() {
cur := it.Cur()
if cur.Name == "manifest.json" {
if err := json.NewDecoder(r).Decode(&ret.Manifest); err != nil {
return nil, err
}
} else {
if !layerRegexp.Match([]byte(cur.Name)) {
continue
}
layer, err := readLayer(tar.NewReader(r))
if err != nil {
return nil, err
}
ret.Layers[cur.Name] = layer
}
}
return ret, it.Err()
}
// Convert the docker image into a tree for the entire filesystem (merging
// the individual layers).
func (di *DockerImage) toTree() (Tree, error) {
tree := Tree{}
for _, manifest := range di.Manifest {
for _, layer := range manifest.Layers {
tree.Merge(di.Layers[layer])
}
removeWhiteout(tree)
}
return tree, nil
}