Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 8d593c57cd01921568460f28edfcba5a41e312b5 (plain) (blame)
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
/*******************************************************************************
 * Copyright (c) 2014 Red Hat.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat - Initial Contribution
 *******************************************************************************/
package org.eclipse.linuxtools.internal.docker.core;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.linuxtools.docker.core.IDockerConnection;
import org.eclipse.linuxtools.docker.core.IDockerImage;

public class DockerImage implements IDockerImage, IAdaptable {

	/** The 'latest' tag. */
	public static final String LATEST_TAG = "latest"; //$NON-NLS-1$

	private static final String REGISTRY_HOST = "[a-zA-Z0-9]+([\\._-][a-zA-Z0-9]+)*"; //$NON-NLS-1$
	private static final String REGISTRY_PORT = "[0-9]+"; //$NON-NLS-1$
	private static final String REPOSITORY = "[a-z0-9]+([\\._-][a-z0-9]+)*"; //$NON-NLS-1$
	private static final String NAME = "[a-z0-9]+([\\._-][a-z0-9]+)*"; //$NON-NLS-1$
	private static final String TAG = "[a-zA-Z0-9]+([\\._-][a-zA-Z0-9]+)*"; //$NON-NLS-1$

	/** the image name pattern. */
	public static final Pattern imageNamePattern = Pattern.compile("(" //$NON-NLS-1$
			+ REGISTRY_HOST + "\\:" + REGISTRY_PORT + "/)?" //$NON-NLS-1$ //$NON-NLS-2$
			+ "((?<repository>" + REPOSITORY + "(/" + REPOSITORY + ")?)/)?" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			+ "(?<name>" + NAME + ")" //$NON-NLS-1$ //$NON-NLS-2$
			+ "(\\:(?<tag>" + TAG + "))?"); //$NON-NLS-1$ //$NON-NLS-2$

	// SimpleDateFormat is not thread-safe, so give one to each thread
	private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>() {
		@Override
		protected SimpleDateFormat initialValue() {
			return new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
		}
	};

	private final DockerConnection parent;
	private final String created;
	private final String createdDate;
	private final String id;
	private final String shortId;
	private final String parentId;
	private final List<String> repoTags;
	private final String repo;
	private final List<String> tags;
	private final Long size;
	private final Long virtualSize;

	private final boolean intermediateImage;
	private final boolean danglingImage;

	public DockerImage(final DockerConnection parent,
			@Deprecated final List<String> repoTags, final String repo,
			final List<String> tags, final String id, final String parentId,
			final String created, final Long size, final Long virtualSize,
			final boolean intermediateImage, final boolean danglingImage) {
		this.parent = parent;
		this.repoTags = repoTags;
		this.repo = repo;
		this.tags = tags;
		this.id = id;
		this.shortId = getShortId(id);
		this.parentId = parentId;
		this.created = created;
		this.createdDate = created != null
				? formatter.get()
						.format(new Date(
								Long.valueOf(created).longValue() * 1000))
				: null;
		this.size = size;
		this.virtualSize = virtualSize;
		this.intermediateImage = intermediateImage;
		this.danglingImage = danglingImage;
	}

	/**
	 * @param id
	 *            the complete {@link IDockerImage} id
	 * @return the short id, excluding the {@code sha256:} prefix if present.
	 */
	private static String getShortId(final String id) {
		if (id.startsWith("sha256:")) {
			return getShortId(id.substring("sha256:".length()));
		}
		if (id.length() > 12) {
			return id.substring(0, 12);
		} else {
			return id;
		}
	}

	/**
	 * Extracts the org/repo and all the associated tags from the given
	 * {@code repoTags}, assuming that the given repoTags elements have the
	 * following format: {@code [org/]repo[:tag]}. Tags are sorted by their
	 * natural order.
	 * 
	 * @param repoTags
	 *            the list of repo/tags to analyze
	 * @return the tags indexed by org/repo
	 */
	public static Map<String, List<String>> extractTagsByRepo(
			final List<String> repoTags) {
		final Map<String, List<String>> results = new HashMap<>();
		for (String entry : repoTags) {
			final int indexOfColonChar = entry.lastIndexOf(':');
			final String repo = (indexOfColonChar > -1)
					? entry.substring(0, indexOfColonChar) : entry;
			if (!results.containsKey(repo)) {
				results.put(repo, new ArrayList<String>());
			}
			if (indexOfColonChar > -1) {
				results.get(repo).add(entry.substring(indexOfColonChar + 1));
			}
		}
		// now sort the tags
		for (Entry<String, List<String>> entry : results.entrySet()) {
			Collections.sort(entry.getValue());
		}
		return results;
	}

