provisionr-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From as...@apache.org
Subject [15/21] PROVISIONR-20. Change groupId from com.axemblr.provisionr to org.apache.provisionr
Date Mon, 01 Apr 2013 08:52:38 GMT
http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/com/axemblr/provisionr/core/templates/xml/XmlTemplate.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/axemblr/provisionr/core/templates/xml/XmlTemplate.java b/core/src/main/java/com/axemblr/provisionr/core/templates/xml/XmlTemplate.java
deleted file mode 100644
index e6f132e..0000000
--- a/core/src/main/java/com/axemblr/provisionr/core/templates/xml/XmlTemplate.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (c) 2013 S.C. Axemblr Software Solutions S.R.L
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.axemblr.provisionr.core.templates.xml;
-
-import com.axemblr.provisionr.api.network.Network;
-import com.axemblr.provisionr.api.network.NetworkBuilder;
-import com.axemblr.provisionr.api.network.Rule;
-import com.axemblr.provisionr.api.pool.Pool;
-import com.axemblr.provisionr.api.pool.PoolBuilder;
-import com.axemblr.provisionr.api.software.Repository;
-import com.axemblr.provisionr.api.software.Software;
-import com.axemblr.provisionr.api.software.SoftwareBuilder;
-import com.axemblr.provisionr.core.templates.PoolTemplate;
-import com.google.common.annotations.VisibleForTesting;
-import static com.google.common.base.Preconditions.checkNotNull;
-import com.google.common.base.Throwables;
-import static com.google.common.collect.Lists.newArrayList;
-import com.google.common.io.CharStreams;
-import com.google.common.io.Closeables;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.List;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-import javax.xml.bind.annotation.XmlRootElement;
-
-/**
- * Basic representation of a pool template
- * <p/>
- * Designed to be consumed *only* by JAXB
- * <p/>
- * If you need to implement a pool template see {@code PoolTemplate}
- */
-@XmlRootElement(name = "template")
-public class XmlTemplate implements PoolTemplate {
-
-    /**
-     * @return an XmlTemplate instance resulted from parsing the content
-     */
-    public static XmlTemplate newXmlTemplate(String content) {
-        try {
-            JAXBContext context = JAXBContext.newInstance(XmlTemplate.class);
-            return (XmlTemplate) context.createUnmarshaller()
-                .unmarshal(new ByteArrayInputStream(content.getBytes()));
-
-        } catch (JAXBException e) {
-            throw Throwables.propagate(e);
-        }
-    }
-
-    /**
-     * @return an XmlTemplate instance resulted from parsing a file
-     */
-    public static XmlTemplate newXmlTemplate(File file) {
-        FileReader reader = null;
-        try {
-            reader = new FileReader(file);
-            return newXmlTemplate(CharStreams.toString(reader));
-
-        } catch (IOException e) {
-            throw Throwables.propagate(e);
-        } finally {
-            Closeables.closeQuietly(reader);
-        }
-    }
-
-    private String id;
-    private String description;
-    private String osVersion;
-
-    private List<String> packages = newArrayList();
-    private List<Integer> ports = newArrayList();
-
-    private List<FileEntry> files = newArrayList();
-
-    private List<RepositoryEntry> repositories = newArrayList();
-
-    public XmlTemplate() {
-    }
-
-    @Override
-    public Pool apply(Pool pool) {
-        PoolBuilder result = pool.toBuilder();
-
-        result.software(apply(pool.getSoftware()));
-        result.network(apply(pool.getNetwork()));
-
-        if (osVersion != null) {
-            result.provider(pool.getProvider().toBuilder()
-                .option("version", osVersion).createProvider());
-        }
-
-        return result.createPool();
-    }
-
-    @VisibleForTesting
-    Software apply(Software software) {
-        SoftwareBuilder result = software.toBuilder();
-
-        // Add all the new packages
-        for (String pkg : packages) {
-            result.addPackage(pkg);
-        }
-
-        // Add all the new files
-        for (FileEntry entry : files) {
-            result.file(entry.getSource(), entry.getDestination());
-        }
-
-        // Add all the new custom repositories
-        for (RepositoryEntry entry : repositories) {
-            result.repository(Repository.builder().name(entry.getId()).key(entry.getKey())
-                .entries(entry.getEntries()).createRepository());
-        }
-
-        return result.createSoftware();
-    }
-
-    @VisibleForTesting
-    Network apply(Network network) {
-        NetworkBuilder result = network.toBuilder();
-        for (int port : ports) {
-            result.addRules(Rule.builder().anySource().tcp().port(port).createRule());
-        }
-        return result.createNetwork();
-    }
-
-    @XmlAttribute(name = "id")
-    @Override
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = checkNotNull(id, "id is null");
-    }
-
-    @Override
-    public String getDescription() {
-        return description.trim();
-    }
-
-    public void setDescription(String description) {
-        this.description = checkNotNull(description, "description is null");
-    }
-
-    @XmlAttribute(name = "os-version")
-    public String getOsVersion() {
-        return osVersion;
-    }
-
-    public void setOsVersion(String osVersion) {
-        this.osVersion = checkNotNull(osVersion, "osVersion is null");
-    }
-
-    @XmlElementWrapper(name = "packages")
-    @XmlElement(name = "package")
-    public List<String> getPackages() {
-        return packages;
-    }
-
-    public void setPackages(List<String> packages) {
-        this.packages = checkNotNull(packages, "packages is null");
-    }
-
-    @XmlElementWrapper(name = "ports")
-    @XmlElement(name = "port")
-    public List<Integer> getPorts() {
-        return ports;
-    }
-
-    public void setPorts(List<Integer> ports) {
-        this.ports = checkNotNull(ports, "ports is null");
-    }
-
-    @XmlElementWrapper(name = "files")
-    @XmlElement(name = "file")
-    public List<FileEntry> getFiles() {
-        return files;
-    }
-
-    public void setFiles(List<FileEntry> files) {
-        this.files = checkNotNull(files, "files is null");
-    }
-
-    @XmlElementWrapper(name = "repositories")
-    @XmlElement(name = "repository")
-    public List<RepositoryEntry> getRepositories() {
-        return repositories;
-    }
-
-    public void setRepositories(List<RepositoryEntry> repositories) {
-        this.repositories = checkNotNull(repositories, "repositories is null");
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        XmlTemplate that = (XmlTemplate) o;
-
-        if (description != null ? !description.equals(that.description) : that.description != null) return false;
-        if (files != null ? !files.equals(that.files) : that.files != null) return false;
-        if (id != null ? !id.equals(that.id) : that.id != null) return false;
-        if (osVersion != null ? !osVersion.equals(that.osVersion) : that.osVersion != null) return false;
-        if (packages != null ? !packages.equals(that.packages) : that.packages != null) return false;
-        if (ports != null ? !ports.equals(that.ports) : that.ports != null) return false;
-        if (repositories != null ? !repositories.equals(that.repositories) : that.repositories != null) return false;
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = id != null ? id.hashCode() : 0;
-        result = 31 * result + (description != null ? description.hashCode() : 0);
-        result = 31 * result + (osVersion != null ? osVersion.hashCode() : 0);
-        result = 31 * result + (packages != null ? packages.hashCode() : 0);
-        result = 31 * result + (ports != null ? ports.hashCode() : 0);
-        result = 31 * result + (files != null ? files.hashCode() : 0);
-        result = 31 * result + (repositories != null ? repositories.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "XmlTemplate{" +
-            "id='" + id + '\'' +
-            ", description='" + description + '\'' +
-            ", osVersion='" + osVersion + '\'' +
-            ", packages=" + packages +
-            ", ports=" + ports +
-            ", files=" + files +
-            ", repositories=" + repositories +
-            '}';
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/CoreConstants.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/CoreConstants.java b/core/src/main/java/org/apache/provisionr/core/CoreConstants.java
new file mode 100644
index 0000000..b24c111
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/CoreConstants.java
@@ -0,0 +1,10 @@
+package org.apache.provisionr.core;
+
+public class CoreConstants {
+
+    private CoreConstants() {
+    }
+
+    public static final String ACTIVITI_EXPLORER_DEFAULT_USER = "kermit";
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/CoreProcessVariables.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/CoreProcessVariables.java b/core/src/main/java/org/apache/provisionr/core/CoreProcessVariables.java
new file mode 100644
index 0000000..0fffcc7
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/CoreProcessVariables.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core;
+
+public class CoreProcessVariables {
+
+    private CoreProcessVariables() {
+        throw new RuntimeException("Should not instantiate");
+    }
+
+    /**
+     * Provider ID as returned by the Provisionr object
+     *
+     * @see org.apache.provisionr.api.Provisionr
+     */
+    public static final String PROVIDER = "provider";
+
+    /**
+     * Pool configuration description
+     *
+     * @see org.apache.provisionr.api.pool.Pool
+     */
+    public static final String POOL = "pool";
+
+    /**
+     * Contains a list of machines that are running
+     *
+     * @see org.apache.provisionr.api.pool.Machine
+     */
+    public static final String MACHINES = "machines";
+
+    /**
+     * Pool status stored as process variable
+     * <p/>
+     * This can be an arbitrary string. We will restrict the domain later on.
+     */
+    public static final String STATUS = "status";
+
+    /**
+     * Variable to store the pool business key. The key is passed as a process variable to all processes that take
+     * part in setting up the pool.
+     */
+    public static final String POOL_BUSINESS_KEY = "poolBusinessKey";
+
+    /**
+     * Configurable value for the interval of time in which to wait for the pool
+     * to become available.
+     */
+    public static final String BOOTSTRAP_TIMEOUT = "bootstrapTimeout";
+
+    /**
+     * Flag that indicates if the image the machines are being built from already has all its software
+     * installed and there's no need to download and install the packages and files.
+     */
+    public static final String IS_CACHED_IMAGE = "isCachedImage";
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/CoreSignals.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/CoreSignals.java b/core/src/main/java/org/apache/provisionr/core/CoreSignals.java
new file mode 100644
index 0000000..da71486
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/CoreSignals.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core;
+
+public class CoreSignals {
+
+    private CoreSignals() {
+    }
+
+    /**
+     * Standard event that triggers pool termination
+     */
+    public static final String TERMINATE_POOL = "terminatePoolEvent";
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/Mustache.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/Mustache.java b/core/src/main/java/org/apache/provisionr/core/Mustache.java
new file mode 100644
index 0000000..895b829
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/Mustache.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.MustacheFactory;
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * Utility functions for rendering mustache templates as strings
+ */
+public class Mustache {
+
+    private Mustache() {
+    }
+
+    public static String toString(Class<?> contextClass, String resource, Map<String, ?> scopes)
+        throws IOException {
+        return toString(Resources.getResource(contextClass, resource), scopes);
+    }
+
+    public static String toString(URL resource, Map<String, ?> scopes) throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        Writer writer = new OutputStreamWriter(outputStream);
+
+        String content = Resources.toString(resource, Charsets.UTF_8);
+
+        MustacheFactory factory = new DefaultMustacheFactory();
+        factory.compile(new StringReader(content), resource.toString())
+            .execute(writer, scopes);
+
+        writer.close();
+        return outputStream.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/PoolStatus.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/PoolStatus.java b/core/src/main/java/org/apache/provisionr/core/PoolStatus.java
new file mode 100644
index 0000000..aef5675
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/PoolStatus.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core;
+
+/**
+ * A standard set of pool statuses
+ */
+public class PoolStatus {
+
+    private PoolStatus() {
+    }
+
+    public static String UNDEFINED = "undefined";
+
+    public static String SETUP = "setup";
+
+    public static String READY = "ready";
+
+    public static String TERMINATED = "terminated";
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/ProvisionrSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/ProvisionrSupport.java b/core/src/main/java/org/apache/provisionr/core/ProvisionrSupport.java
new file mode 100644
index 0000000..b0dbc94
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/ProvisionrSupport.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core;
+
+import org.apache.provisionr.api.Provisionr;
+import org.apache.provisionr.api.pool.Machine;
+import org.apache.provisionr.api.provider.Provider;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import org.activiti.engine.ProcessEngine;
+import org.activiti.engine.RuntimeService;
+import org.activiti.engine.runtime.Execution;
+import org.activiti.engine.runtime.ProcessInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class ProvisionrSupport implements Provisionr {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ProvisionrSupport.class);
+
+    @Override
+    public Optional<Provider> getDefaultProvider() {
+        return Optional.absent();
+    }
+
+    @Override
+    public String getStatus(String businessKey) {
+        return PoolStatus.UNDEFINED;
+    }
+
+    @Override
+    public List<Machine> getMachines(String businessKey) {
+        return ImmutableList.of();
+    }
+
+    /**
+     * Trigger a signal event on all executions for a specific business key.
+     *
+     * @see <a href="http://www.activiti.org/userguide/index.html#bpmnSignalEventDefinition" />
+     * @see CoreSignals
+     */
+    protected void triggerSignalEvent(ProcessEngine processEngine, String businessKey, String signalName) {
+        RuntimeService runtimeService = processEngine.getRuntimeService();
+
+        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+            .processInstanceBusinessKey(businessKey).singleResult();
+
+        List<Execution> executions = runtimeService.createExecutionQuery()
+            .processInstanceId(processInstance.getProcessInstanceId())
+            .signalEventSubscriptionName(signalName).list();
+
+        if (executions.isEmpty()) {
+            throw new NoSuchElementException(String.format("No executions found waiting " +
+                "for signal '%s' on process %s", signalName, businessKey));
+        }
+        for (Execution execution : executions) {
+            LOG.info("Sending '{}' signal to execution {} for process {}",
+                new Object[]{signalName, execution.getId(), businessKey});
+            runtimeService.signalEventReceived(signalName, execution.getId());
+
+        }
+    }
+
+    /**
+     * Convert a timeout specified in seconds to a string representation that can be used
+     * inside the Activiti definitions of timeouts. 
+     * 
+     * @see http://en.wikipedia.org/wiki/ISO_8601#Durations
+     */
+    protected String convertTimeoutToISO8601TimeDuration(int bootstrapTimeoutInSeconds) {
+        StringBuilder result = new StringBuilder("PT");
+        if (bootstrapTimeoutInSeconds % 60 == 0) {
+            result.append(bootstrapTimeoutInSeconds / 60).append("M");
+        } else {
+            result.append(bootstrapTimeoutInSeconds).append("S");
+        }
+        return result.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/Ssh.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/Ssh.java b/core/src/main/java/org/apache/provisionr/core/Ssh.java
new file mode 100644
index 0000000..8905990
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/Ssh.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core;
+
+import org.apache.provisionr.api.access.AdminAccess;
+import org.apache.provisionr.api.pool.Machine;
+import org.apache.provisionr.core.logging.ErrorStreamLogger;
+import org.apache.provisionr.core.logging.InfoStreamLogger;
+import com.google.common.base.Charsets;
+import static com.google.common.base.Preconditions.checkArgument;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.PublicKey;
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.common.SecurityUtils;
+import net.schmizz.sshj.connection.channel.direct.Session;
+import net.schmizz.sshj.transport.verification.HostKeyVerifier;
+import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
+import net.schmizz.sshj.xfer.InMemorySourceFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Marker;
+import org.slf4j.MarkerFactory;
+
+public class Ssh {
+
+    private static final Logger LOG = LoggerFactory.getLogger(Ssh.class);
+
+    /**
+     * Fail if a connection can't be established in this amount of time
+     */
+    public static final int DEFAULT_CONNECT_TIMEOUT = 30 * 1000; /* milliseconds */
+
+    public static final int DEFAULT_READ_TIMEOUT = 10 * 60 * 1000; /* milliseconds */
+
+    private Ssh() {
+    }
+
+    /**
+     * Accept any host key from the remote machines
+     */
+    private enum AcceptAnyHostKeyVerifier implements HostKeyVerifier {
+        INSTANCE;
+
+        @Override
+        public boolean verify(String hostname, int port, PublicKey key) {
+            String fingerprint = SecurityUtils.getFingerprint(key);
+            LOG.info("Automatically accepting host key for {}:{} with fingerprint {}",
+                new Object[]{hostname, port, fingerprint});
+            return true;
+        }
+    }
+
+    public static SSHClient newClient(Machine machine, AdminAccess adminAccess) throws IOException {
+        return newClient(machine, adminAccess, DEFAULT_READ_TIMEOUT);
+    }
+
+    /**
+     * Create a new {@code SSHClient} connected to the remote machine using the
+     * AdminAccess credentials as provided
+     */
+    public static SSHClient newClient(
+        Machine machine, AdminAccess adminAccess, int timeoutInMillis
+    ) throws IOException {
+        checkArgument(timeoutInMillis >= 0, "timeoutInMillis should be positive or 0");
+
+        final SSHClient client = new SSHClient();
+        client.addHostKeyVerifier(AcceptAnyHostKeyVerifier.INSTANCE);
+
+        if (timeoutInMillis != 0) {
+            client.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
+            client.setTimeout(timeoutInMillis);
+        }
+        client.connect(machine.getPublicDnsName(), machine.getSshPort());
+
+        OpenSSHKeyFile key = new OpenSSHKeyFile();
+        key.init(adminAccess.getPrivateKey(), adminAccess.getPublicKey());
+        client.authPublickey(adminAccess.getUsername(), key);
+
+        return client;
+    }
+
+    /**
+     * Stream command output as log message for easy debugging
+     */
+    public static void logCommandOutput(Logger logger, String instanceId, Session.Command command) {
+        final Marker marker = MarkerFactory.getMarker("ssh-" + instanceId);
+
+        new InfoStreamLogger(command.getInputStream(), logger, marker)
+            .start();
+        new ErrorStreamLogger(command.getErrorStream(), logger, marker)
+            .start();
+    }
+
+    /**
+     * Create a remote file on SSH from a string
+     *
+     * @param client      ssh client instance
+     * @param content     content for the new file
+     * @param permissions unix permissions
+     * @param destination destination path
+     * @throws IOException
+     */
+    public static void createFile(
+        SSHClient client, String content, final int permissions, String destination
+    ) throws IOException {
+        final byte[] bytes = content.getBytes(Charsets.UTF_8);
+        client.newSCPFileTransfer().upload(new InMemorySourceFile() {
+            @Override
+            public String getName() {
+                return "in-memory";
+            }
+
+            @Override
+            public long getLength() {
+                return bytes.length;
+            }
+
+            @Override
+            public int getPermissions() throws IOException {
+                return permissions;
+            }
+
+            @Override
+            public InputStream getInputStream() throws IOException {
+                return new ByteArrayInputStream(bytes);
+            }
+        }, destination);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activiti/ConfigurableFailedJobCommandFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activiti/ConfigurableFailedJobCommandFactory.java b/core/src/main/java/org/apache/provisionr/core/activiti/ConfigurableFailedJobCommandFactory.java
new file mode 100644
index 0000000..ed9bfb6
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activiti/ConfigurableFailedJobCommandFactory.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activiti;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Date;
+import java.util.UUID;
+import org.activiti.engine.impl.calendar.BusinessCalendar;
+import org.activiti.engine.impl.calendar.CycleBusinessCalendar;
+import org.activiti.engine.impl.context.Context;
+import org.activiti.engine.impl.interceptor.Command;
+import org.activiti.engine.impl.interceptor.CommandContext;
+import org.activiti.engine.impl.jobexecutor.FailedJobCommandFactory;
+import org.activiti.engine.impl.persistence.entity.JobEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * We need a custom @{link FailedJobCommandFactory} implementation that allows
+ * us to customize the global number of retries per job
+ */
+public class ConfigurableFailedJobCommandFactory implements FailedJobCommandFactory {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ConfigurableFailedJobCommandFactory.class);
+
+    /**
+     * This class is a bit of a hack because there is no easy way to
+     * change @{link JobEntity.DEFAULT_RETRIES}
+     * <p/>
+     * We get the desired behaviour by incrementing the number of retries until we
+     * exceed maxNumberOfRetries + DEFAULT_RETRIES (considered to be 0)
+     * <p/>
+     * If maxNumberOfRetries is -1 (infinity) the job will be always retried.
+     */
+    public static class IncrementJobRetriesCmd implements Command<Object> {
+
+        private final String LOCK_OWNER = "job-retries-" + UUID.randomUUID().toString();
+
+        private final String jobId;
+        private final Throwable exception;
+
+        private final int maxNumberOfRetries;
+        private final int waitBetweenRetriesInSeconds;
+
+        public IncrementJobRetriesCmd(String jobId, Throwable exception, int maxNumberOfRetries,
+                                      int waitBetweenRetriesInSeconds) {
+            this.jobId = checkNotNull(jobId, "jobId is null");
+            this.exception = exception;
+
+            this.maxNumberOfRetries = maxNumberOfRetries;
+            this.waitBetweenRetriesInSeconds = waitBetweenRetriesInSeconds;
+        }
+
+        @Override
+        public Object execute(CommandContext commandContext) {
+            JobEntity job = Context.getCommandContext()
+                .getJobManager()
+                .findJobById(jobId);
+
+            updateNumberOfRetries(job);
+
+            if (exception != null) {
+                job.setExceptionMessage(exception.getMessage());
+                job.setExceptionStacktrace(getExceptionStacktrace());
+            }
+
+            return null;
+        }
+
+        private void updateNumberOfRetries(JobEntity job) {
+            if (maxNumberOfRetries == -1) {
+                job.setLockOwner(LOCK_OWNER);
+                job.setLockExpirationTime(calculateDueDate());
+                return; /* always retry without counting */
+            }
+
+            int realUpperBound = maxNumberOfRetries + JobEntity.DEFAULT_RETRIES;
+            if (job.getRetries() >= realUpperBound) {
+                LOG.warn("Job {} from process {} has no more retries left. The process will block and may " +
+                    "require human intervention.", job.getId(), job.getProcessInstanceId());
+
+                job.setRetries(0);  /* stop retrying this job */
+                job.setLockOwner(null);
+
+            } else {
+                final Date newDate = calculateDueDate();
+                LOG.info("Scheduling job {} from process {} to be retried at {}. Try {}/{}",
+                    new Object[]{job.getId(), job.getProcessInstanceId(), newDate,
+                        job.getRetries() - JobEntity.DEFAULT_RETRIES, maxNumberOfRetries});
+
+                /* I've tried to use job.setDuedate() but it doesn't work as expected */
+
+                job.setLockOwner(LOCK_OWNER);
+                job.setLockExpirationTime(newDate);
+
+                job.setRetries(job.getRetries() + 1);
+            }
+        }
+
+        /**
+         * Based on code from @{link TimerEntity.calculateDueDate}
+         */
+        private Date calculateDueDate() {
+            BusinessCalendar businessCalendar = Context
+                .getProcessEngineConfiguration()
+                .getBusinessCalendarManager()
+                .getBusinessCalendar(CycleBusinessCalendar.NAME);
+
+            return businessCalendar.resolveDuedate(
+                String.format("R/PT%dS", waitBetweenRetriesInSeconds));
+        }
+
+        private String getExceptionStacktrace() {
+            StringWriter stringWriter = new StringWriter();
+            exception.printStackTrace(new PrintWriter(stringWriter));
+            return stringWriter.toString();
+        }
+    }
+
+    private final int maxNumberOfRetries;
+    private final int waitBetweenRetriesInSeconds;
+
+    public ConfigurableFailedJobCommandFactory(int maxNumberOfRetries, int waitBetweenRetriesInSeconds) {
+        checkArgument(maxNumberOfRetries > 0 || maxNumberOfRetries == -1,
+            "Max number of retries should be a positive number or -1 (infinite)");
+        checkArgument(waitBetweenRetriesInSeconds > 0, "waitBetweenRetriesInSeconds should be positive");
+
+        this.maxNumberOfRetries = maxNumberOfRetries;
+        this.waitBetweenRetriesInSeconds = waitBetweenRetriesInSeconds;
+    }
+
+    @Override
+    public Command<Object> getCommand(String jobId, Throwable exception) {
+        return new IncrementJobRetriesCmd(jobId, exception, maxNumberOfRetries, waitBetweenRetriesInSeconds);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activiti/ConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activiti/ConfigurationFactory.java b/core/src/main/java/org/apache/provisionr/core/activiti/ConfigurationFactory.java
new file mode 100644
index 0000000..88341e1
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activiti/ConfigurationFactory.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activiti;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import javax.sql.DataSource;
+import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
+import org.activiti.engine.impl.jobexecutor.FailedJobCommandFactory;
+import org.activiti.engine.impl.jobexecutor.JobExecutor;
+
+public class ConfigurationFactory {
+
+    private DataSource dataSource;
+    private String databaseSchemaUpdate;
+
+    private boolean jobExecutorActivate = true;
+    private JobExecutor jobExecutor;
+
+    private FailedJobCommandFactory failedJobCommandFactory;
+
+    public StandaloneProcessEngineConfiguration getConfiguration() {
+        StandaloneProcessEngineConfiguration conf = new StandaloneProcessEngineConfiguration();
+
+        conf.setDataSource(dataSource);
+        conf.setDatabaseSchemaUpdate(databaseSchemaUpdate);
+
+        conf.setJobExecutorActivate(jobExecutorActivate);
+        conf.setJobExecutor(jobExecutor);
+
+        conf.setFailedJobCommandFactory(failedJobCommandFactory);
+
+        return conf;
+    }
+
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = checkNotNull(dataSource, "dataSource is null");
+    }
+
+    public void setDatabaseSchemaUpdate(String databaseSchemaUpdate) {
+        this.databaseSchemaUpdate = checkNotNull(databaseSchemaUpdate, "databaseSchemaUpdate is null");
+    }
+
+    public void setJobExecutorActivate(boolean jobExecutorActivate) {
+        this.jobExecutorActivate = jobExecutorActivate;
+    }
+
+    public void setJobExecutor(JobExecutor jobExecutor) {
+        this.jobExecutor = checkNotNull(jobExecutor, "jobExecutor is null");
+    }
+
+    public void setFailedJobCommandFactory(FailedJobCommandFactory failedJobCommandFactory) {
+        this.failedJobCommandFactory = checkNotNull(failedJobCommandFactory, "failedJobCommandFactory is null");
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activities/CheckProcessesEnded.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activities/CheckProcessesEnded.java b/core/src/main/java/org/apache/provisionr/core/activities/CheckProcessesEnded.java
new file mode 100644
index 0000000..be7a5aa
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activities/CheckProcessesEnded.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activities;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.List;
+import org.activiti.engine.RuntimeService;
+import org.activiti.engine.delegate.DelegateExecution;
+import org.activiti.engine.delegate.JavaDelegate;
+import org.activiti.engine.runtime.ProcessInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CheckProcessesEnded implements JavaDelegate {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CheckProcessesEnded.class);
+
+    private final RuntimeService runtimeService;
+
+    private final String variableWithProcessIds;
+    private final String resultVariable;
+
+    public CheckProcessesEnded(RuntimeService runtimeService, String variableWithProcessIds,
+                               String resultVariable) {
+        this.runtimeService = checkNotNull(runtimeService, "runtimeService is null");
+        this.variableWithProcessIds = checkNotNull(variableWithProcessIds, "variableWithProcessIds is null");
+        this.resultVariable = checkNotNull(resultVariable, "resultVariable is null");
+    }
+
+    @Override
+    public void execute(DelegateExecution execution) throws Exception {
+        @SuppressWarnings("unchecked")
+        List<String> processIds = (List<String>) execution.getVariable(variableWithProcessIds);
+
+        List<String> ended = Lists.newArrayList(Iterables.filter(processIds, new Predicate<String>() {
+            @Override
+            public boolean apply(String processInstanceId) {
+                ProcessInstance instance = runtimeService.createProcessInstanceQuery()
+                    .processInstanceId(processInstanceId).singleResult();
+
+                return instance == null || instance.isEnded();
+            }
+        }));
+
+        boolean done = (processIds.size() == ended.size());
+        execution.setVariable(resultVariable, done);
+
+        if (done) {
+            LOG.info("All background processes ENDED: {}", processIds);
+        } else {
+            if (LOG.isInfoEnabled()) {
+                LOG.info("Still waiting for: {}", Sets.difference(ImmutableSet.copyOf(processIds),
+                    ImmutableSet.copyOf(ended)));
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activities/DownloadFiles.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activities/DownloadFiles.java b/core/src/main/java/org/apache/provisionr/core/activities/DownloadFiles.java
new file mode 100644
index 0000000..7687b40
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activities/DownloadFiles.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activities;
+
+import org.apache.provisionr.api.pool.Machine;
+import org.apache.provisionr.api.pool.Pool;
+import org.apache.provisionr.api.software.Software;
+import org.apache.provisionr.core.Mustache;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import java.util.List;
+import java.util.Map;
+
+public class DownloadFiles extends PuppetActivity {
+
+    public static final String FILES_TEMPLATE = "/org/apache/provisionr/core/puppet/files.pp.mustache";
+
+    public DownloadFiles() {
+        super("files");
+    }
+
+    @Override
+    public String createPuppetScript(Pool pool, Machine machine) throws Exception {
+        return Mustache.toString(InstallPackages.class, FILES_TEMPLATE,
+            ImmutableMap.of("files", filesAsListOfMaps(pool.getSoftware())));
+    }
+
+    private List<Map<String, String>> filesAsListOfMaps(Software software) {
+        return Lists.newArrayList(Iterables.transform(software.getFiles().entrySet(),
+            new Function<Map.Entry<String, String>, Map<String, String>>() {
+                @Override
+                public Map<String, String> apply(Map.Entry<String, String> entry) {
+                    return ImmutableMap.of("source", entry.getKey(), "destination", entry.getValue());
+                }
+            }));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activities/InstallPackages.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activities/InstallPackages.java b/core/src/main/java/org/apache/provisionr/core/activities/InstallPackages.java
new file mode 100644
index 0000000..f92d7d5
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activities/InstallPackages.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activities;
+
+import org.apache.provisionr.api.pool.Machine;
+import org.apache.provisionr.api.pool.Pool;
+import org.apache.provisionr.api.software.Software;
+import org.apache.provisionr.core.Mustache;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public class InstallPackages extends PuppetActivity {
+
+    public static final String PACKAGES_TEMPLATE = "/org/apache/provisionr/core/puppet/packages.pp.mustache";
+
+    public InstallPackages() {
+        super("packages");
+    }
+
+    @Override
+    public String createPuppetScript(Pool pool, Machine machine) throws IOException {
+        return Mustache.toString(InstallPackages.class, PACKAGES_TEMPLATE,
+            ImmutableMap.of("packages", packagesAsListOfMaps(pool.getSoftware())));
+    }
+
+    private List<Map<String, String>> packagesAsListOfMaps(Software software) {
+        ImmutableList.Builder<Map<String, String>> result = ImmutableList.builder();
+        for (String pkg : software.getPackages()) {
+            result.add(ImmutableMap.of("package", pkg));
+        }
+        return result.build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activities/InstallRepositories.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activities/InstallRepositories.java b/core/src/main/java/org/apache/provisionr/core/activities/InstallRepositories.java
new file mode 100644
index 0000000..1154612
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activities/InstallRepositories.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activities;
+
+import org.apache.provisionr.api.pool.Machine;
+import org.apache.provisionr.api.pool.Pool;
+import org.apache.provisionr.api.software.Repository;
+import org.apache.provisionr.api.software.Software;
+import org.apache.provisionr.core.Mustache;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.util.List;
+import java.util.Map;
+
+public class InstallRepositories extends PuppetActivity {
+
+    public static final String REPOSITORIES_TEMPLATE =
+        "/org/apache/provisionr/core/puppet/repositories.pp.mustache";
+
+    public InstallRepositories() {
+        super("repositories");
+    }
+
+    @Override
+    public String createPuppetScript(Pool pool, Machine machine) throws Exception {
+        return Mustache.toString(getClass(), REPOSITORIES_TEMPLATE,
+            ImmutableMap.<String, List<Map<String, String>>>of(
+                "repositories", repositoriesAsListOfMaps(pool.getSoftware())));
+    }
+
+    private List<Map<String, String>> repositoriesAsListOfMaps(Software software) {
+        return Lists.transform(software.getRepositories(), new Function<Repository, Map<String, String>>() {
+            @Override
+            public Map<String, String> apply(Repository repository) {
+                ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder()
+                    .put("name", repository.getName())
+                    .put("content", Joiner.on("\\n").join(repository.getEntries()));
+
+                if (repository.getKey().isPresent()) {
+                    builder.put("key", repository.getKey().get().replace("\n", "\\n"));
+                }
+                return builder.build();
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activities/IsMachinePortOpen.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activities/IsMachinePortOpen.java b/core/src/main/java/org/apache/provisionr/core/activities/IsMachinePortOpen.java
new file mode 100644
index 0000000..f19f542
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activities/IsMachinePortOpen.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activities;
+
+import org.apache.provisionr.api.pool.Machine;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import org.activiti.engine.delegate.DelegateExecution;
+import org.activiti.engine.delegate.JavaDelegate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Check that we can connect to a specific port on a remote machine
+ * <p/>
+ * This activity expects to find an environment variable named 'machine'
+ */
+public class IsMachinePortOpen implements JavaDelegate {
+
+    private static final Logger LOG = LoggerFactory.getLogger(IsMachinePortOpen.class);
+
+    public static final String MACHINE = "machine";
+    public static final int TIMEOUT_IN_MILLISECONDS = 1000;
+
+    private final String resultVariable;
+    private final int port;
+
+    public IsMachinePortOpen(String resultVariable, int port) {
+        checkArgument(port > 0, "invalid port number");
+        this.resultVariable = checkNotNull(resultVariable, "resultVariable is null");
+        this.port = port;
+    }
+
+    @Override
+    public void execute(DelegateExecution execution) throws Exception {
+        Machine machine = (Machine) execution.getVariable(MACHINE);
+        checkNotNull(machine, "expecting a process variable named machine (multi-instance?)");
+
+        if (isPortOpen(machine, port)) {
+            LOG.info("<< Port {} is OPEN on {}", port, machine.getPublicDnsName());
+            execution.setVariable(resultVariable, true);
+
+        } else {
+            LOG.info("<< Port {} is CLOSED on {}", port, machine.getPublicDnsName());
+            execution.setVariable(resultVariable, false);
+        }
+    }
+
+    private boolean isPortOpen(Machine machine, int port) {
+        InetSocketAddress socketAddress = new InetSocketAddress(machine.getPublicDnsName(), port);
+
+        Socket socket = null;
+        try {
+            socket = new Socket();
+            socket.setReuseAddress(false);
+            socket.setSoLinger(false, 1);
+            socket.setSoTimeout(TIMEOUT_IN_MILLISECONDS);
+            socket.connect(socketAddress, TIMEOUT_IN_MILLISECONDS);
+
+        } catch (IOException e) {
+            return false;
+        } finally {
+            if (socket != null) {
+                try {
+                    socket.close();
+                } catch (IOException ioe) {
+                    // no work to do
+                }
+            }
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activities/KillMachineSetUpProcesses.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activities/KillMachineSetUpProcesses.java b/core/src/main/java/org/apache/provisionr/core/activities/KillMachineSetUpProcesses.java
new file mode 100644
index 0000000..08b4841
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activities/KillMachineSetUpProcesses.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activities;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+import org.activiti.engine.RuntimeService;
+import org.activiti.engine.delegate.DelegateExecution;
+import org.activiti.engine.delegate.JavaDelegate;
+import org.activiti.engine.runtime.ProcessInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class KillMachineSetUpProcesses implements JavaDelegate {
+
+    private static final Logger LOG = LoggerFactory.getLogger(KillMachineSetUpProcesses.class);
+
+    private RuntimeService runtimeService;
+    private String variableWithProcessIds;
+
+    public KillMachineSetUpProcesses(RuntimeService runtimeService, String variableWithProcessIds) {
+        this.runtimeService = checkNotNull(runtimeService, "runtimeService is null");
+        this.variableWithProcessIds = checkNotNull(variableWithProcessIds, "variableWithProcessIds is null");
+    }
+
+    @Override
+    public void execute(DelegateExecution execution) throws Exception {
+        @SuppressWarnings("unchecked")
+        List<String> processIds = (List<String>) execution.getVariable(variableWithProcessIds);
+
+        List<String> forceEnded = Lists.newArrayList(Iterables.filter(processIds, new Predicate<String>() {
+            @Override
+            public boolean apply(String processInstanceId) {
+                ProcessInstance instance = runtimeService.createProcessInstanceQuery()
+                    .processInstanceId(processInstanceId).singleResult();
+                if (instance != null && !instance.isEnded()) {
+                    runtimeService.deleteProcessInstance(processInstanceId, "Pending process needs to be killed");
+                    return true;
+                }
+                return false;
+            }
+        }));
+        LOG.info("Killed pending machine setup processes: {}", forceEnded);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activities/PuppetActivity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activities/PuppetActivity.java b/core/src/main/java/org/apache/provisionr/core/activities/PuppetActivity.java
new file mode 100644
index 0000000..704f9e1
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activities/PuppetActivity.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activities;
+
+import org.apache.provisionr.api.access.AdminAccess;
+import org.apache.provisionr.api.pool.Machine;
+import org.apache.provisionr.api.pool.Pool;
+import org.apache.provisionr.core.CoreProcessVariables;
+import org.apache.provisionr.core.Ssh;
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.connection.channel.direct.Session;
+import org.activiti.engine.delegate.DelegateExecution;
+import org.activiti.engine.delegate.JavaDelegate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract activity useful for implementing activities that execute
+ * puppet scripts on pool machines
+ */
+public abstract class PuppetActivity implements JavaDelegate {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PuppetActivity.class);
+
+    /**
+     * Puppet apply had no failures during the current transaction
+     * <p/>
+     * --detailed-exitcodes Provide transaction information via exit codes. If this is
+     * enabled, an exit code of '2' means there were changes, an exit code of '4' means
+     * there were failures during the transaction, and an exit code of '6' means there
+     * were both changes and failures.
+     *
+     * @see <a href="http://docs.puppetlabs.com/man/apply.html" />
+     */
+    public static final int PUPPET_FINISHED_WITH_NO_FAILURES = 2;
+
+    private final String remoteFileName;
+
+    public PuppetActivity(String remoteFileName) {
+        this.remoteFileName = checkNotNull(remoteFileName);
+    }
+
+    /**
+     * This method creates a Puppet script for remote execution
+     */
+    public abstract String createPuppetScript(Pool pool, Machine machine) throws Exception;
+
+    /**
+     * Override this method to change the credentials used for SSH access
+     */
+    public AdminAccess overrideAdminAccess(Pool pool) {
+        return pool.getAdminAccess();
+    }
+
+    /**
+     * Map of additional files to create on the remote machine. Contains pairs of (remotePath, content)
+     */
+    public Map<String, String> createAdditionalFiles(Pool pool, Machine machine) throws Exception {
+        return ImmutableMap.of();
+    }
+
+    @Override
+    public void execute(DelegateExecution execution) throws Exception {
+        Pool pool = (Pool) execution.getVariable(CoreProcessVariables.POOL);
+        checkNotNull(pool, "Please add the pool description as a process " +
+            "variable with the name '%s'.", CoreProcessVariables.POOL);
+
+        Machine machine = (Machine) execution.getVariable("machine");
+        checkNotNull(machine, "expecting a process variable named 'machine'");
+
+        LOG.info(">> Connecting to machine {} to run puppet script", machine);
+
+        SSHClient client = Ssh.newClient(machine, overrideAdminAccess(pool));
+        try {
+            for (Map.Entry<String, String> entry : createAdditionalFiles(pool, machine).entrySet()) {
+                Ssh.createFile(client, /* content = */ entry.getValue(), 0600, /* destination= */ entry.getKey());
+            }
+
+            final String destination = "/tmp/" + remoteFileName + ".pp";
+            Ssh.createFile(client, createPuppetScript(pool, machine), 0600, destination);
+
+            Session session = client.startSession();
+            try {
+                session.allocateDefaultPTY();
+
+                // TODO: extract this loop outside of this activity (probably using a business process error)
+                final String runScriptWithWaitCommand = "while ! which puppet &> /dev/null ; " +
+                    "do echo 'Puppet command not found. Waiting for userdata.sh script to finish (10s)' " +
+                    "&& sleep 10; " +
+                    "done " +
+                    "&& sudo puppet apply --detailed-exitcodes --debug --verbose " + destination;
+                Session.Command command = session.exec(runScriptWithWaitCommand);
+
+                Ssh.logCommandOutput(LOG, machine.getExternalId(), command);
+                command.join();
+
+                final Integer exitStatus = command.getExitStatus();
+                if (exitStatus != PUPPET_FINISHED_WITH_NO_FAILURES && exitStatus != 0) {
+                    throw new RuntimeException(String.format("Failed to execute puppet. " +
+                        "Exit code: %d. Exit message: %s", exitStatus, command.getExitErrorMessage()));
+
+                } else {
+                    LOG.info("<< Command completed successfully with exit code 0");
+                }
+
+
+            } finally {
+                session.close();
+            }
+        } finally {
+            client.close();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/activities/SpawnProcessForEachMachine.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/activities/SpawnProcessForEachMachine.java b/core/src/main/java/org/apache/provisionr/core/activities/SpawnProcessForEachMachine.java
new file mode 100644
index 0000000..c27fd0b
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/activities/SpawnProcessForEachMachine.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2013 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.activities;
+
+import org.apache.provisionr.api.pool.Machine;
+import org.apache.provisionr.api.pool.Pool;
+import org.apache.provisionr.core.CoreConstants;
+import org.apache.provisionr.core.CoreProcessVariables;
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.util.List;
+import org.activiti.engine.ProcessEngine;
+import org.activiti.engine.delegate.DelegateExecution;
+import org.activiti.engine.delegate.JavaDelegate;
+import org.activiti.engine.runtime.ProcessInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Create an Activiti process for each machine and store the process IDs
+ */
+public class SpawnProcessForEachMachine implements JavaDelegate {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SpawnProcessForEachMachine.class);
+
+    /**
+     * Name of the process variable that stores a {@link org.apache.provisionr.api.pool.Machine Machine} object.
+     * Used inside the process to connect to that machine.
+     */
+    private static final String MACHINE = "machine";
+
+    private final ProcessEngine processEngine;
+    private final String processKey;
+    private final String type;
+    private final String resultVariable;
+
+    public SpawnProcessForEachMachine(
+        ProcessEngine processEngine, String processKey, String type, String resultVariable
+    ) {
+        this.processEngine = checkNotNull(processEngine, "processEngine is null");
+        this.processKey = checkNotNull(processKey, "processKey is null");
+        this.type = checkNotNull(type, "type is null");
+        this.resultVariable = checkNotNull(resultVariable, "resultVariable is null");
+    }
+
+    @Override
+    public void execute(DelegateExecution execution) throws Exception {
+        final Pool pool = (Pool) execution.getVariable(CoreProcessVariables.POOL);
+        checkNotNull(pool, "Expecting to find a pool description as process variable");
+
+        @SuppressWarnings("unchecked")
+        List<Machine> machines = (List<Machine>) execution.getVariable(CoreProcessVariables.MACHINES);
+        checkNotNull(machines, "Expecting to find the list of machines as process variable");
+
+        final String poolBusinessKey = String.class.cast(execution.getVariable(CoreProcessVariables.POOL_BUSINESS_KEY));
+        checkNotNull(poolBusinessKey, "No way to link sub-processes to master process, poolBusinessKey is null");
+
+        /* Authenticate as kermit to make the process visible in the Explorer UI */
+        processEngine.getIdentityService().setAuthenticatedUserId(CoreConstants.ACTIVITI_EXPLORER_DEFAULT_USER);
+
+        List<String> processIds = Lists.newArrayList();
+        for (Machine machine : machines) {
+            final String perMachineProcessBusinessKey = String.format("%s-%s-%s",
+                execution.getProcessBusinessKey(), type, machine.getExternalId());
+
+            ProcessInstance perMachineProcess = processEngine.getRuntimeService().startProcessInstanceByKey(
+                processKey, perMachineProcessBusinessKey,
+                ImmutableMap.<String, Object>of(CoreProcessVariables.POOL, pool,
+                    CoreProcessVariables.POOL_BUSINESS_KEY, poolBusinessKey,
+                    CoreProcessVariables.IS_CACHED_IMAGE, pool.getSoftware().isCachedImage(),
+                    MACHINE, machine));
+
+            LOG.info("Started background '" + type + "' process {} ({}) for machine {}",
+                new Object[]{perMachineProcessBusinessKey, perMachineProcess.getId(), machine.getExternalId()});
+            processIds.add(perMachineProcess.getId());
+        }
+
+        LOG.info("Saving process IDs {} as {}", processIds, resultVariable);
+        execution.setVariable(resultVariable, processIds);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/logging/ErrorStreamLogger.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/logging/ErrorStreamLogger.java b/core/src/main/java/org/apache/provisionr/core/logging/ErrorStreamLogger.java
new file mode 100644
index 0000000..2369268
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/logging/ErrorStreamLogger.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.logging;
+
+import java.io.InputStream;
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+public class ErrorStreamLogger extends StreamLogger {
+
+    public ErrorStreamLogger(InputStream inputStream, Logger logger, Marker marker) {
+        super(inputStream, logger, marker);
+    }
+
+    @Override
+    protected void log(Logger logger, Marker marker, String line) {
+        logger.error(marker, line);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/logging/InfoStreamLogger.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/logging/InfoStreamLogger.java b/core/src/main/java/org/apache/provisionr/core/logging/InfoStreamLogger.java
new file mode 100644
index 0000000..810131b
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/logging/InfoStreamLogger.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.logging;
+
+import java.io.InputStream;
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+public class InfoStreamLogger extends StreamLogger {
+
+    public InfoStreamLogger(InputStream inputStream, Logger logger, Marker marker) {
+        super(inputStream, logger, marker);
+    }
+
+    @Override
+    protected void log(Logger logger, Marker marker, String line) {
+        logger.info(marker, line);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/logging/StreamLogger.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/logging/StreamLogger.java b/core/src/main/java/org/apache/provisionr/core/logging/StreamLogger.java
new file mode 100644
index 0000000..1e1e3eb
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/logging/StreamLogger.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.logging;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.base.Throwables;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+public abstract class StreamLogger extends Thread {
+
+    private final InputStream inputStream;
+    private final Logger logger;
+    private final Marker marker;
+
+    public StreamLogger(InputStream inputStream, Logger logger, Marker marker) {
+        this.inputStream = checkNotNull(inputStream, "inputStream is null");
+        this.logger = checkNotNull(logger, "logger is null");
+        this.marker = checkNotNull(marker, "marker is null");
+
+        setName(marker.getName());
+        setDaemon(true);
+    }
+
+    /**
+     * Write the log message at a specific level including the marker
+     *
+     * @param logger reference to external logger
+     * @param marker log message marker
+     * @param line   line collected from the input stream
+     */
+    protected abstract void log(Logger logger, Marker marker, String line);
+
+
+    @Override
+    public void run() {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+        try {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (!line.isEmpty()) {
+                    log(logger, marker, line);
+                }
+            }
+        } catch (IOException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/templates/PoolTemplate.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/templates/PoolTemplate.java b/core/src/main/java/org/apache/provisionr/core/templates/PoolTemplate.java
new file mode 100644
index 0000000..81b6e58
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/templates/PoolTemplate.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.templates;
+
+import org.apache.provisionr.api.pool.Pool;
+import com.google.common.base.Function;
+
+/**
+ * Marker interface for pre-configured pools. You can use it by
+ * applying the function on an existing pool definition
+ */
+public interface PoolTemplate extends Function<Pool, Pool> {
+
+    /**
+     * Get the pre-configured pool template identifier
+     */
+    String getId();
+
+    /**
+     * A short description of this template
+     */
+    String getDescription();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/templates/PoolTemplateInstaller.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/templates/PoolTemplateInstaller.java b/core/src/main/java/org/apache/provisionr/core/templates/PoolTemplateInstaller.java
new file mode 100644
index 0000000..150becd
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/templates/PoolTemplateInstaller.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2012 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.templates;
+
+import org.apache.provisionr.core.templates.xml.XmlTemplate;
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.Maps;
+import java.io.File;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.felix.fileinstall.ArtifactInstaller;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Listener for pool template files
+ * <p/>
+ * When a new xml file is places under templates this class is
+ * notified and a new pool template is registered as a service
+ */
+public class PoolTemplateInstaller implements ArtifactInstaller {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PoolTemplateInstaller.class);
+
+    public static final String TEMPLATES_FOLDER = "templates";
+    public static final String TEMPLATES_EXTENSION = ".xml";
+
+    /**
+     * A bundle context provided by the OSGi framework
+     */
+    private final BundleContext bundleContext;
+
+    /**
+     * A map with all the pool templates registered by this listener
+     */
+    private final ConcurrentMap<String, ServiceRegistration> templates;
+
+    public PoolTemplateInstaller(BundleContext bundleContext) {
+        this.bundleContext = checkNotNull(bundleContext, "bundleContext is null");
+        this.templates = Maps.newConcurrentMap();
+    }
+
+    @Override
+    public boolean canHandle(File file) {
+        return TEMPLATES_FOLDER.equals(file.getParentFile().getName())
+            && file.getName().endsWith(TEMPLATES_EXTENSION);
+    }
+
+    /**
+     * Install a new pool template as a service using the file content
+     * <p/>
+     * The absolute file path is the unique identifier
+     */
+    @Override
+    public void install(File file) throws Exception {
+        final String absolutePath = file.getAbsolutePath();
+        LOG.info("Installing Pool template from  " + absolutePath);
+
+        if (!templates.containsKey(absolutePath)) {
+            PoolTemplate template = XmlTemplate.newXmlTemplate(file);
+            ServiceRegistration registration = bundleContext
+                .registerService(PoolTemplate.class.getName(), template, null);
+
+            templates.put(absolutePath, registration);
+            LOG.info("Registered new template with ID: " + template.getId());
+        }
+    }
+
+    /**
+     * Uninstall a pool description identified by the absolute file path
+     */
+    @Override
+    public void uninstall(File file) throws Exception {
+        final String absolutePath = file.getAbsolutePath();
+        LOG.info("Uninstalling Pool template for path " + absolutePath);
+
+        if (templates.containsKey(absolutePath)) {
+            templates.remove(absolutePath).unregister();
+        }
+    }
+
+    /**
+     * Update a pool template
+     * <p/>
+     * This method performs no actions if there is no pool registered for this file
+     */
+    @Override
+    public void update(File file) throws Exception {
+        if (templates.containsKey(file.getAbsolutePath())) {
+            uninstall(file);
+            install(file);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/templates/xml/FileEntry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/templates/xml/FileEntry.java b/core/src/main/java/org/apache/provisionr/core/templates/xml/FileEntry.java
new file mode 100644
index 0000000..5e5811e
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/templates/xml/FileEntry.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2013 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.templates.xml;
+
+import com.google.common.annotations.VisibleForTesting;
+import static com.google.common.base.Preconditions.checkNotNull;
+import javax.xml.bind.annotation.XmlAttribute;
+
+/**
+ * Represents a file entry from a pool template
+ * <p/>
+ * Designed to be consumed only by JAXB. It looks like this in xml:
+ * <p/>
+ * <file source="http://archive.cloudera.com/cm4/installer/latest/cloudera-manager-installer.bin"
+ * destination="/opt/cloudera-manager-installer.bin"/>
+ */
+public class FileEntry {
+
+    private String source;
+    private String destination;
+
+    public FileEntry() {
+    }
+
+    @VisibleForTesting
+    FileEntry(String source, String destination) {
+        this.source = checkNotNull(source, "source is null");
+        this.destination = checkNotNull(destination, "destination is null");
+    }
+
+    @XmlAttribute(name = "source")
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = checkNotNull(source, "source is null");
+    }
+
+    @XmlAttribute(name = "destination")
+    public String getDestination() {
+        return destination;
+    }
+
+    public void setDestination(String destination) {
+        this.destination = checkNotNull(destination, "destination is null");
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        FileEntry fileEntry = (FileEntry) o;
+
+        if (destination != null ? !destination.equals(fileEntry.destination) : fileEntry.destination != null)
+            return false;
+        if (source != null ? !source.equals(fileEntry.source) : fileEntry.source != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = source != null ? source.hashCode() : 0;
+        result = 31 * result + (destination != null ? destination.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "FileEntry{" +
+            "source='" + source + '\'' +
+            ", destination='" + destination + '\'' +
+            '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-provisionr/blob/6ba40c4b/core/src/main/java/org/apache/provisionr/core/templates/xml/RepositoryEntry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/provisionr/core/templates/xml/RepositoryEntry.java b/core/src/main/java/org/apache/provisionr/core/templates/xml/RepositoryEntry.java
new file mode 100644
index 0000000..3220298
--- /dev/null
+++ b/core/src/main/java/org/apache/provisionr/core/templates/xml/RepositoryEntry.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2013 S.C. Axemblr Software Solutions S.R.L
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.provisionr.core.templates.xml;
+
+import com.google.common.annotations.VisibleForTesting;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Lists.newArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * Represents a custom repository entry from a pool template
+ * <p/>
+ * Designed to be consumed only by JAXB. It looks like this in xml:
+ * <p/>
+ * <repository id="cloudera-cdh3">
+ * <entries>
+ * <entry>deb http://archive.cloudera.com/debian lucid-cdh3 contrib</entry>
+ * </entries>
+ * <key><![CDATA[-----BEGIN PGP PUBLIC KEY BLOCK----- ]]</key>
+ * </repository>
+ */
+public class RepositoryEntry {
+
+    private String id;
+
+    private List<String> entries = newArrayList();
+
+    private String key;
+
+    public RepositoryEntry() {
+    }
+
+    @VisibleForTesting
+    RepositoryEntry(String id, List<String> entries, String key) {
+        this.id = checkNotNull(id, "id is null");
+        this.entries = checkNotNull(entries, "entries is null");
+        this.key = checkNotNull(key, "key is null");
+    }
+
+    @XmlAttribute(name = "id")
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = checkNotNull(id, "id is null");
+    }
+
+    @XmlElementWrapper(name = "entries")
+    @XmlElement(name = "entry")
+    public List<String> getEntries() {
+        return entries;
+    }
+
+    public void setEntries(List<String> entries) {
+        this.entries = checkNotNull(entries, "entries is null");
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = checkNotNull(key, "key is null");
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        RepositoryEntry that = (RepositoryEntry) o;
+
+        if (entries != null ? !entries.equals(that.entries) : that.entries != null) return false;
+        if (id != null ? !id.equals(that.id) : that.id != null) return false;
+        if (key != null ? !key.equals(that.key) : that.key != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = id != null ? id.hashCode() : 0;
+        result = 31 * result + (entries != null ? entries.hashCode() : 0);
+        result = 31 * result + (key != null ? key.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "RepositoryEntry{" +
+            "id='" + id + '\'' +
+            ", entries=" + entries +
+            ", key='" + key + '\'' +
+            '}';
+    }
+}


Mime
View raw message