/** * Copyright 2005-2016 Red Hat, Inc. * * Red Hat licenses this file to you 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.maven; import com.fasterxml.jackson.core.JsonProcessingException; import io.fabric8.kubernetes.api.Controller; import io.fabric8.kubernetes.api.KubernetesHelper; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesList; import io.fabric8.kubernetes.api.model.KubernetesResource; import io.fabric8.kubernetes.api.model.ObjectReference; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.ReplicationController; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.internal.HasMetadataComparator; 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.api.model.RouteTargetReference; import io.fabric8.openshift.api.model.RouteTargetReferenceBuilder; import io.fabric8.openshift.api.model.Template; import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.utils.Files; import io.fabric8.utils.Strings; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import static io.fabric8.kubernetes.api.KubernetesHelper.loadJson; /** * Applies the Kubernetes JSON to a namespace in a kubernetes environment */ @Mojo(name = "apply", requiresDependencyResolution = ResolutionScope.RUNTIME, defaultPhase = LifecyclePhase.INSTALL) public class ApplyMojo extends AbstractFabric8Mojo { /** * Used to look up Artifacts in the remote repository. */ @Component protected ArtifactResolver resolver; @Parameter(defaultValue = "${project}", readonly = true, required = false) private MavenProject project; /** * Location of the localRepository repository. */ @Parameter(defaultValue = "${localRepository}", readonly = true, required = true) private ArtifactRepository localRepository; /** * List of Remote Repositories used by the resolver */ @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true) protected List<ArtifactRepository> remoteRepositories; /** * Should we create new kubernetes resources? */ @Parameter(property = "fabric8.apply.create", defaultValue = "true") private boolean createNewResources; /** * Should we use rolling upgrades to apply changes? */ @Parameter(property = "fabric8.rolling", defaultValue = "false") private boolean rollingUpgrades; /** * Should we fail if there is no kubernetes json */ @Parameter(property = "fabric8.apply.failOnNoKubernetesJson", defaultValue = "false") private boolean failOnNoKubernetesJson; /** * In services only mode we only process services so that those can be recursively created/updated first * before creating/updating any pods and replication controllers */ @Parameter(property = "fabric8.apply.servicesOnly", defaultValue = "false") private boolean servicesOnly; /** * Do we want to ignore services. This is particularly useful when in recreate mode * to let you easily recreate all the ReplicationControllers and Pods but leave any service * definitions alone to avoid changing the clusterIP addresses and breaking existing pods using * the service. */ @Parameter(property = "fabric8.apply.ignoreServices", defaultValue = "false") private boolean ignoreServices; /** * Process templates locally in Java so that we can apply OpenShift templates on any Kubernetes environment */ @Parameter(property = "fabric8.apply.processTemplatesLocally", defaultValue = "false") private boolean processTemplatesLocally; /** * Should we delete all the pods if we update a Replication Controller */ @Parameter(property = "fabric8.apply.deletePods", defaultValue = "true") private boolean deletePodsOnReplicationControllerUpdate; /** * Do we want to ignore OAuthClients which are already running?. OAuthClients are shared across namespaces * so we should not try to update or create/delete global oauth clients */ @Parameter(property = "fabric8.apply.ignoreRunningOAuthClients", defaultValue = "true") private boolean ignoreRunningOAuthClients; /** * Should we create routes for any services which don't already have them. */ @Parameter(property = "fabric8.apply.createRoutes", defaultValue = "true") private boolean createRoutes; /** * The folder we should store any temporary json files or results */ @Parameter(property = "fabric8.apply.jsonLogDir", defaultValue = "${basedir}/target/fabric8/applyJson") private File jsonLogDir; @Override public void execute() throws MojoExecutionException, MojoFailureException { File json = getKubernetesJson(); if (!Files.isFile(json)) { if (Files.isFile(kubernetesSourceJson)) { json = kubernetesSourceJson; } else { if (failOnNoKubernetesJson) { throw new MojoFailureException("No such generated kubernetes json file: " + json + " or source json file " + kubernetesSourceJson); } else { getLog().warn("No such generated kubernetes json file: " + json + " or source json file " + kubernetesSourceJson + " for this project so ignoring"); return; } } } KubernetesClient kubernetes = getKubernetes(); if (kubernetes.getMasterUrl() == null || Strings.isNullOrBlank(kubernetes.getMasterUrl().toString())) { throw new MojoFailureException("Cannot find Kubernetes master URL"); } getLog().info("Using kubernetes at: " + kubernetes.getMasterUrl() + " in namespace " + getNamespace()); getLog().info("Kubernetes JSON: " + json); try { Controller controller = createController(); controller.setAllowCreate(createNewResources); controller.setServicesOnlyMode(servicesOnly); controller.setIgnoreServiceMode(ignoreServices); controller.setLogJsonDir(jsonLogDir); controller.setBasedir(getRootProjectFolder()); controller.setIgnoreRunningOAuthClients(ignoreRunningOAuthClients); controller.setProcessTemplatesLocally(processTemplatesLocally); controller.setDeletePodsOnReplicationControllerUpdate(deletePodsOnReplicationControllerUpdate); controller.setRollingUpgrade(rollingUpgrades); controller.setRollingUpgradePreserveScale(isRollingUpgradePreserveScale()); boolean openShift = KubernetesHelper.isOpenShift(kubernetes); if (openShift) { getLog().info("OpenShift platform detected"); } else { disableOpenShiftFeatures(controller); } String fileName = json.getName(); Object dto = KubernetesHelper.loadJson(json); if (dto == null) { throw new MojoFailureException("Cannot load kubernetes json: " + json); } // lets check we have created the namespace String namespace = getNamespace(); controller.applyNamespace(namespace); controller.setNamespace(namespace); if (dto instanceof Template) { Template template = (Template) dto; dto = applyTemplates(template, kubernetes, controller, fileName); } Set<KubernetesResource> resources = new LinkedHashSet<>(); if (!combineDependencies) { for (File dependency : getDependencies()) { getLog().info("Found dependency: " + dependency); loadDependency(getLog(), resources, dependency); } } Set<HasMetadata> entities = new TreeSet<>(new HasMetadataComparator()); for (KubernetesResource resource : resources) { entities.addAll(KubernetesHelper.toItemList(resource)); } entities.addAll(KubernetesHelper.toItemList(dto)); if (createRoutes) { createRoutes(kubernetes, entities); } addEnvironmentAnnotations(entities); //Apply all items for (HasMetadata entity : entities) { if (entity instanceof Pod) { Pod pod = (Pod) entity; controller.applyPod(pod, fileName); } else if (entity instanceof Service) { Service service = (Service) entity; controller.applyService(service, fileName); } else if (entity instanceof ReplicationController) { ReplicationController replicationController = (ReplicationController) entity; controller.applyReplicationController(replicationController, fileName); } else if (entity != null) { controller.apply(entity, fileName); } } } catch (Exception e) { throw new MojoExecutionException(e.getMessage(), e); } } public boolean isRollingUpgrades() { return rollingUpgrades; } public void setRollingUpgrades(boolean rollingUpgrades) { this.rollingUpgrades = rollingUpgrades; } public boolean isRollingUpgradePreserveScale() { return false; } @Override public MavenProject getProject() { return project; } /** * Lets disable OpenShift-only features if we are not running on OpenShift */ protected void disableOpenShiftFeatures(Controller controller) { // TODO we could check if the Templates service is running and if so we could still support templates? this.processTemplatesLocally = true; this.createRoutes = false; controller.setSupportOAuthClients(false); controller.setProcessTemplatesLocally(true); } protected Object applyTemplates(Template template, KubernetesClient kubernetes, Controller controller, String fileName) throws Exception { KubernetesHelper.setNamespace(template, getNamespace()); overrideTemplateParameters(template); return controller.applyTemplate(template, fileName); } protected void createRoutes(KubernetesClient kubernetes, Collection<HasMetadata> collection) { String routeDomainPostfix = this.routeDomain; Log log = getLog(); String namespace = getNamespace(); // lets get the routes first to see if we should bother try { RouteList routes = kubernetes.adapt(OpenShiftClient.class).routes().inNamespace(namespace).list(); if (routes != null) { routes.getItems(); } } catch (Exception e) { log.warn("Cannot load OpenShift Routes; maybe not connected to an OpenShift platform? " + e, e); return; } List<Route> routes = new ArrayList<>(); for (Object object : collection) { if (object instanceof Service) { Service service = (Service) object; Route route = createRouteForService(routeDomainPostfix, namespace, service, log); if (route != null) { routes.add(route); } } } collection.addAll(routes); } public static Route createRouteForService(String routeDomainPostfix, String namespace, Service service, Log log) { Route route = null; String id = KubernetesHelper.getName(service); if (Strings.isNotBlank(id) && shouldCreateRouteForService(log, service, id)) { route = new Route(); String routeId = id; KubernetesHelper.setName(route, namespace, routeId); RouteSpec routeSpec = new RouteSpec(); RouteTargetReference objectRef = new RouteTargetReferenceBuilder().withName(id).build(); // objectRef.setNamespace(namespace); routeSpec.setTo(objectRef); if (!Strings.isNullOrBlank(routeDomainPostfix)) { String host = Strings.stripSuffix(Strings.stripSuffix(id, "-service"), "."); routeSpec.setHost(host + "." + Strings.stripPrefix(routeDomainPostfix, ".")); } else { routeSpec.setHost(""); } route.setSpec(routeSpec); String json; try { json = KubernetesHelper.toJson(route); } catch (JsonProcessingException e) { json = e.getMessage() + ". object: " + route; } log.debug("Created route: " + json); } return route; } /** * Should we try to create a route for the given service? * <p/> * By default lets ignore the kubernetes services and any service which does not expose ports 80 and 443 * * @return true if we should create an OpenShift Route for this service. */ protected static boolean shouldCreateRouteForService(Log log, Service service, String id) { if ("kubernetes".equals(id) || "kubernetes-ro".equals(id)) { return false; } Set<Integer> ports = KubernetesHelper.getPorts(service); log.debug("Service " + id + " has ports: " + ports); if (ports.size() == 1) { return true; } else { log.info("Not generating route for service " + id + " as only single port services are supported. Has ports: " + ports); return false; } } public static void addConfig(Collection<KubernetesResource> resources, Object resource) { if (resource instanceof KubernetesList) { resources.add((KubernetesList)resource); } else if (resource instanceof Template) { resources.add((Template)resource); } } public static void loadDependency(Log log, Collection<KubernetesResource> resources, File file) throws IOException { if (file.isFile()) { log.debug("Loading file " + file); addConfig(resources, loadJson(file)); } else { File[] children = file.listFiles(); if (children != null) { for (File child : children) { String name = child.getName().toLowerCase(); if (name.endsWith(".json") || name.endsWith(".yaml")) { loadDependency(log, resources, child); } } } } } }