package com.heroku.sdk.deploy;
import com.heroku.sdk.deploy.endpoints.Builds;
import com.heroku.sdk.deploy.utils.Logger;
import com.heroku.sdk.deploy.utils.RestClient;
import com.heroku.sdk.deploy.utils.Tar;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
public class BuildsDeployer extends Deployer {
private List<String> buildpacks;
public BuildsDeployer(String client, String name, File rootDir, File targetDir, List<String> buildpacks, Logger logger) {
super(client, name, rootDir, targetDir, logger);
this.buildpacks = buildpacks;
}
@Override
protected void addExtras(Map<String, String> processTypes) throws IOException {
super.addExtras(processTypes);
addProcfile(processTypes);
addJdkOverlay();
}
private void addProcfile(Map<String, String> processTypes) throws IOException {
Map<String, String> allProcessTypes = getProcfile();
allProcessTypes.putAll(processTypes);
if (allProcessTypes.isEmpty()) logWarn("No processTypes specified!");
String procfile = "";
for (String key : allProcessTypes.keySet()) {
procfile += key + ": " + allProcessTypes.get(key) + "\n";
}
logDebug("Procfile:\n===================\n" + procfile + "\n===================");
Files.write(
Paths.get(new File(getAppDir(), "Procfile").getPath()),
(procfile).getBytes(StandardCharsets.UTF_8)
);
}
private void addJdkOverlay() throws IOException {
File jdkOverlayDir = new File(getRootDir(), ".jdk-overlay");
File toJdkOverlayDir = new File(getAppDir(), ".jdk-overlay");
if (jdkOverlayDir.exists()) {
logInfo(" - including JDK overlay");
FileUtils.copyDirectory(jdkOverlayDir, toJdkOverlayDir);
}
}
protected void vendorJdk(String jdkVersion, URL jdkUrl, String stackName) throws IOException, InterruptedException, ArchiveException {
String realJdkVersion = jdkVersion == null ? getJdkVersion() : jdkVersion;
if (realJdkVersion != null) {
Files.write(
Paths.get(new File(getAppDir(), "system.properties").getPath()),
("java.runtime.version=" + realJdkVersion).getBytes(StandardCharsets.UTF_8)
);
} else if (jdkUrl != null) {
logWarn("JDK URL is not supported with partial slug deployment! Ignoring...");
}
}
protected File buildSlugFile(String tarFilename)
throws InterruptedException, ArchiveException, IOException {
logInfo("-----> Creating build...");
try {
FileUtils.forceDelete(new File(getHerokuDir(), tarFilename));
} catch (IOException e) { /* no-op */ }
// NOTE Big difference here!
File tarFile = Tar.create(tarFilename, "./", getAppDir(), getHerokuDir());
Long size = tarFile.length() / (1024 * 1024);
if (size == 0l) size = 1l;
logInfo(" - file: " + relativize(tarFile));
logInfo(" - size: " + size + "MB");
return tarFile;
}
protected void deploySlug(String stack, Map<String, String> processTypes, File tarFile)
throws IOException, ArchiveException, InterruptedException {
Builds builds = new Builds(name, stack, parseCommit(), getEncodedApiKey(), buildpacks);
Map sourceResponse = builds.createSource();
logDebug("Heroku Source response: " + sourceResponse);
logDebug("Heroku Blob URL: " + builds.getBlobUrl());
logInfo("-----> Uploading build...");
builds.upload(tarFile, logger);
logInfo(" - success");
logInfo("-----> Deploying...");
Map buildInfo = builds.build(new RestClient.OutputLogger() {
@Override
public void log(String line) {
logInfo("remote: " + line);
}
});
if (!"succeeded".equals(buildInfo.get("status"))) {
Thread.sleep(4000);
Map secondAttemptBuildInfo = builds.getBuildInfo((String) buildInfo.get("id"));
if (!"succeeded".equals(secondAttemptBuildInfo.get("status"))) {
logDebug("Failed Build ID: " + buildInfo.get("id"));
logDebug("Failed Build Status: " + buildInfo.get("status"));
logDebug("Failed Build UpdatedAt: " + buildInfo.get("updated_at"));
throw new RuntimeException("The build failed");
}
}
}
}