package org.jfrog.hudson.pipeline.docker.utils; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import hudson.Launcher; import hudson.model.Node; import hudson.remoting.Callable; import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; import org.jfrog.hudson.pipeline.docker.DockerImage; import org.jfrog.hudson.pipeline.docker.proxy.BuildInfoProxy; import org.jfrog.hudson.util.JenkinsBuildInfoLog; import java.io.IOException; import java.io.Serializable; import java.util.*; /** * Created by romang on 8/15/16. */ public class DockerAgentUtils implements Serializable { /** * Using the structure below to create link between the desired docker image to the build. */ //Contains image id to build-info id for registering imageId to build-info private static Multimap<String, Integer> imageIdToBuildInfoId = ArrayListMultimap.create(); //Contains all the prepared docker images for build-info private static Multimap<Integer, DockerImage> buildInfoIdToDockerImage = ArrayListMultimap.create(); //Image id to image tag private static Map<String, String> imageIdToImageTag = new HashMap<String, String>(); //Image tag to target repository private static Map<String, String> imageTagToTargetRepo = new HashMap<String, String>(); /** * Register image to be captured for building build-info. * * @param imageTag * @param host * @param targetRepo * @param buildInfoId * @throws IOException * @throws InterruptedException */ public synchronized static void registerImageOnAgents(Launcher launcher, final String imageTag, final String host, final String targetRepo, final int buildInfoId) throws IOException, InterruptedException { // Master final String imageId = getImageIdFromAgent(launcher, imageTag, host); registerImage(imageId, imageTag, host, targetRepo, buildInfoId); // Agents List<Node> nodes = Jenkins.getInstance().getNodes(); for (Node node : nodes) { if (node == null || node.getChannel() == null) { continue; } node.getChannel().call(new Callable<Boolean, IOException>() { public Boolean call() throws IOException { registerImage(imageId, imageTag, host, targetRepo, buildInfoId); return true; } }); } } private static void registerImage(String imageId, String imageTag, String host, String targetRepo, int buildInfoId) { if (!DockerUtils.isDockerHostExists(host)) { return; } imageIdToBuildInfoId.put(imageId, buildInfoId); imageIdToImageTag.put(imageId, imageTag); imageTagToTargetRepo.put(imageTag, targetRepo); } /** * Capture manifest and extract layers data from it to create docker images build-info * * @param content * @param properties */ public synchronized static void captureContent(String content, Properties properties) { try { String digest = DockerUtils.getConfigDigest(content); for (Integer buildInfoId : imageIdToBuildInfoId.get(digest)) { String imageTag = imageIdToImageTag.get(digest); DockerImage dockerImage = new DockerImage(digest, imageTag, imageTagToTargetRepo.get(imageTag), content, BuildInfoProxy.getAgentName()); dockerImage.addProperties(properties); buildInfoIdToDockerImage.put(buildInfoId, dockerImage); } imageIdToBuildInfoId.removeAll(digest); } catch (IOException e) { e.printStackTrace(); } } /** * Retrieve prepared docker images from an agent(could be master as well) related to the given build-info id * * @param buildInfoId * @return * @throws IOException * @throws InterruptedException */ public static List<DockerImage> getDockerImagesFromAgents(final int buildInfoId) throws IOException, InterruptedException { List<DockerImage> dockerImages = new ArrayList<DockerImage>(); //Master dockerImages.addAll(buildInfoIdToDockerImage.get(buildInfoId)); buildInfoIdToDockerImage.removeAll(buildInfoId); //Agents List<Node> nodes = Jenkins.getInstance().getNodes(); for (Node node : nodes) { if (node == null || node.getChannel() == null) { continue; } List<DockerImage> partialDockerImages = node.getChannel().call(new Callable<List<DockerImage>, IOException>() { public List<DockerImage> call() throws IOException { List<DockerImage> dockerImages = new ArrayList<DockerImage>(); dockerImages.addAll(buildInfoIdToDockerImage.get(buildInfoId)); buildInfoIdToDockerImage.removeAll(buildInfoId); return dockerImages; } }); dockerImages.addAll(partialDockerImages); } return dockerImages; } /** * Execute push docker image on agent * * @param launcher * @param log * @param imageTag * @param username * @param password * @param host @return * @throws IOException * @throws InterruptedException */ public static boolean pushImage(Launcher launcher, final JenkinsBuildInfoLog log, final String imageTag, final String username, final String password, final String host) throws IOException, InterruptedException { return launcher.getChannel().call(new Callable<Boolean, IOException>() { public Boolean call() throws IOException { String message = "Pushing image: " + imageTag; if (StringUtils.isNotEmpty(host)) { message += " using docker daemon host: " + host; } log.info(message); DockerUtils.pushImage(imageTag, username, password, host); return true; } }); } /** * Execute pull docker image on agent * * @param launcher * @param imageTag * @param username * @param password * @param host * @return * @throws IOException * @throws InterruptedException */ public static boolean pullImage(Launcher launcher, final String imageTag, final String username, final String password, final String host) throws IOException, InterruptedException { return launcher.getChannel().call(new Callable<Boolean, IOException>() { public Boolean call() throws IOException { DockerUtils.pullImage(imageTag, username, password, host); return true; } }); } /** * Updates property of parent id for the image provided. * Returns false if image was not captured true otherwise. * * @param log * @param imageTag * @param host * @param buildInfoId * @return * @throws IOException * @throws InterruptedException */ public static boolean updateImageParentOnAgents(final JenkinsBuildInfoLog log, final String imageTag, final String host, final int buildInfoId) throws IOException, InterruptedException { boolean parentUpdated = updateImageParent(log, imageTag, host, buildInfoId); List<Node> nodes = Jenkins.getInstance().getNodes(); for (Node node : nodes) { if (node == null || node.getChannel() == null) { continue; } boolean parentNodeUpdated = node.getChannel().call(new Callable<Boolean, IOException>() { public Boolean call() throws IOException { return updateImageParent(log, imageTag, host, buildInfoId); } }); parentUpdated = parentUpdated ? parentUpdated : parentNodeUpdated; } return parentUpdated; } private static boolean updateImageParent(JenkinsBuildInfoLog log, String imageTag, String host, int buildInfoId) { boolean parentUpdated = false; for (DockerImage image : buildInfoIdToDockerImage.get(buildInfoId)) { if (image.getImageTag().equals(imageTag)) { String parentId = DockerUtils.getParentId(image.getImageId(), host); if (StringUtils.isNotEmpty(parentId)) { Properties properties = new Properties(); properties.setProperty("docker.image.parent", DockerUtils.getShaValue(parentId)); image.addProperties(properties); } log.info("Docker build-info captured on '" + image.getAgentName() + "' agent."); parentUpdated = true; } } return parentUpdated; } /** * Get image Id from imageTag using DockerBuildInfoHelper client on agent * * @param imageTag * @return */ private static String getImageIdFromAgent(Launcher launcher, final String imageTag, final String host) throws IOException, InterruptedException { return launcher.getChannel().call(new Callable<String, IOException>() { public String call() throws IOException { return DockerUtils.getImageIdFromTag(imageTag, host); } }); } }