All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.gravitee.gateway.services.sync.process.kubernetes.fetcher.ConfigMapEventFetcher Maven / Gradle / Ivy

There is a newer version: 4.5.2
Show newest version
/*
 * Copyright © 2015 The Gravitee team (http://gravitee.io)
 *
 * 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.gravitee.gateway.services.sync.process.kubernetes.fetcher;

import static io.gravitee.repository.management.model.Event.EventProperties.API_ID;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.gravitee.definition.model.DefinitionVersion;
import io.gravitee.kubernetes.client.KubernetesClient;
import io.gravitee.kubernetes.client.api.LabelSelector;
import io.gravitee.kubernetes.client.api.WatchQuery;
import io.gravitee.kubernetes.client.config.KubernetesConfig;
import io.gravitee.kubernetes.client.model.v1.ConfigMap;
import io.gravitee.kubernetes.client.model.v1.Event;
import io.gravitee.repository.management.model.EventType;
import io.gravitee.repository.management.model.LifecycleState;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Maybe;
import java.util.*;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author Guillaume LAMIRAND (guillaume.lamirand at graviteesource.com)
 * @author GraviteeSource Team
 */
@Slf4j
@RequiredArgsConstructor
public class ConfigMapEventFetcher {

    private static final String LABEL_MANAGED_BY = "managed-by";
    private static final String LABEL_GIO_TYPE = "gio-type";
    protected static final String GRAVITEE_IO = "gravitee.io";
    protected static final String API_DEFINITION_V1_ALPHA1 = "v1alpha1";
    protected static final String APIDEFINITIONS_TYPE = "apidefinitions.gravitee.io";
    protected static final String DATA_ENVIRONMENT_ID = "environmentId";
    protected static final String DATA_API_DEFINITION_VERSION = "apiDefinitionVersion";
    protected static final String DATA_DEFINITION = "definition";
    private static final int RETRY_DELAY_MILLIS = 10000;
    private final KubernetesClient client;
    private final String[] namespaces;
    private final ObjectMapper objectMapper;

    public int bulkEvents() {
        return 1;
    }

    public Flowable> fetchLatest() {
        return watchConfigMaps()
            .flatMapMaybe(configMapEvent ->
                convertTo(configMapEvent)
                    .onErrorResumeNext(throwable -> {
                        log.warn("Error occurred while handling event. Ignoring.", throwable);
                        return Maybe.empty();
                    })
            )
            .buffer(bulkEvents());
    }

    private Flowable> watchConfigMaps() {
        List namespacesAsList = getNamespacesAsList();
        if (namespacesAsList.contains("ALL")) {
            return watchConfigMaps(null);
        }
        return Flowable.fromIterable(namespacesAsList).flatMap(this::watchConfigMaps);
    }

    private Flowable> watchConfigMaps(final String namespace) {
        return client
            .watch(
                WatchQuery
                    .configMaps()
                    .namespace(namespace)
                    .labelSelector(LabelSelector.equals(LABEL_MANAGED_BY, GRAVITEE_IO))
                    .labelSelector(LabelSelector.equals(LABEL_GIO_TYPE, APIDEFINITIONS_TYPE))
                    .build()
            )
            .retryWhen(errors -> errors.delay(RETRY_DELAY_MILLIS, TimeUnit.MILLISECONDS));
    }

    public Maybe convertTo(final Event configMapEvent) {
        ConfigMap configMap = configMapEvent.getObject();
        try {
            final String definition = configMap.getData().get(DATA_DEFINITION);

            // Extract the API definition version from the data. Consider it to be V2 to keep backward compatibility.
            final String apiDefinitionVersion = Optional
                .ofNullable(configMap.getData().get(DATA_API_DEFINITION_VERSION))
                .orElse(DefinitionVersion.V2.getLabel());

            if (definition != null) {
                String apiId;
                DefinitionVersion definitionVersion = DefinitionVersion.valueOfLabel(apiDefinitionVersion);

                if (definitionVersion == DefinitionVersion.V2) {
                    io.gravitee.definition.model.Api apiDefinition = objectMapper.readValue(
                        definition,
                        io.gravitee.definition.model.Api.class
                    );
                    apiId = apiDefinition.getId();
                    definitionVersion = apiDefinition.getDefinitionVersion();
                } else if (definitionVersion == DefinitionVersion.V4) {
                    io.gravitee.definition.model.v4.Api apiDefinition = objectMapper.readValue(
                        definition,
                        io.gravitee.definition.model.v4.Api.class
                    );
                    apiId = apiDefinition.getId();
                    definitionVersion = apiDefinition.getDefinitionVersion();
                } else {
                    return Maybe.error(new RuntimeException("ApiDefinitionVersion is missing for this configmap: " + definition));
                }

                // Need to deserialize api definition in order to recreate a regular Event which can be handled by the ApiSynchronizer.
                final io.gravitee.repository.management.model.Event event = new io.gravitee.repository.management.model.Event();
                event.setProperties(Collections.singletonMap(API_ID.getValue(), apiId));
                event.setCreatedAt(new Date());

                final io.gravitee.repository.management.model.Api api = new io.gravitee.repository.management.model.Api();
                api.setEnvironmentId(configMap.getData().get(DATA_ENVIRONMENT_ID));
                api.setDefinition(definition);
                api.setDefinitionVersion(definitionVersion);
                api.setId(apiId);

                switch (configMapEvent.getType()) {
                    case "ADDED":
                    case "MODIFIED":
                        event.setType(EventType.PUBLISH_API);
                        api.setLifecycleState(LifecycleState.STARTED);
                        break;
                    case "DELETED":
                        event.setType(EventType.UNPUBLISH_API);
                        api.setLifecycleState(LifecycleState.STOPPED);
                        break;
                    default:
                        log.error("Unsupported configMap event type {}.", configMapEvent.getType());
                }

                event.setPayload(objectMapper.writeValueAsString(api));
                return Maybe.just(event);
            }
        } catch (Exception ex) {
            // Log the error and ignore this event.
            log.error("Unable to extract api definition from config map.", ex);
        }
        return Maybe.empty();
    }

    private List getNamespacesAsList() {
        if (namespaces == null || namespaces.length == 0) {
            // By default, we will only watch configmaps in the current namespace
            return List.of(KubernetesConfig.getInstance().getCurrentNamespace());
        }
        return Arrays.asList(namespaces);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy