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

org.zalando.baigan.service.github.GitCacheLoader Maven / Gradle / Ivy

package org.zalando.baigan.service.github;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;

import org.eclipse.egit.github.core.RepositoryContents;
import org.eclipse.egit.github.core.RepositoryId;
import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.service.ContentsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zalando.baigan.model.Configuration;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.cache.CacheLoader;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;

import javax.annotation.Nonnull;

import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
import static org.apache.commons.codec.binary.Base64.*;

/**
 * This class implements the {@link CacheLoader} offering Configuration loading
 * from a remote Git repository.
 *
 * @author mchand
 *
 */
public class GitCacheLoader
        extends CacheLoader> {

    private static final Logger LOG = LoggerFactory
            .getLogger(GitCacheLoader.class);

    private String latestSha;

    private GitConfig config;

    private final ListeningExecutorService executorService = listeningDecorator(Executors.newFixedThreadPool(1));
    private ObjectMapper objectMapper = new ObjectMapper().registerModule(new GuavaModule());

    private final ContentsService contentsService;

    private static ContentsService buildContentsService(@Nonnull final GitConfig gitConfig) {
        Objects.requireNonNull(gitConfig, "gitConfig is required");
        final GitHubClient client = new GitHubClient(gitConfig.getGitHost());
        client.setOAuth2Token(gitConfig.getOauthToken());
        return new ContentsService(client);
    }

    public GitCacheLoader(@Nonnull final  GitConfig gitConfig) {
        this(gitConfig, buildContentsService(gitConfig));
    }

    @VisibleForTesting
    GitCacheLoader(GitConfig gitConfig, ContentsService contentsService) {
        this.config = gitConfig;
        this.contentsService = contentsService;
    }

    @Override
    public Map load(String key) throws Exception {
        final RepositoryContents contents = getContentsForFile(key);
        if (contents != null) {
            return updateContent(contents);
        }
        LOG.warn("Failed to load the repository contents for {}", key);
        return ImmutableMap.of();
    }

    private Map updateContent(
            @Nonnull final RepositoryContents contents) {
        final String contentsSha = contents.getSha();
        LOG.info("Loading the new repository contents [ SHA:{} ; NAME:{} ] ",
                contentsSha, contents.getPath());

        final List configurations = getConfigurations(getTextContent(contents));

        latestSha = contentsSha;

        final ImmutableMap.Builder builder = ImmutableMap.builder();
        for (final Configuration each : configurations) {
            builder.put(each.getAlias(), each);
        }
        return builder.build();
    }

    public ListenableFuture> reload(final String key,
            final Map oldValue) throws Exception {
        return createFuture(key, oldValue);
    }

    private ListenableFuture> createFuture(
            final String sourceFile,
            final Map oldValue) {

        final Callable> callable = () -> {
            final RepositoryContents contents = getContentsForFile(sourceFile);
            // If the contents is null, return old value, this is to
            // preserve in case Github is down.
            // If the hash is null which is very unlikely, or it is same as
            // the earlier one, we don't reload it
            if (contents == null || Strings.isNullOrEmpty(contents.getSha())
                    || contents.getSha().equals(latestSha)) {
                return oldValue;
            }
            return updateContent(contents);
        };

        return executorService.submit(callable);
    }

    private RepositoryContents getContentsForFile(final String sourceFile) {

        try {
            final List contents = contentsService
                    .getContents(
                            new RepositoryId(config.getRepoOwner(),
                                    config.getRepoName()),
                            sourceFile, config.getRepoRefs());
            return contents.get(0);
        } catch (Exception e) {
            LOG.warn("Failed to get contents from the Github repository ", e);
        }
        return null;
    }

    private String getTextContent(final RepositoryContents content) {
        final String stringContent = content.getContent();
        return new String(decodeBase64(stringContent.getBytes()));
    }

    private List getConfigurations(final String text) {
        try {
            return objectMapper.readValue(text,
                    new TypeReference>() {
                    });
        } catch (IOException e) {
            LOG.warn(
                    "Exception while deserializing the Configuration from the Github repository contents." +
                            "Please check to see if if matches the Configuration schema at " +
                            "https://github.com/zalando/baigan-config.",
                    e);
        }
        return ImmutableList.of();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy