Java tutorial
/** * Copyright (C) 2016 Red Hat, Inc. * * 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 io.fabric8.jenkins.openshiftsync; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ReplicationController; import io.fabric8.kubernetes.api.model.ReplicationControllerStatus; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceSpec; import io.fabric8.kubernetes.client.Config; import io.fabric8.openshift.api.model.Build; import io.fabric8.openshift.api.model.BuildConfig; import io.fabric8.openshift.api.model.BuildConfigSpec; import io.fabric8.openshift.api.model.BuildSource; import io.fabric8.openshift.api.model.BuildStatus; import io.fabric8.openshift.api.model.GitBuildSource; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RouteList; import io.fabric8.openshift.api.model.RouteSpec; import io.fabric8.openshift.client.DefaultOpenShiftClient; import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.openshift.client.OpenShiftConfigBuilder; import org.apache.commons.lang.StringUtils; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import static io.fabric8.jenkins.openshiftsync.BuildPhases.NEW; import static io.fabric8.jenkins.openshiftsync.BuildPhases.PENDING; import static io.fabric8.jenkins.openshiftsync.BuildPhases.RUNNING; import static io.fabric8.jenkins.openshiftsync.Constants.OPENSHIFT_DEFAULT_NAMESPACE; import static java.util.logging.Level.FINE; /** */ public class OpenShiftUtils { private final static Logger logger = Logger.getLogger(OpenShiftUtils.class.getName()); private static OpenShiftClient openShiftClient; private static final DateTimeFormatter dateFormatter = ISODateTimeFormat.dateTimeNoMillis(); /** * Initializes an {@link OpenShiftClient} * * @param serverUrl the optional URL of where the OpenShift cluster API server is running */ public synchronized static void initializeOpenShiftClient(String serverUrl) { OpenShiftConfigBuilder configBuilder = new OpenShiftConfigBuilder(); if (serverUrl != null && !serverUrl.isEmpty()) { configBuilder.withMasterUrl(serverUrl); } Config config = configBuilder.build(); openShiftClient = new DefaultOpenShiftClient(config); } public synchronized static OpenShiftClient getOpenShiftClient() { return openShiftClient; } public synchronized static void shutdownOpenShiftClient() { if (openShiftClient != null) { openShiftClient.close(); openShiftClient = null; } } /** * Checks if a {@link BuildConfig} relates to a Jenkins build * * @param bc the BuildConfig * @return true if this is an OpenShift BuildConfig which should be mirrored to * a Jenkins Job */ public static boolean isJenkinsBuildConfig(BuildConfig bc) { if (BuildConfigToJobMapper.JENKINS_PIPELINE_BUILD_STRATEGY .equalsIgnoreCase(bc.getSpec().getStrategy().getType()) && bc.getSpec().getStrategy().getJenkinsPipelineStrategy() != null) { return true; } ObjectMeta metadata = bc.getMetadata(); if (metadata != null) { Map<String, String> annotations = metadata.getAnnotations(); if (annotations != null) { if (annotations.get("fabric8.link.jenkins.job/label") != null) { return true; } } } return false; } /** * Finds the Jenkins job name for the given {@link BuildConfig}. * * @param bc the BuildConfig * @return the jenkins job name for the given BuildConfig */ public static String jenkinsJobName(BuildConfig bc) { String namespace = bc.getMetadata().getNamespace(); String name = bc.getMetadata().getName(); return jenkinsJobName(namespace, name); } /** * Creates the Jenkins Job name for the given buildConfigName * * @param namespace the namespace of the build * @param buildConfigName the name of the {@link BuildConfig} in in the namespace * @return the jenkins job name for the given namespace and name */ public static String jenkinsJobName(String namespace, String buildConfigName) { return namespace + "-" + buildConfigName; } /** * Finds the Jenkins job display name for the given {@link BuildConfig}. * * @param bc the BuildConfig * @return the jenkins job display name for the given BuildConfig */ public static String jenkinsJobDisplayName(BuildConfig bc) { String namespace = bc.getMetadata().getNamespace(); String name = bc.getMetadata().getName(); return jenkinsJobDisplayName(namespace, name); } /** * Creates the Jenkins Job display name for the given buildConfigName * * @param namespace the namespace of the build * @param buildConfigName the name of the {@link BuildConfig} in in the namespace * @return the jenkins job display name for the given namespace and name */ public static String jenkinsJobDisplayName(String namespace, String buildConfigName) { return namespace + "/" + buildConfigName; } /** * Gets the current namespace running Jenkins inside or returns a reasonable default * * @param configuredNamespaces the optional configured namespace(s) * @param client the OpenShift client * @return the default namespace using either the configuration value, the default namespace on the client or "default" */ public static String[] getNamespaceOrUseDefault(String[] configuredNamespaces, OpenShiftClient client) { String[] namespaces = configuredNamespaces; if (namespaces == null || namespaces.length == 0) { namespaces = new String[] { client.getNamespace() }; if (StringUtils.isBlank(namespaces[0])) { namespaces = new String[] { OPENSHIFT_DEFAULT_NAMESPACE }; } } return namespaces; } /** * Returns the public URL of the given service * * @param openShiftClient the OpenShiftClient to use * @param defaultProtocolText the protocol text part of a URL such as <code>http://</code> * @param namespace the Kubernetes namespace * @param serviceName the service name * @return the external URL of the service */ public static String getExternalServiceUrl(OpenShiftClient openShiftClient, String defaultProtocolText, String namespace, String serviceName) { try { RouteList routes = openShiftClient.routes().inNamespace(namespace).list(); for (Route route : routes.getItems()) { RouteSpec spec = route.getSpec(); if (spec != null && spec.getTo() != null && "Service".equalsIgnoreCase(spec.getTo().getKind()) && serviceName.equalsIgnoreCase(spec.getTo().getName())) { String host = spec.getHost(); if (host != null && host.length() > 0) { if (spec.getTls() != null) { return "https://" + host; } return "http://" + host; } } } } catch (Exception e) { logger.log(Level.WARNING, "Could not find Route for service " + namespace + "/" + serviceName + ". " + e, e); } // lets try the portalIP instead try { Service service = openShiftClient.services().inNamespace(namespace).withName(serviceName).get(); if (service != null) { ServiceSpec spec = service.getSpec(); if (spec != null) { String host = spec.getClusterIP(); if (host != null && host.length() > 0) { return defaultProtocolText + host; } } } } catch (Exception e) { logger.log(Level.WARNING, "Could not find Route for service " + namespace + "/" + serviceName + ". " + e, e); } // lets default to the service DNS name return defaultProtocolText + serviceName; } /** * Calculates the external URL to access Jenkins * * @param namespace the namespace Jenkins is runing inside * @param openShiftClient the OpenShift client * @return the external URL to access Jenkins */ public static String getJenkinsURL(OpenShiftClient openShiftClient, String namespace) { return getExternalServiceUrl(openShiftClient, "http://", namespace, "jenkins"); } /** * Lazily creates the GitSource if need be then updates the git URL * @param buildConfig the BuildConfig to update * @param gitUrl the URL to the git repo * @param ref the git ref (commit/branch/etc) for the build */ public static void updateGitSourceUrl(BuildConfig buildConfig, String gitUrl, String ref) { BuildConfigSpec spec = buildConfig.getSpec(); if (spec == null) { spec = new BuildConfigSpec(); buildConfig.setSpec(spec); } BuildSource source = spec.getSource(); if (source == null) { source = new BuildSource(); spec.setSource(source); } source.setType("Git"); GitBuildSource gitSource = source.getGit(); if (gitSource == null) { gitSource = new GitBuildSource(); source.setGit(gitSource); } gitSource.setUri(gitUrl); gitSource.setRef(ref); } public static void updateOpenShiftBuildPhase(Build build, String phase) { logger.log(FINE, "setting build to {0} in namespace {1}/{2}", new Object[] { phase, build.getMetadata().getNamespace(), build.getMetadata().getName() }); getOpenShiftClient().builds().inNamespace(build.getMetadata().getNamespace()) .withName(build.getMetadata().getName()).edit().editStatus().withPhase(phase).endStatus().done(); } /** * Maps a Jenkins Job name to an ObjectShift BuildConfig name * * @return the namespaced name for the BuildConfig * @param jobName the job to associate to a BuildConfig name * @param namespace the default namespace that Jenkins is running inside */ public static NamespaceName buildConfigNameFromJenkinsJobName(String jobName, String namespace) { // TODO lets detect the namespace separator in the jobName for cases where a jenkins is used for // BuildConfigs in multiple namespaces? return new NamespaceName(namespace, jobName); } public static long parseResourceVersion(HasMetadata obj) { return parseResourceVersion(obj.getMetadata().getResourceVersion()); } public static long parseResourceVersion(String resourceVersion) { try { return Long.parseLong(resourceVersion); } catch (NumberFormatException e) { return 0; } } public static String formatTimestamp(long timestamp) { return dateFormatter.print(new DateTime(timestamp)); } public static long parseTimestamp(String timestamp) { return dateFormatter.parseMillis(timestamp); } public static boolean isResourceWithoutStateEqual(HasMetadata oldObj, HasMetadata newObj) { try { byte[] oldDigest = MessageDigest.getInstance("MD5") .digest(dumpWithoutRuntimeStateAsYaml(oldObj).getBytes(StandardCharsets.UTF_8)); byte[] newDigest = MessageDigest.getInstance("MD5") .digest(dumpWithoutRuntimeStateAsYaml(newObj).getBytes(StandardCharsets.UTF_8)); return Arrays.equals(oldDigest, newDigest); } catch (NoSuchAlgorithmException | JsonProcessingException e) { throw new RuntimeException(e); } } public static String dumpWithoutRuntimeStateAsYaml(HasMetadata obj) throws JsonProcessingException { ObjectMapper statelessMapper = new ObjectMapper(new YAMLFactory()); statelessMapper.addMixInAnnotations(ObjectMeta.class, ObjectMetaMixIn.class); statelessMapper.addMixInAnnotations(ReplicationController.class, StatelessReplicationControllerMixIn.class); return statelessMapper.writeValueAsString(obj); } public static boolean isCancellable(BuildStatus buildStatus) { String phase = buildStatus.getPhase(); return phase.equals(NEW) || phase.equals(PENDING) || phase.equals(RUNNING); } public static boolean isNew(BuildStatus buildStatus) { return buildStatus.getPhase().equals(NEW); } public static boolean isCancelled(BuildStatus status) { return status != null && status.getCancelled() != null && Boolean.TRUE.equals(status.getCancelled()); } abstract class StatelessReplicationControllerMixIn extends ReplicationController { @JsonIgnore private ReplicationControllerStatus status; StatelessReplicationControllerMixIn() { } @JsonIgnore public abstract ReplicationControllerStatus getStatus(); } abstract class ObjectMetaMixIn extends ObjectMeta { @JsonIgnore private String creationTimestamp; @JsonIgnore private String deletionTimestamp; @JsonIgnore private Long generation; @JsonIgnore private String resourceVersion; @JsonIgnore private String selfLink; @JsonIgnore private String uid; ObjectMetaMixIn() { } @JsonIgnore public abstract String getCreationTimestamp(); @JsonIgnore public abstract String getDeletionTimestamp(); @JsonIgnore public abstract Long getGeneration(); @JsonIgnore public abstract String getResourceVersion(); @JsonIgnore public abstract String getSelfLink(); @JsonIgnore public abstract String getUid(); } }