Skip to content

Instantly share code, notes, and snippets.

@joshlong
Created May 28, 2015 20:13
Show Gist options
  • Select an option

  • Save joshlong/fc19f5b7436e6d1fe431 to your computer and use it in GitHub Desktop.

Select an option

Save joshlong/fc19f5b7436e6d1fe431 to your computer and use it in GitHub Desktop.
A simple client that recursively scans a source code tree and invokes all `cf-deploy.sh`s it finds or, absent that, any present Cloud Foundry `manifest.yml`s by calling `cf push`. Ideally for continuous integration validation that code deploys and to do smoke tests like call the application's health codes and optionally stop a production deploy.
package deployment;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.client.lib.CloudCredentials;
import org.cloudfoundry.client.lib.CloudFoundryClient;
import org.cloudfoundry.client.lib.domain.CloudApplication;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.FileSystemResource;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
interface Deployer {
/**
* Deploy all the applications given a root file system folder
*/
void deploy(File root) throws IOException;
/**
* Undeploy (remove) all the applications given a root file system folder
*/
void reset(File root, boolean removeBackingServices) throws IOException;
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.web(false)
.run(args);
}
@Configuration
public static class CloudFoundryConfiguration {
@Bean
CloudCredentials cloudCredentials(
@Value("${cf.user}") String user,
@Value("${cf.password}") String pw) {
return new CloudCredentials(user, pw);
}
@Bean
CloudFoundryClient cloudFoundryClient(CloudCredentials cc,
@Value("${cf.api:https://api.run.pivotal.io}") String cloudControllerUrl)
throws MalformedURLException {
CloudFoundryClient cloudFoundryClient = new CloudFoundryClient(cc, URI.create(cloudControllerUrl).toURL());
cloudFoundryClient.login();
return cloudFoundryClient;
}
@Bean
Deployer cloudFoundryDeployer(CloudFoundryClient client) {
return new CloudFoundryDeployer(client);
}
@Bean
CommandLineRunner cloudFoundryDeploymentRunner(
CloudFoundryDeployer deployer,
@Value("${root.dir}") String root) {
return new CloudFoundryDeploymentRunner(deployer, new File(root));
}
}
}
class CloudFoundryDeploymentRunner implements CommandLineRunner {
private final CloudFoundryDeployer deployer;
private final File rootDir;
public CloudFoundryDeploymentRunner(CloudFoundryDeployer deployer, File rootDir) {
this.deployer = deployer;
this.rootDir = rootDir;
}
@Override
public void run(String... args) throws Exception {
this.deployer.reset(this.rootDir, true);
this.deployer.deploy(this.rootDir);
}
}
// TODO can this be multi threaded?
class CloudFoundryDeployer implements Deployer {
private final Log log = LogFactory.getLog(getClass());
private final CloudFoundryClient cloudFoundryClient;
private final Map<String, CloudApplication> cloudApplicationMap = new ConcurrentHashMap<>();
public CloudFoundryDeployer(CloudFoundryClient cloudFoundryClient) {
this.cloudFoundryClient = cloudFoundryClient;
}
@Override
public void deploy(File root) throws IOException {
this.clearCloudFoundryClientMap();
FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
try {
File currentWorkingDir = dir.toFile();
File[] files;
if ((files = currentWorkingDir.listFiles(pathname -> pathname.getName().equals("cf-deploy.sh"))).length > 0) {
deployWithDeployScript(root, files[0]);
return FileVisitResult.SKIP_SUBTREE;
} else if ((files = currentWorkingDir.listFiles(pathname -> pathname.getName().equals("manifest.yml"))).length > 0) {
deployWithManifest(files[0]);
}
return FileVisitResult.CONTINUE;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
};
Files.walkFileTree(root.toPath(), visitor);
}
@Override
public void reset(File root, boolean removeBackingServices) throws IOException {
this.clearCloudFoundryClientMap();
Set<File> paths = new HashSet<>();
Files.walkFileTree(root.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.getFileName().endsWith("manifest.yml")) {
paths.add(file.toFile());
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
});
Set<String> serviceNames = new HashSet<>();
paths.forEach(p -> {
try {
serviceNames.addAll(this.resetCloudFoundryApplication(p));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
serviceNames.forEach(svc -> {
String msg = removeBackingServices ? String.format("\t\tdeleted backing service %s", svc) : String.format("\t\tdid not delete backing service %s", svc);
this.cloudFoundryClient.deleteService(svc);
log.info(msg);
});
}
protected void deployWithDeployScript(File root, File script) throws Exception {
File parentFile = script.getParentFile();
ProcessBuilder process = new ProcessBuilder("./" + script.getName())
.directory(parentFile)
.inheritIO();
process.
environment().put("ROOT_DIR", root.getAbsolutePath());
int returnValue = process.start()
.waitFor();
log.info(String.format("deploy with deploy script %s in directory %s having return value %s",
script.getAbsolutePath(), parentFile.getAbsolutePath(), returnValue));
}
protected void deployWithManifest(File file) throws Exception {
File parentFile = file.getParentFile();
Process process = new ProcessBuilder("cf", "push")
.directory(parentFile)
.inheritIO()
.start();
int returnValue = process.waitFor();
log.info(String.format("deploy with manifest %s in directory %s having return value %s",
file.getAbsolutePath(), parentFile.getAbsolutePath(), returnValue));
}
private Collection<String> resetCloudFoundryApplication(File manifest) throws IOException {
YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
PropertySource<?> propertySource = yamlPropertySourceLoader.load("manifest.yml", new FileSystemResource(manifest), null);
String applicationName = (String) propertySource.getProperty("applications[0].name");
log.info(String.format("\tprocessing Cloud Foundry application '%s' in %s", applicationName, manifest.getAbsolutePath()));
Set<String> services = new HashSet<>();
if (this.cloudApplicationMap.containsKey(applicationName)) {
CloudApplication application = this.cloudApplicationMap.get(applicationName);
services.addAll(this.resetCloudFoundryApplicationByApplicationName(application));
}
return services;
}
private Collection<String> resetCloudFoundryApplicationByApplicationName(CloudApplication application) {
String appName = application.getName();
log.info(String.format("\t\tdeleting application %s", appName));
this.cloudFoundryClient.deleteApplication(appName);
return application.getServices();
}
private void clearCloudFoundryClientMap() {
this.cloudApplicationMap.clear();
this.cloudFoundryClient.getApplications().forEach(
a -> this.cloudApplicationMap.put(a.getName(), a));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment