Skip to content

Instantly share code, notes, and snippets.

Revisions

  1. lukewpatterson revised this gist Dec 8, 2016. No changes.
  2. lukewpatterson revised this gist Dec 8, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion deleteOrphanedDockerComponentsAndAssets.groovy
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,7 @@ try {
    // remove Components which don't own any Assets. Docker Registry API-based DELETE calls were causing
    // this scenario in Nexus 3.1.0-4. symptoms included deleted Component remaining in API retrieval results, but
    // not appearing on the UI Components list.
    // https://docs.docker.com/registry/spec/api/#deleting-an-image
    // https://github.com/docker/distribution/blob/8d096a4f4213ef0d856459f80f09dc5ce33bbdcf/docs/spec/api.md#deleting-an-image
    if (storageTx.browseAssets(component).asCollection().isEmpty()) {
    log.info("Deleting Orphaned Nexus Component: ${component.toString()}")
    storageTx.deleteComponent(component)
  3. lukewpatterson created this gist Dec 8, 2016.
    88 changes: 88 additions & 0 deletions deleteOrphanedDockerComponentsAndAssets.groovy
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,88 @@
    import groovy.json.JsonSlurper
    import org.sonatype.nexus.repository.storage.Asset
    import org.sonatype.nexus.repository.storage.StorageFacet

    def DOCKER_REPOSITORY_NAME = 'docker-hosted'

    def dockerRepository = repository.repositoryManager.get(DOCKER_REPOSITORY_NAME)
    def dockerBlobStore = blobStore.blobStoreManager.get(dockerRepository.configuration.attributes.storage.blobStoreName)
    def storageTx = dockerRepository.facet(StorageFacet.class).txSupplier().get()
    try {
    storageTx.begin()
    dockerRepository.stop()
    def bucket = storageTx.findBucket(dockerRepository)

    def images = storageTx.browseComponents(bucket).asCollection().asImmutable()
    log.info("Nexus Components - Docker Images: ${images.size()}")
    images.forEach { component ->
    // remove Components which don't own any Assets. Docker Registry API-based DELETE calls were causing
    // this scenario in Nexus 3.1.0-4. symptoms included deleted Component remaining in API retrieval results, but
    // not appearing on the UI Components list.
    // https://docs.docker.com/registry/spec/api/#deleting-an-image
    if (storageTx.browseAssets(component).asCollection().isEmpty()) {
    log.info("Deleting Orphaned Nexus Component: ${component.toString()}")
    storageTx.deleteComponent(component)
    }
    }

    def manifests = [] as Set<Asset>
    def nonManifests = [] as Set<Asset>
    storageTx.browseAssets(bucket).forEach { asset ->
    def contentType = asset.contentType()
    // https://github.com/docker/distribution/blob/844b92879f179f16a26ce441631880aa3079b7f4/docs/spec/manifest-v2-2.md#media-types
    switch (contentType) {
    case 'application/vnd.docker.distribution.manifest.v2+json':
    manifests.add(asset)
    break
    case 'application/vnd.docker.image.rootfs.diff.tar.gzip': // blob/layer
    case 'application/vnd.docker.container.image.v1+json': // configuration
    nonManifests.add(asset)
    break
    default:
    // fail if any type we aren't prepared for and/or don't understand
    throw new IllegalArgumentException("Unexpected Content-Type '${contentType}' on Asset '${asset.name()}'")
    }
    }
    log.info("Nexus Assets - Docker Manifests: ${manifests.size()}")
    log.info("Nexus Assets - Docker Non-Manifests: ${nonManifests.size()}")
    log.info("Nexus Assets Total: ${manifests.size() + nonManifests.size()}")

    // initialized with digest of 'zero size' layer, something about backwards compat with v1
    // https://github.com/docker/distribution/issues/1810#issuecomment-231152078
    def digests = ['sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4'] as Set<String>
    manifests.each { manifest ->
    def manifestJson = new JsonSlurper().parse(dockerBlobStore.get(manifest.blobRef().blobId).inputStream)
    if (manifestJson.schemaVersion != 2) {
    throw new IllegalArgumentException(
    "Unexpected schemaVersion '${manifestJson.schemaVersion}' on Manifest '${manifest.name()}'")
    }
    // https://github.com/docker/distribution/blob/844b92879f179f16a26ce441631880aa3079b7f4/docs/spec/manifest-v2-2.md#image-manifest
    digests.add(manifestJson.config.digest)
    manifestJson.layers.forEach { layer ->
    def digest = layer.digest.toString()
    // fail if assumption made for the algorithm of the hardcoded 'zero size' value from earlier isn't valid
    // https://github.com/docker/docker.github.io/blob/84cbb88f7580307bc89eefc1c476f675e09a4394/registry/spec/api.md#content-digests
    if (!digest.startsWith('sha256:')) {
    throw new IllegalArgumentException("Unexpected Digest algorithm in '${digest}' on Asset '${asset.name()}'")
    }
    digests.add(digest)
    }
    }
    digests.forEach { digest ->
    nonManifests.removeIf { candidateNonManifest ->
    candidateNonManifest.name().endsWith(digest)
    }
    }
    log.info("Nexus Assets - Docker Non-Manifests - Orphaned: ${nonManifests.size()}")

    nonManifests.forEach { nonManifest ->
    log.info("Deleting Orphaned Docker Non-Manifest: ${nonManifest.name().replaceAll('v2/-/blobs/','')}")
    storageTx.deleteAsset(nonManifest)
    }

    storageTx.commit()
    dockerBlobStore.compact()
    } finally {
    dockerRepository.start()
    storageTx.close()
    }