	/**
	 * Extracts the list of tags in the given repo/tags, assuming that the given
	 * repoTags elements have the following format: {@code [org/]repo[:tag]}.
	 * 
	 * @param repoTags
	 *            the repo/tags list to analyze
	 * @return the list of tags or empty list if none was found.
	 */
	public static List<String> extractTags(final List<String> repoTags) {
		if (repoTags.isEmpty()) {
			return Collections.emptyList();
		}
		final List<String> tags = new ArrayList<>();
		for (String repoTag : repoTags) {
			final int indexOfColonChar = repoTag.lastIndexOf(':');
			if (indexOfColonChar == -1) {
				continue;
			}
			final String tag = repoTag.substring(indexOfColonChar + 1);
			tags.add(tag);
		}
		return tags;
	}

	/**
	 * Extracts the tag in the given repo/tag, assuming that the given repoTag
	 * element has the following format: {@code [org/]repo[:tag]}.
	 * 
	 * @param repoTag
	 *            the repo/tag to analyze
	 * @return the tag or null if none was found.
	 */
	public static String extractRepo(final String repoTag) {
		if (repoTag == null) {
			return null;
		}
		final int indexOfColonChar = repoTag.lastIndexOf(':');
		if (indexOfColonChar == -1) {
			return repoTag;
		}
		return repoTag.substring(0, indexOfColonChar);
	}

	/**
	 * Extracts the tag in the given repo/tag, assuming that the given repoTag
	 * element has the following format: {@code [org/]repo[:tag]}
	 * 
	 * @param repoTag
	 *            the repo/tag to analyze
	 * @return the tag or <code>null</code> if none was found.
	 */
	public static String extractTag(final String repoTag) {
		if (repoTag == null) {
			return null;
		}
		final int indexOfColonChar = repoTag.lastIndexOf(':');
		if (indexOfColonChar == -1) {
			return null;
		}
		return repoTag.substring(indexOfColonChar + 1);
	}

	/**
	 * Duplicates the given {@code image} into as many {@link IDockerImage} has
	 * it has distinct repoTags entries
	 * 
	 * @param image
	 *            the source image
	 * @return a {@link Stream} of duplicate {@link IDockerImage}
	 */
	public static Stream<IDockerImage> duplicateImageByRepo(
			final IDockerImage image) {
		return DockerImage.extractTagsByRepo(image.repoTags()).entrySet()
				.stream()
				.map(entry -> new DockerImage(
						(DockerConnection) image.getConnection(),
						image.repoTags(), entry.getKey(), entry.getValue(),
						image.id(), image.parentId(), image.created(),
						image.size(), image.virtualSize(),
						image.isIntermediateImage(), image.isDangling()));
	}

	@Override
	public DockerConnection getConnection() {
		return parent;
	}

	@Override
	public List<String> repoTags() {
		return repoTags;
	}

	@Override
	public String repo() {
		return repo;
	}

	@Override
	public List<String> tags() {
		return tags;
	}

	@Override
	public String id() {
		return this.id;
	}

	/**
	 * @return the short image id, ie, the first 12 figures, excluding the
	 *         <code>sha256:</code> prefix.
	 */
	// TODO: add to the API in version 3.0.0
	public String shortId() {
		return this.shortId;
	}

	@Override
	public String parentId() {
		return parentId;
	}

	@Override
	public String created() {
		return created;
	}

	@Override
	public String createdDate() {
		return createdDate;
	}

	@Override
	public Long size() {
		return size;
	}

	@Override
	public Long virtualSize() {
		return virtualSize;
	}

	@Override
	public boolean isDangling() {
		return danglingImage;
	}

	@Override
	public boolean isIntermediateImage() {
		return intermediateImage;
	}

	@Override
	public String toString() {
		return "Image: id=" + id() + "\n  parentId=" + parentId()
				+ "\n  created=" + created() + "\n  repo=" + repo()
				+ "\n  tags=" + tags() + "\n  size=" + size()
				+ "\n  virtualSize=" + virtualSize();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((repo == null) ? 0 : repo.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final DockerImage other = (DockerImage) obj;
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		if (repo == null) {
			if (other.repo != null) {
				return false;
			}
		} else if (!repo.equals(other.repo)) {
			return false;
		}

		return true;
	}

	/**
	 * Appends the <code>latest</code> tag to the given {@code imageName}
	 * 
	 * @param imageName
	 *            the image name to check
	 * @return the image name with the <code>latest</code> tag if none was set
	 */
	public static String setDefaultTagIfMissing(final String imageName) {
		if (DockerImage.extractTag(imageName) == null) {
			return imageName + ':' + LATEST_TAG;
		}
		return imageName;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> adapter) {
		if (adapter.equals(IDockerConnection.class))
			return (T) this.parent;
		return null;
	}

}

Back to the top