LinkCommand.java

/*
 *
 * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 *  This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 */

package org.entando.kubernetes.controller.link.support;

import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.extensions.Ingress;
import java.util.Objects;
import java.util.Optional;
import org.entando.kubernetes.controller.spi.client.SerializedEntandoResource;
import org.entando.kubernetes.controller.spi.common.EntandoOperatorSpiConfig;
import org.entando.kubernetes.controller.spi.common.NameUtils;
import org.entando.kubernetes.controller.spi.container.ProvidedSsoCapability;
import org.entando.kubernetes.controller.spi.deployable.SsoConnectionInfo;
import org.entando.kubernetes.controller.support.client.ServiceClient;
import org.entando.kubernetes.controller.support.client.SimpleK8SClient;
import org.entando.kubernetes.controller.support.client.SimpleKeycloakClient;
import org.entando.kubernetes.controller.support.creators.IngressPathCreator;
import org.entando.kubernetes.controller.support.creators.ServiceCreator;
import org.entando.kubernetes.model.common.CustomResourceReference;
import org.entando.kubernetes.model.common.EntandoCustomResource;
import org.entando.kubernetes.model.common.ServerStatus;

public class LinkCommand {

    private final EntandoCustomResource linkResource;
    private LinkInfo linkInfo;
    private final IngressPathCreator ingressCreator;
    private final ServerStatus status;
    private final Linkable linkable;
    private Ingress sourceIngress;

    public LinkCommand(Linkable linkable) {
        this.linkable = linkable;
        this.linkResource = linkable.getLinkResource();
        this.ingressCreator = new IngressPathCreator(linkable.getLinkResource());
        this.status = new ServerStatus(NameUtils.MAIN_QUALIFIER).withOriginatingCustomResource(linkResource);
    }

    static SerializedEntandoResource resolveResource(SimpleK8SClient<?> k8SClient, EntandoCustomResource linkResource,
            CustomResourceReference source) {
        return k8SClient.entandoResources()
                .loadCustomResource(source.getApiVersion(), source.getKind(),
                        source.getNamespace().orElse(linkResource.getMetadata().getNamespace()),
                        source.getName());
    }

    public ServerStatus execute(SimpleK8SClient<?> k8sClient, SimpleKeycloakClient keycloakClient) {
        this.linkInfo = new LinkInfo(linkable,
                resolveResource(k8sClient, linkable.getLinkResource(), linkable.getSource()),
                resolveResource(k8sClient, linkable.getLinkResource(), linkable.getTarget()));
        status.withOriginatingControllerPod(k8sClient.entandoResources().getNamespace(), EntandoOperatorSpiConfig.getControllerPodName());
        if (this.linkable.getAccessStrategy() == AccessStrategy.SSO) {
            if (usingSameHostname(k8sClient)) {
                status.setServiceName(linkInfo.getSourceServiceName());
                status.setIngressName(linkInfo.getSourceIngressName());
                k8sClient.entandoResources().updateStatus(linkResource, status);
            } else {
                Service service = prepareReachableTargetService(k8sClient);
                status.setServiceName(service.getMetadata().getName());
                k8sClient.entandoResources().updateStatus(linkResource, status);
                Ingress ingress = addMissingIngressPaths(k8sClient, service);
                status.setIngressName(ingress.getMetadata().getName());
                k8sClient.entandoResources().updateStatus(linkResource, status);
            }
            grantSourceAccessToTarget(keycloakClient, k8sClient);
        }
        return this.status;
        //TODO wait for result - when new ingress path is available
    }

    private boolean usingSameHostname(SimpleK8SClient<?> client) {
        Optional<Ingress> targetIngress = linkInfo.getTargetIngressName()
                .map(ingressName -> client.ingresses().loadIngress(linkInfo.getTargetNamespace(), ingressName));
        return targetIngress
                .map(ti -> ti.getSpec().getRules().get(0).getHost().equals(getSourceIngress(client).getSpec().getRules().get(0).getHost()))
                .orElse(Boolean.FALSE);
    }

    private void grantSourceAccessToTarget(SimpleKeycloakClient keycloakClient, SimpleK8SClient<?> client) {
        SsoConnectionInfo keycloakConnectionConfig = new ProvidedSsoCapability(client.entandoResources()
                .loadCapabilityProvisioningResult(linkInfo.getSsoServiceStatus()));
        keycloakClient.login(keycloakConnectionConfig.getBaseUrlToUse(), keycloakConnectionConfig.getUsername(),
                keycloakConnectionConfig.getPassword());
        linkInfo.getPermissions().forEach(linkPermission ->
                keycloakClient.assignRoleToClientServiceAccount(
                        linkInfo.getSsoRealm(),
                        linkPermission.getSourceClientId(),
                        linkPermission));
    }

    private Ingress addMissingIngressPaths(SimpleK8SClient<?> k8sClient, Service service) {
        Ingress ingress = getSourceIngress(k8sClient);
        return ingressCreator.addMissingHttpPaths(k8sClient.ingresses(), linkInfo.getPathsOnPorts(), ingress, service, status);
    }

    private Ingress getSourceIngress(SimpleK8SClient<?> k8sClient) {
        this.sourceIngress = Objects.requireNonNullElseGet(this.sourceIngress, () -> k8sClient.ingresses()
                .loadIngress(linkInfo.getSourceNamespace(), linkInfo.getSourceIngressName()));
        return this.sourceIngress;
    }

    private Service prepareReachableTargetService(SimpleK8SClient<?> k8sClient) {
        ServiceClient services = k8sClient.services();
        final Service targetService = services.loadService(linkInfo.getTargetResource(), linkInfo.getTargetServiceName());
        if (linkInfo.getSourceNamespace().equals(linkInfo.getTargetNamespace())) {
            return targetService;
        } else {
            return new ServiceCreator(linkResource, targetService).newDelegatingService(services, linkInfo);
        }
    }

    public ServerStatus getStatus() {
        return status;
    }
}