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 2016/01/21 01:03:29 UTC

[05/19] jclouds git commit: JCLOUDS-613: Implement the DigitalOcean v2 API

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
new file mode 100644
index 0000000..3e4aae3
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
@@ -0,0 +1,217 @@
+/*
+ * 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.digitalocean2.compute.strategy;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.compute.config.CustomizationResponse;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
+import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
+import org.jclouds.digitalocean2.domain.Key;
+import org.jclouds.digitalocean2.ssh.DSAKeys;
+import org.jclouds.digitalocean2.ssh.ECDSAKeys;
+import org.jclouds.logging.Logger;
+import org.jclouds.ssh.SshKeyPairGenerator;
+import org.jclouds.ssh.SshKeys;
+
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+@Singleton
+public class CreateKeyPairsThenCreateNodes extends CreateNodesWithGroupEncodedIntoNameThenAddToSet {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final DigitalOcean2Api api;
+   private final SshKeyPairGenerator keyGenerator;
+   private final Function<String, PublicKey> sshKeyToPublicKey;
+
+   @Inject
+   protected CreateKeyPairsThenCreateNodes(
+         CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
+         ListNodesStrategy listNodesStrategy,
+         GroupNamingConvention.Factory namingConvention,
+         @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
+         CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
+         DigitalOcean2Api api, SshKeyPairGenerator keyGenerator, Function<String, PublicKey> sshKeyToPublicKey) {
+      super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
+            customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
+      this.api = checkNotNull(api, "api cannot be null");
+      this.keyGenerator = checkNotNull(keyGenerator, "keyGenerator cannot be null");
+      checkNotNull(userExecutor, "userExecutor cannot be null");
+      this.sshKeyToPublicKey = checkNotNull(sshKeyToPublicKey, "sshKeyToPublicKey cannot be null");
+   }
+
+   @Override
+   public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template,
+         Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
+         Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
+
+      DigitalOcean2TemplateOptions options = template.getOptions().as(DigitalOcean2TemplateOptions.class);
+      Set<Integer> generatedSshKeyIds = Sets.newHashSet();
+
+      // If no key has been configured and the auto-create option is set, then generate a key pair
+      if (options.getSshKeyIds().isEmpty() && options.getAutoCreateKeyPair()
+            && Strings.isNullOrEmpty(options.getPublicKey())) {
+         generateKeyPairAndAddKeyToSet(options, generatedSshKeyIds, group);
+      }
+
+      // If there is a script to run in the node, make sure a private key has been configured so jclouds will be able to
+      // access the node
+      if (options.getRunScript() != null) {
+         checkArgument(!Strings.isNullOrEmpty(options.getLoginPrivateKey()),
+               "no private key configured for: %s; please use options.overrideLoginPrivateKey(rsa_private_text)", group);
+      }
+
+      // If there is a key configured, then make sure there is a key pair for it
+      if (!Strings.isNullOrEmpty(options.getPublicKey())) {
+         createKeyPairForPublicKeyInOptionsAndAddToSet(options, generatedSshKeyIds);
+      }
+
+      // Set all keys (the provided and the auto-generated) in the options object so the
+      // DigitalOceanComputeServiceAdapter adds them all
+      options.sshKeyIds(Sets.union(generatedSshKeyIds, options.getSshKeyIds()));
+
+      Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,
+            customizationResponses);
+
+      // Key pairs in DigitalOcean are only required to create the Droplets. They aren't used anymore so it is better
+      // to delete the auto-generated key pairs at this point where we know exactly which ones have been
+      // auto-generated by jclouds.
+      registerAutoGeneratedKeyPairCleanupCallbacks(responses, generatedSshKeyIds);
+
+      return responses;
+   }
+
+   private void createKeyPairForPublicKeyInOptionsAndAddToSet(DigitalOcean2TemplateOptions options,
+         Set<Integer> generatedSshKeyIds) {
+      logger.debug(">> checking if the key pair already exists...");
+
+      PublicKey userKey = sshKeyToPublicKey.apply(options.getPublicKey());
+      String userFingerprint = computeFingerprint(userKey);
+      Key key = api.keyApi().get(userFingerprint);  
+
+      if (key == null) {
+         logger.debug(">> key pair not found. creating a new one...");
+
+         Key newKey = api.keyApi().create(userFingerprint, options.getPublicKey());
+
+         generatedSshKeyIds.add(newKey.id());
+         logger.debug(">> key pair created! %s", newKey);
+      } else {
+         logger.debug(">> key pair found! %s", key);
+         generatedSshKeyIds.add(key.id());
+      }
+   }
+
+   private void generateKeyPairAndAddKeyToSet(DigitalOcean2TemplateOptions options, Set<Integer> generatedSshKeyIds, String prefix) {
+      logger.debug(">> creating default keypair for node...");
+
+      Map<String, String> defaultKeys = keyGenerator.get();
+
+      Key defaultKey = api.keyApi().create(prefix + "-" + System.getProperty("user.name"), defaultKeys.get("public"));
+      generatedSshKeyIds.add(defaultKey.id());
+
+      logger.debug(">> keypair created! %s", defaultKey);
+
+      // If a private key has not been explicitly set, configure the auto-generated one
+      if (Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
+         options.overrideLoginPrivateKey(defaultKeys.get("private"));
+      }
+   }
+
+   private void registerAutoGeneratedKeyPairCleanupCallbacks(Map<?, ListenableFuture<Void>> responses,
+         final Set<Integer> generatedSshKeyIds) {
+      // The Futures.allAsList fails immediately if some of the futures fail. The Futures.successfulAsList, however,
+      // returns a list containing the results or 'null' for those futures that failed. We want to wait for all them
+      // (even if they fail), so better use the latter form.
+      ListenableFuture<List<Void>> aggregatedResponses = Futures.successfulAsList(responses.values());
+
+      // Key pairs must be cleaned up after all futures completed (even if some failed).
+      Futures.addCallback(aggregatedResponses, new FutureCallback<List<Void>>() {
+         @Override
+         public void onSuccess(List<Void> result) {
+            cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
+         }
+
+         @Override
+         public void onFailure(Throwable t) {
+            cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
+         }
+
+         private void cleanupAutoGeneratedKeyPairs(Set<Integer> generatedSshKeyIds) {
+            logger.debug(">> cleaning up auto-generated key pairs...");
+            for (Integer sshKeyId : generatedSshKeyIds) {
+               try {
+                  api.keyApi().delete(sshKeyId);
+               } catch (Exception ex) {
+                  logger.warn(">> could not delete key pair %s: %s", sshKeyId, ex.getMessage());
+               }
+            }
+         }
+
+      }, userExecutor);
+   }
+   
+   private static String computeFingerprint(PublicKey key) {
+      if (key instanceof RSAPublicKey) {
+         RSAPublicKey rsaKey = (RSAPublicKey) key;
+         return SshKeys.fingerprint(rsaKey.getPublicExponent(), rsaKey.getModulus());
+      } else if (key instanceof DSAPublicKey) {
+         DSAPublicKey dsaKey = (DSAPublicKey) key;
+         return DSAKeys.fingerprint(dsaKey.getParams().getP(), dsaKey.getParams().getQ(), dsaKey.getParams().getG(),
+               dsaKey.getY());
+      } else if (key instanceof ECPublicKey) {
+         ECPublicKey ecdsaKey = (ECPublicKey) key;
+         return ECDSAKeys.fingerprint(ecdsaKey);
+      } else {
+         throw new IllegalArgumentException("Only RSA and DSA keys are supported");
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2HttpApiModule.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2HttpApiModule.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2HttpApiModule.java
new file mode 100644
index 0000000..8bfe266
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2HttpApiModule.java
@@ -0,0 +1,57 @@
+/*
+ * 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.digitalocean2.config;
+
+import java.net.URI;
+
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.options.ImageListOptions;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+import org.jclouds.digitalocean2.functions.LinkToImageListOptions;
+import org.jclouds.digitalocean2.functions.LinkToListOptions;
+import org.jclouds.digitalocean2.handlers.DigitalOcean2ErrorHandler;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.annotation.ClientError;
+import org.jclouds.http.annotation.Redirection;
+import org.jclouds.http.annotation.ServerError;
+import org.jclouds.oauth.v2.config.OAuthScopes;
+import org.jclouds.rest.ConfiguresHttpApi;
+import org.jclouds.rest.config.HttpApiModule;
+
+import com.google.common.base.Function;
+import com.google.inject.TypeLiteral;
+
+@ConfiguresHttpApi
+public class DigitalOcean2HttpApiModule extends HttpApiModule<DigitalOcean2Api> {
+
+   @Override
+   protected void bindErrorHandlers() {
+      bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(DigitalOcean2ErrorHandler.class);
+      bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(DigitalOcean2ErrorHandler.class);
+      bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(DigitalOcean2ErrorHandler.class);
+   }
+
+   @Override
+   protected void configure() {
+      super.configure();
+      bind(OAuthScopes.class).toInstance(OAuthScopes.ReadOrWriteScopes.create("read", "read write"));
+      bind(new TypeLiteral<Function<URI, ListOptions>>() {
+      }).to(LinkToListOptions.class);
+      bind(new TypeLiteral<Function<URI, ImageListOptions>>() {
+      }).to(LinkToImageListOptions.class);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOceanParserModule.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOceanParserModule.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOceanParserModule.java
new file mode 100644
index 0000000..e4bb9bc
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOceanParserModule.java
@@ -0,0 +1,144 @@
+/*
+ * 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.digitalocean2.config;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.collect.Iterables.get;
+import static com.google.common.collect.Iterables.size;
+import static com.google.inject.Scopes.SINGLETON;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Map;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.digitalocean2.ssh.DSAKeys;
+import org.jclouds.digitalocean2.ssh.ECDSAKeys;
+import org.jclouds.json.config.GsonModule.DateAdapter;
+import org.jclouds.json.config.GsonModule.Iso8601DateAdapter;
+import org.jclouds.ssh.SshKeys;
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+/**
+ * Custom parser bindings.
+ */
+public class DigitalOceanParserModule extends AbstractModule {
+
+   @Override
+   protected void configure() {
+      bind(DateAdapter.class).to(Iso8601DateAdapter.class).in(SINGLETON);
+   }
+
+   @Singleton
+   public static class SshPublicKeyAdapter extends TypeAdapter<PublicKey> {
+
+      private final Function<PublicKey, String> publicKeyToSshKey;
+      private final Function<String, PublicKey> sshKeyToPublicKey;
+
+      @Inject
+      public SshPublicKeyAdapter(Function<PublicKey, String> publicKeyToSshKey,
+            Function<String, PublicKey> sshKeyToPublicKey) {
+         this.publicKeyToSshKey = checkNotNull(publicKeyToSshKey, "publicKeyToSshKey cannot be null");
+         this.sshKeyToPublicKey = checkNotNull(sshKeyToPublicKey, "sshKeyToPublicKey cannot be null");
+      }
+
+      @Override
+      public void write(JsonWriter out, PublicKey value) throws IOException {
+         out.value(publicKeyToSshKey.apply(value));
+      }
+
+      @Override
+      public PublicKey read(JsonReader in) throws IOException {
+         return sshKeyToPublicKey.apply(in.nextString().trim());
+      }
+   }
+
+   @Provides
+   @Singleton
+   public Function<PublicKey, String> publicKeyToSshKey() {
+      return new Function<PublicKey, String>() {
+         @Override
+         public String apply(PublicKey input) {
+            if (input instanceof RSAPublicKey) {
+               return SshKeys.encodeAsOpenSSH((RSAPublicKey) input);
+            } else if (input instanceof DSAPublicKey) {
+               return DSAKeys.encodeAsOpenSSH((DSAPublicKey) input);
+            } else {
+               throw new IllegalArgumentException("Only RSA and DSA keys are supported");
+            }
+         }
+      };
+   }
+
+   @Provides
+   @Singleton
+   public Function<String, PublicKey> sshKeyToPublicKey() {
+      return new Function<String, PublicKey>() {
+         @Override
+         public PublicKey apply(String input) {
+            Iterable<String> parts = Splitter.on(' ').split(input);
+            checkArgument(size(parts) >= 2, "bad format, should be: [ssh-rsa|ssh-dss] AAAAB3...");
+            String type = get(parts, 0);
+
+            try {
+               if ("ssh-rsa".equals(type)) {
+                  RSAPublicKeySpec spec = SshKeys.publicKeySpecFromOpenSSH(input);
+                  return KeyFactory.getInstance("RSA").generatePublic(spec);
+               } else if ("ssh-dss".equals(type)) {
+                  DSAPublicKeySpec spec = DSAKeys.publicKeySpecFromOpenSSH(input);
+                  return KeyFactory.getInstance("DSA").generatePublic(spec);
+               } else if (type.startsWith("ecdsa-sha2-")) {
+                  ECPublicKeySpec spec = ECDSAKeys.publicKeySpecFromOpenSSH(input);
+                  return KeyFactory.getInstance("EC").generatePublic(spec);
+               } else {
+                  throw new IllegalArgumentException("bad format, jclouds supports ssh-rsa, ssh-dss, ecdsa-sha2-nistp[256|384|521]");
+               }
+            } catch (InvalidKeySpecException ex) {
+               throw propagate(ex);
+            } catch (NoSuchAlgorithmException ex) {
+               throw propagate(ex);
+            }
+         }
+      };
+   }
+
+   @Provides
+   @Singleton
+   public Map<Type, Object> provideCustomAdapterBindings(SshPublicKeyAdapter sshPublicKeyAdapter) {
+      return ImmutableMap.<Type, Object> of(PublicKey.class, sshPublicKeyAdapter);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Action.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Action.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Action.java
new file mode 100644
index 0000000..b800105
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Action.java
@@ -0,0 +1,71 @@
+/*
+ * 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.digitalocean2.domain;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Date;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+
+@AutoValue
+public abstract class Action {
+   
+   public enum Status {
+      COMPLETED, IN_PROGRESS, ERRORED;
+
+      Status() {}
+
+      public static Status fromValue(String value) {
+         Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+         if (!status.isPresent()) {
+            String upperCamelValue = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, value.toLowerCase());
+            status = Enums.getIfPresent(Status.class, upperCamelValue);
+         }
+         checkArgument(status.isPresent(), "Expected one of %s but was", Joiner.on(',').join(Status.values()), value);
+         return status.get();
+      }
+   }
+
+   public abstract int id();
+   public abstract Status status();
+   public abstract String type();
+   public abstract Date startedAt();
+   @Nullable public abstract Date completedAt();
+   public abstract Integer resourceId();
+   public abstract String resourceType();
+   @Nullable public abstract Region region();
+   @Nullable public abstract String regionSlug();
+
+   @SerializedNames({ "id", "status", "type", "started_at", "completed_at", "resource_id", "resource_type",
+      "region", "region_slug" })
+   public static Action create(int id, Status status, String type, Date startedAt, Date completedAt, int resourceId,
+         String resourceType, Region region, String regionSlug) {
+      return new AutoValue_Action(id, status, type, startedAt, completedAt, resourceId, resourceType, region,
+            regionSlug);
+   }
+
+   Action() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Backup.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Backup.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Backup.java
new file mode 100644
index 0000000..536a187
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Backup.java
@@ -0,0 +1,43 @@
+/*
+ * 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.digitalocean2.domain;
+
+import java.util.List;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Backup {
+   public abstract int id();
+   public abstract String name();
+   public abstract String distribution();
+   @Nullable public abstract String slug();
+   public abstract boolean isPublic();
+   public abstract List<String> regions();
+   public abstract int minDiskSize();
+
+   @SerializedNames({ "id", "name", "distribution", "slug", "public", "regions", "min_disk_size" })
+   public static Backup create(int id, String name, String distribution, String slug, boolean isPublic,
+         List<String> regions, int minDiskSize) {
+      return new AutoValue_Backup(id, name, distribution, slug, isPublic, regions, minDiskSize);
+   }
+
+   Backup() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Distribution.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Distribution.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Distribution.java
new file mode 100644
index 0000000..1ea0f0e
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Distribution.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.digitalocean2.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.tryFind;
+import static java.util.Arrays.asList;
+
+import java.util.List;
+
+import org.jclouds.compute.domain.OsFamily;
+import com.google.common.base.Predicate;
+
+/**
+ * DigitalOcean image distributions.
+ */
+public enum Distribution {
+   ARCHLINUX(OsFamily.ARCH, "Arch Linux"), 
+   CENTOS(OsFamily.CENTOS, "CentOS"), 
+   DEBIAN(OsFamily.DEBIAN, "Debian"), 
+   FEDORA(OsFamily.FEDORA, "Fedora"), 
+   UBUNTU(OsFamily.UBUNTU, "Ubuntu"), 
+   UNRECOGNIZED(OsFamily.UNRECOGNIZED, ""); 
+
+   private static final List<Distribution> values = asList(Distribution.values());
+
+   private final OsFamily osFamily;
+   private final String value;
+
+   private Distribution(OsFamily osFamily, String value) {
+      this.osFamily = checkNotNull(osFamily, "osFamily cannot be null");
+      this.value = checkNotNull(value, "value cannot be null");
+   }
+
+   public OsFamily osFamily() {
+      return this.osFamily;
+   }
+   
+   public String value() {
+      return this.value;
+   }
+
+   public static Distribution fromValue(String value) {
+      return tryFind(values, hasValue(value)).or(UNRECOGNIZED);
+   }
+
+   private static Predicate<Distribution> hasValue(final String value) {
+      return new Predicate<Distribution>() {
+         @Override
+         public boolean apply(Distribution input) {
+            return input.value.equalsIgnoreCase(value);
+         }
+      };
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Droplet.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Droplet.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Droplet.java
new file mode 100644
index 0000000..4d0dd4c
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Droplet.java
@@ -0,0 +1,92 @@
+/*
+ * 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.digitalocean2.domain;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.copyOf;
+import static com.google.common.collect.Iterables.concat;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+
+@AutoValue
+public abstract class Droplet {
+   
+   public enum Status {
+      NEW, ACTIVE, ARCHIVE, OFF;
+
+      public static Status fromValue(String value) {
+         Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+         checkArgument(status.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Status.values()), value);
+         return status.get();
+      }
+   }
+   
+   public abstract int id();
+   public abstract String name();
+   public abstract int memory();
+   public abstract int vcpus();
+   public abstract int disk();
+   public abstract boolean locked();
+   public abstract Date createdAt();
+   public abstract Status status();
+   public abstract List<Integer> backupsIds();
+   public abstract List<Integer> snapshotIds();
+   public abstract List<String> features();
+   @Nullable public abstract Region region();
+   @Nullable public abstract Image image();
+   @Nullable public abstract Size size();
+   public abstract String sizeSlug();
+   @Nullable  public abstract Networks networks();
+   @Nullable public abstract Kernel kernel();
+
+   @SerializedNames({ "id", "name", "memory", "vcpus", "disk", "locked", "created_at", "status", "backup_ids",
+         "snapshot_ids", "features", "region", "image", "size", "size_slug", "networks", "kernel" })
+   public static Droplet create(int id, String name, int memory, int vcpus, int disk, boolean locked, Date createdAt,
+         Status status, List<Integer> backupIds, List<Integer> snapshotIds, List<String> features, Region region,
+         Image image, Size size, String sizeSlug, Networks network, Kernel kernel) {
+      return new AutoValue_Droplet(id, name, memory, vcpus, disk, locked, createdAt, status, 
+            backupIds == null ? ImmutableList.<Integer> of() : copyOf(backupIds),
+            snapshotIds == null ? ImmutableList.<Integer> of() : copyOf(snapshotIds), copyOf(features), region, image,
+            size, sizeSlug, network, kernel);
+   }
+
+   public Set<Networks.Address> getPublicAddresses() {
+      return FluentIterable.from(concat(networks().ipv4(), networks().ipv6()))
+            .filter(Networks.Predicates.publicNetworks())
+            .toSet();
+   }
+
+   public Set<Networks.Address> getPrivateAddresses() {
+      return FluentIterable.from(concat(networks().ipv4(), networks().ipv6()))
+            .filter(Networks.Predicates.privateNetworks())
+            .toSet();
+   }
+
+   Droplet() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/DropletCreate.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/DropletCreate.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/DropletCreate.java
new file mode 100644
index 0000000..06ed12b
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/DropletCreate.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.digitalocean2.domain;
+
+import static com.google.common.collect.ImmutableList.copyOf;
+
+import java.net.URI;
+import java.util.List;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class DropletCreate {
+   public abstract Droplet droplet();
+   public abstract Links links();
+
+   @AutoValue
+   public abstract static class Links {
+      
+      @AutoValue
+      public abstract static class ActionLink {
+         public abstract int id();
+         public abstract String rel();
+         public abstract URI href();
+         
+         @SerializedNames({"id", "rel", "href"})
+         public static ActionLink create(int id, String rel, URI href) {
+            return new AutoValue_DropletCreate_Links_ActionLink(id, rel, href);
+         }
+         
+         ActionLink() {}
+      }
+      
+      public abstract List<ActionLink> actions();
+
+      @SerializedNames({ "actions" })
+      public static Links create(List<ActionLink> actions) {
+         return new AutoValue_DropletCreate_Links(copyOf(actions));
+      }
+
+      Links() {}
+   }
+
+   @SerializedNames({ "droplet", "links" })
+   public static DropletCreate create(Droplet droplet, Links links) {
+      return new AutoValue_DropletCreate(droplet, links);
+   }
+
+   DropletCreate() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Image.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Image.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Image.java
new file mode 100644
index 0000000..dd2c3b6
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Image.java
@@ -0,0 +1,48 @@
+/*
+ * 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.digitalocean2.domain;
+
+import static com.google.common.collect.ImmutableList.copyOf;
+
+import java.util.Date;
+import java.util.List;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Image {
+
+   public abstract int id();
+   public abstract String name();
+   public abstract String type();
+   public abstract String distribution();
+   @Nullable public abstract String slug();
+   public abstract boolean isPublic();
+   public abstract List<String> regions();
+   public abstract Date createdAt();
+
+   @SerializedNames({ "id", "name", "type", "distribution", "slug", "public", "regions", "created_at" })
+   public static Image create(int id, String name, String type, String distribution, String slug, boolean isPublic,
+         List<String> regions, Date createdAt) {
+      return new AutoValue_Image(id, name, type, distribution, slug, isPublic, copyOf(regions), createdAt);
+   }
+
+   Image() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Kernel.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Kernel.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Kernel.java
new file mode 100644
index 0000000..7eb5467
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Kernel.java
@@ -0,0 +1,35 @@
+/*
+ * 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.digitalocean2.domain;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Kernel {
+   public abstract int id();
+   public abstract String name();
+   public abstract String version();
+
+   @SerializedNames({ "id", "name", "version" })
+   public static Kernel create(int id, String name, String version) {
+      return new AutoValue_Kernel(id, name, version);
+   }
+
+   Kernel() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Key.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Key.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Key.java
new file mode 100644
index 0000000..c1a7ae3
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Key.java
@@ -0,0 +1,39 @@
+/*
+ * 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.digitalocean2.domain;
+
+import java.security.PublicKey;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Key {
+
+   public abstract int id();
+   public abstract String name();
+   public abstract String fingerprint();
+   public abstract PublicKey publicKey();
+
+   @SerializedNames({ "id", "name", "fingerprint", "public_key" })
+   public static Key create(int id, String name, String fingerprint, PublicKey publicKey) {
+      return new AutoValue_Key(id, name, fingerprint, publicKey);
+   }
+
+   Key() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Networks.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Networks.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Networks.java
new file mode 100644
index 0000000..cd3dbd4
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Networks.java
@@ -0,0 +1,77 @@
+/*
+ * 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.digitalocean2.domain;
+
+import static com.google.common.collect.ImmutableList.copyOf;
+
+import java.util.List;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Predicate;
+
+@AutoValue
+public abstract class Networks {
+   
+   @AutoValue
+   public abstract static class Address {
+      public abstract String ip();
+      public abstract String netmask();
+      public abstract String gateway();
+      public abstract String type();
+
+      @SerializedNames({ "ip_address", "netmask", "gateway", "type"})
+      public static Address create(String ip, String netmask, String gateway, String type) {
+         return new AutoValue_Networks_Address(ip, netmask, gateway, type);
+      }
+      
+      Address() {}
+   }
+   
+   public abstract List<Address> ipv4();
+   public abstract List<Address> ipv6();
+   
+   @SerializedNames({ "v4", "v6" })
+   public static Networks create(List<Address> ipv4, List<Address> ipv6) {
+      return new AutoValue_Networks(copyOf(ipv4), copyOf(ipv6));
+   }
+   
+   Networks() {}
+
+   public static class Predicates {
+      
+      public static Predicate<Address> publicNetworks() {
+         return new Predicate<Address>() {
+            @Override
+            public boolean apply(Address network) {
+               return network.type().equals("public");
+            }
+         };
+      }
+      
+      public static Predicate<Address> privateNetworks() {
+         return new Predicate<Address>() {
+            @Override
+            public boolean apply(Address network) {
+               return network.type().equals("private");
+            }
+         };
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/OperatingSystem.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/OperatingSystem.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/OperatingSystem.java
new file mode 100644
index 0000000..041ea20
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/OperatingSystem.java
@@ -0,0 +1,60 @@
+/*
+ * 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.digitalocean2.domain;
+
+import static com.google.common.base.Strings.nullToEmpty;
+import static java.util.regex.Pattern.compile;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * The operating system of an image.
+ * <p>
+ * This class parses the <code>name</code> string (e.g. "Ubuntu 12.10 x64") of the images and properly sets each field
+ * to the right value.
+ */
+@AutoValue
+public abstract class OperatingSystem {
+   
+   // Parse something like "12.10 x64" or "Ubuntu 12.10.1 x64" and matches the version and architecture
+   private static final Pattern VERSION_PATTERN = compile("(?:[a-zA-Z\\s]*\\s+)?(\\d+(?:\\.?\\d+)*)?(?:\\s*(x\\d{2}))?.*");
+   private static final String IS_64_BIT = "x64";
+
+   public abstract Distribution distribution();
+   public abstract String version();
+   public abstract String arch();
+
+   public static OperatingSystem create(String name, String distribution) {
+      return new AutoValue_OperatingSystem(Distribution.fromValue(distribution), match(VERSION_PATTERN, name, 1),
+            match(VERSION_PATTERN, name, 2));
+   }
+
+   public boolean is64bit() {
+      return IS_64_BIT.equals(arch());
+   }
+   
+   OperatingSystem() {}
+
+   private static String match(final Pattern pattern, final String input, int group) {
+      Matcher m = pattern.matcher(input);
+      return m.find() ? nullToEmpty(m.group(group)) : "";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Region.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Region.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Region.java
new file mode 100644
index 0000000..2b3441c
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Region.java
@@ -0,0 +1,39 @@
+/*
+ * 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.digitalocean2.domain;
+
+import java.util.List;
+
+import org.jclouds.json.SerializedNames;
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Region {
+   public abstract String slug();
+   public abstract String name();
+   public abstract List<String> sizes();
+   public abstract boolean available();
+   public abstract List<String> features();
+
+   @SerializedNames({ "slug", "name", "sizes", "available", "features" })
+   public static Region create(String slug, String name, List<String> sizes, boolean available, List<String> features) {
+      return new AutoValue_Region(slug, name, sizes, available, features);
+   }
+
+   Region() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Size.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Size.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Size.java
new file mode 100644
index 0000000..03d9492
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Size.java
@@ -0,0 +1,46 @@
+/*
+ * 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.digitalocean2.domain;
+
+import java.util.List;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Size {
+   public abstract String slug();
+   public abstract boolean available();
+   public abstract float transfer();
+   public abstract float priceMonthly();
+   public abstract float priceHourly();
+   public abstract int memory();
+   public abstract int vcpus();
+   public abstract int disk();
+   public abstract List<String> regions();
+
+   @SerializedNames({ "slug", "available", "transfer", "price_monthly", "price_hourly", "memory", "vcpus", "disk",
+         "regions" })
+   public static Size create(String slug, boolean available, float transfer, float priceMonthly, float priceHourly,
+         int memory, int vcpus, int disk, List<String> regions) {
+      return new AutoValue_Size(slug, available, transfer, priceMonthly, priceHourly, memory, vcpus, disk, regions);
+   }
+
+   Size() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Snapshot.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Snapshot.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Snapshot.java
new file mode 100644
index 0000000..12daaa2
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Snapshot.java
@@ -0,0 +1,47 @@
+/*
+ * 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.digitalocean2.domain;
+
+import java.util.Date;
+import java.util.List;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Snapshot {
+   public abstract int id();
+   public abstract String name();
+   public abstract String type();
+   public abstract String distribution();
+   @Nullable public abstract String slug();
+   public abstract boolean isPublic();
+   public abstract List<String> regions();
+   public abstract int minDiskSize();
+   public abstract Date createdAt();
+
+   @SerializedNames({ "id", "name", "type", "distribution", "slug", "public", "regions", "min_disk_size", "created_at"})
+   public static Snapshot create(int id, String name, String type, String distribution, String slug, boolean isPublic,
+         List<String> regions, int minDiskSize, Date createdAt) {
+      return new AutoValue_Snapshot(id, name, type, distribution, slug, isPublic, regions, minDiskSize, createdAt);
+   }
+   
+   Snapshot() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/internal/PaginatedCollection.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/internal/PaginatedCollection.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/internal/PaginatedCollection.java
new file mode 100644
index 0000000..8ca7b21
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/internal/PaginatedCollection.java
@@ -0,0 +1,111 @@
+/*
+ * 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.digitalocean2.domain.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Base class for all collections that return paginated results.
+ */
+public abstract class PaginatedCollection<T> extends IterableWithMarker<T> {
+
+   @AutoValue
+   public abstract static class Meta {
+      public abstract long total();
+
+      @SerializedNames({ "total" })
+      public static Meta create(long total) {
+         return new AutoValue_PaginatedCollection_Meta(total);
+      }
+
+      Meta() { }
+   }
+
+   @AutoValue
+   public abstract static class Links {
+
+      @AutoValue
+      public abstract static class Pages {
+         @Nullable public abstract URI first();
+         @Nullable public abstract URI prev();
+         @Nullable public abstract URI next();
+         @Nullable public abstract URI last();
+
+         @SerializedNames({ "first", "prev", "next", "last" })
+         public static Pages create(URI first, URI next, URI prev, URI last) {
+            return new AutoValue_PaginatedCollection_Links_Pages(first, next, prev, last);
+         }
+
+         Pages() { }
+      }
+
+      @Nullable public abstract Pages pages();
+
+      @SerializedNames({ "pages" })
+      public static Links create(Pages pages) {
+         return new AutoValue_PaginatedCollection_Links(pages);
+      }
+
+      Links() { }
+   }
+
+   private final List<T> items;
+   private final Meta meta;
+   private final Links links;
+
+   protected PaginatedCollection(List<T> items, Meta meta, Links links) {
+      this.items = ImmutableList.copyOf(checkNotNull(items, "items cannot be null"));
+      this.meta = checkNotNull(meta, "meta cannot be null");
+      this.links = checkNotNull(links, "links cannot be null");
+   }
+
+   public List<T> items() {
+      return items;
+   }
+
+   public Meta meta() {
+      return meta;
+   }
+
+   public Links links() {
+      return links;
+   }
+
+   @Override public Iterator<T> iterator() {
+      return items.iterator();
+   }
+
+   @Override public Optional<Object> nextMarker() {
+      if (links.pages() == null) {
+         return Optional.absent();
+      }
+      return Optional.fromNullable((Object) links.pages().next());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
new file mode 100644
index 0000000..b20fc96
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
@@ -0,0 +1,155 @@
+/*
+ * 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.digitalocean2.domain.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Options to customize droplet creation.
+ */
+public class CreateDropletOptions implements MapBinder {
+
+   @Inject private BindToJsonPayload jsonBinder;
+
+   private final Set<Integer> sshKeys;
+   private final boolean backupsEnabled;
+   private final boolean ipv6Enabled;
+   private final boolean privateNetworking;
+   private final String userData;
+
+   private CreateDropletOptions(Set<Integer> sshKeys, boolean backupsEnabled, boolean ipv6Enabled,
+         boolean privateNetworking, @Nullable String userData) {
+      this.sshKeys = sshKeys;
+      this.backupsEnabled = backupsEnabled;
+      this.ipv6Enabled = ipv6Enabled;
+      this.privateNetworking = privateNetworking;
+      this.userData = userData;
+   }
+
+   @AutoValue
+   abstract static class DropletRequest {
+      abstract String name();
+      abstract String region();
+      abstract String size();
+      abstract String image();
+      abstract Set<Integer> sshKeys();
+      abstract Boolean backups();
+      abstract Boolean ipv6();
+      abstract Boolean privateNetworking();
+      @Nullable abstract String userData();
+      
+      @SerializedNames({"name", "region", "size", "image", "ssh_keys", "backups", "ipv6", "private_networking", "user_data"})
+      static DropletRequest create(String name, String region, String size, String image, Set<Integer> sshKeys,
+            Boolean backups, Boolean ipv6, Boolean privateNetworking, String userData) {
+         return new AutoValue_CreateDropletOptions_DropletRequest(name, region, size, image, sshKeys, backups, ipv6,
+               privateNetworking, userData);
+      }
+      
+      DropletRequest() {}
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+      DropletRequest droplet = DropletRequest.create(checkNotNull(postParams.get("name"), "name parameter not present").toString(),
+            checkNotNull(postParams.get("region"), "region parameter not present").toString(),
+            checkNotNull(postParams.get("size"), "size parameter not present").toString(),
+            checkNotNull(postParams.get("image"), "image parameter not present").toString(),
+            sshKeys, backupsEnabled, ipv6Enabled, privateNetworking, userData);
+
+      return bindToRequest(request, droplet);
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      return jsonBinder.bindToRequest(request, input);
+   }
+
+   public Set<Integer> getSshKeys() {
+      return sshKeys;
+   }
+
+   public Boolean getPrivateNetworking() {
+      return privateNetworking;
+   }
+
+   public Boolean getBackupsEnabled() {
+      return backupsEnabled;
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private ImmutableSet.Builder<Integer> sshKeyIds = ImmutableSet.builder();
+      private boolean backupsEnabled;
+      private boolean ipv6Enabled;
+      private boolean privateNetworking;
+      private String userData;
+
+      /**
+       * Adds a set of ssh key ids to be added to the droplet.
+       */
+      public Builder addSshKeyIds(Iterable<Integer> sshKeyIds) {
+         this.sshKeyIds.addAll(sshKeyIds);
+         return this;
+      }
+
+      /**
+       * Adds an ssh key id to be added to the droplet.
+       */
+      public Builder addSshKeyId(int sshKeyId) {
+         this.sshKeyIds.add(sshKeyId);
+         return this;
+      }
+
+      /**
+       * Enables a private network interface if the region supports private
+       * networking.
+       */
+      public Builder privateNetworking(boolean privateNetworking) {
+         this.privateNetworking = privateNetworking;
+         return this;
+      }
+
+      /**
+       * Enabled backups for the droplet.
+       */
+      public Builder backupsEnabled(boolean backupsEnabled) {
+         this.backupsEnabled = backupsEnabled;
+         return this;
+      }
+
+      public CreateDropletOptions build() {
+         return new CreateDropletOptions(sshKeyIds.build(), backupsEnabled, ipv6Enabled, privateNetworking, userData);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ImageListOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ImageListOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ImageListOptions.java
new file mode 100644
index 0000000..9f6415d
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ImageListOptions.java
@@ -0,0 +1,74 @@
+/*
+ * 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.digitalocean2.domain.options;
+
+/**
+ * Custom options to filter the list of images.
+ */
+public class ImageListOptions extends ListOptions {
+   public static final String TYPE_PARAM = "type";
+   public static final String PRIVATE_PARAM = "private";
+   
+   /**
+    * Configures the type of the images to be retrieved.
+    */
+   public ImageListOptions type(String type) {
+      queryParameters.put(TYPE_PARAM, type);
+      return this;
+   }
+   
+   /**
+    * Get the images of the current user.
+    */
+   public ImageListOptions privateImages(boolean privateImages) {
+      queryParameters.put(PRIVATE_PARAM, String.valueOf(privateImages));
+      return this;
+   }
+   
+   @Override public ImageListOptions perPage(int perPage) {
+      super.perPage(perPage);
+      return this;
+   }
+
+   @Override public ImageListOptions page(int page) {
+      super.page(page);
+      return this;
+   }
+   
+   public static final class Builder {
+      
+      /**
+       * @see {@link ImageListOptions#type(String)}
+       */
+      public static ImageListOptions type(String type) {
+         return new ImageListOptions().type(type);
+      }
+      
+      /**
+       * @see {@link ImageListOptions#privateImages(boolean)}
+       */
+      public static ImageListOptions privateImages(boolean privateImages) {
+         return new ImageListOptions().privateImages(privateImages);
+      }
+      /**
+       * @see {@link ImageListOptions#page(int)}
+       */
+      public static ImageListOptions page(int page) {
+         return new ImageListOptions().page(page);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ListOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ListOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ListOptions.java
new file mode 100644
index 0000000..f859c1c
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ListOptions.java
@@ -0,0 +1,60 @@
+/*
+ * 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.digitalocean2.domain.options;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+/**
+ * Options to customize how paginated lists are returned.
+ */
+public class ListOptions extends BaseHttpRequestOptions {
+   public static final String PAGE_PARAM = "page";
+   public static final String PER_PAGE_PARAM = "per_page";
+   
+   /**
+    * Configures the number of entries to return in each page.
+    */
+   public ListOptions perPage(int perPage) {
+      queryParameters.put(PER_PAGE_PARAM, String.valueOf(perPage));
+      return this;
+   }
+   
+   /**
+    * Configures the number of the page to be returned.
+    */
+   public ListOptions page(int page) {
+      queryParameters.put(PAGE_PARAM, String.valueOf(page));
+      return this;
+   }
+   
+   public static final class Builder {
+      
+      /**
+       * @see {@link ListOptions#perPage(int)}
+       */
+      public static ListOptions perPage(int perPage) {
+         return new ListOptions().perPage(perPage);
+      }
+      
+      /**
+       * @see {@link ListOptions#page(int)}
+       */
+      public static ListOptions page(int page) {
+         return new ListOptions().page(page);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ActionApi.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ActionApi.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ActionApi.java
new file mode 100644
index 0000000..4a7a8bd
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ActionApi.java
@@ -0,0 +1,113 @@
+/*
+ * 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.digitalocean2.features;
+
+import java.beans.ConstructorProperties;
+import java.io.Closeable;
+import java.net.URI;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
+import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.Json;
+import org.jclouds.oauth.v2.filters.OAuthFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Provides access to Actions via the REST API.
+ *
+ * @see <a href="https://developers.digitalocean.com/v2/#actions"/>
+ * @see ActionApi
+ */
+@Path("/actions")
+@RequestFilters(OAuthFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface ActionApi extends Closeable {
+
+   @Named("action:list")
+   @GET
+   @ResponseParser(ParseActions.class)
+   @Transform(ParseActions.ToPagedIterable.class)
+   @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<Action> list();
+   
+   @Named("action:list")
+   @GET
+   @ResponseParser(ParseActions.class)
+   @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<Action> list(ListOptions options);
+   
+   static final class ParseActions extends ParseJson<ParseActions.Actions> {
+      @Inject ParseActions(Json json) {
+         super(json, TypeLiteral.get(Actions.class));
+      }
+
+      private static class Actions extends PaginatedCollection<Action> {
+         @ConstructorProperties({ "actions", "meta", "links" })
+         public Actions(List<Action> items, Meta meta, Links links) {
+            super(items, meta, links);
+         }
+      }
+
+      private static class ToPagedIterable extends BaseToPagedIterable<Action, ListOptions> {
+         @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+            super(api, linkToOptions);
+         }
+
+         @Override
+         protected IterableWithMarker<Action> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+            return api.actionApi().list(options);
+         }
+      }
+   }
+
+   @Named("action:get")
+   @GET
+   @SelectJson("action")
+   @Path("/{id}")
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   Action get(@PathParam("id") int id);
+   
+}
+