package io.cattle.platform.configitem.server.model.impl;
import io.cattle.platform.configitem.context.ConfigItemContextFactory;
import io.cattle.platform.configitem.model.ItemVersion;
import io.cattle.platform.configitem.server.model.Request;
import io.cattle.platform.configitem.server.resource.ResourceRoot;
import io.cattle.platform.configitem.version.ConfigItemStatusManager;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.io.IOUtils;
public abstract class AbstractArchiveBasedConfigItem extends AbstractResourceRootConfigItem {
List<ConfigItemContextFactory> contextFactories;
public AbstractArchiveBasedConfigItem(String name, ConfigItemStatusManager versionManager, ResourceRoot resourceRoot,
List<ConfigItemContextFactory> contextFactories) {
super(name, versionManager, resourceRoot);
this.contextFactories = contextFactories;
}
@Override
public void handleRequest(Request req) throws IOException {
req.setContentType("application/octet-stream");
OutputStream os = req.getOutputStream();
GZIPOutputStream gzos = new GZIPOutputStream(os, 8192);
TarArchiveOutputStream taos = null;
try {
taos = new TarArchiveOutputStream(gzos);
taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
ArchiveContext context = new ArchiveContext(req, taos, getVersion(req));
ItemVersion current = req.getCurrentVersion();
if (current != null && current.toExternalForm().equals(context.getVersion())) {
writeUpToDate(context);
} else {
for (ConfigItemContextFactory factory : contextFactories) {
factory.populateContext(req, this, context);
}
writeContent(context);
}
writeHashes(context);
} finally {
IOUtils.closeQuietly(taos);
}
}
protected static void writeHashes(final ArchiveContext context) throws IOException {
StringBuilder stringContent = new StringBuilder();
Map<String, String> hashes = context.getHashes();
for (Map.Entry<String, String> entry : hashes.entrySet()) {
stringContent.append(entry.getValue());
stringContent.append(" *");
stringContent.append(entry.getKey());
stringContent.append("\n");
}
hashes.clear();
final byte[] content = stringContent.toString().getBytes("UTF-8");
withEntry(context, "SHA1SUMS", content.length, new WithEntry() {
@Override
public void with(OutputStream os) throws IOException {
os.write(content);
}
});
Map.Entry<String, String> entry = hashes.entrySet().iterator().next();
final byte[] sumSum = String.format("%s *%s\n", entry.getValue(), entry.getKey()).getBytes("UTF-8");
withEntry(context, "SHA1SUMSSUM", sumSum.length, new WithEntry() {
@Override
public void with(OutputStream os) throws IOException {
os.write(sumSum);
}
});
}
protected void writeContent(final ArchiveContext context) throws IOException {
writeVersion(context);
}
protected static void writeVersion(final ArchiveContext context) throws IOException {
final byte[] content = (context.getVersion() + "\n").getBytes("UTF-8");
withEntry(context, "version", content.length, new WithEntry() {
@Override
public void with(OutputStream os) throws IOException {
os.write(content);
}
});
}
protected static void writeUpToDate(final ArchiveContext context) throws IOException {
final byte[] content = (context.getVersion() + "\n").getBytes("UTF-8");
withEntry(context, "uptodate", content.length, new WithEntry() {
@Override
public void with(OutputStream os) throws IOException {
os.write(content);
}
});
}
protected static void withEntry(ArchiveContext context, String entryName, long size, WithEntry with) throws IOException {
if (size < 0) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
with.with(baos);
size = baos.size();
with = new WithEntry() {
@Override
public void with(OutputStream os) throws IOException {
os.write(baos.toByteArray());
}
};
}
withEntry(context, getDefaultEntry(context, entryName, size), with);
};
protected static void withEntry(ArchiveContext context, TarArchiveEntry entry, WithEntry with) throws IOException {
try {
TarArchiveOutputStream taos = context.getOutputStream();
DigestOutputStream dos = new DigestOutputStream(taos, MessageDigest.getInstance("SHA1"));
taos.putArchiveEntry(entry);
with.with(dos);
taos.closeArchiveEntry();
String hash = Hex.encodeHexString(dos.getMessageDigest().digest());
context.getHashes().put(entry.getName(), hash);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Missing SHA1 digest", e);
}
};
protected static TarArchiveEntry getDefaultEntry(ArchiveContext context, String name, long size) {
StringBuilder entryName = new StringBuilder(context.getRequest().getItemName());
entryName.append("-").append(context.getVersion());
if (!name.startsWith(File.separator)) {
entryName.append(File.separator);
}
entryName.append(name);
TarArchiveEntry entry = new TarArchiveEntry(entryName.toString());
entry.setUserName("root");
entry.setGroupName("root");
entry.setMode(0644);
entry.setSize(size);
entry.setModTime(new Date(System.currentTimeMillis() - (60 * 60 * 24 * 1000)));
return entry;
}
@Override
public String getSourceRevision() {
String hash = super.getSourceRevision();
for (ConfigItemContextFactory factory : contextFactories) {
hash = factory.getContentHash(hash);
}
return hash;
}
protected interface WithEntry {
public void with(OutputStream os) throws IOException;
}
}