package org.cloudsmith.geppetto.validation.runner;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.cloudsmith.geppetto.common.os.OsUtil;
import org.cloudsmith.geppetto.common.os.StreamUtil;
import org.cloudsmith.geppetto.diagnostic.Diagnostic;
import org.cloudsmith.geppetto.diagnostic.DiagnosticType;
import org.cloudsmith.geppetto.diagnostic.FileDiagnostic;
import org.cloudsmith.geppetto.validation.ValidationService;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
public class PuppetCatalogCompilerRunner {
/**
* Returns message as message, filename and line are available as Data[0]
* and Data[1] (or via the more specific CatalogDiagnostic methods).
*
*/
public static class CatalogDiagnostic extends FileDiagnostic {
private static final long serialVersionUID = 1L;
public static CatalogDiagnostic create(String message, String fileName, String line, String nodeName) {
int severity = Diagnostic.ERROR;
if(message.startsWith("err:")) {
message = message.substring(4);
}
else if(message.startsWith("warning:")) {
severity = Diagnostic.WARNING;
message = message.substring(8);
}
else if(message.startsWith("info:")) {
severity = Diagnostic.INFO;
message = message.substring(5);
}
else if(message.startsWith("notice:")) {
severity = Diagnostic.INFO;
message = message.substring(7);
}
DiagnosticType type = message.startsWith("Could not parse")
? ValidationService.CATALOG_PARSER
: ValidationService.CATALOG;
CatalogDiagnostic diag = new CatalogDiagnostic(severity, type, message, new File(fileName));
try {
diag.setLineNumber(Integer.parseInt(line));
}
catch(NumberFormatException e) {
diag.setLineNumber(-1);
}
diag.setNode(nodeName);
return diag;
}
public CatalogDiagnostic(int severity, DiagnosticType type, String message, File file) {
super(severity, type, message, file);
}
}
public static final String[] DEFAULT_ARGUMENTS;
public static final int MESSAGE_GROUP = 1;
public static final int FILE_GROUP = 2;
public static final int LINE_GROUP = 3;
public static final int NODE_GROUP = 4;
static {
try {
InputStream compileCatalog = PuppetCatalogCompilerRunner.class.getResourceAsStream("/puppet/compile_catalog");
if(compileCatalog == null)
throw new IllegalStateException("Compile script not found");
try {
File tmpScript = File.createTempFile("compile_catalog-", ".rb");
tmpScript.deleteOnExit();
OutputStream out = new FileOutputStream(tmpScript);
try {
StreamUtil.copy(compileCatalog, out);
}
finally {
StreamUtil.close(out);
}
DEFAULT_ARGUMENTS = new String[] { "ruby", tmpScript.getAbsolutePath() };
}
finally {
StreamUtil.close(compileCatalog);
}
}
catch(Exception e) {
throw new ExceptionInInitializerError(e);
}
}
final Pattern errorPattern;
final List<CatalogDiagnostic> diagnostics;
private String[] arguments;
public PuppetCatalogCompilerRunner() {
this(DEFAULT_ARGUMENTS);
}
public PuppetCatalogCompilerRunner(String... arguments) {
this.arguments = arguments;
errorPattern = Pattern.compile("(.*) at (.*):([0-9]+) on node (.*)$");
diagnostics = new ArrayList<CatalogDiagnostic>();
}
private void clear(StringBuffer buf) {
buf.delete(0, buf.length());
}
public int compileCatalog(File manifest, File moduleDirectory, String nodeName, File factorData,
IProgressMonitor monitor) {
SubMonitor ticker = SubMonitor.convert(monitor, 2);
int offset = arguments.length;
String[] args = Arrays.copyOf(arguments, offset + 4);
args[offset++] = manifest.getAbsolutePath();
args[offset++] = moduleDirectory == null
? ""
: moduleDirectory.getAbsolutePath();
if(nodeName == null)
throw new IllegalArgumentException("Node name to compile the catalog for must be specified");
args[offset++] = nodeName;
args[offset++] = factorData.getAbsolutePath();
ticker.worked(1);
int result;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
result = OsUtil.runProcess(null, out, err, args);
readErrorStream(new ByteArrayInputStream(err.toByteArray()));
}
catch(IOException e) {
result = -1;
}
ticker.worked(1);
return result;
}
public List<CatalogDiagnostic> getDiagnostics() {
return diagnostics;
}
private boolean isStartOfNew(String line) {
return (line.startsWith("err:") || line.startsWith("info:") || line.startsWith("notice:") || line.startsWith("warning:"));
}
/**
* Output buffer as just a message
*
* @param buf
*/
private void outputBuffer(StringBuffer buf) {
diagnostics.add(CatalogDiagnostic.create(buf.toString(), "", "", ""));
clear(buf);
}
protected void readErrorStream(InputStream inputStream) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
String line = null;
StringBuffer buf = new StringBuffer();
while((line = reader.readLine()) != null) {
// uncomment for debug echo
// System.err.println(line);
// if buffer has data, and the line is the start of a new
// message - output the old
if(buf.length() > 0 && isStartOfNew(line))
outputBuffer(buf);
// else, this may be a continuation of the previous line
if(buf.length() > 0)
buf.append("\\n"); // if joining lines insert a visible new
// line
buf.append(line);
// if we can match as a complete (possibly joined) message - a
// diagnostic is produced
Matcher matcher = errorPattern.matcher(buf);
if(matcher.matches()) {
diagnostics.add(CatalogDiagnostic.create(
matcher.group(MESSAGE_GROUP), matcher.group(FILE_GROUP), matcher.group(LINE_GROUP),
matcher.group(NODE_GROUP)));
clear(buf);
}
// else,
// error is probably reported using several lines, keep line(s)
// in buffer and continue
}
// if things remain in the buffer, output them
if(buf.length() > 0)
outputBuffer(buf);
}
catch(IOException e) {
// ignore
}
finally {
try {
reader.close();
}
catch(IOException e) {
// ignore
}
}
}
}