
net.roboconf.target.docker.internal.DockerUtils Maven / Gradle / Ivy
The newest version!
/**
* Copyright 2015-2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.roboconf.target.docker.internal;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.commons.lang.WordUtils;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.command.InspectContainerResponse.ContainerState;
import com.github.dockerjava.api.model.Capability;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DefaultDockerClientConfig.Builder;
import com.github.dockerjava.core.DockerClientBuilder;
import net.roboconf.core.utils.Utils;
import net.roboconf.target.api.TargetException;
/**
* @author Vincent Zurczak - Linagora
*/
public final class DockerUtils {
/**
* Private empty constructor.
*/
private DockerUtils() {
// nothing
}
/**
* Creates a Docker client from target properties.
* @param targetProperties a non-null map
* @return a Docker client
* @throws TargetException if something went wrong
*/
public static DockerClient createDockerClient( Map targetProperties ) throws TargetException {
// Validate what needs to be validated.
Logger logger = Logger.getLogger( DockerHandler.class.getName());
logger.fine( "Setting the target properties." );
String edpt = targetProperties.get( DockerHandler.ENDPOINT );
if( Utils.isEmptyOrWhitespaces( edpt ))
edpt = DockerHandler.DEFAULT_ENDPOINT;
// The configuration is straight-forward.
Builder config =
DefaultDockerClientConfig.createDefaultConfigBuilder()
.withDockerHost( edpt )
.withRegistryUsername( targetProperties.get( DockerHandler.USER ))
.withRegistryPassword( targetProperties.get( DockerHandler.PASSWORD ))
.withRegistryEmail( targetProperties.get( DockerHandler.EMAIL ))
.withApiVersion( targetProperties.get( DockerHandler.VERSION ));
// Build the client.
DockerClientBuilder clientBuilder = DockerClientBuilder.getInstance( config.build());
return clientBuilder.build();
}
/**
* Deletes a Docker image if it exists.
* @param imageId the image ID (not null)
* @param dockerClient a Docker client
*/
public static void deleteImageIfItExists( String imageId, DockerClient dockerClient ) {
if( imageId != null ) {
List images = dockerClient.listImagesCmd().exec();
if( findImageById( imageId, images ) != null )
dockerClient.removeImageCmd( imageId ).withForce( true ).exec();
}
}
/**
* Finds an image by ID or by tag.
* @param name an image ID or a tag name (can be null)
* @param dockerClient a Docker client (not null)
* @return an image, or null if none matched
*/
public static Image findImageByIdOrByTag( String name, DockerClient dockerClient ) {
Image image = null;
if( ! Utils.isEmptyOrWhitespaces( name )) {
Logger logger = Logger.getLogger( DockerUtils.class.getName());
List images = dockerClient.listImagesCmd().exec();
if(( image = DockerUtils.findImageById( name, images )) != null )
logger.fine( "Found a Docker image with ID " + name );
else if(( image = DockerUtils.findImageByTag( name, images )) != null )
logger.fine( "Found a Docker image with tag " + name );
}
return image;
}
/**
* Finds an image by ID.
* @param imageId the image ID (not null)
* @param images a non-null list of images
* @return an image, or null if none was found
*/
public static Image findImageById( String imageId, List images ) {
Image result = null;
for( Image img : images ) {
if( img.getId().equals(imageId)) {
result = img;
break;
}
}
return result;
}
/**
* Finds an image by tag.
* @param imageTag the image tag (not null)
* @param images a non-null list of images
* @return an image, or null if none was found
*/
public static Image findImageByTag( String imageTag, List images ) {
Image result = null;
for( Image img : images ) {
String[] tags = img.getRepoTags();
if( tags == null )
continue;
for( String s : tags ) {
if( s.contains( imageTag )) {
result = img;
break;
}
}
}
return result;
}
/**
* Finds a container by ID or by name.
* @param name the container ID or name (not null)
* @param dockerClient a Docker client
* @return a container, or null if none was found
*/
public static Container findContainerByIdOrByName( String name, DockerClient dockerClient ) {
Container result = null;
List containers = dockerClient.listContainersCmd().withShowAll( true ).exec();
for( Container container : containers ) {
List names = Arrays.asList( container.getNames());
// Docker containers are prefixed with '/'.
// At least, those we created, since their parent is the Docker daemon.
if( container.getId().equals( name )
|| names.contains( "/" + name )) {
result = container;
break;
}
}
return result;
}
/**
* Gets the state of a container.
* @param containerId the container ID
* @param dockerClient the Docker client
* @return a container state, or null if the container was not found
*/
public static ContainerState getContainerState( String containerId, DockerClient dockerClient ) {
ContainerState result = null;
try {
InspectContainerResponse resp = dockerClient.inspectContainerCmd( containerId ).exec();
if( resp != null )
result = resp.getState();
} catch( Exception e ) {
// nothing
}
return result;
}
/**
* Finds the options and tries to configure them on the creation command.
* @param options the options (key = name, value = option value)
* @param cmd a non-null command to create a container
* @throws TargetException
*/
public static void configureOptions( Map options, CreateContainerCmd cmd )
throws TargetException {
Logger logger = Logger.getLogger( DockerUtils.class.getName());
// Basically, we had two choices:
// 1. Map our properties to the Java REST API.
// 2. By-pass it and send our custom JSon object.
//
// The second option is much more complicated.
// So, we use Java reflection and some hacks to match Docker properties
// with the setter methods available in the API. The API remains in charge
// of generating the right JSon objects.
Map> hackedSetterNames = new HashMap<> ();
// List known types
List> types = new ArrayList<> ();
types.add( String.class );
types.add( String[].class );
types.add( long.class );
types.add( Long.class );
types.add( int.class );
types.add( Integer.class );
types.add( boolean.class );
types.add( Boolean.class );
types.add( Capability[].class );
// Deal with the options
for( Map.Entry entry : options.entrySet()) {
String optionValue = entry.getValue();
// Now, guess what option to set
String methodName = entry.getKey().replace( "-", " " ).trim();
methodName = WordUtils.capitalize( methodName );
methodName = methodName.replace( " ", "" );
methodName = "with" + methodName;
Method _m = null;
for( Method m : cmd.getClass().getMethods()) {
boolean sameMethod = methodName.equalsIgnoreCase( m.getName());
boolean methodWithAlias = hackedSetterNames.containsKey( methodName )
&& hackedSetterNames.get( methodName ).contains( m.getName());
if( sameMethod || methodWithAlias ) {
// Only one parameter?
if( m.getParameterTypes().length != 1 ) {
logger.warning( "A method was found for " + entry.getKey() + " but it does not have the right number of parameters." );
continue;
}
// The right type?
if( ! types.contains( m.getParameterTypes()[ 0 ])) {
// Since Docker-java 3.x, there are two methods to set cap-add and cap-drop.
// One takes an array as parameter, the other takes a list.
logger.warning(
"A method was found for " + entry.getKey() + " but it does not have the right parameter type. "
+ "Skipping it. You may want to add a feature request." );
continue;
}
// That's probably the right one.
_m = m;
break;
}
}
// Handle errors
if( _m == null )
throw new TargetException( "Nothing matched the " + entry.getKey() + " option in the REST API. Please, report it." );
// Try to set the option in the REST client
try {
Object o = prepareParameter( optionValue, _m.getParameterTypes()[ 0 ]);
_m.invoke( cmd, o );
} catch( ReflectiveOperationException | IllegalArgumentException e ) {
throw new TargetException( "Option " + entry.getKey() + " could not be set." );
}
}
}
/**
* Prepares the parameter to pass it to the REST API.
* @param rawValue the raw value, as a string
* @param clazz the class associated with the input parameter
* @return the object, converted to the right class
* @throws TargetException
*/
public static Object prepareParameter( String rawValue, Class> clazz ) throws TargetException {
// Simple types
Object result;
if( clazz == int.class || clazz == Integer.class )
result = Integer.parseInt( rawValue );
else if( clazz == long.class || clazz == Long.class )
result = Long.parseLong( rawValue );
else if( clazz == boolean.class || clazz == Boolean.class )
result = Boolean.parseBoolean( rawValue );
// Arrays of string
else if( clazz == String[].class ) {
List parts = Utils.splitNicely( rawValue, "," );
result = parts.toArray( new String[ parts.size()]);
}
// Capabilities
else if( clazz == Capability[].class ) {
List caps = new ArrayList<> ();
for( String s : Utils.splitNicely( rawValue, "," )) {
try {
caps.add( Capability.valueOf( s ));
} catch( Exception e ) {
throw new TargetException( "Unknown capability: " + s );
}
}
result = caps.toArray( new Capability[ caps.size()]);
}
// Default: keep the string
else
result = rawValue;
return result;
}
/**
* Handles Boolean values.
*
* Docker-java 3.x annotates state methods with "@CheckNotNull".
* So, we must verify the state attributes are not null (why Boolean instead of boolean?!).
*
*
* @param bool a Boolean value
* @return the boolean value, or false
if it was null
*/
public static boolean extractBoolean( Boolean bool ) {
return bool != null ? bool.booleanValue() : false;
}
/**
* A constant linked to {@link #findDefaultImageVersion(String)}.
*/
public static final String LATEST = "latest";
/**
* Finds the version of the default Docker image.
* @param rawVersion the raw version (e.g. ManifestUtils.findBundleVersion())
* @return {@value #LATEST} if the raw version is null or a snapshot, a specific version otherwise
*/
public static String findDefaultImageVersion( String rawVersion ) {
String rbcfVersion = rawVersion;
if( rbcfVersion == null
|| rbcfVersion.toLowerCase().endsWith( "snapshot" ))
rbcfVersion = LATEST;
return rbcfVersion;
}
/**
* Builds a container name.
* @param scopedInstancePath a scoped instance path
* @param applicationName an application name
* @return a non-null string
*/
public static String buildContainerNameFrom( String scopedInstancePath, String applicationName ) {
String containerName = scopedInstancePath + "_from_" + applicationName;
containerName = containerName.replaceFirst( "^/", "" ).replace( "/", "-" ).replaceAll( "\\s+", "_" );
// Prevent container names from being too long (see #480)
if( containerName.length() > 61 )
containerName = containerName.substring( 0, 61 );
return containerName;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy