blob: 5dda217765a98e0fd5e2a84b30e1016587585bbf [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001# Copyright 2015 The Bazel Authors. All rights reserved.
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +00002#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""This tool creates a docker image from a layer and the various metadata."""
15
Sam Guymer6e3e48e2016-06-08 11:26:46 +000016import json
17import re
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +000018import sys
19import tarfile
20
Sam Guymer6e3e48e2016-06-08 11:26:46 +000021from tools.build_defs.docker import utils
Damien Martin-Guillerez80245bc2015-10-09 14:10:42 +000022from tools.build_defs.pkg import archive
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +000023from third_party.py import gflags
24
25# Hardcoded docker versions that we are claiming to be.
26DATA_FORMAT_VERSION = '1.0'
27
28gflags.DEFINE_string(
29 'output', None,
30 'The output file, mandatory')
31gflags.MarkFlagAsRequired('output')
32
Sam Guymer6e3e48e2016-06-08 11:26:46 +000033gflags.DEFINE_multistring(
34 'layer', [],
35 'Layer tar files and their identifiers that make up this image')
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +000036
37gflags.DEFINE_string(
38 'id', None,
39 'The hex identifier of this image (hexstring or @filename), mandatory.')
40gflags.MarkFlagAsRequired('id')
41
Sam Guymer6e3e48e2016-06-08 11:26:46 +000042gflags.DEFINE_string('config', None,
43 'The JSON configuration file for this image, mandatory.')
44gflags.MarkFlagAsRequired('config')
45
46gflags.DEFINE_string('base', None, 'The base image file for this image.')
47
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +000048gflags.DEFINE_string(
Sam Guymer6e3e48e2016-06-08 11:26:46 +000049 'legacy_id', None,
50 'The legacy hex identifier of this layer (hexstring or @filename).')
51
52gflags.DEFINE_string('metadata', None,
53 'The legacy JSON metadata file for this layer.')
54
55gflags.DEFINE_string('legacy_base', None,
56 'The legacy base image file for this image.')
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +000057
58gflags.DEFINE_string(
59 'repository', None,
60 'The name of the repository to add this image.')
61
62gflags.DEFINE_string(
63 'name', None,
64 'The symbolic name of this image.')
65
Sam Guymer6e3e48e2016-06-08 11:26:46 +000066gflags.DEFINE_multistring('tag', None,
67 'The repository tags to apply to the image')
68
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +000069FLAGS = gflags.FLAGS
70
71
72def _base_name_filter(name):
73 """Do not add multiple times 'top' and 'repositories' when merging images."""
Sam Guymer6e3e48e2016-06-08 11:26:46 +000074 filter_names = ['top', 'repositories', 'manifest.json']
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +000075 return all([not name.endswith(s) for s in filter_names])
76
77
Sam Guymer6e3e48e2016-06-08 11:26:46 +000078def create_image(output,
79 identifier,
80 layers,
81 config,
82 tags=None,
83 base=None,
84 legacy_base=None,
85 metadata_id=None,
86 metadata=None,
87 name=None,
88 repository=None):
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +000089 """Creates a Docker image.
90
91 Args:
92 output: the name of the docker image file to create.
Sam Guymer6e3e48e2016-06-08 11:26:46 +000093 identifier: the identifier for this image (sha256 of the metadata).
94 layers: the layer content (a sha256 and a tar file).
95 config: the configuration file for the image.
96 tags: tags that apply to this image.
97 base: a base layer (optional) to build on top of.
98 legacy_base: a base layer (optional) to build on top of.
99 metadata_id: the identifier of the top layer for this image.
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +0000100 metadata: the json metadata file for the top layer.
101 name: symbolic name for this docker image.
102 repository: repository name for this docker image.
103 """
104 tar = archive.TarFileWriter(output)
Sam Guymer6e3e48e2016-06-08 11:26:46 +0000105
106 # add the image config referenced by the Config section in the manifest
107 # the name can be anything but docker uses the format below
108 config_file_name = identifier + '.json'
109 tar.add_file(config_file_name, file_content=config)
110
111 layer_file_names = []
112
113 if metadata_id:
114 # Write our id to 'top' as we are now the topmost layer.
115 tar.add_file('top', content=metadata_id)
116
117 # Each layer is encoded as a directory in the larger tarball of the form:
118 # {id}\
119 # layer.tar
120 # VERSION
121 # json
122 # Create the directory for us to now fill in.
123 tar.add_file(metadata_id + '/', tarfile.DIRTYPE)
124 # VERSION generally seems to contain 1.0, not entirely sure
125 # what the point of this is.
126 tar.add_file(metadata_id + '/VERSION', content=DATA_FORMAT_VERSION)
127 # Add the layer file
128 layer_file_name = metadata_id + '/layer.tar'
129 layer_file_names.append(layer_file_name)
130 tar.add_file(layer_file_name, file_content=layers[0]['layer'])
131 # Now the json metadata
132 tar.add_file(metadata_id + '/json', file_content=metadata)
133
134 # Merge the base if any
135 if legacy_base:
136 tar.add_tar(legacy_base, name_filter=_base_name_filter)
137 else:
138 for layer in layers:
139 # layers can be called anything, so just name them by their sha256
140 layer_file_name = identifier + '/' + layer['name'] + '.tar'
141 layer_file_names.append(layer_file_name)
142 tar.add_file(layer_file_name, file_content=layer['layer'])
143
144 base_layer_file_names = []
145 parent = None
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +0000146 if base:
Sam Guymer6e3e48e2016-06-08 11:26:46 +0000147 latest_item = utils.GetLatestManifestFromTar(base)
148 if latest_item:
149 base_layer_file_names = latest_item.get('Layers', [])
150 config_file = latest_item['Config']
151 parent_search = re.search('^(.+)\\.json$', config_file)
152 if parent_search:
153 parent = parent_search.group(1)
154
155 manifest_item = {
156 'Config': config_file_name,
157 'Layers': base_layer_file_names + layer_file_names,
158 'RepoTags': tags or []
159 }
160 if parent:
161 manifest_item['Parent'] = 'sha256:' + parent
162
163 manifest = [manifest_item]
164
165 manifest_content = json.dumps(manifest, sort_keys=True)
166 tar.add_file('manifest.json', content=manifest_content)
167
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +0000168 # In addition to N layers of the form described above, there is
169 # a single file at the top of the image called repositories.
170 # This file contains a JSON blob of the form:
171 # {
172 # 'repo':{
173 # 'tag-name': 'top-most layer hex',
174 # ...
175 # },
176 # ...
177 # }
178 if repository:
179 tar.add_file('repositories', content='\n'.join([
180 '{',
181 ' "%s": {' % repository,
182 ' "%s": "%s"' % (name, identifier),
183 ' }',
184 '}']))
185
186
187# Main program to create a docker image. It expect to be run with:
188# create_image --output=output_file \
189# --id=@identifier \
190# [--base=base] \
Sam Guymer6e3e48e2016-06-08 11:26:46 +0000191# --layer=@identifier=layer.tar \
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +0000192# --metadata=metadata.json \
Sam Guymer6e3e48e2016-06-08 11:26:46 +0000193# --name=myname --repository=repositoryName \
194# --tag=repo/image:tag
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +0000195# See the gflags declaration about the flags argument details.
196def main(unused_argv):
Sam Guymer6e3e48e2016-06-08 11:26:46 +0000197 identifier = utils.ExtractValue(FLAGS.id)
198 legacy_id = utils.ExtractValue(FLAGS.legacy_id)
199
200 layers = []
201 for kv in FLAGS.layer:
202 (k, v) = kv.split('=', 1)
203 layers.append({
204 'name': utils.ExtractValue(k),
205 'layer': v,
206 })
207
208 create_image(FLAGS.output, identifier, layers, FLAGS.config, FLAGS.tag,
209 FLAGS.base, FLAGS.legacy_base, legacy_id, FLAGS.metadata,
Damien Martin-Guillerez5f258912015-07-27 08:27:53 +0000210 FLAGS.name, FLAGS.repository)
211
212if __name__ == '__main__':
213 main(FLAGS(sys.argv))