org.cfg4j.source.git.GitConfigurationSource Maven / Gradle / Ivy
Show all versions of cfg4j-git Show documentation
/*
* Copyright 2015 Norbert Potocki ([email protected])
*
* 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 org.cfg4j.source.git;
import static java.util.Objects.requireNonNull;
import org.cfg4j.source.ConfigurationSource;
import org.cfg4j.source.SourceCommunicationException;
import org.cfg4j.source.context.environment.Environment;
import org.cfg4j.source.context.environment.MissingEnvironmentException;
import org.cfg4j.source.context.filesprovider.ConfigFilesProvider;
import org.cfg4j.source.context.propertiesprovider.PropertiesProvider;
import org.cfg4j.source.context.propertiesprovider.PropertiesProviderSelector;
import org.cfg4j.utils.FileUtils;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CreateBranchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* Note: use {@link GitConfigurationSourceBuilder} for building instances of this class.
*
* Read configuration from the remote GIT repository. Keeps a local clone of the repository.
*/
class GitConfigurationSource implements ConfigurationSource, Closeable {
private static final Logger LOG = LoggerFactory.getLogger(GitConfigurationSource.class);
private final BranchResolver branchResolver;
private final PathResolver pathResolver;
private final ConfigFilesProvider configFilesProvider;
private final PropertiesProviderSelector propertiesProviderSelector;
private final String repositoryURI;
private final Path tmpPath;
private final String tmpRepoPrefix;
private Git clonedRepo;
private Path clonedRepoPath;
private boolean initialized;
/**
* Note: use {@link GitConfigurationSourceBuilder} for building instances of this class.
*
* Read configuration from the remote GIT repository residing at {@code repositoryURI}. Keeps a local
* clone of the repository in the {@code tmpRepoPrefix} directory under {@code tmpPath} path.
* Uses provided {@code branchResolver} and {@code pathResolver} for branch and path resolution.
*
* @param repositoryURI URI to the remote git repository
* @param tmpPath path to the tmp directory
* @param tmpRepoPrefix prefix for the name of the local directory keeping the repository clone
* @param branchResolver {@link BranchResolver} used for extracting git branch from an {@link Environment}
* @param pathResolver {@link PathResolver} used for extracting git path from an {@link Environment}
* @param configFilesProvider {@link ConfigFilesProvider} used for determining which files in repository should be read
* @param propertiesProviderSelector selector used for choosing {@link PropertiesProvider} based on a configuration file extension
* as config files
*/
GitConfigurationSource(String repositoryURI, Path tmpPath, String tmpRepoPrefix, BranchResolver branchResolver,
PathResolver pathResolver, ConfigFilesProvider configFilesProvider,
PropertiesProviderSelector propertiesProviderSelector) {
this.branchResolver = requireNonNull(branchResolver);
this.pathResolver = requireNonNull(pathResolver);
this.configFilesProvider = requireNonNull(configFilesProvider);
this.propertiesProviderSelector = requireNonNull(propertiesProviderSelector);
this.repositoryURI = requireNonNull(repositoryURI);
this.tmpPath = requireNonNull(tmpPath);
this.tmpRepoPrefix = requireNonNull(tmpRepoPrefix);
initialized = false;
}
@Override
public Properties getConfiguration(Environment environment) {
if (!initialized) {
throw new IllegalStateException("Configuration source has to be successfully initialized before you request configuration.");
}
try {
checkoutToBranch(branchResolver.getBranchNameFor(environment));
} catch (GitAPIException e) {
throw new MissingEnvironmentException(environment.getName(), e);
}
Properties properties = new Properties();
List paths = new ArrayList<>();
for (Path path : configFilesProvider.getConfigFiles()) {
paths.add(clonedRepoPath.resolve(pathResolver.getPathFor(environment)).resolve(path));
}
for (Path path : paths) {
try (InputStream input = new FileInputStream(path.toFile())) {
PropertiesProvider provider = propertiesProviderSelector.getProvider(path.getFileName().toString());
properties.putAll(provider.getProperties(input));
} catch (IOException e) {
throw new IllegalStateException("Unable to load properties from application.properties file", e);
}
}
return properties;
}
/**
* @throws IllegalStateException when unable to create directories for local repo clone
* @throws SourceCommunicationException when unable to clone repository
*/
@Override
public void init() {
LOG.info("Initializing " + GitConfigurationSource.class + " pointing to " + repositoryURI);
try {
clonedRepoPath = Files.createTempDirectory(tmpPath, tmpRepoPrefix);
// This folder can't exist or JGit will throw NPE on clone
Files.delete(clonedRepoPath);
} catch (IOException e) {
throw new IllegalStateException("Unable to create local clone directory: " + tmpRepoPrefix, e);
}
try {
clonedRepo = Git.cloneRepository()
.setURI(repositoryURI)
.setDirectory(clonedRepoPath.toFile())
.call();
} catch (GitAPIException e) {
throw new SourceCommunicationException("Unable to clone repository: " + repositoryURI, e);
}
initialized = true;
}
@Override
public void reload() {
try {
LOG.debug("Reloading configuration by pulling changes");
clonedRepo.pull().call();
} catch (GitAPIException e) {
initialized = false;
throw new IllegalStateException("Unable to pull from remote repository", e);
}
}
@Override
public void close() throws IOException {
LOG.debug("Closing local repository: " + clonedRepoPath);
clonedRepo.close();
new FileUtils().deleteDir(clonedRepoPath);
}
private void checkoutToBranch(String branch) throws GitAPIException {
CheckoutCommand checkoutCommand = clonedRepo.checkout()
.setCreateBranch(false)
.setName(branch);
List refList = clonedRepo.branchList().call();
if (!anyRefMatches(refList, branch)) {
checkoutCommand = checkoutCommand
.setCreateBranch(true)
.setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
.setStartPoint("origin/" + branch);
}
checkoutCommand
.call();
}
private boolean anyRefMatches(List refList, String branch) {
for (Ref ref : refList) {
if (ref.getName().replace("refs/heads/", "").equals(branch)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "GitConfigurationSource{" +
"clonedRepo=" + clonedRepo +
", clonedRepoPath=" + clonedRepoPath +
", branchResolver=" + branchResolver +
", pathResolver=" + pathResolver +
", configFilesProvider=" + configFilesProvider +
'}';
}
}