package org.jfrog.hudson.pipeline.docker.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.AuthConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.command.PullImageResultCallback;
import com.github.dockerjava.core.command.PushImageResultCallback;
import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import org.apache.commons.lang.StringUtils;
import org.jfrog.hudson.pipeline.Utils;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Created by romang on 7/28/16.
*/
public class DockerUtils implements Serializable {
public static boolean isDockerHostExists(String host) {
try {
DockerClient dockerClient = getDockerClient(host);
dockerClient.pingCmd().exec();
return true;
} catch (Exception e) {
return false;
}
}
/**
* Get image Id from imageTag using DockerBuildInfoHelper client
*
* @param imageTag
* @return
*/
public static String getImageIdFromTag(String imageTag, String host) {
DockerClient dockerClient = getDockerClient(host);
return dockerClient.inspectImageCmd(imageTag).exec().getId();
}
/**
* Push docker image using the docker java client
*
* @param imageTag
* @param authConfig
* @param host
*/
public static void pushImage(String imageTag, String username, String password, String host) {
final AuthConfig authConfig = new AuthConfig();
authConfig.withUsername(username);
authConfig.withPassword(password);
DockerClient dockerClient = getDockerClient(host);
dockerClient.pushImageCmd(imageTag).withAuthConfig(authConfig).exec(new PushImageResultCallback()).awaitSuccess();
}
/**
* Pull docker image using the docker java client
*
* @param imageTag
* @param authConfig
* @param host
*/
public static void pullImage(String imageTag, String username, String password, String host) {
final AuthConfig authConfig = new AuthConfig();
authConfig.withUsername(username);
authConfig.withPassword(password);
DockerClient dockerClient = getDockerClient(host);
dockerClient.pullImageCmd(imageTag).withAuthConfig(authConfig).exec(new PullImageResultCallback()).awaitSuccess();
}
/**
* Get parent digest of an image
*
* @param digest
* @param host
* @return
*/
public static String getParentId(String digest, String host) {
DockerClient dockerClient = getDockerClient(host);
return dockerClient.inspectImageCmd(digest).exec().getParent();
}
/**
* Get config digest from manifest (image id)
*
* @param manifest
* @return
* @throws IOException
*/
public static String getConfigDigest(String manifest) throws IOException {
JsonNode manifestTree = Utils.mapper().readTree(manifest);
JsonNode config = manifestTree.get("config");
if (config == null) {
throw new IllegalStateException("Could not find 'config' in manifest");
}
JsonNode digest = config.get("digest");
if (digest == null) {
throw new IllegalStateException("Could not find config digest in manifest");
}
return StringUtils.remove(digest.toString(), "\"");
}
/**
* Get a list of layer digests from docker manifest
*
* @param manifestContent
* @return
*/
public static List<String> getLayersDigests(String manifestContent) throws IOException {
List<String> dockerLayersDependencies = new ArrayList<String>();
JsonNode manifest = Utils.mapper().readTree(manifestContent);
JsonNode schemaVersion = manifest.get("schemaVersion");
if (schemaVersion == null) {
throw new IllegalStateException("Could not find 'schemaVersion' in manifest");
}
boolean isSchemeVersion1 = schemaVersion.asInt() == 1;
JsonNode fsLayers = getFsLayers(manifest, isSchemeVersion1);
for (JsonNode fsLayer : fsLayers) {
JsonNode blobSum = getBlobSum(isSchemeVersion1, fsLayer);
dockerLayersDependencies.add(blobSum.asText());
}
dockerLayersDependencies.add(getConfigDigest(manifestContent));
//Add manifest sha1
String manifestSha1 = Hashing.sha1().hashString(manifestContent, Charsets.UTF_8).toString();
dockerLayersDependencies.add("sha1:" + manifestSha1);
return dockerLayersDependencies;
}
/**
* return blob sum depend on scheme version
*
* @param isSchemeVersion1 - if true scheme version 1
* @param manifest - docker manifest
* @return - layer element
*/
private static JsonNode getFsLayers(JsonNode manifest, boolean isSchemeVersion1) {
JsonNode fsLayers;
if (isSchemeVersion1) {
fsLayers = manifest.get("fsLayers");
} else {
fsLayers = manifest.get("layers");
}
if (fsLayers == null) {
throw new IllegalStateException("Could not find 'fsLayers' or 'layers' in manifest");
}
return fsLayers;
}
/**
* return blob sum depend on scheme version
*
* @param isSchemeVersion1 - if true scheme version 1
* @param fsLayer - docker layers
* @return - manifest element
*/
private static JsonNode getBlobSum(boolean isSchemeVersion1, JsonNode fsLayer) {
JsonNode blobSum;
if (isSchemeVersion1) {
blobSum = fsLayer.get("blobSum");
} else {
blobSum = fsLayer.get("digest");
}
if (blobSum == null) {
throw new IllegalStateException("Could not find 'blobSub' or 'digest' in manifest");
}
return blobSum;
}
/**
* Get sha value from digest
* example: sha256:abcabcabc12334 the value is abcabcabc12334
*
* @param digest
* @return
*/
public static String getShaValue(String digest) {
return StringUtils.substring(digest, StringUtils.indexOf(digest, ":") + 1);
}
/**
* Get sha value from digest
* example: sha256:abcabcabc12334 the value is sha256
*
* @param digest
* @return
*/
public static String getShaVersion(String digest) {
return StringUtils.substring(digest, 0, StringUtils.indexOf(digest, ":"));
}
/**
* Parse imageTag and get the relative path of the pushed image.
* example: url:8081/image:version to image/version
*
* @param imageTag
* @return
*/
public static String getImagePath(String imageTag) {
int indexOfFirstSlash = imageTag.indexOf("/");
int indexOfLastColon = imageTag.lastIndexOf(":");
String imageName;
String imageVersion;
if (indexOfLastColon < 0 || indexOfLastColon < indexOfFirstSlash) {
imageName = imageTag.substring(indexOfFirstSlash + 1);
imageVersion = "latest";
} else {
imageName = imageTag.substring(indexOfFirstSlash + 1, indexOfLastColon);
imageVersion = imageTag.substring(indexOfLastColon + 1);
}
return imageName + "/" + imageVersion;
}
public static Boolean isImageVersioned(String imageTag) {
int indexOfFirstSlash = imageTag.indexOf("/");
int indexOfLastColon = imageTag.lastIndexOf(":");
return indexOfFirstSlash < indexOfLastColon;
}
/**
* layer file name to digest format
*
* @param fileName
* @return
*/
public static String fileNameToDigest(String fileName) {
return StringUtils.replace(fileName, "__", ":");
}
/**
* digest format to layer file name
*
* @param digest
* @return
*/
public static String digestToFileName(String digest) {
if (StringUtils.startsWith(digest, "sha1")) {
return "manifest.json";
}
return getShaVersion(digest) + "__" + getShaValue(digest);
}
/**
* Returns number of dependencies layers in the image.
*
* @param imageContent
* @return
* @throws IOException
*/
public static int getNumberOfDependentLayers(String imageContent) throws IOException {
JsonNode history = Utils.mapper().readTree(imageContent).get("history");
if (history == null) {
throw new IllegalStateException("Could not find 'history' tag");
}
int layersNum = history.size();
boolean newImageLayers = true;
for (int i = history.size() - 1; i >= 0; i--) {
if (newImageLayers) {
layersNum--;
}
JsonNode layer = history.get(i);
JsonNode emptyLayer = layer.get("empty_layer");
if (!newImageLayers && emptyLayer != null) {
layersNum--;
}
if (layer.get("created_by") == null) {
continue;
}
String createdBy = layer.get("created_by").textValue();
if (createdBy.contains("ENTRYPOINT") || createdBy.contains("MAINTAINER")) {
newImageLayers = false;
}
}
return layersNum;
}
private static DockerClient getDockerClient(String host) {
if (StringUtils.isEmpty(host)) {
return DockerClientBuilder.getInstance().build();
}
DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
.withDockerHost(host)
.build();
return DockerClientBuilder.getInstance(config).build();
}
}