
com.opentable.versionedconfig.GitService Maven / Gradle / Ivy
Show all versions of otj-versioned-config Show documentation
/*
* 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 com.opentable.versionedconfig;
import static java.util.Optional.empty;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.annotation.PreDestroy;
import javax.annotation.concurrent.NotThreadSafe;
import javax.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opentable.io.DeleteRecursively;
/**
* Responsible for noticing when service configuration has been updated.
*/
@NotThreadSafe
class GitService implements VersioningService {
private static final Logger LOG = LoggerFactory.getLogger(GitService.class);
private final Path checkoutDirectory;
private final GitProperties config;
private final GitOperations gitOperations;
private final AtomicReference latestKnownObjectId;
@Inject
GitService(GitProperties config) throws VersioningServiceException {
this.config = config;
this.checkoutDirectory = getCheckoutPath();
try {
this.gitOperations = new GitOperations(config, checkoutDirectory);
gitOperations.checkoutBranch(config.getBranch());
this.latestKnownObjectId = new AtomicReference<>(ObjectId.zeroId());
LOG.info("Initializing {}, next update = {}", checkoutDirectory, latestKnownObjectId.get());
} catch (IOException exception) {
throw new VersioningServiceException("Configuration initialization failed, application can't start", exception);
}
}
private Path getCheckoutPath() {
final Path configuredFile = config.getLocalRepository();
if (configuredFile != null) {
return configuredFile;
}
try {
final Path result = Files.createTempDirectory("config");
LOG.info("Checking out into a temporary directory: {}", result);
return result;
} catch (IOException e) {
throw new VersioningServiceException(e);
}
}
@Override
public VersionedConfigUpdate getCurrentState() {
return new VersionedConfigUpdate(
checkoutDirectory, Collections.emptySet(), null, latestKnownObjectId.get());
}
/**
* Look and see if the latest SHA on the config directory in the config repo is different to the latest
* we know about. If it is, grab a reference to an input stream of the mapping file and feed it to the
* consumer.
*
* The first time we are called we should just return an update with all files.
*
* @return set of affected files, if any, or empty set
*/
@Override
public Optional checkForUpdate() throws VersioningServiceException {
LOG.trace("checkForUpdate");
if (!gitOperations.pull()) {
LOG.trace("pull did nothing");
return empty();
}
final ObjectId pulled = gitOperations.getCurrentHead();
LOG.trace("Get currenthead {}", pulled);
final ObjectId current = latestKnownObjectId.get();
LOG.trace("Current {}", current);
if (pulled.equals(current)) {
LOG.trace("head {} didn't change", current);
return empty();
}
LOG.trace("Gonna call getAffectedPatth");
final Set affectedPaths = getAffectedPaths(current, pulled);
LOG.trace("AffectedPaths {}", affectedPaths);
latestKnownObjectId.set(pulled);
return Optional.of(new VersionedConfigUpdate(
checkoutDirectory, affectedPaths, current, pulled));
}
@Override
public Set getAffectedPaths(ObjectId currentHash, ObjectId newHash ) {
final Set affectedPaths;
if (currentHash.equals(ObjectId.zeroId()) || newHash.equals(ObjectId.zeroId())) {
try {
LOG.trace("Start from scratch");
affectedPaths = Files.walk(checkoutDirectory)
.map(checkoutDirectory::relativize)
.filter(p -> !p.toString().startsWith(".git"))
.collect(Collectors.toSet());
} catch (IOException e) {
throw new VersioningServiceException(e);
}
LOG.trace("Use innerAffectedFile");
} else {
affectedPaths = gitOperations.affectedFiles(currentHash, newHash)
.stream()
.map(Paths::get)
.collect(Collectors.toSet());
}
LOG.info("Update from {} to {} affected paths = {}", currentHash, newHash, affectedPaths);
return affectedPaths;
}
@Override
public Path getCheckoutDirectory() {
return checkoutDirectory;
}
@Override
public String getLatestRevision() {
return latestKnownObjectId.get().getName();
}
@Override
public Optional getHead() {
return Optional.ofNullable(gitOperations.getCurrentHead());
}
public List getRemoteRepositories() {
return config.getRemoteRepositories();
}
@Override
public String getBranch() {
return config.getBranch();
}
@Override
@PreDestroy
public void close() throws IOException {
if (config.getLocalRepository() != null) {
return;
}
Files.walkFileTree(checkoutDirectory, DeleteRecursively.INSTANCE);
}
}