Created
May 28, 2015 20:13
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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