You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2017/01/25 09:58:55 UTC
[3/3] jclouds-labs git commit: Add Vagrant provider
Add Vagrant provider
Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/fadcd5d9
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/fadcd5d9
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/fadcd5d9
Branch: refs/heads/2.0.x
Commit: fadcd5d9f715f6661d550f613a83b0ab50dc718b
Parents: 5343918
Author: Svetoslav Neykov <sv...@neykov.name>
Authored: Thu Apr 2 22:35:47 2015 +0300
Committer: Ignasi Barrera <na...@apache.org>
Committed: Wed Jan 25 10:58:40 2017 +0100
----------------------------------------------------------------------
pom.xml | 2 +
vagrant/README.md | 122 ++++++
vagrant/pom.xml | 129 ++++++
.../org/jclouds/vagrant/VagrantApiMetadata.java | 81 ++++
.../jclouds/vagrant/api/VagrantApiFacade.java | 42 ++
.../compute/VagrantComputeServiceAdapter.java | 401 +++++++++++++++++++
.../config/PersistVagrantCredentialsModule.java | 148 +++++++
.../VagrantComputeServiceContextModule.java | 109 +++++
.../org/jclouds/vagrant/domain/VagrantNode.java | 70 ++++
.../jclouds/vagrant/functions/BoxToImage.java | 75 ++++
.../functions/MachineToNodeMetadata.java | 120 ++++++
.../vagrant/functions/OutdatedBoxesFilter.java | 69 ++++
.../org/jclouds/vagrant/internal/BoxConfig.java | 110 +++++
.../jclouds/vagrant/internal/MachineConfig.java | 116 ++++++
.../vagrant/internal/VagrantCliFacade.java | 109 +++++
.../vagrant/internal/VagrantNodeRegistry.java | 112 ++++++
.../vagrant/internal/VagrantOutputRecorder.java | 59 +++
.../vagrant/internal/VagrantWireLogger.java | 65 +++
.../vagrant/reference/VagrantConstants.java | 66 +++
.../VagrantDefaultImageCredentials.java | 120 ++++++
.../suppliers/VagrantHardwareSupplier.java | 54 +++
.../org/jclouds/vagrant/util/VagrantUtils.java | 67 ++++
vagrant/src/main/resources/Vagrantfile | 80 ++++
.../vagrant/compute/BoxConfigLiveTest.java | 60 +++
.../VagrantComputeServiceAdapterLiveTest.java | 85 ++++
.../compute/VagrantTemplateBuilderLiveTest.java | 60 +++
.../vagrant/compute/WindowsLiveTest.java | 106 +++++
.../vagrant/functions/BoxToImageTest.java | 74 ++++
.../functions/MachineToNodeMetadataTest.java | 295 ++++++++++++++
.../functions/OutdatedBoxesFilterTest.java | 90 +++++
.../jclouds/vagrant/internal/BoxConfigTest.java | 66 +++
.../vagrant/internal/MachineConfigTest.java | 97 +++++
.../internal/VagrantNodeRegistryTest.java | 80 ++++
.../internal/VagrantOutputRecorderTest.java | 58 +++
.../vagrant/internal/VagrantWireLoggerTest.java | 54 +++
.../VagrantDefaultImageCredentialsTest.java | 179 +++++++++
.../src/test/resources/Vagrantfile.boxconfig | 25 ++
vagrant/src/test/resources/logback-test.xml | 37 ++
vagrant/src/test/resources/machine-config.yaml | 20 +
39 files changed, 3712 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 7df21cb..b010cd2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,8 @@
<module>abiquo</module>
<module>profitbricks-rest</module>
<module>oneandone</module>
+ <module>packet</module>
+ <module>vagrant</module>
</modules>
<build>
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/README.md
----------------------------------------------------------------------
diff --git a/vagrant/README.md b/vagrant/README.md
new file mode 100644
index 0000000..3fa71a3
--- /dev/null
+++ b/vagrant/README.md
@@ -0,0 +1,122 @@
+Vagrant provider for jclouds
+============================
+
+Building
+--------
+
+ * `git clone https://github.com/jclouds/jclouds-labs`
+ * `cd jclouds-labs/vagrant`
+ * `mvn clean install`
+ * Copy `target/vagrant-2.0.0-SNAPSHOT.jar` to your classpath
+
+Local caching proxy
+-------------------
+
+### Polipo
+
+Use `polipo` for caching proxy. On OS X install with
+
+```
+brew install polipo
+```
+
+From [SO](http://superuser.com/questions/192696/how-can-i-make-tor-and-polipo-run-and-automatically-restart-using-launchd-on-m):
+
+* Create a config file at ~/.polipo/config
+
+```
+# logLevel = 0xFF
+dnsNameServer=8.8.8.8
+diskCacheRoot = "~/.polipo/cache/"
+
+```
+
+* As root create the file `/Library/LaunchDaemons/fr.jussieu.pps.polipo.plist`, replace $USER with your username:
+```
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Disabled</key>
+ <false/>
+ <key>Label</key>
+ <string>fr.jussieu.pps.polipo</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>/usr/local/bin/polipo</string>
+ <string>-c</string>
+ <string>/Users/$USER/.polipo/config</string>
+ </array>
+ <key>RunAtLoad</key>
+ <true/>
+ <key>OnDemand</key>
+ <false/>
+ <key>UserName</key>
+ <string>$USER</string>
+ <key>GroupName</key>
+ <string>daemon</string>
+ <key>StandardOutPath</key>
+ <string>/Users/$USER/.polipo/polipo.log</string>
+ <key>StandardErrorPath</key>
+ <string>/Users/$USER/.polipo/polipo.log</string>
+</dict>
+</plist>
+```
+
+* `sudo chown root:wheel /Library/LaunchDaemons/fr.jussieu.pps.polipo.plist`
+* `sudo chmod 755 /Library/LaunchDaemons/fr.jussieu.pps.polipo.plist`
+* `sudo launchctl load -w /Library/LaunchDaemons/fr.jussieu.pps.polipo.plist`
+
+### Vagrant
+
+* `vagrant plugin install vagrant-proxyconf`
+* add to `~/.vagrant.d/Vagrantfile`:
+
+```
+Vagrant.configure("2") do |config|
+ if Vagrant.has_plugin?("vagrant-proxyconf")
+ config.proxy.http = "http://10.0.2.2:8123/"
+ config.proxy.https = "http://10.0.2.2:8123/"
+ config.proxy.no_proxy = "localhost,127.0.0.1"
+ end
+end
+```
+
+Where `10.0.2.2` is the IP of your host as seen from the vagrant machines (in this case the NAT interface).
+Optionally could add all your private network IPs from your Vagrant subnet to `no_proxy` to skip the proxy for inter-VM communications.
+
+Testing
+-----------
+
+```
+mvn clean install -Plive
+```
+
+Cleaning up
+-----------
+
+Sometimes users (or tests) do not stop correctly the machines so they need to be destroyed manually periodically.
+All machines live in `~/.jclouds/vagrant`. Create `cleanup.sh` in the folder and execute it to destroy machines created by the provider:
+
+```
+for node in `find ~/.jclouds/vagrant -name Vagrantfile | xargs -n1 dirname`; do
+ pushd $node > /dev/null
+ echo Destroying $node
+ vagrant destroy --force
+ popd> /dev/null
+ rm -rf $machine
+done
+```
+
+Same as a one-liner
+
+```
+for f in `find ~/.jclouds/vagrant/tests -name Vagrantfile | xargs -n1 dirname`; do pushd $f; vagrant destroy --force; popd; rm -rf $f; done
+```
+
+
+Limitations
+-----------
+
+* Machines are created sequentially, no support for parallel execution from virtualbox provider
+* Something prevents using vagrant at the same time with other jclouds providers - they try to login with vagrant user.
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/pom.xml
----------------------------------------------------------------------
diff --git a/vagrant/pom.xml b/vagrant/pom.xml
new file mode 100644
index 0000000..a790f1b
--- /dev/null
+++ b/vagrant/pom.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.jclouds.labs</groupId>
+ <artifactId>jclouds-labs</artifactId>
+ <version>2.0.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>vagrant</artifactId>
+ <name>Vagrant provider</name>
+ <packaging>bundle</packaging>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <test.vagrant.template>imageId=ubuntu/xenial64</test.vagrant.template>
+ <jclouds.osgi.export>org.jclouds.vagrant*;version="${project.version}"</jclouds.osgi.export>
+ <jclouds.osgi.import>org.jclouds*;version="${project.version}",*</jclouds.osgi.import>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>name.neykov</groupId>
+ <artifactId>vagrant-java-bindings</artifactId>
+ <version>0.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-compute</artifactId>
+ <version>${project.parent.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.auto.service</groupId>
+ <artifactId>auto-service</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.auto.value</groupId>
+ <artifactId>auto-value</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-core</artifactId>
+ <version>${project.parent.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-compute</artifactId>
+ <version>${project.parent.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.driver</groupId>
+ <artifactId>jclouds-slf4j</artifactId>
+ <version>${project.parent.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.driver</groupId>
+ <artifactId>jclouds-slf4j</artifactId>
+ <version>${project.parent.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.driver</groupId>
+ <artifactId>jclouds-sshj</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <id>live</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>integration</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <systemPropertyVariables>
+ <test.vagrant.template>${test.vagrant.template}</test.vagrant.template>
+ </systemPropertyVariables>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/VagrantApiMetadata.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/VagrantApiMetadata.java b/vagrant/src/main/java/org/jclouds/vagrant/VagrantApiMetadata.java
new file mode 100644
index 0000000..deebb81
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/VagrantApiMetadata.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.apis.ApiMetadata;
+import org.jclouds.apis.internal.BaseApiMetadata;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.compute.config.ComputeServiceProperties;
+import org.jclouds.vagrant.config.VagrantComputeServiceContextModule;
+import org.jclouds.vagrant.reference.VagrantConstants;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(ApiMetadata.class)
+public class VagrantApiMetadata extends BaseApiMetadata {
+
+ public VagrantApiMetadata() {
+ this(new Builder());
+ }
+
+ protected VagrantApiMetadata(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public Builder toBuilder() {
+ return new Builder().fromApiMetadata(this);
+ }
+
+ public static class Builder extends BaseApiMetadata.Builder<Builder> {
+
+ protected Builder() {
+ id("vagrant")
+ .name("Vagrant API")
+ .identityName("User")
+ .credentialName("Password")
+ .defaultEndpoint("https://atlas.hashicorp.com/")
+ .documentation(URI.create("https://www.vagrantup.com/docs"))
+ .view(ComputeServiceContext.class)
+ .defaultIdentity("guest")
+ .defaultCredential("guest")
+ .defaultProperties(defaultProperties())
+ .defaultModule(VagrantComputeServiceContextModule.class);
+ }
+
+ private Properties defaultProperties() {
+ Properties defaultProperties = BaseApiMetadata.defaultProperties();
+ defaultProperties.setProperty(VagrantConstants.JCLOUDS_VAGRANT_HOME, VagrantConstants.JCLOUDS_VAGRANT_HOME_DEFAULT);
+ defaultProperties.put(ComputeServiceProperties.TEMPLATE, "osFamily=UBUNTU");
+ return defaultProperties;
+ }
+
+ @Override
+ public ApiMetadata build() {
+ return new VagrantApiMetadata(this);
+ }
+
+ @Override
+ protected Builder self() {
+ return this;
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/api/VagrantApiFacade.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/api/VagrantApiFacade.java b/vagrant/src/main/java/org/jclouds/vagrant/api/VagrantApiFacade.java
new file mode 100644
index 0000000..c3bccfd
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/api/VagrantApiFacade.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.api;
+
+import java.io.File;
+import java.util.Collection;
+
+import org.jclouds.domain.LoginCredentials;
+
+public interface VagrantApiFacade<B> {
+ interface Factory<B> {
+ VagrantApiFacade<B> create(File path);
+ }
+
+ /**
+ * Start the named machine
+ *
+ * @return the raw output of the configured provisioners
+ */
+ String up(String machineName);
+ void halt(String machineName);
+ void destroy(String machineName);
+ LoginCredentials sshConfig(String machineName);
+ Collection<B> listBoxes();
+ B getBox(String boxName);
+ void haltForced(String name);
+ boolean exists();
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapter.java b/vagrant/src/main/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapter.java
new file mode 100644
index 0000000..665504f
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapter.java
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.compute;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata.Status;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.Volume;
+import org.jclouds.compute.domain.Volume.Type;
+import org.jclouds.compute.util.AutomaticHardwareIdSpec;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.location.suppliers.all.JustProvider;
+import org.jclouds.logging.Logger;
+import org.jclouds.vagrant.api.VagrantApiFacade;
+import org.jclouds.vagrant.domain.VagrantNode;
+import org.jclouds.vagrant.internal.MachineConfig;
+import org.jclouds.vagrant.internal.VagrantNodeRegistry;
+import org.jclouds.vagrant.reference.VagrantConstants;
+import org.jclouds.vagrant.util.VagrantUtils;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class VagrantComputeServiceAdapter<B> implements ComputeServiceAdapter<VagrantNode, Hardware, B, Location> {
+ private static final Pattern PATTERN_IP_ADDR = Pattern.compile("inet ([0-9\\.]+)/(\\d+)");
+ private static final Pattern PATTERN_IPCONFIG = Pattern.compile("IPv4 Address[ .]+: ([0-9\\.]+)");
+
+ @Resource
+ protected Logger logger = Logger.NULL;
+
+ private final File home;
+ private final JustProvider locationSupplier;
+ private final VagrantNodeRegistry nodeRegistry;
+ private final MachineConfig.Factory machineConfigFactory;
+ private final Function<Collection<B>, Collection<B>> outdatedBoxesFilter;
+ private final VagrantApiFacade.Factory<B> cliFactory;
+ private final Supplier<? extends Map<String, Hardware>> hardwareSupplier;
+
+ @Inject
+ VagrantComputeServiceAdapter(@Named(VagrantConstants.JCLOUDS_VAGRANT_HOME) String home,
+ JustProvider locationSupplier,
+ VagrantNodeRegistry nodeRegistry,
+ MachineConfig.Factory machineConfigFactory,
+ Function<Collection<B>, Collection<B>> outdatedBoxesFilter,
+ VagrantApiFacade.Factory<B> cliFactory,
+ Supplier<? extends Map<String, Hardware>> hardwareSupplier) {
+ this.home = new File(checkNotNull(home, "home"));
+ this.locationSupplier = checkNotNull(locationSupplier, "locationSupplier");
+ this.nodeRegistry = checkNotNull(nodeRegistry, "nodeRegistry");
+ this.machineConfigFactory = checkNotNull(machineConfigFactory, "machineConfigFactory");
+ this.outdatedBoxesFilter = checkNotNull(outdatedBoxesFilter, "outdatedBoxesFilter");
+ this.cliFactory = checkNotNull(cliFactory, "cliFactory");
+ this.hardwareSupplier = checkNotNull(hardwareSupplier, "hardwareSupplier");
+ this.home.mkdirs();
+ }
+
+ @Override
+ public NodeAndInitialCredentials<VagrantNode> createNodeWithGroupEncodedIntoName(String group, String name, Template template) {
+ String machineName = removeFromStart(name, group);
+ File nodePath = new File(home, group);
+
+ init(nodePath, machineName, template);
+
+ NodeAndInitialCredentials<VagrantNode> node = startMachine(nodePath, group, machineName, template.getImage());
+ nodeRegistry.add(node.getNode());
+ return node;
+ }
+
+ private NodeAndInitialCredentials<VagrantNode> startMachine(File path, String group, String name, Image image) {
+
+ VagrantApiFacade<B> vagrant = cliFactory.create(path);
+ String rawOutput = vagrant.up(name);
+ String output = normalizeOutput(name, rawOutput);
+
+ OsFamily osFamily = image.getOperatingSystem().getFamily();
+ String id = group + "/" + name;
+ VagrantNode node = VagrantNode.builder()
+ .setPath(path)
+ .setId(id)
+ .setGroup(group)
+ .setName(name)
+ .setImage(image)
+ .setNetworks(getNetworks(output, getOsInterfacePattern(osFamily)))
+ .setHostname(getHostname(output))
+ .build();
+ node.setMachineState(Status.RUNNING);
+
+ LoginCredentials loginCredentials = null;
+ if (osFamily != OsFamily.WINDOWS) {
+ loginCredentials = vagrant.sshConfig(name);
+ }
+
+ // PrioritizeCredentialsFromTemplate will overwrite loginCredentials with image credentials
+ // AdaptingComputeServiceStrategies saves the merged credentials in credentialStore
+ return new NodeAndInitialCredentials<VagrantNode>(node, node.id(), loginCredentials);
+ }
+
+ private String normalizeOutput(String name, String output) {
+ return output
+ .replaceAll("(?m)^([^,]*,){4}", "")
+ .replace("==> " + name + ": ", "")
+ // Vagrant shows some of the \n verbatim in provisioning command results.
+ .replace("\\n", "\n");
+ }
+
+ private Pattern getOsInterfacePattern(OsFamily osFamily) {
+ if (osFamily == OsFamily.WINDOWS) {
+ return PATTERN_IPCONFIG;
+ } else {
+ return PATTERN_IP_ADDR;
+ }
+ }
+
+ private Collection<String> getNetworks(String output, Pattern ifPattern) {
+ String networks = getDelimitedString(
+ output,
+ VagrantConstants.DELIMITER_NETWORKS_START,
+ VagrantConstants.DELIMITER_NETWORKS_END);
+ Matcher m = ifPattern.matcher(networks);
+ Collection<String> ips = new ArrayList<String>();
+ while (m.find()) {
+ String network = m.group(1);
+ // TODO figure out a more generic approach to ignore unreachable networkds (this one is the NAT'd address).
+ if (network.startsWith("10.")) continue;
+ ips.add(network);
+ }
+ return ips;
+ }
+
+ private String getHostname(String output) {
+ return getDelimitedString(
+ output,
+ VagrantConstants.DELIMITER_HOSTNAME_START,
+ VagrantConstants.DELIMITER_HOSTNAME_END);
+ }
+
+ private String getDelimitedString(String value, String delimStart, String delimEnd) {
+ int startPos = value.indexOf(delimStart);
+ int endPos = value.indexOf(delimEnd);
+ if (startPos == -1) {
+ throw new IllegalStateException("Delimiter " + delimStart + " not found in output \n" + value);
+ }
+ if (endPos == -1) {
+ throw new IllegalStateException("Delimiter " + delimEnd + " not found in output \n" + value);
+ }
+ return value.substring(startPos + delimStart.length(), endPos).trim();
+ }
+
+ private void init(File path, String name, Template template) {
+ try {
+ writeVagrantfile(path);
+ initMachineConfig(path, name, template);
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to initialize Vagrant configuration at " +
+ path + " for machine " + name, e);
+ }
+ }
+
+ private void writeVagrantfile(File path) throws IOException {
+ path.mkdirs();
+ VagrantUtils.write(
+ new File(path, VagrantConstants.VAGRANTFILE),
+ getClass().getClassLoader().getResourceAsStream(VagrantConstants.VAGRANTFILE));
+ }
+
+ private void initMachineConfig(File path, String name, Template template) {
+ MachineConfig config = machineConfigFactory.newInstance(path, name);
+ List<? extends Volume> volumes = template.getHardware().getVolumes();
+ if (volumes != null) {
+ if (volumes.size() == 1) {
+ Volume volume = Iterables.getOnlyElement(volumes);
+ if (volume.getType() != Type.LOCAL || volume.getSize() != null) {
+ throw new IllegalStateException("Custom volume settings not supported. Volumes required: " + volumes);
+ }
+ } else if (volumes.size() > 1) {
+ throw new IllegalStateException("Custom volume settings not supported. Volumes required: " + volumes);
+ }
+ }
+ config.save(ImmutableMap.<String, Object>of(
+ VagrantConstants.CONFIG_BOX, template.getImage().getName(),
+ VagrantConstants.CONFIG_OS_FAMILY, template.getImage().getOperatingSystem().getFamily(),
+ VagrantConstants.CONFIG_HARDWARE_ID, getHardwareId(template),
+ VagrantConstants.CONFIG_MEMORY, Integer.toString(template.getHardware().getRam()),
+ VagrantConstants.CONFIG_CPUS, Integer.toString(countProcessors(template))));
+ }
+
+ private String getHardwareId(Template template) {
+ String id = template.getHardware().getId();
+ if (AutomaticHardwareIdSpec.isAutomaticId(id)) {
+ return VagrantConstants.MACHINES_AUTO_HARDWARE;
+ } else {
+ return id;
+ }
+ }
+
+ private int countProcessors(Template template) {
+ int cnt = 0;
+ for (Processor p : template.getHardware().getProcessors()) {
+ cnt += p.getCores();
+ }
+ return cnt;
+ }
+
+ @Override
+ public Iterable<Hardware> listHardwareProfiles() {
+ return hardwareSupplier.get().values();
+ }
+
+ @Override
+ public Iterable<B> listImages() {
+ Collection<B> allBoxes = cliFactory.create(new File(".")).listBoxes();
+ return outdatedBoxesFilter.apply(allBoxes);
+ }
+
+ @Override
+ public B getImage(String id) {
+ return cliFactory.create(new File(".")).getBox(id);
+ }
+
+ @Override
+ public Iterable<Location> listLocations() {
+ Location provider = Iterables.getOnlyElement(locationSupplier.get());
+ return ImmutableList.of(
+ new LocationBuilder().id("localhost").description("localhost").parent(provider).scope(LocationScope.HOST).build());
+ }
+
+ @Override
+ public VagrantNode getNode(String id) {
+ // needed for BaseComputeServiceLiveTest.testAScriptExecutionAfterBootWithBasicTemplate()
+ // waits for the thread updating the credentialStore to execute
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw Throwables.propagate(e);
+ }
+
+ return nodeRegistry.get(id);
+ }
+
+ @Override
+ public void destroyNode(String id) {
+ VagrantNode node = nodeRegistry.get(id);
+ node.setMachineState(Status.TERMINATED);
+ getMachine(node).destroy(node.name());
+ nodeRegistry.onTerminated(node);
+ deleteMachine(node);
+ }
+
+ private void deleteMachine(VagrantNode node) {
+ File nodeFolder = node.path();
+ File machinesFolder = new File(nodeFolder, VagrantConstants.MACHINES_CONFIG_SUBFOLDER);
+ String filePattern = node.name() + ".";
+ logger.debug("Deleting machine %s", node.id());
+ VagrantUtils.deleteFiles(machinesFolder, filePattern);
+ // No more machines in this group, remove everything
+ if (machinesFolder.list().length == 0) {
+ logger.debug("Machine %s is last in group, deleting Vagrant folder %s", node.id(), nodeFolder.getAbsolutePath());
+ VagrantUtils.deleteFolder(nodeFolder);
+ }
+ }
+
+ @Override
+ public void rebootNode(String id) {
+ halt(id);
+
+ VagrantNode node = nodeRegistry.get(id);
+ String name = node.name();
+ VagrantApiFacade<B> vagrant = getMachine(node);
+ vagrant.up(name);
+ }
+
+ private void halt(String id) {
+ VagrantNode node = nodeRegistry.get(id);
+ String name = node.name();
+ VagrantApiFacade<B> vagrant = getMachine(node);
+
+ try {
+ vagrant.halt(name);
+ } catch (IllegalStateException e) {
+ logger.warn(e, "Failed graceful shutdown of machine " + id + ". Will try to halt it forcefully instead.");
+ vagrant.haltForced(name);
+ }
+ }
+
+ @Override
+ public void resumeNode(String id) {
+ VagrantNode node = nodeRegistry.get(id);
+ String name = node.name();
+ VagrantApiFacade<B> vagrant = getMachine(node);
+ vagrant.up(name);
+ node.setMachineState(Status.RUNNING);
+ }
+
+ @Override
+ public void suspendNode(String id) {
+ halt(id);
+ VagrantNode node = nodeRegistry.get(id);
+ node.setMachineState(Status.SUSPENDED);
+ }
+
+ @Override
+ public Iterable<VagrantNode> listNodes() {
+ return FluentIterable.from(Arrays.asList(home.listFiles()))
+ .transformAndConcat(new Function<File, Collection<VagrantNode>>() {
+ @Override
+ public Collection<VagrantNode> apply(File input) {
+ File machines = new File(input, VagrantConstants.MACHINES_CONFIG_SUBFOLDER);
+ VagrantApiFacade<B> vagrant = cliFactory.create(input);
+ if (input.isDirectory() && machines.exists() && vagrant.exists()) {
+ Collection<VagrantNode> nodes = new ArrayList<VagrantNode>();
+ for (File machine : machines.listFiles()) {
+ if (machine.getName().endsWith(VagrantConstants.MACHINES_CONFIG_EXTENSION)) {
+ String id = input.getName() + "/" + machine.getName().replace(VagrantConstants.MACHINES_CONFIG_EXTENSION, "");
+ VagrantNode n = nodeRegistry.get(id);
+ if (n != null) {
+ nodes.add(n);
+ }
+ }
+ }
+ return nodes;
+ } else {
+ return ImmutableList.of();
+ }
+ }
+ });
+ }
+
+ @Override
+ public Iterable<VagrantNode> listNodesByIds(final Iterable<String> ids) {
+ return Iterables.filter(listNodes(), new Predicate<VagrantNode>() {
+ @Override
+ public boolean apply(VagrantNode input) {
+ return Iterables.contains(ids, input.id());
+ }
+ });
+ }
+
+ private VagrantApiFacade<B> getMachine(VagrantNode node) {
+ File nodePath = node.path();
+ return cliFactory.create(nodePath);
+ }
+
+ private String removeFromStart(String name, String group) {
+ if (name.startsWith(group)) {
+ String machineName = name.substring(group.length());
+ // Can't pass names starting with dash on the command line
+ if (machineName.startsWith("-")) {
+ return machineName.substring(1);
+ } else {
+ return machineName;
+ }
+ } else {
+ return name;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/config/PersistVagrantCredentialsModule.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/config/PersistVagrantCredentialsModule.java b/vagrant/src/main/java/org/jclouds/vagrant/config/PersistVagrantCredentialsModule.java
new file mode 100644
index 0000000..d140b76
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/config/PersistVagrantCredentialsModule.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.config;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.internal.PersistNodeCredentials;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.jclouds.scriptbuilder.functions.CredentialsFromAdminAccess;
+import org.jclouds.vagrant.domain.VagrantNode;
+import org.jclouds.vagrant.internal.MachineConfig;
+import org.jclouds.vagrant.internal.VagrantNodeRegistry;
+import org.jclouds.vagrant.internal.MachineConfig.Factory;
+import org.jclouds.vagrant.reference.VagrantConstants;
+import org.jclouds.vagrant.util.VagrantUtils;
+
+import com.google.common.base.Function;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.TypeLiteral;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+import com.google.inject.name.Names;
+
+public class PersistVagrantCredentialsModule extends AbstractModule {
+
+ static class RefreshCredentialsForNodeIfRanAdminAccess implements Function<NodeMetadata, NodeMetadata> {
+ protected final Map<String, Credentials> credentialStore;
+ protected final VagrantNodeRegistry vagrantNodeRegistry;
+ protected final Statement statement;
+ protected final Factory machineConfigFactory;
+
+ @Inject
+ RefreshCredentialsForNodeIfRanAdminAccess(
+ VagrantNodeRegistry vagrantNodeRegistry,
+ Map<String, Credentials> credentialStore,
+ @Nullable @Assisted Statement statement,
+ MachineConfig.Factory machineConfigFactory) {
+ this.vagrantNodeRegistry = checkNotNull(vagrantNodeRegistry, "vagrantNodeRegistry");
+ this.credentialStore = checkNotNull(credentialStore, "credentialStore");
+ this.statement = statement;
+ this.machineConfigFactory = checkNotNull(machineConfigFactory, "machineConfigFactory");
+ }
+
+ @Override
+ public NodeMetadata apply(NodeMetadata input) {
+ if (statement == null)
+ return input;
+ Credentials credentials = CredentialsFromAdminAccess.INSTANCE.apply(statement);
+ if (credentials != null) {
+ LoginCredentials creds = LoginCredentials.fromCredentials(credentials);
+ input = NodeMetadataBuilder.fromNodeMetadata(input).credentials(creds).build();
+ credentialStore.put("node#" + input.getId(), input.getCredentials());
+ updateMachine(input.getId(), creds);
+ }
+ return input;
+ }
+
+ protected void updateMachine(String id, LoginCredentials credentials) {
+ VagrantNode node = vagrantNodeRegistry.get(id);
+ if (node == null) {
+ throw new IllegalStateException("Updating node credentials failed because node " + id + " not found.");
+ }
+ String provider = node.image().getUserMetadata().get(VagrantConstants.USER_META_PROVIDER);
+
+ MachineConfig machineConfig = machineConfigFactory.newInstance(node);
+ Map<String, Object> config = machineConfig.load();
+
+ config.put(VagrantConstants.CONFIG_USERNAME, credentials.getUser());
+ config.remove(VagrantConstants.CONFIG_PASSWORD);
+ if (credentials.getOptionalPassword().isPresent()) {
+ config.put(VagrantConstants.CONFIG_PASSWORD, credentials.getOptionalPassword().get());
+ }
+ if (credentials.getOptionalPrivateKey().isPresent()) {
+ // Overwrite existing private key and dont't use config.ssh.private_key_path - doesn't work, is ignored.
+ File privateKeyFile = new File(node.path(), ".vagrant/machines/" + node.name() + "/" + provider + "/private_key");
+ try {
+ VagrantUtils.write(privateKeyFile, credentials.getOptionalPrivateKey().get());
+ } catch (IOException e) {
+ throw new IllegalStateException("Failure updating credentials for " + id +
+ ". Can't save private key to " + privateKeyFile.getAbsolutePath(), e);
+ }
+ }
+
+ machineConfig.save(config);
+ }
+ }
+
+ static class RefreshCredentialsForNode extends RefreshCredentialsForNodeIfRanAdminAccess {
+
+ @Inject
+ RefreshCredentialsForNode(
+ VagrantNodeRegistry vagrantNodeRegistry,
+ Map<String, Credentials> credentialStore,
+ @Assisted @Nullable Statement statement,
+ MachineConfig.Factory machineConfigFactory) {
+ super(vagrantNodeRegistry, credentialStore, statement, machineConfigFactory);
+ }
+
+ @Override
+ public NodeMetadata apply(NodeMetadata input) {
+ input = super.apply(input);
+ if (input.getCredentials() != null) {
+ credentialStore.put("node#" + input.getId(), input.getCredentials());
+ updateMachine(input.getId(), input.getCredentials());
+ }
+ return input;
+ }
+
+ }
+
+
+ @Override
+ protected void configure() {
+ install(new FactoryModuleBuilder()
+ .implement(new TypeLiteral<Function<NodeMetadata, NodeMetadata>>() {},
+ Names.named("ifAdminAccess"),
+ RefreshCredentialsForNodeIfRanAdminAccess.class)
+ .implement(new TypeLiteral<Function<NodeMetadata, NodeMetadata>>() {},
+ Names.named("always"),
+ RefreshCredentialsForNode.class)
+ .build(PersistNodeCredentials.class));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/config/VagrantComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/config/VagrantComputeServiceContextModule.java b/vagrant/src/main/java/org/jclouds/vagrant/config/VagrantComputeServiceContextModule.java
new file mode 100644
index 0000000..61f30fd
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/config/VagrantComputeServiceContextModule.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.config;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
+import org.jclouds.compute.config.PersistNodeCredentialsModule;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.internal.ArbitraryCpuRamTemplateBuilderImpl;
+import org.jclouds.compute.domain.internal.TemplateBuilderImpl;
+import org.jclouds.compute.strategy.PopulateDefaultLoginCredentialsForImageStrategy;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.domain.Location;
+import org.jclouds.functions.IdentityFunction;
+import org.jclouds.vagrant.api.VagrantApiFacade;
+import org.jclouds.vagrant.compute.VagrantComputeServiceAdapter;
+import org.jclouds.vagrant.domain.VagrantNode;
+import org.jclouds.vagrant.functions.BoxToImage;
+import org.jclouds.vagrant.functions.MachineToNodeMetadata;
+import org.jclouds.vagrant.functions.OutdatedBoxesFilter;
+import org.jclouds.vagrant.internal.VagrantCliFacade;
+import org.jclouds.vagrant.internal.VagrantWireLogger;
+import org.jclouds.vagrant.strategy.VagrantDefaultImageCredentials;
+import org.jclouds.vagrant.suppliers.VagrantHardwareSupplier;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+
+import vagrant.api.CommandIOListener;
+import vagrant.api.domain.Box;
+
+public class VagrantComputeServiceContextModule extends ComputeServiceAdapterContextModule<VagrantNode, Hardware, Box, Location> {
+
+ @Override
+ protected void configure() {
+ super.configure();
+ bind(new TypeLiteral<ComputeServiceAdapter<VagrantNode, Hardware, Box, Location>>() {
+ }).to(new TypeLiteral<VagrantComputeServiceAdapter<Box>>() {});
+ bind(new TypeLiteral<Function<VagrantNode, NodeMetadata>>() {
+ }).to(MachineToNodeMetadata.class);
+ bind(new TypeLiteral<Function<Box, Image>>() {
+ }).to(BoxToImage.class);
+ bind(new TypeLiteral<Supplier<? extends Map<String, Hardware>>>() {
+ }).to(VagrantHardwareSupplier.class).in(Singleton.class);
+ bind(new TypeLiteral<Function<Hardware, Hardware>>() {
+ }).to(this.<Hardware>castIdentityFunction());
+ bind(new TypeLiteral<Function<Location, Location>>() {
+ }).to(this.<Location>castIdentityFunction());
+ bind(new TypeLiteral<Function<Collection<Box>, Collection<Box>>>() {
+ }).to(OutdatedBoxesFilter.class);
+ install(new FactoryModuleBuilder()
+ .implement(new TypeLiteral<VagrantApiFacade<Box>>() {}, VagrantCliFacade.class)
+ .build(new TypeLiteral<VagrantApiFacade.Factory<Box>>() {}));
+ bind(PopulateDefaultLoginCredentialsForImageStrategy.class).to(VagrantDefaultImageCredentials.class);
+ bind(TemplateBuilderImpl.class).to(ArbitraryCpuRamTemplateBuilderImpl.class);
+ bind(CommandIOListener.class).to(VagrantWireLogger.class).in(Singleton.class);
+ }
+
+ @Provides
+ @TimeStamp
+ public Supplier<Long> timeSupplier() {
+ return new Supplier<Long>() {
+ @Override
+ public Long get() {
+ return System.currentTimeMillis();
+ }
+ };
+ }
+
+ @Override
+ protected void install(Module module) {
+ // override PersistNodeCredentialsModule bindings, any better way to do it?
+ if (module instanceof PersistNodeCredentialsModule) {
+ super.install(new PersistVagrantCredentialsModule());
+ } else {
+ super.install(module);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> Class<Function<T, T>> castIdentityFunction() {
+ return (Class<Function<T, T>>)(Class<?>)IdentityFunction.class;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/domain/VagrantNode.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/domain/VagrantNode.java b/vagrant/src/main/java/org/jclouds/vagrant/domain/VagrantNode.java
new file mode 100644
index 0000000..cfb02ca
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/domain/VagrantNode.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.domain;
+
+import java.io.File;
+import java.util.Collection;
+
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata.Status;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class VagrantNode {
+
+ private volatile Status machineState = Status.PENDING;
+
+ public abstract File path();
+
+ public abstract String id();
+
+ public abstract String group();
+
+ public abstract String name();
+
+ public abstract Image image();
+
+ public abstract Collection<String> networks();
+
+ public abstract String hostname();
+
+ public static Builder builder() {
+ return new AutoValue_VagrantNode.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setPath(File path);
+ public abstract Builder setId(String id);
+ public abstract Builder setGroup(String group);
+ public abstract Builder setName(String name);
+ public abstract Builder setImage(Image image);
+ public abstract Builder setNetworks(Collection<String> networks);
+ public abstract Builder setHostname(String hostname);
+ public abstract VagrantNode build();
+ }
+
+ public Status machineState() {
+ return machineState;
+ }
+
+ public void setMachineState(Status machineState) {
+ this.machineState = machineState;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/functions/BoxToImage.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/functions/BoxToImage.java b/vagrant/src/main/java/org/jclouds/vagrant/functions/BoxToImage.java
new file mode 100644
index 0000000..1fd77fa
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/functions/BoxToImage.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.Image.Status;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.vagrant.internal.BoxConfig;
+import org.jclouds.vagrant.reference.VagrantConstants;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+
+import vagrant.api.domain.Box;
+
+public class BoxToImage implements Function<Box, Image> {
+ private BoxConfig.Factory boxConfigFactory;
+
+ @Inject
+ BoxToImage(BoxConfig.Factory boxConfigFactory) {
+ this.boxConfigFactory = checkNotNull(boxConfigFactory, "boxConfigFactory");
+ }
+
+ @Override
+ public Image apply(Box input) {
+ OperatingSystem os = new OperatingSystem(inferOsFamily(input), input.getName(), input.getVersion(), null, input.getName(), true);
+ return new ImageBuilder()
+ .ids(input.getName())
+ .name(input.getName())
+ .version(input.getVersion())
+ .operatingSystem(os)
+ .status(Status.AVAILABLE)
+ // Overriden by AddDefaultCredentialsToImage
+ //.defaultCredentials()
+ .userMetadata(ImmutableMap.of(VagrantConstants.USER_META_PROVIDER, input.getProvider()))
+ .build();
+ }
+
+ private OsFamily inferOsFamily(Box input) {
+ String name = input.getName().toUpperCase();
+ for (OsFamily family : OsFamily.values()) {
+ if (name.contains(family.name())) {
+ return family;
+ }
+ }
+
+ BoxConfig configParser = boxConfigFactory.newInstance(input);
+ Optional<String> guest = configParser.getKey(VagrantConstants.KEY_VM_GUEST);
+ if (guest.isPresent() && guest.get().equals(VagrantConstants.VM_GUEST_WINDOWS)) {
+ return OsFamily.WINDOWS;
+ }
+ return OsFamily.UNRECOGNIZED;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/functions/MachineToNodeMetadata.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/functions/MachineToNodeMetadata.java b/vagrant/src/main/java/org/jclouds/vagrant/functions/MachineToNodeMetadata.java
new file mode 100644
index 0000000..298b7cc
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/functions/MachineToNodeMetadata.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.util.AutomaticHardwareIdSpec;
+import org.jclouds.domain.Location;
+import org.jclouds.vagrant.domain.VagrantNode;
+import org.jclouds.vagrant.internal.BoxConfig;
+import org.jclouds.vagrant.internal.MachineConfig;
+import org.jclouds.vagrant.reference.VagrantConstants;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+
+public class MachineToNodeMetadata implements Function<VagrantNode, NodeMetadata> {
+ private final Location location;
+ private final BoxConfig.Factory boxConfigFactory;
+ private final MachineConfig.Factory machineConfigFactory;
+ private final Supplier<? extends Map<String, Hardware>> hardwareSupplier;
+
+ @Inject
+ MachineToNodeMetadata(
+ @Memoized Supplier<Set<? extends Location>> locations,
+ BoxConfig.Factory boxConfigFactory,
+ MachineConfig.Factory machineConfigFactory,
+ Supplier<? extends Map<String, Hardware>> hardwareSupplier) {
+ this.location = Iterables.getOnlyElement(checkNotNull(locations, "locations").get());
+ this.boxConfigFactory = checkNotNull(boxConfigFactory, "boxConfigFactory");
+ this.machineConfigFactory = checkNotNull(machineConfigFactory, "machineConfigFactory");
+ this.hardwareSupplier = checkNotNull(hardwareSupplier, "hardwareSupplier");
+ }
+
+ @Override
+ public NodeMetadata apply(VagrantNode node) {
+ NodeMetadataBuilder nodeMetadataBuilder = new NodeMetadataBuilder()
+ .ids(node.id())
+ .name(node.name())
+ .group(node.group())
+ .imageId(node.image().getId())
+ .location(location)
+ .hardware(getHardware(node))
+ .operatingSystem(node.image().getOperatingSystem())
+ .hostname(node.name())
+ .status(node.machineState())
+ .loginPort(getLoginPort(node.image()))
+ .privateAddresses(node.networks())
+ .publicAddresses(ImmutableList.<String> of())
+ .hostname(node.hostname());
+ // Credentials fetched from cache from AdaptingComputeServiceStrategies.addLoginCredentials.
+ // Cache already initialized just after creating the node.
+ return nodeMetadataBuilder.build();
+ }
+
+ private Hardware getHardware(VagrantNode node) {
+ MachineConfig machineConfig = machineConfigFactory.newInstance(node);
+
+ Map<String, Object> config = machineConfig.load();
+ String hardwareId = config.get(VagrantConstants.CONFIG_HARDWARE_ID).toString();
+ if (hardwareId.equals(VagrantConstants.MACHINES_AUTO_HARDWARE)) {
+ double cpus = Double.parseDouble(config.get(VagrantConstants.CONFIG_CPUS).toString());
+ int memory = Integer.parseInt(config.get(VagrantConstants.CONFIG_MEMORY).toString());
+ AutomaticHardwareIdSpec hardwareSpec = AutomaticHardwareIdSpec.automaticHardwareIdSpecBuilder(cpus, memory, Optional.<Float>absent());
+ return new HardwareBuilder()
+ .id(hardwareSpec.toString())
+ .providerId(hardwareSpec.toString())
+ .processor(new Processor(cpus, 1.0))
+ .ram(memory)
+ .build();
+ } else {
+ Hardware hardware = hardwareSupplier.get().get(hardwareId);
+ if (hardware == null) {
+ throw new IllegalStateException("Unsupported hardwareId " + hardwareId + " for machine " + node.id());
+ }
+ return hardware;
+ }
+ }
+
+ private int getLoginPort(Image image) {
+ BoxConfig config = boxConfigFactory.newInstance(image);
+ String port;
+ if (image.getOperatingSystem().getFamily() == OsFamily.WINDOWS) {
+ port = config.getKey(VagrantConstants.KEY_WINRM_PORT).or("5985");
+ } else {
+ port = config.getKey(VagrantConstants.KEY_SSH_PORT).or("22");
+ }
+ return Integer.parseInt(port);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/functions/OutdatedBoxesFilter.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/functions/OutdatedBoxesFilter.java b/vagrant/src/main/java/org/jclouds/vagrant/functions/OutdatedBoxesFilter.java
new file mode 100644
index 0000000..83bfd80
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/functions/OutdatedBoxesFilter.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.functions;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Ordering;
+
+import vagrant.api.domain.Box;
+
+public class OutdatedBoxesFilter implements Function<Collection<Box>, Collection<Box>> {
+ private static final Comparator<Box> VERSION_COMPARATOR = new Comparator<Box>() {
+ private final Ordering<Iterable<Comparable<?>>> LEXI_COMPARATOR = Ordering.natural().lexicographical();
+ private final Function<String, Comparable<?>> NUMBER_PARSER = new Function<String, Comparable<?>>() {
+ @Override
+ public Comparable<?> apply(String input) {
+ try {
+ return Integer.parseInt(input);
+ } catch (NumberFormatException e) {
+ return input;
+ }
+ }
+ };
+
+ @Override
+ public int compare(Box o1, Box o2) {
+ int nameCompare = o1.getName().compareTo(o2.getName());
+ if (nameCompare != 0) return nameCompare;
+
+ Iterable<Comparable<?>> v1 = Iterables.transform(Splitter.on('.').split(o1.getVersion()), NUMBER_PARSER);
+ Iterable<Comparable<?>> v2 = Iterables.transform(Splitter.on('.').split(o2.getVersion()), NUMBER_PARSER);
+ return LEXI_COMPARATOR.compare(v1, v2);
+ }
+ };
+
+ @Override
+ public Collection<Box> apply(Collection<Box> input) {
+ ArrayList<Box> sorted = new ArrayList<Box>(input);
+ Collections.sort(sorted, VERSION_COMPARATOR);
+ Map<String, Box> boxes = Maps.newHashMap();
+ for (Box box : sorted) {
+ boxes.put(box.getName(), box);
+ }
+ return boxes.values();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/BoxConfig.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/BoxConfig.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/BoxConfig.java
new file mode 100644
index 0000000..f5ceef2
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/BoxConfig.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.jclouds.compute.domain.Image;
+import org.jclouds.vagrant.reference.VagrantConstants;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.io.Files;
+
+import vagrant.api.domain.Box;
+
+public class BoxConfig {
+ public static class Factory {
+ public BoxConfig newInstance(Image image) {
+ String provider = image.getUserMetadata().get(VagrantConstants.USER_META_PROVIDER);
+ return new BoxConfig(getVagrantHome(), image.getName(), image.getVersion(), provider);
+ }
+
+ public BoxConfig newInstance(File vagrantHome, Image image) {
+ return this.newInstance(getVagrantHome(), image);
+ }
+
+ public BoxConfig newInstance(Box box) {
+ return this.newInstance(getVagrantHome(), box);
+ }
+
+ public BoxConfig newInstance(File vagrantHome, Box box) {
+ return new BoxConfig(vagrantHome, box.getName(), box.getVersion(), box.getProvider());
+ }
+
+ private File getVagrantHome() {
+ Optional<String> home = Optional.fromNullable(System.getenv(VagrantConstants.ENV_VAGRANT_HOME));
+ return new File(home.or(VagrantConstants.ENV_VAGRANT_HOME_DEFAULT));
+ }
+
+ }
+
+ private String config;
+ private File providerPath;
+
+ protected BoxConfig(File vagrantHome, String name, String version, String provider) {
+ File boxes = new File(vagrantHome, VagrantConstants.VAGRANT_BOXES_SUBFOLDER);
+ File boxPath = new File(boxes, name.replace("/", VagrantConstants.ESCAPE_SLASH));
+ File versionPath = new File(boxPath, version);
+ File providerPath = new File(versionPath, provider);
+ File vagrantfilePath = new File(providerPath, VagrantConstants.VAGRANTFILE);
+
+ if (!vagrantfilePath.exists()) {
+ throw new IllegalStateException("Vagrantfile for box '" + name + "'" +
+ " at " + vagrantfilePath.getAbsolutePath() + " not found");
+ }
+
+ try {
+ config = Files.toString(vagrantfilePath, Charsets.UTF_8);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failure reading box '" + name + "'" +
+ " at " + vagrantfilePath.getAbsolutePath(), e);
+ }
+
+ this.providerPath = providerPath;
+ }
+
+ public File getFolder() {
+ return providerPath;
+ }
+
+ public Optional<String> getKey(String key) {
+ String keyQuoted = Pattern.quote(key);
+ String search = keyQuoted + "\\s*=\\s*(.*)";
+ Matcher matcher = Pattern.compile(search).matcher(config);
+ if (matcher.find()) {
+ return Optional.of(matcher.group(1).trim());
+ } else {
+ return Optional.absent();
+ }
+ }
+
+ public Optional<String> getStringKey(String key) {
+ String keyQuoted = Pattern.quote(key);
+ String search = keyQuoted + "\\s*=\\s*\"(.*)\"";
+ Matcher matcher = Pattern.compile(search).matcher(config);
+ if (matcher.find()) {
+ return Optional.of(matcher.group(1));
+ } else {
+ return Optional.absent();
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/MachineConfig.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/MachineConfig.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/MachineConfig.java
new file mode 100644
index 0000000..8f23d87
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/MachineConfig.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.internal;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.jclouds.JcloudsVersion;
+import org.jclouds.util.Closeables2;
+import org.jclouds.vagrant.domain.VagrantNode;
+import org.jclouds.vagrant.reference.VagrantConstants;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Maps;
+
+public class MachineConfig {
+ public static class Factory {
+ public MachineConfig newInstance(File path, String name) {
+ return new MachineConfig(path, name);
+ }
+
+ public MachineConfig newInstance(VagrantNode node) {
+ return newInstance(node.path(), node.name());
+ }
+ }
+
+ private File configPath;
+
+ protected MachineConfig(File path, String name) {
+ this.configPath = new File(new File(path, VagrantConstants.MACHINES_CONFIG_SUBFOLDER), name + ".yaml");
+ }
+
+ public Map<String, Object> load() {
+ Map<String, Object> config = new LinkedHashMap<String, Object>();
+ Properties yaml = new Properties();
+ FileInputStream fileIn;
+ try {
+ fileIn = new FileInputStream(configPath);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException("Machine config not found: " + configPath.getAbsolutePath(), e);
+ }
+ Reader in = new InputStreamReader(fileIn, Charsets.UTF_8);
+ try {
+ // Poor man's YAML parser. It's controlled content, generated by us - not coming from a user so it's fine.
+ yaml.load(in);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed loading machine config " + configPath.getAbsolutePath(), e);
+ } finally {
+ Closeables2.closeQuietly(in);
+ }
+ for (String key : yaml.stringPropertyNames()) {
+ config.put(key, yaml.getProperty(key));
+ }
+ return config;
+ }
+
+ // Write the config ad-hoc, imitating yaml which can be read by ruby
+ // Could pull in snakeyaml to be more resilient to edge-cases in values
+ // Alternatively use JSON if jclouds already depends on it in core
+ public void save(Map<String, Object> config) {
+ File parent = configPath.getParentFile();
+ if (!parent.exists() && !parent.mkdirs()) {
+ if (!parent.exists()) {
+ throw new IllegalStateException("Failure creating folder " + parent.getAbsolutePath());
+ }
+ }
+
+ Map<String, Object> configWithoutVersion = Maps.filterKeys(config,
+ Predicates.not(Predicates.equalTo(VagrantConstants.CONFIG_JCLOUDS_VERSION)));
+ String version = VagrantConstants.CONFIG_JCLOUDS_VERSION + ": " + JcloudsVersion.get().toString() + "\n";
+ String output = version + Joiner.on("\n").withKeyValueSeparator(": ").join(configWithoutVersion);
+
+ FileOutputStream fileOut = null;
+ BufferedWriter out = null;
+
+ try {
+ fileOut = new FileOutputStream(configPath);
+ out = new BufferedWriter(new OutputStreamWriter(fileOut, Charsets.UTF_8));
+ out.write(output);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed writing to machine config file " + configPath.getAbsolutePath(), e);
+ } finally {
+ if (out != null) {
+ Closeables2.closeQuietly(out);
+ } else if (fileOut != null) {
+ Closeables2.closeQuietly(fileOut);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantCliFacade.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantCliFacade.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantCliFacade.java
new file mode 100644
index 0000000..26d592f
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantCliFacade.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Collection;
+
+import javax.inject.Inject;
+
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.vagrant.api.VagrantApiFacade;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+import com.google.inject.assistedinject.Assisted;
+
+import vagrant.Vagrant;
+import vagrant.api.CommandIOListener;
+import vagrant.api.VagrantApi;
+import vagrant.api.domain.Box;
+import vagrant.api.domain.SshConfig;
+
+public class VagrantCliFacade implements VagrantApiFacade<Box> {
+ private final VagrantApi vagrant;
+ private final VagrantOutputRecorder outputRecorder;
+
+ @Inject
+ VagrantCliFacade(CommandIOListener wireLogger, @Assisted File path) {
+ this.outputRecorder = new VagrantOutputRecorder(checkNotNull(wireLogger, "wireLogger"));
+ this.vagrant = Vagrant.forPath(path, outputRecorder);
+ }
+
+ @Override
+ public String up(String machineName) {
+ outputRecorder.record();
+ vagrant.up(machineName);
+ return outputRecorder.stopRecording();
+ }
+
+ @Override
+ public void halt(String machineName) {
+ vagrant.halt(machineName);
+ }
+
+ @Override
+ public void destroy(String machineName) {
+ vagrant.destroy(machineName);
+ }
+
+ @Override
+ public LoginCredentials sshConfig(String machineName) {
+ SshConfig sshConfig = vagrant.sshConfig(machineName);
+ LoginCredentials.Builder loginCredentialsBuilder = LoginCredentials.builder()
+ .user(sshConfig.getUser());
+ try {
+ String privateKey = Files.toString(new File(sshConfig.getIdentityFile()), Charset.defaultCharset());
+ loginCredentialsBuilder.privateKey(privateKey);
+ } catch (IOException e) {
+ throw new IllegalStateException("Invalid private key " + sshConfig.getIdentityFile(), e);
+ }
+
+ return loginCredentialsBuilder.build();
+ }
+
+ @Override
+ public Collection<Box> listBoxes() {
+ return vagrant.box().list();
+ }
+
+ @Override
+ public Box getBox(final String boxName) {
+ return Iterables.find(listBoxes(), new Predicate<Box>() {
+ @Override
+ public boolean apply(Box input) {
+ return boxName.equals(input.getName());
+ }
+ }, null);
+ }
+
+ @Override
+ public void haltForced(String name) {
+ vagrant.haltForced(name);
+ }
+
+ @Override
+ public boolean exists() {
+ return vagrant.exists();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantNodeRegistry.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantNodeRegistry.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantNodeRegistry.java
new file mode 100644
index 0000000..e4121fd
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantNodeRegistry.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.internal;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.date.TimeStamp;
+import org.jclouds.vagrant.domain.VagrantNode;
+
+import com.google.common.base.Supplier;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class VagrantNodeRegistry {
+ private static final long TERMINATED_NODES_EXPIRY_MS = TimeUnit.MINUTES.toMillis(5);
+ private static final long VACUUM_PERIOD_MS = TimeUnit.SECONDS.toMillis(15);
+
+ private static class TerminatedNode implements Delayed {
+ Supplier<Long> timeSupplier;
+ long expiryTime;
+ VagrantNode node;
+
+ TerminatedNode(VagrantNode node, Supplier<Long> timeSupplier) {
+ this.expiryTime = System.currentTimeMillis() + TERMINATED_NODES_EXPIRY_MS;
+ this.node = node;
+ this.timeSupplier = timeSupplier;
+ }
+ @Override
+ public int compareTo(Delayed o) {
+ if (this == o) {
+ return 0;
+ } else if (o instanceof TerminatedNode) {
+ TerminatedNode other = (TerminatedNode)o;
+ if (expiryTime < other.expiryTime) {
+ return -1;
+ } else if (expiryTime > other.expiryTime) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ long diff = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
+ if (diff < 0) {
+ return -1;
+ } else if (diff > 0) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ @Override
+ public long getDelay(TimeUnit unit) {
+ return unit.convert(expiryTime - timeSupplier.get(), TimeUnit.MILLISECONDS);
+ }
+ }
+ private Map<String, VagrantNode> nodes = new ConcurrentHashMap<String, VagrantNode>();
+ private DelayQueue<TerminatedNode> terminatedNodes = new DelayQueue<TerminatedNode>();
+
+ private volatile long lastVacuumMs;
+ private Supplier<Long> timeSupplier;
+
+ @Inject
+ VagrantNodeRegistry(@TimeStamp Supplier<Long> timeSupplier) {
+ this.timeSupplier = timeSupplier;
+ }
+
+
+ public VagrantNode get(String id) {
+ vacuum();
+ return nodes.get(id);
+ }
+
+ protected void vacuum() {
+ // No need to lock on lastVacuumMs - not critical if we miss/do double vacuuming.
+ if (timeSupplier.get() - lastVacuumMs > VACUUM_PERIOD_MS) {
+ TerminatedNode terminated;
+ while ((terminated = terminatedNodes.poll()) != null) {
+ nodes.remove(terminated.node.id());
+ }
+ lastVacuumMs = timeSupplier.get();
+ }
+ }
+
+ public void add(VagrantNode node) {
+ nodes.put(node.id(), node);
+ }
+
+ public void onTerminated(VagrantNode node) {
+ terminatedNodes.add(new TerminatedNode(node, timeSupplier));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantOutputRecorder.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantOutputRecorder.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantOutputRecorder.java
new file mode 100644
index 0000000..32ef561
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantOutputRecorder.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.internal;
+
+import vagrant.api.CommandIOListener;
+
+public class VagrantOutputRecorder implements CommandIOListener {
+
+ private CommandIOListener next;
+ private StringBuilder output = new StringBuilder();
+ private boolean isRecording;
+
+ public VagrantOutputRecorder(CommandIOListener next) {
+ this.next = next;
+ }
+
+ @Override
+ public void onInput(String input) {
+ if (isRecording) {
+ next.onInput(input);
+ }
+ }
+
+ @Override
+ public void onOutput(String output) {
+ if (isRecording) {
+ next.onOutput(output);
+ if (output != null) {
+ this.output.append(output);
+ }
+ }
+ }
+
+ public void record() {
+ isRecording = true;
+ }
+
+ public String stopRecording() {
+ isRecording = false;
+ String out = output.toString();
+ output.setLength(0);
+ return out;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantWireLogger.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantWireLogger.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantWireLogger.java
new file mode 100644
index 0000000..8e97311
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantWireLogger.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.internal;
+
+import java.io.ByteArrayInputStream;
+
+import org.jclouds.http.internal.HttpWire;
+
+import com.google.common.base.Charsets;
+import com.google.inject.Inject;
+
+import vagrant.api.CommandIOListener;
+
+public class VagrantWireLogger implements CommandIOListener {
+ private HttpWire wire;
+
+ // Vagrant commands are sequential (non-concurrent)
+ private String lastPartialLine = "";
+
+ @Inject
+ VagrantWireLogger(HttpWire wire) {
+ this.wire = wire;
+ }
+
+ @Override
+ public void onInput(String input) {
+ // Inputs are always single-line
+ if (input != null) {
+ wire.input(new ByteArrayInputStream(input.getBytes(Charsets.UTF_8)));
+ }
+ }
+
+ @Override
+ public void onOutput(String output) {
+ if (output != null) {
+ int nlPos = output.indexOf('\n');
+ String fullLineOutput;
+ if (nlPos != -1) {
+ fullLineOutput = lastPartialLine + output.substring(0, nlPos + 1);
+ lastPartialLine = output.substring(nlPos + 1);
+ wire.output(fullLineOutput);
+ } else {
+ lastPartialLine += output;
+ }
+ } else if (!lastPartialLine.isEmpty()) {
+ wire.output(lastPartialLine);
+ lastPartialLine = "";
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/reference/VagrantConstants.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/reference/VagrantConstants.java b/vagrant/src/main/java/org/jclouds/vagrant/reference/VagrantConstants.java
new file mode 100644
index 0000000..c008375
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/reference/VagrantConstants.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.vagrant.reference;
+
+import java.io.File;
+
+public final class VagrantConstants {
+ private VagrantConstants() {}
+
+ public static final String JCLOUDS_VAGRANT_HOME = "vagrant.home";
+ public static final String JCLOUDS_VAGRANT_HOME_DEFAULT = new File(System.getProperty("user.home"), ".jclouds/vagrant").getAbsolutePath();
+ public static final String VAGRANTFILE = "Vagrantfile";
+ public static final String DEFAULT_USERNAME = "vagrant";
+ public static final String DEFAULT_PASSWORD = "vagrant";
+ public static final String USER_META_PROVIDER = "provider";
+
+ public static final String ENV_VAGRANT_HOME = "VAGRANT_HOME";
+ public static final String ENV_VAGRANT_HOME_DEFAULT = new File(System.getProperty("user.home"), ".vagrant.d").getAbsolutePath();
+ public static final String VAGRANT_BOXES_SUBFOLDER = "boxes";
+
+ public static final String ESCAPE_SLASH = "-VAGRANTSLASH-";
+
+ public static final String DELIMITER_NETWORKS_START = "================= Networks start =================";
+ public static final String DELIMITER_NETWORKS_END = "================= Networks end ===================";
+ public static final String DELIMITER_HOSTNAME_START = "================= Hostname start ==========================";
+ public static final String DELIMITER_HOSTNAME_END = "================= Hostname end ============================";
+
+ // Vagrantfile config
+ public static final String KEY_VM_GUEST = ".vm.guest";
+ public static final String VM_GUEST_WINDOWS = ":windows";
+ public static final String KEY_WINRM_USERNAME = ".winrm.username";
+ public static final String KEY_WINRM_PASSWORD = ".winrm.password";
+ public static final String KEY_WINRM_PORT = ".winrm.port";
+ public static final String KEY_SSH_USERNAME = ".ssh.username";
+ public static final String KEY_SSH_PASSWORD = ".ssh.password";
+ public static final String KEY_SSH_PRIVATE_KEY_PATH = ".ssh.private_key_path";
+ public static final String KEY_SSH_PORT = ".ssh.port";
+
+ public static final String MACHINES_CONFIG_SUBFOLDER = "machines";
+ public static final String MACHINES_CONFIG_EXTENSION = ".yaml";
+ public static final String MACHINES_AUTO_HARDWARE = "automatic";
+
+ // Config file keys
+ public static final String CONFIG_JCLOUDS_VERSION = "jcloudsVersion";
+ public static final String CONFIG_BOX = "box";
+ public static final String CONFIG_HARDWARE_ID = "hardwareId";
+ public static final String CONFIG_OS_FAMILY = "osFamily";
+ public static final String CONFIG_MEMORY = "memory";
+ public static final String CONFIG_CPUS = "cpus";
+ public static final String CONFIG_USERNAME = "username";
+ public static final String CONFIG_PASSWORD = "password";
+}