{"action":"created","comment":{"url":"https://api.github.com/repos/sonatype/nexus-oss/pulls/comments/35689189","id":35689189,"diff_hunk":"@@ -0,0 +1,669 @@\n+/*\n+ * Sonatype Nexus (TM) Open Source Version\n+ * Copyright (c) 2008-present Sonatype, Inc.\n+ * All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.\n+ *\n+ * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,\n+ * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.\n+ *\n+ * Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. \"Sonatype\" and \"Sonatype Nexus\" are trademarks\n+ * of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the\n+ * Eclipse Foundation. All other trademarks are the property of their respective owners.\n+ */\n+package org.sonatype.nexus.repository.docker.internal;\n+\n+import java.io.ByteArrayInputStream;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.OutputStream;\n+import java.nio.charset.Charset;\n+import java.util.ArrayList;\n+import java.util.HashMap;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Map.Entry;\n+import java.util.Objects;\n+import java.util.regex.Pattern;\n+\n+import javax.annotation.Nullable;\n+import javax.inject.Named;\n+\n+import org.sonatype.nexus.blobstore.api.Blob;\n+import org.sonatype.nexus.common.collect.AttributesMap;\n+import org.sonatype.nexus.common.collect.NestedAttributesMap;\n+import org.sonatype.nexus.common.hash.HashAlgorithm;\n+import org.sonatype.nexus.common.io.TempStreamSupplier;\n+import org.sonatype.nexus.repository.FacetSupport;\n+import org.sonatype.nexus.repository.InvalidContentException;\n+import org.sonatype.nexus.repository.docker.DockerFacet;\n+import org.sonatype.nexus.repository.http.HttpStatus;\n+import org.sonatype.nexus.repository.storage.Asset;\n+import org.sonatype.nexus.repository.storage.Bucket;\n+import org.sonatype.nexus.repository.storage.Component;\n+import org.sonatype.nexus.repository.storage.ComponentQuery;\n+import org.sonatype.nexus.repository.storage.ComponentQuery.Builder;\n+import org.sonatype.nexus.repository.storage.StorageFacet;\n+import org.sonatype.nexus.repository.storage.StorageTx;\n+import org.sonatype.nexus.repository.util.TypeTokens;\n+import org.sonatype.nexus.repository.view.Content;\n+import org.sonatype.nexus.repository.view.ContentTypes;\n+import org.sonatype.nexus.repository.view.Payload;\n+import org.sonatype.nexus.repository.view.payloads.BlobPayload;\n+import org.sonatype.nexus.repository.view.payloads.StringPayload;\n+import org.sonatype.nexus.transaction.Transactional;\n+import org.sonatype.nexus.transaction.UnitOfWork;\n+\n+import com.fasterxml.jackson.core.type.TypeReference;\n+import com.fasterxml.jackson.databind.ObjectMapper;\n+import com.google.common.base.Charsets;\n+import com.google.common.base.Supplier;\n+import com.google.common.collect.Lists;\n+import com.google.common.hash.Funnels;\n+import com.google.common.hash.Hasher;\n+import com.google.common.hash.Hashing;\n+import com.google.common.io.ByteStreams;\n+import com.orientechnologies.common.concur.ONeedRetryException;\n+import com.orientechnologies.orient.core.storage.ORecordDuplicatedException;\n+import org.apache.commons.lang.StringUtils;\n+\n+import static java.util.Collections.singletonList;\n+import static org.sonatype.nexus.common.hash.HashAlgorithm.SHA256;\n+import static org.sonatype.nexus.repository.docker.internal.DockerAttributes.*;\n+import static org.sonatype.nexus.repository.storage.StorageFacet.P_NAME;\n+import static org.sonatype.nexus.repository.storage.StorageFacet.P_PATH;\n+import static org.sonatype.nexus.repository.storage.StorageFacet.P_VERSION;\n+\n+/**\n+ * {@link DockerFacet} implementation.\n+ *\n+ * @since 3.0\n+ */\n+@Named\n+public class DockerFacetImpl\n+    extends FacetSupport\n+    implements DockerFacet\n+{\n+\n+  private final static List<HashAlgorithm> hashAlgorithms = Lists.newArrayList(SHA256);\n+\n+  private final static ObjectMapper mapper = new ObjectMapper();\n+\n+  private final static Pattern validLayerId = Pattern.compile(\"^([a-f0-9]{16}|[a-f0-9]{64})$\");\n+\n+  private final static Pattern validTag = Pattern.compile(\"^[\\\\w][\\\\w.-]{0,127}$\");\n+\n+\n+  @Override\n+  @Transactional(retryOn = IllegalStateException.class)\n+  public Payload getImagesIndex(final String name) throws IOException {\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    String imagesIndexAssetName = name + \"/images\";\n+    Bucket bucket = tx.getBucket();\n+    // check that we have images index asset\n+    Asset asset = findAsset(tx, bucket, imagesIndexAssetName);\n+    if (asset == null) {\n+      throw new V1Exception(\"images not found\", HttpStatus.NOT_FOUND);\n+    }\n+\n+    return marshall(asset, tx.requireBlob(asset.requireBlobRef()));\n+  }\n+\n+  @Override\n+  public void putImagesIndex(final String name, final Payload content) throws IOException {\n+    try (TempStreamSupplier streamSupplier = new TempStreamSupplier(content.openInputStream())) {\n+      readImagesIndex(streamSupplier);\n+      doPutImagesIndex(name, streamSupplier, content);\n+    }\n+  }\n+\n+  @Transactional(retryOn = {ONeedRetryException.class, ORecordDuplicatedException.class})\n+  protected void doPutImagesIndex(final String name,\n+                                  final Supplier<InputStream> streamSupplier,\n+                                  final Payload payload)\n+      throws IOException\n+  {\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    InputStream indexStream;\n+\n+    String imagesIndexAssetName = name + \"/images\";\n+\n+    Bucket bucket = tx.getBucket();\n+    Asset imagesIndexAsset = findAsset(tx, bucket, imagesIndexAssetName);\n+    if (imagesIndexAsset == null) {\n+      imagesIndexAsset = tx.createAsset(bucket, getRepository().getFormat());\n+      imagesIndexAsset.name(imagesIndexAssetName);\n+      imagesIndexAsset.formatAttributes().set(P_PATH, \"/v1/repositories/\" + imagesIndexAssetName);\n+\n+      indexStream = streamSupplier.get();\n+    }\n+    else {\n+      // merge existing index with the new one\n+      List<Map<String, Object>> existingIndex = readImagesIndex(blobStreamSupplier(tx, imagesIndexAsset));\n+      List<Map<String, Object>> newIndex = readImagesIndex(streamSupplier);\n+      List<Map<String, Object>> mergedIndex = new ArrayList<>(existingIndex);\n+      for (Map<String, Object> newImage : newIndex) {\n+        boolean found = false;\n+        for (Map<String, Object> existingImage : existingIndex) {\n+          if (Objects.equals(existingImage.get(\"id\"), newImage.get(\"id\")) &&\n+              Objects.equals(existingImage.get(\"Tag\"), newImage.get(\"Tag\"))) {\n+            existingImage.putAll(newImage);\n+            found = true;\n+            break;\n+          }\n+        }\n+        if (!found) {\n+          mergedIndex.add(newImage);\n+        }\n+      }\n+      indexStream = new ByteArrayInputStream(mapper.writeValueAsBytes(mergedIndex));\n+    }\n+\n+    AttributesMap contentAttributes = null;\n+    if (payload instanceof Content) {\n+      contentAttributes = ((Content) payload).getAttributes();\n+    }\n+    Content.applyToAsset(imagesIndexAsset, Content.maintainLastModified(imagesIndexAsset, contentAttributes));\n+    tx.setBlob(imagesIndexAsset, imagesIndexAssetName, indexStream, hashAlgorithms, null, payload.getContentType());\n+    tx.saveAsset(imagesIndexAsset);\n+  }\n+\n+  @Override\n+  @Transactional(retryOn = IllegalStateException.class)\n+  public Payload getLayerMetadata(final String layerId) throws IOException {\n+    validateLayerId(layerId);\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    String metadataAssetName = layerId + \"/json\";\n+\n+    Bucket bucket = tx.getBucket();\n+    Asset metadataAsset = findAsset(tx, bucket, metadataAssetName);\n+    // check that we have the layer and layer content was fully uploaded\n+    if (metadataAsset == null || Boolean.TRUE.equals(metadataAsset.formatAttributes().get(P_IN_PROGRESS))) {\n+      throw new V1Exception(\"Image not found\", HttpStatus.NOT_FOUND);\n+    }\n+\n+    Content content = marshall(metadataAsset, tx.requireBlob(metadataAsset.requireBlobRef()));\n+\n+    NestedAttributesMap componentAttributes = metadataAsset.formatAttributes();\n+    content.getAttributes().set(P_X_DOCKER_SIZE, componentAttributes.get(P_X_DOCKER_SIZE));\n+    content.getAttributes().set(P_X_DOCKER_CHECKSUM_PAYLOAD, componentAttributes.get(P_X_DOCKER_CHECKSUM_PAYLOAD));\n+\n+    return content;\n+  }\n+\n+  @Override\n+  public void putLayerMetadata(final String layerId, final Payload content) throws IOException {\n+    validateLayerId(layerId);\n+    try (TempStreamSupplier streamSupplier = new TempStreamSupplier(content.openInputStream())) {\n+      doPutLayerMetadata(layerId, streamSupplier, content);\n+    }\n+  }\n+\n+  @Transactional(retryOn = {ONeedRetryException.class, ORecordDuplicatedException.class})\n+  protected void doPutLayerMetadata(final String layerId,\n+                                    final Supplier<InputStream> streamSupplier,\n+                                    final Payload payload)\n+      throws IOException\n+  {\n+    Map<String, Object> data = readLayerMetadata(streamSupplier);\n+    if (!data.containsKey(\"id\")) {\n+      throw new V1Exception(\"Missing key 'id' in JSON\");\n+    }\n+    if (!data.get(\"id\").equals(layerId)) {\n+      throw new V1Exception(\"JSON data contains invalid id\");\n+    }\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    String metadataAssetName = layerId + \"/json\";\n+    String parentId = (String) data.get(DockerAttributes.P_PARENT);\n+\n+    Bucket bucket = tx.getBucket();\n+\n+    Asset parentAsset = null;\n+    List<String> ancestry = null;\n+    if (parentId != null) {\n+      // check that we have the parent layer and parent layer content was fully uploaded\n+      parentAsset = findAsset(tx, bucket, parentId + \"/json\");\n+      if (parentAsset == null || Boolean.TRUE.equals(parentAsset.formatAttributes().get(P_IN_PROGRESS))) {\n+        throw new V1Exception(\"Image depends on a non existing parent\");\n+      }\n+    }\n+    // TODO check that parent asset is accessible via authorisation once we have per assets auth\n+    if (parentAsset != null) {\n+      ancestry = parentAsset.formatAttributes().get(P_ANCESTRY, TypeTokens.LIST_STRING);\n+    }\n+    if (ancestry == null) {\n+      ancestry = Lists.newArrayList();\n+    }\n+    ancestry.add(0, layerId);\n+\n+    Asset metadataAsset = findAsset(tx, bucket, metadataAssetName);\n+    if (metadataAsset == null) {\n+      metadataAsset = tx.createAsset(bucket, getRepository().getFormat());\n+      metadataAsset.name(metadataAssetName);\n+      metadataAsset.formatAttributes().set(P_PATH, \"/v1/images/\" + metadataAssetName);\n+      metadataAsset.formatAttributes().set(P_IN_PROGRESS, true);\n+    }\n+    else {\n+      if (!Boolean.TRUE.equals(metadataAsset.formatAttributes().get(P_IN_PROGRESS))) {\n+        throw new V1Exception(\"Image already exists\", HttpStatus.CONFLICT);\n+      }\n+      // cleanup checksums as we have potentially new metadata/layer\n+      metadataAsset.formatAttributes().remove(P_X_DOCKER_CHECKSUM_PAYLOAD);\n+    }\n+    metadataAsset.formatAttributes().set(P_ANCESTRY, ancestry);\n+    metadataAsset.formatAttributes().set(P_ARCH, data.get(\"os\"));\n+    metadataAsset.formatAttributes().set(P_OS, data.get(\"architecture\"));\n+\n+    // If we reach that point, it means that this is a new image or a retry on a failed push\n+    AttributesMap contentAttributes = null;\n+    if (payload instanceof Content) {\n+      contentAttributes = ((Content) payload).getAttributes();\n+    }\n+    Content.applyToAsset(metadataAsset, Content.maintainLastModified(metadataAsset, contentAttributes));\n+    tx.setBlob(metadataAsset, metadataAssetName, streamSupplier.get(), hashAlgorithms, null, payload.getContentType());\n+    tx.saveAsset(metadataAsset);\n+  }\n+\n+  @Override\n+  @Transactional(retryOn = IllegalStateException.class)\n+  public Payload getLayerContent(final String layerId) throws IOException {\n+    validateLayerId(layerId);\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    String metadataAssetName = layerId + \"/json\";\n+    String layerAssetName = layerId + \"/layer\";\n+\n+    Bucket bucket = tx.getBucket();\n+    // check that we have the layer and layer content was fully uploaded\n+    Asset metadataAsset = findAsset(tx, bucket, metadataAssetName);\n+    if (metadataAsset == null || Boolean.TRUE.equals(metadataAsset.formatAttributes().get(P_IN_PROGRESS))) {\n+      throw new V1Exception(\"Image not found\", HttpStatus.NOT_FOUND);\n+    }\n+    // check that we have layer content\n+    Asset layerAsset = findAsset(tx, bucket, layerAssetName);\n+    if (layerAsset == null) {\n+      throw new V1Exception(\"Image not found\", HttpStatus.NOT_FOUND);\n+    }\n+\n+    return marshall(layerAsset, tx.requireBlob(layerAsset.requireBlobRef()));\n+  }\n+\n+  @Override\n+  public void putLayerContent(final String layerId, final Payload content) throws IOException, InvalidContentException {\n+    validateLayerId(layerId);\n+    try (TempStreamSupplier streamSupplier = new TempStreamSupplier(content.openInputStream())) {\n+      doPutLayerContent(layerId, streamSupplier, content);\n+    }\n+  }\n+\n+  @Transactional(retryOn = {ONeedRetryException.class, ORecordDuplicatedException.class})\n+  protected void doPutLayerContent(final String layerId,\n+                                   final Supplier<InputStream> streamSupplier,\n+                                   final Payload payload)\n+      throws IOException\n+  {\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    String metadataAssetName = layerId + \"/json\";\n+    String layerAssetName = layerId + \"/layer\";\n+\n+    Bucket bucket = tx.getBucket();\n+    // check that layer metadata was previously uploaded\n+    Asset metadataAsset = findAsset(tx, bucket, metadataAssetName);\n+    if (metadataAsset == null) {\n+      throw new V1Exception(\"Image not found\", HttpStatus.NOT_FOUND);\n+    }\n+    // check that image was not already fully updated\n+    if (!Boolean.TRUE.equals(metadataAsset.formatAttributes().get(P_IN_PROGRESS))) {\n+      throw new V1Exception(\"Image already exists\", HttpStatus.CONFLICT);\n+    }\n+    Asset layerAsset = findAsset(tx, bucket, layerAssetName);\n+    if (layerAsset == null) {\n+      layerAsset = tx.createAsset(bucket, getRepository().getFormat());\n+      layerAsset.name(layerAssetName);\n+      layerAsset.formatAttributes().set(P_PATH, \"/v1/images/\" + layerAssetName);\n+    }\n+\n+    AttributesMap contentAttributes = null;\n+    if (payload instanceof Content) {\n+      contentAttributes = ((Content) payload).getAttributes();\n+    }\n+    Content.applyToAsset(layerAsset, Content.maintainLastModified(layerAsset, contentAttributes));\n+    tx.setBlob(layerAsset, layerAssetName, streamSupplier.get(), hashAlgorithms, null, payload.getContentType());\n+    tx.saveAsset(layerAsset);\n+\n+    // calculate sha256 of layer metadata + \"\\n\" + layer content\n+    Hasher hasher = Hashing.sha256().newHasher();\n+    OutputStream output = Funnels.asOutputStream(hasher);\n+    try (InputStream in = tx.requireBlob(metadataAsset.requireBlobRef()).getInputStream()) {\n+      ByteStreams.copy(in, output);\n+    }\n+    hasher.putString(\"\\n\", Charset.forName(\"UTF-8\"));\n+    try (InputStream in = tx.requireBlob(layerAsset.requireBlobRef()).getInputStream()) {\n+      ByteStreams.copy(in, output);\n+    }\n+\n+    // set extra component attributes\n+    metadataAsset.formatAttributes().set(P_X_DOCKER_SIZE, layerAsset.size());\n+    metadataAsset.formatAttributes().set(P_X_DOCKER_CHECKSUM_PAYLOAD, SHA256.name() + \":\" + hasher.hash());\n+    tx.saveAsset(metadataAsset);\n+  }\n+\n+  @Override\n+  public void putLayerChecksum(final String layerId, final String checksum) {\n+    validateLayerId(layerId);\n+    // checksum must be specified (cannot be null)\n+    if (checksum == null) {\n+      throw new V1Exception(\n+          P_X_DOCKER_CHECKSUM_PAYLOAD +\n+              \" not set.  If you are using the Docker daemon, you should upgrade to version 0.10 or later\",\n+          HttpStatus.PRECONDITION_FAILED\n+      );\n+    }\n+    // checksum must be specified (cannot be blank)\n+    if (StringUtils.isBlank(checksum)) {\n+      throw new V1Exception(\"Missing Image's checksum\");\n+    }\n+    // checksum should be of format <checksum algorithm>:<checksum>\n+    if (checksum.split(\":\").length != 2) {\n+      throw new V1Exception(\"Invalid checksum format\");\n+    }\n+    doPutLayerChecksum(layerId, checksum);\n+  }\n+\n+  @Transactional(retryOn = {ONeedRetryException.class, ORecordDuplicatedException.class})\n+  protected void doPutLayerChecksum(final String layerId, final String checksum)\n+  {\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    String metadataAssetName = layerId + \"/json\";\n+\n+    Bucket bucket = tx.getBucket();\n+    Asset metadataAsset = findAsset(tx, bucket, metadataAssetName);\n+    // check that we have the layer\n+    if (metadataAsset == null) {\n+      throw new V1Exception(\"Image not found\", HttpStatus.NOT_FOUND);\n+    }\n+    // check that layer content was fully uploaded\n+    if (!Boolean.TRUE.equals(metadataAsset.formatAttributes().get(P_IN_PROGRESS))) {\n+      throw new V1Exception(\"Cannot set this image checksum\", HttpStatus.CONFLICT);\n+    }\n+    // compare checksum with pre-calculated checksum (calculated when layer content was uploaded)\n+    Object actualChecksum = metadataAsset.formatAttributes().get(P_X_DOCKER_CHECKSUM_PAYLOAD);\n+    if (!checksum.equals(actualChecksum)) {\n+      log.error(\"Wrong checksum. Actual: {}; Expected: {}\", actualChecksum, checksum);\n+      throw new V1Exception(\"Checksum mismatch\");\n+    }\n+    // checksum match, so mark layer as fully uploaded\n+    metadataAsset.formatAttributes().remove(P_IN_PROGRESS);\n+    tx.saveAsset(metadataAsset);\n+  }\n+\n+  @Override\n+  @Transactional(retryOn = IllegalStateException.class)\n+  public Payload getLayerAncestry(final String layerId) throws IOException {\n+    validateLayerId(layerId);\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    String metadataAssetName = layerId + \"/json\";\n+\n+    Bucket bucket = tx.getBucket();\n+    Asset metadataAsset = findAsset(tx, bucket, metadataAssetName);\n+    // check that we have the layer and layer content was fully uploaded\n+    if (metadataAsset == null || Boolean.TRUE.equals(metadataAsset.formatAttributes().get(P_IN_PROGRESS))) {\n+      throw new V1Exception(\"Image not found\", HttpStatus.NOT_FOUND);\n+    }\n+\n+    String ancestry = mapper.writeValueAsString(metadataAsset.formatAttributes().get(P_ANCESTRY));\n+    return new StringPayload(ancestry, Charsets.UTF_8, ContentTypes.APPLICATION_JSON);\n+  }\n+\n+  @Override\n+  @Transactional(retryOn = IllegalStateException.class)\n+  public Payload getTags(final String name) throws IOException {\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    Iterable<Component> tagComponents = findComponents(tx, name);\n+    Map<String, String> tags = new HashMap<>();\n+    for (Component tagComponent : tagComponents) {\n+      tags.put(tagComponent.version(), tagComponent.formatAttributes().get(P_LAYER_ID, String.class));\n+    }\n+    if (tags.isEmpty()) {\n+      throw new V1Exception(\"images not found\", HttpStatus.NOT_FOUND);\n+    }\n+    return new StringPayload(mapper.writeValueAsString(tags), Charsets.UTF_8, ContentTypes.APPLICATION_JSON);\n+  }\n+\n+  @Override\n+  @Transactional(retryOn = IllegalStateException.class)\n+  public Payload getTag(final String name, final String tag) throws IOException {\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    Component tagComponent = findComponent(tx, name, tag);\n+    if (tagComponent == null) {\n+      throw new V1Exception(\"Tag not found\", HttpStatus.NOT_FOUND);\n+    }\n+    String layerId = tagComponent.formatAttributes().get(P_LAYER_ID, String.class);\n+    return new StringPayload(mapper.writeValueAsString(layerId), Charsets.UTF_8, ContentTypes.APPLICATION_JSON);\n+  }\n+\n+  @Override\n+  public void putTag(final String name,\n+                     final String tag,\n+                     final Map<String, String> daemonMetadata,\n+                     final Payload content)\n+      throws IOException\n+  {\n+    validateTagName(tag);\n+    try (TempStreamSupplier streamSupplier = new TempStreamSupplier(content.openInputStream())) {\n+      doPutTag(name, tag, readLayerId(streamSupplier), daemonMetadata);\n+    }\n+  }\n+\n+  @Transactional(retryOn = {ONeedRetryException.class, ORecordDuplicatedException.class})\n+  protected void doPutTag(final String name,\n+                          final String tag,\n+                          final String layerId,\n+                          final Map<String, String> daemonMetadata)\n+      throws IOException\n+  {\n+    StorageTx tx = UnitOfWork.currentTransaction();\n+\n+    String imageIndexAssetName = name + \"/images\";\n+    String metadataAssetName = layerId + \"/json\";\n+\n+    Bucket bucket = tx.getBucket();\n+    // check that we have an image index\n+    Asset imageIndexAsset = findAsset(tx, bucket, imageIndexAssetName);\n+    if (imageIndexAsset == null) {\n+      throw new V1Exception(\"images not found\", HttpStatus.NOT_FOUND);\n+    }\n+    // check that we have the layer specified by tag and layer content was fully uploaded\n+    Asset metadataAsset = findAsset(tx, bucket, metadataAssetName);\n+    if (metadataAsset == null || Boolean.TRUE.equals(metadataAsset.formatAttributes().get(P_IN_PROGRESS))) {\n+      throw new V1Exception(\"Image not found\", HttpStatus.NOT_FOUND);\n+    }\n+\n+    // create a component / name / tag (version) if we do not already have one\n+    Component tagComponent = findComponent(tx, name, tag);\n+    if (tagComponent == null) {\n+      tagComponent = tx.createComponent(bucket, getRepository().getFormat())\n+          .name(name)\n+          .version(tag);\n+    }\n+\n+    // set image name and tag\n+    tagComponent.formatAttributes().set(P_IMAGE_NAME, name);\n+    tagComponent.formatAttributes().set(P_IMAGE_TAG, tag);\n+    // set layer reference\n+    tagComponent.formatAttributes().set(P_LAYER_ID, layerId);\n+    // copy ancestry from referenced layer (so we can search images that include the layer)\n+    tagComponent.formatAttributes().set(P_LAYER_ANCESTRY, metadataAsset.formatAttributes().get(P_ANCESTRY));\n+    // set docker daemon metadata\n+    NestedAttributesMap tagDaemonMetadata = tagComponent.formatAttributes().child(P_DAEMON_METADATA);\n+    tagDaemonMetadata.clear();\n+    for (Entry<String, String> entry : daemonMetadata.entrySet()) {\n+      tagDaemonMetadata.set(entry.getKey(), entry.getValue());\n+    }\n+    tx.saveComponent(tagComponent);\n+  }\n+\n+  /**\n+   * Find a component by its name and tag (version)\n+   *\n+   * @param tx   storage transaction\n+   * @param name image name\n+   * @param tag  image tag\n+   * @return found component of null if not found\n+   */\n+  @Nullable\n+  private Component findComponent(final StorageTx tx, final String name, final String tag) {\n+    ComponentQuery query = new Builder()\n+        .where(P_NAME + \"=\").param(name)\n+        .where(\" AND \" + P_VERSION + \"=\").param(tag)\n+        .build();\n+    Iterable<Component> components = tx.findComponents(\n+        query.getWhere(), query.getParameters(), singletonList(getRepository()), null\n+    );\n+    if (components.iterator().hasNext()) {\n+      return components.iterator().next();\n+    }\n+    return null;\n+  }\n+\n+  /**\n+   * Find all components (tags) given a component name.\n+   *\n+   * @param tx   storage transaction\n+   * @param name image name\n+   * @return found components\n+   */\n+  private Iterable<Component> findComponents(final StorageTx tx, final String name) {\n+    ComponentQuery query = new Builder()\n+        .where(P_NAME + \"=\").param(name)\n+        .build();\n+    return tx.findComponents(\n+        query.getWhere(), query.getParameters(), singletonList(getRepository()), null\n+    );\n+  }\n+\n+  /**\n+   * Find an asset by its name.\n+   *\n+   * @param tx        storage transaction\n+   * @param bucket    repository bucket\n+   * @param assetName asset name\n+   * @return found asset or null if not found\n+   */\n+  @Nullable\n+  private Asset findAsset(final StorageTx tx, final Bucket bucket, final String assetName) {\n+    return tx.findAssetWithProperty(StorageFacet.P_NAME, assetName, bucket);\n+  }\n+\n+  /**\n+   * Validate that layer metadata is a proper JSON map.\n+   *\n+   * @param layerMetadataContent layer metadata content\n+   * @return parsed layer metadata\n+   */\n+  private Map<String, Object> readLayerMetadata(final Supplier<InputStream> layerMetadataContent) {\n+    TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {};\n+    try {\n+      return mapper.readValue(layerMetadataContent.get(), typeRef);\n+    }\n+    catch (Exception e) {","path":"plugins/basic/nexus-repository-docker/src/main/java/org/sonatype/nexus/repository/docker/internal/DockerFacetImpl.java","position":577,"original_position":577,"commit_id":"3c73d4498a171fb51027e187cf2a3baad6c17cbb","original_commit_id":"3c73d4498a171fb51027e187cf2a3baad6c17cbb","user":{"login":"sonatype-zion","id":1497996,"avatar_url":"https://avatars.githubusercontent.com/u/1497996?v=3","gravatar_id":"","url":"https://api.github.com/users/sonatype-zion","html_url":"https://github.com/sonatype-zion","followers_url":"https://api.github.com/users/sonatype-zion/followers","following_url":"https://api.github.com/users/sonatype-zion/following{/other_user}","gists_url":"https://api.github.com/users/sonatype-zion/gists{/gist_id}","starred_url":"https://api.github.com/users/sonatype-zion/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonatype-zion/subscriptions","organizations_url":"https://api.github.com/users/sonatype-zion/orgs","repos_url":"https://api.github.com/users/sonatype-zion/repos","events_url":"https://api.github.com/users/sonatype-zion/events{/privacy}","received_events_url":"https://api.github.com/users/sonatype-zion/received_events","type":"User","site_admin":false},"body":"![CRITICAL](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-critical.png) Either log or rethrow this exception. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](http://nemo.sonarqube.org/coding_rules#rule_key=squid%3AS1166)\n","created_at":"2015-07-28T19:24:15Z","updated_at":"2015-07-28T19:24:15Z","html_url":"https://github.com/sonatype/nexus-oss/pull/1596#discussion_r35689189","pull_request_url":"https://api.github.com/repos/sonatype/nexus-oss/pulls/1596","_links":{"self":{"href":"https://api.github.com/repos/sonatype/nexus-oss/pulls/comments/35689189"},"html":{"href":"https://github.com/sonatype/nexus-oss/pull/1596#discussion_r35689189"},"pull_request":{"href":"https://api.github.com/repos/sonatype/nexus-oss/pulls/1596"}}},"pull_request":{"url":"https://api.github.com/repos/sonatype/nexus-oss/pulls/1596","id":40696641,"html_url":"https://github.com/sonatype/nexus-oss/pull/1596","diff_url":"https://github.com/sonatype/nexus-oss/pull/1596.diff","patch_url":"https://github.com/sonatype/nexus-oss/pull/1596.patch","issue_url":"https://api.github.com/repos/sonatype/nexus-oss/issues/1596","number":1596,"state":"open","locked":false,"title":"NEXUS-8136 docker hosted","user":{"login":"kellyrob99","id":76356,"avatar_url":"https://avatars.githubusercontent.com/u/76356?v=3","gravatar_id":"","url":"https://api.github.com/users/kellyrob99","html_url":"https://github.com/kellyrob99","followers_url":"https://api.github.com/users/kellyrob99/followers","following_url":"https://api.github.com/users/kellyrob99/following{/other_user}","gists_url":"https://api.github.com/users/kellyrob99/gists{/gist_id}","starred_url":"https://api.github.com/users/kellyrob99/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kellyrob99/subscriptions","organizations_url":"https://api.github.com/users/kellyrob99/orgs","repos_url":"https://api.github.com/users/kellyrob99/repos","events_url":"https://api.github.com/users/kellyrob99/events{/privacy}","received_events_url":"https://api.github.com/users/kellyrob99/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-07-23T17:27:14Z","updated_at":"2015-07-28T19:24:17Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f8e101b2d339a716e1b4016669e83f187835603c","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/sonatype/nexus-oss/pulls/1596/commits","review_comments_url":"https://api.github.com/repos/sonatype/nexus-oss/pulls/1596/comments","review_comment_url":"https://api.github.com/repos/sonatype/nexus-oss/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sonatype/nexus-oss/issues/1596/comments","statuses_url":"https://api.github.com/repos/sonatype/nexus-oss/statuses/3c73d4498a171fb51027e187cf2a3baad6c17cbb","head":{"label":"sonatype:NEXUS-8136-docker-hosted","ref":"NEXUS-8136-docker-hosted","sha":"3c73d4498a171fb51027e187cf2a3baad6c17cbb","user":{"login":"sonatype","id":44938,"avatar_url":"https://avatars.githubusercontent.com/u/44938?v=3","gravatar_id":"","url":"https://api.github.com/users/sonatype","html_url":"https://github.com/sonatype","followers_url":"https://api.github.com/users/sonatype/followers","following_url":"https://api.github.com/users/sonatype/following{/other_user}","gists_url":"https://api.github.com/users/sonatype/gists{/gist_id}","starred_url":"https://api.github.com/users/sonatype/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonatype/subscriptions","organizations_url":"https://api.github.com/users/sonatype/orgs","repos_url":"https://api.github.com/users/sonatype/repos","events_url":"https://api.github.com/users/sonatype/events{/privacy}","received_events_url":"https://api.github.com/users/sonatype/received_events","type":"Organization","site_admin":false},"repo":{"id":10146961,"name":"nexus-oss","full_name":"sonatype/nexus-oss","owner":{"login":"sonatype","id":44938,"avatar_url":"https://avatars.githubusercontent.com/u/44938?v=3","gravatar_id":"","url":"https://api.github.com/users/sonatype","html_url":"https://github.com/sonatype","followers_url":"https://api.github.com/users/sonatype/followers","following_url":"https://api.github.com/users/sonatype/following{/other_user}","gists_url":"https://api.github.com/users/sonatype/gists{/gist_id}","starred_url":"https://api.github.com/users/sonatype/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonatype/subscriptions","organizations_url":"https://api.github.com/users/sonatype/orgs","repos_url":"https://api.github.com/users/sonatype/repos","events_url":"https://api.github.com/users/sonatype/events{/privacy}","received_events_url":"https://api.github.com/users/sonatype/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sonatype/nexus-oss","description":"Sonatype Nexus OSS","fork":false,"url":"https://api.github.com/repos/sonatype/nexus-oss","forks_url":"https://api.github.com/repos/sonatype/nexus-oss/forks","keys_url":"https://api.github.com/repos/sonatype/nexus-oss/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sonatype/nexus-oss/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sonatype/nexus-oss/teams","hooks_url":"https://api.github.com/repos/sonatype/nexus-oss/hooks","issue_events_url":"https://api.github.com/repos/sonatype/nexus-oss/issues/events{/number}","events_url":"https://api.github.com/repos/sonatype/nexus-oss/events","assignees_url":"https://api.github.com/repos/sonatype/nexus-oss/assignees{/user}","branches_url":"https://api.github.com/repos/sonatype/nexus-oss/branches{/branch}","tags_url":"https://api.github.com/repos/sonatype/nexus-oss/tags","blobs_url":"https://api.github.com/repos/sonatype/nexus-oss/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sonatype/nexus-oss/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sonatype/nexus-oss/git/refs{/sha}","trees_url":"https://api.github.com/repos/sonatype/nexus-oss/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sonatype/nexus-oss/statuses/{sha}","languages_url":"https://api.github.com/repos/sonatype/nexus-oss/languages","stargazers_url":"https://api.github.com/repos/sonatype/nexus-oss/stargazers","contributors_url":"https://api.github.com/repos/sonatype/nexus-oss/contributors","subscribers_url":"https://api.github.com/repos/sonatype/nexus-oss/subscribers","subscription_url":"https://api.github.com/repos/sonatype/nexus-oss/subscription","commits_url":"https://api.github.com/repos/sonatype/nexus-oss/commits{/sha}","git_commits_url":"https://api.github.com/repos/sonatype/nexus-oss/git/commits{/sha}","comments_url":"https://api.github.com/repos/sonatype/nexus-oss/comments{/number}","issue_comment_url":"https://api.github.com/repos/sonatype/nexus-oss/issues/comments{/number}","contents_url":"https://api.github.com/repos/sonatype/nexus-oss/contents/{+path}","compare_url":"https://api.github.com/repos/sonatype/nexus-oss/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sonatype/nexus-oss/merges","archive_url":"https://api.github.com/repos/sonatype/nexus-oss/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sonatype/nexus-oss/downloads","issues_url":"https://api.github.com/repos/sonatype/nexus-oss/issues{/number}","pulls_url":"https://api.github.com/repos/sonatype/nexus-oss/pulls{/number}","milestones_url":"https://api.github.com/repos/sonatype/nexus-oss/milestones{/number}","notifications_url":"https://api.github.com/repos/sonatype/nexus-oss/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sonatype/nexus-oss/labels{/name}","releases_url":"https://api.github.com/repos/sonatype/nexus-oss/releases{/id}","created_at":"2013-05-18T20:48:57Z","updated_at":"2015-07-28T02:41:36Z","pushed_at":"2015-07-28T19:15:30Z","git_url":"git://github.com/sonatype/nexus-oss.git","ssh_url":"git@github.com:sonatype/nexus-oss.git","clone_url":"https://github.com/sonatype/nexus-oss.git","svn_url":"https://github.com/sonatype/nexus-oss","homepage":"http://links.sonatype.com/products/nexus/oss/home","size":896560,"stargazers_count":169,"watchers_count":169,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":108,"mirror_url":null,"open_issues_count":13,"forks":108,"open_issues":13,"watchers":169,"default_branch":"master"}},"base":{"label":"sonatype:master","ref":"master","sha":"2fec9ba3cbea12bb40f3b82af3ee7762e0c179ce","user":{"login":"sonatype","id":44938,"avatar_url":"https://avatars.githubusercontent.com/u/44938?v=3","gravatar_id":"","url":"https://api.github.com/users/sonatype","html_url":"https://github.com/sonatype","followers_url":"https://api.github.com/users/sonatype/followers","following_url":"https://api.github.com/users/sonatype/following{/other_user}","gists_url":"https://api.github.com/users/sonatype/gists{/gist_id}","starred_url":"https://api.github.com/users/sonatype/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonatype/subscriptions","organizations_url":"https://api.github.com/users/sonatype/orgs","repos_url":"https://api.github.com/users/sonatype/repos","events_url":"https://api.github.com/users/sonatype/events{/privacy}","received_events_url":"https://api.github.com/users/sonatype/received_events","type":"Organization","site_admin":false},"repo":{"id":10146961,"name":"nexus-oss","full_name":"sonatype/nexus-oss","owner":{"login":"sonatype","id":44938,"avatar_url":"https://avatars.githubusercontent.com/u/44938?v=3","gravatar_id":"","url":"https://api.github.com/users/sonatype","html_url":"https://github.com/sonatype","followers_url":"https://api.github.com/users/sonatype/followers","following_url":"https://api.github.com/users/sonatype/following{/other_user}","gists_url":"https://api.github.com/users/sonatype/gists{/gist_id}","starred_url":"https://api.github.com/users/sonatype/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonatype/subscriptions","organizations_url":"https://api.github.com/users/sonatype/orgs","repos_url":"https://api.github.com/users/sonatype/repos","events_url":"https://api.github.com/users/sonatype/events{/privacy}","received_events_url":"https://api.github.com/users/sonatype/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sonatype/nexus-oss","description":"Sonatype Nexus OSS","fork":false,"url":"https://api.github.com/repos/sonatype/nexus-oss","forks_url":"https://api.github.com/repos/sonatype/nexus-oss/forks","keys_url":"https://api.github.com/repos/sonatype/nexus-oss/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sonatype/nexus-oss/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sonatype/nexus-oss/teams","hooks_url":"https://api.github.com/repos/sonatype/nexus-oss/hooks","issue_events_url":"https://api.github.com/repos/sonatype/nexus-oss/issues/events{/number}","events_url":"https://api.github.com/repos/sonatype/nexus-oss/events","assignees_url":"https://api.github.com/repos/sonatype/nexus-oss/assignees{/user}","branches_url":"https://api.github.com/repos/sonatype/nexus-oss/branches{/branch}","tags_url":"https://api.github.com/repos/sonatype/nexus-oss/tags","blobs_url":"https://api.github.com/repos/sonatype/nexus-oss/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sonatype/nexus-oss/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sonatype/nexus-oss/git/refs{/sha}","trees_url":"https://api.github.com/repos/sonatype/nexus-oss/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sonatype/nexus-oss/statuses/{sha}","languages_url":"https://api.github.com/repos/sonatype/nexus-oss/languages","stargazers_url":"https://api.github.com/repos/sonatype/nexus-oss/stargazers","contributors_url":"https://api.github.com/repos/sonatype/nexus-oss/contributors","subscribers_url":"https://api.github.com/repos/sonatype/nexus-oss/subscribers","subscription_url":"https://api.github.com/repos/sonatype/nexus-oss/subscription","commits_url":"https://api.github.com/repos/sonatype/nexus-oss/commits{/sha}","git_commits_url":"https://api.github.com/repos/sonatype/nexus-oss/git/commits{/sha}","comments_url":"https://api.github.com/repos/sonatype/nexus-oss/comments{/number}","issue_comment_url":"https://api.github.com/repos/sonatype/nexus-oss/issues/comments{/number}","contents_url":"https://api.github.com/repos/sonatype/nexus-oss/contents/{+path}","compare_url":"https://api.github.com/repos/sonatype/nexus-oss/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sonatype/nexus-oss/merges","archive_url":"https://api.github.com/repos/sonatype/nexus-oss/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sonatype/nexus-oss/downloads","issues_url":"https://api.github.com/repos/sonatype/nexus-oss/issues{/number}","pulls_url":"https://api.github.com/repos/sonatype/nexus-oss/pulls{/number}","milestones_url":"https://api.github.com/repos/sonatype/nexus-oss/milestones{/number}","notifications_url":"https://api.github.com/repos/sonatype/nexus-oss/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sonatype/nexus-oss/labels{/name}","releases_url":"https://api.github.com/repos/sonatype/nexus-oss/releases{/id}","created_at":"2013-05-18T20:48:57Z","updated_at":"2015-07-28T02:41:36Z","pushed_at":"2015-07-28T19:15:30Z","git_url":"git://github.com/sonatype/nexus-oss.git","ssh_url":"git@github.com:sonatype/nexus-oss.git","clone_url":"https://github.com/sonatype/nexus-oss.git","svn_url":"https://github.com/sonatype/nexus-oss","homepage":"http://links.sonatype.com/products/nexus/oss/home","size":896560,"stargazers_count":169,"watchers_count":169,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":108,"mirror_url":null,"open_issues_count":13,"forks":108,"open_issues":13,"watchers":169,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/sonatype/nexus-oss/pulls/1596"},"html":{"href":"https://github.com/sonatype/nexus-oss/pull/1596"},"issue":{"href":"https://api.github.com/repos/sonatype/nexus-oss/issues/1596"},"comments":{"href":"https://api.github.com/repos/sonatype/nexus-oss/issues/1596/comments"},"review_comments":{"href":"https://api.github.com/repos/sonatype/nexus-oss/pulls/1596/comments"},"review_comment":{"href":"https://api.github.com/repos/sonatype/nexus-oss/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sonatype/nexus-oss/pulls/1596/commits"},"statuses":{"href":"https://api.github.com/repos/sonatype/nexus-oss/statuses/3c73d4498a171fb51027e187cf2a3baad6c17cbb"}}}}