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 2014/02/05 10:40:21 UTC
[3/4] Added the DigitalOcean provider
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Droplet.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Droplet.java b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Droplet.java
new file mode 100644
index 0000000..307cbc4
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Droplet.java
@@ -0,0 +1,253 @@
+/*
+ * 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.digitalocean.domain;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Named;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * A droplet.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+public 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();
+ }
+ }
+
+ private final int id;
+ private final String name;
+ @Named("image_id")
+ private final int imageId;
+ @Named("size_id")
+ private final int sizeId;
+ @Named("region_id")
+ private final int regionId;
+ @Named("backups_active")
+ private final boolean backupsActive;
+ private final List<Object> backups;
+ private final List<Object> snapshots;
+ @Named("ip_address")
+ private final String ip;
+ @Named("private_ip_address")
+ private final String privateIp;
+ private final boolean locked;
+ private final Status status;
+ @Named("created_at")
+ private final Date creationDate;
+
+ @ConstructorProperties({ "id", "name", "image_id", "size_id", "region_id", "backups_active", "backups", "snapshots",
+ "ip_address", "private_ip_address", "locked", "status", "created_at" })
+ public Droplet(int id, String name, int imageId, int sizeId, int regionId, boolean backupsActive,
+ @Nullable List<Object> backups, @Nullable List<Object> snapshots, String ip, @Nullable String privateIp,
+ boolean locked, Status status, @Nullable Date creationDate) throws ParseException {
+ this.id = id;
+ this.name = checkNotNull(name, "name cannot be null");
+ this.imageId = imageId;
+ this.sizeId = sizeId;
+ this.regionId = regionId;
+ this.backupsActive = backupsActive;
+ this.backups = backups != null ? ImmutableList.copyOf(backups) : ImmutableList.of();
+ this.snapshots = snapshots != null ? ImmutableList.copyOf(snapshots) : ImmutableList.of();
+ this.ip = ip;
+ this.privateIp = privateIp;
+ this.locked = locked;
+ this.status = checkNotNull(status, "status cannot be null");
+ this.creationDate = creationDate;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getImageId() {
+ return imageId;
+ }
+
+ public int getSizeId() {
+ return sizeId;
+ }
+
+ public int getRegionId() {
+ return regionId;
+ }
+
+ public boolean isBackupsActive() {
+ return backupsActive;
+ }
+
+ public List<Object> getBackups() {
+ return backups;
+ }
+
+ public List<Object> getSnapshots() {
+ return snapshots;
+ }
+
+ public String getIp() {
+ return ip;
+ }
+
+ public String getPrivateIp() {
+ return privateIp;
+ }
+
+ public boolean isLocked() {
+ return locked;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public Date getCreationDate() {
+ return creationDate;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (backups == null ? 0 : backups.hashCode());
+ result = prime * result + (backupsActive ? 1231 : 1237);
+ result = prime * result + (creationDate == null ? 0 : creationDate.hashCode());
+ result = prime * result + id;
+ result = prime * result + imageId;
+ result = prime * result + (ip == null ? 0 : ip.hashCode());
+ result = prime * result + (locked ? 1231 : 1237);
+ result = prime * result + (name == null ? 0 : name.hashCode());
+ result = prime * result + (privateIp == null ? 0 : privateIp.hashCode());
+ result = prime * result + regionId;
+ result = prime * result + sizeId;
+ result = prime * result + (snapshots == null ? 0 : snapshots.hashCode());
+ result = prime * result + (status == null ? 0 : status.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Droplet other = (Droplet) obj;
+ if (backups == null) {
+ if (other.backups != null) {
+ return false;
+ }
+ } else if (!backups.equals(other.backups)) {
+ return false;
+ }
+ if (backupsActive != other.backupsActive) {
+ return false;
+ }
+ if (creationDate == null) {
+ if (other.creationDate != null) {
+ return false;
+ }
+ } else if (!creationDate.equals(other.creationDate)) {
+ return false;
+ }
+ if (id != other.id) {
+ return false;
+ }
+ if (imageId != other.imageId) {
+ return false;
+ }
+ if (ip == null) {
+ if (other.ip != null) {
+ return false;
+ }
+ } else if (!ip.equals(other.ip)) {
+ return false;
+ }
+ if (locked != other.locked) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ if (privateIp == null) {
+ if (other.privateIp != null) {
+ return false;
+ }
+ } else if (!privateIp.equals(other.privateIp)) {
+ return false;
+ }
+ if (regionId != other.regionId) {
+ return false;
+ }
+ if (sizeId != other.sizeId) {
+ return false;
+ }
+ if (snapshots == null) {
+ if (other.snapshots != null) {
+ return false;
+ }
+ } else if (!snapshots.equals(other.snapshots)) {
+ return false;
+ }
+ if (status != other.status) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Droplet [id=" + id + ", name=" + name + ", imageId=" + imageId + ", sizeId=" + sizeId + ", regionId="
+ + regionId + ", backupsActive=" + backupsActive + ", backups=" + backups + ", snapshots=" + snapshots
+ + ", ip=" + ip + ", privateIp=" + privateIp + ", locked=" + locked + ", status=" + status
+ + ", creationDate=" + creationDate + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/domain/DropletCreation.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/domain/DropletCreation.java b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/DropletCreation.java
new file mode 100644
index 0000000..6706ac6
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/DropletCreation.java
@@ -0,0 +1,123 @@
+/*
+ * 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.digitalocean.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+
+import com.google.inject.name.Named;
+
+/**
+ * An DropletCreation response.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+public class DropletCreation {
+
+ private final int id;
+ private final String name;
+ @Named("image_id")
+ private final int imageId;
+ @Named("size_id")
+ private final int sizeId;
+ @Named("event_id")
+ private final int eventId;
+
+ @ConstructorProperties({ "id", "name", "image_id", "size_id", "event_id" })
+ public DropletCreation(int id, String name, int imageId, int sizeId, int eventId) {
+ this.id = id;
+ this.name = checkNotNull(name, "name cannot be null");
+ this.imageId = imageId;
+ this.sizeId = sizeId;
+ this.eventId = eventId;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getImageId() {
+ return imageId;
+ }
+
+ public int getSizeId() {
+ return sizeId;
+ }
+
+ public int getEventId() {
+ return eventId;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + eventId;
+ result = prime * result + id;
+ result = prime * result + imageId;
+ result = prime * result + (name == null ? 0 : name.hashCode());
+ result = prime * result + sizeId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DropletCreation other = (DropletCreation) obj;
+ if (eventId != other.eventId) {
+ return false;
+ }
+ if (id != other.id) {
+ return false;
+ }
+ if (imageId != other.imageId) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ if (sizeId != other.sizeId) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "DropletCreation [id=" + id + ", name=" + name + ", imageId=" + imageId + ", sizeId=" + sizeId
+ + ", eventId=" + eventId + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Event.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Event.java b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Event.java
new file mode 100644
index 0000000..6b39da1
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Event.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.digitalocean.domain;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.primitives.Ints.tryParse;
+
+import java.beans.ConstructorProperties;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.inject.name.Named;
+
+/**
+ * An Event.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+public class Event {
+
+ public enum Status {
+ DONE, PENDING;
+
+ public static Status fromValue(String value) {
+ // DigitalOcean return a 'null' status when the operation is still in
+ // progress
+ if (value == null) {
+ return PENDING;
+ }
+ Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+ checkArgument(status.isPresent(), "Expected one of %s but was", Joiner.on(',').join(Status.values()), value);
+ return status.get();
+ }
+ }
+
+ private final int id;
+ @Named("action_status")
+ private final Status status;
+ @Named("event_type_id")
+ private final int typeId;
+ private final Integer percentage;
+ @Named("droplet_id")
+ private final int dropletId;
+
+ @ConstructorProperties({ "id", "action_status", "event_type_id", "percentage", "droplet_id" })
+ public Event(int id, @Nullable Status status, int typeId, @Nullable String percentage, int dropletId) {
+ this.id = id;
+ this.status = status == null ? Status.PENDING : status;
+ this.typeId = typeId;
+ this.percentage = percentage == null ? null : tryParse(percentage);
+ this.dropletId = dropletId;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public int getTypeId() {
+ return typeId;
+ }
+
+ public Integer getPercentage() {
+ return percentage;
+ }
+
+ public int getDropletId() {
+ return dropletId;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + dropletId;
+ result = prime * result + id;
+ result = prime * result + (percentage == null ? 0 : percentage.hashCode());
+ result = prime * result + (status == null ? 0 : status.hashCode());
+ result = prime * result + typeId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Event other = (Event) obj;
+ if (dropletId != other.dropletId) {
+ return false;
+ }
+ if (id != other.id) {
+ return false;
+ }
+ if (percentage == null) {
+ if (other.percentage != null) {
+ return false;
+ }
+ } else if (!percentage.equals(other.percentage)) {
+ return false;
+ }
+ if (status != other.status) {
+ return false;
+ }
+ if (typeId != other.typeId) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Event [id=" + id + ", status=" + status + ", typeId=" + typeId + ", percentage=" + percentage
+ + ", dropletId=" + dropletId + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Image.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Image.java b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Image.java
new file mode 100644
index 0000000..e1bc545
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Image.java
@@ -0,0 +1,128 @@
+/*
+ * 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.digitalocean.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+
+import org.jclouds.javax.annotation.Nullable;
+
+/**
+ * An Image.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ *
+ */
+public class Image {
+ private final int id;
+ private final String name;
+ private final OperatingSystem os;
+ private final boolean publicImage;
+ private final String slug;
+
+ @ConstructorProperties({ "id", "name", "distribution", "public", "slug" })
+ public Image(int id, String name, String distribution, boolean publicImage, @Nullable String slug) {
+ this.id = id;
+ this.name = checkNotNull(name, "name");
+ this.os = OperatingSystem.builder().from(name, checkNotNull(distribution, "distribution")).build();
+ this.publicImage = publicImage;
+ this.slug = slug;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public OperatingSystem getOs() {
+ return os;
+ }
+
+ public boolean isPublicImage() {
+ return publicImage;
+ }
+
+ public String getSlug() {
+ return slug;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + id;
+ result = prime * result + (name == null ? 0 : name.hashCode());
+ result = prime * result + (os == null ? 0 : os.hashCode());
+ result = prime * result + (publicImage ? 1231 : 1237);
+ result = prime * result + (slug == null ? 0 : slug.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Image other = (Image) obj;
+ if (id != other.id) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ if (os == null) {
+ if (other.os != null) {
+ return false;
+ }
+ } else if (!os.equals(other.os)) {
+ return false;
+ }
+ if (publicImage != other.publicImage) {
+ return false;
+ }
+ if (slug == null) {
+ if (other.slug != null) {
+ return false;
+ }
+ } else if (!slug.equals(other.slug)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Image [id=" + id + ", name=" + name + ", os=" + os + ", publicImage=" + publicImage + ", slug=" + slug
+ + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/domain/OperatingSystem.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/domain/OperatingSystem.java b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/OperatingSystem.java
new file mode 100644
index 0000000..ac67c51
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/OperatingSystem.java
@@ -0,0 +1,139 @@
+/*
+ * 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.digitalocean.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+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;
+
+/**
+ * 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.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+public class OperatingSystem {
+
+ // Parse something like "Ubuntu 12.10 x64"
+ private static final Pattern VERSION_PATTERN = compile("\\s(\\d+(?:\\.?\\d+)?)");
+ private static final Pattern ARCH_PATTERN = compile("x\\d{2}");
+ private static final String IS_64_BIT = "x64";
+
+ private final Distribution distribution;
+ private final String version;
+ private final String arch;
+
+ private OperatingSystem(String distribution, String version, String arch) {
+ this.distribution = checkNotNull(Distribution.fromValue(distribution), "distribution cannot be null");
+ this.version = checkNotNull(version, "version cannot be null");
+ this.arch = checkNotNull(arch, "arch cannot be null");
+ }
+
+ public Distribution getDistribution() {
+ return distribution;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getArch() {
+ return arch;
+ }
+
+ public boolean is64bit() {
+ return IS_64_BIT.equals(arch);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String name;
+ private String distribution;
+
+ public Builder from(String name, String distribution) {
+ this.name = checkNotNull(name, "name cannot be null");
+ this.distribution = checkNotNull(distribution, "distribution cannot be null");
+ return this;
+ }
+
+ public OperatingSystem build() {
+ return new OperatingSystem(distribution, match(VERSION_PATTERN, name, 1), match(ARCH_PATTERN, name, 0));
+ }
+ }
+
+ private static String match(final Pattern pattern, final String input, int group) {
+ Matcher m = pattern.matcher(input);
+ return m.find() ? nullToEmpty(m.group(group)) : "";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (arch == null ? 0 : arch.hashCode());
+ result = prime * result + (distribution == null ? 0 : distribution.hashCode());
+ result = prime * result + (version == null ? 0 : version.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ OperatingSystem other = (OperatingSystem) obj;
+ if (arch == null) {
+ if (other.arch != null) {
+ return false;
+ }
+ } else if (!arch.equals(other.arch)) {
+ return false;
+ }
+ if (distribution != other.distribution) {
+ return false;
+ }
+ if (version == null) {
+ if (other.version != null) {
+ return false;
+ }
+ } else if (!version.equals(other.version)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "OperatingSystem [distribution=" + distribution + ", version=" + version + ", arch=" + arch + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Region.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Region.java b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Region.java
new file mode 100644
index 0000000..cfefada
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Region.java
@@ -0,0 +1,101 @@
+/*
+ * 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.digitalocean.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+
+/**
+ * A Region.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+public class Region {
+
+ private final int id;
+ private final String name;
+ private final String slug;
+
+ @ConstructorProperties({ "id", "name", "slug" })
+ public Region(int id, String name, String slug) {
+ this.id = id;
+ this.name = checkNotNull(name, "name cannot be null");
+ this.slug = checkNotNull(slug, "slug cannot be null");
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getSlug() {
+ return slug;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + id;
+ result = prime * result + (name == null ? 0 : name.hashCode());
+ result = prime * result + (slug == null ? 0 : slug.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Region other = (Region) obj;
+ if (id != other.id) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ if (slug == null) {
+ if (other.slug != null) {
+ return false;
+ }
+ } else if (!slug.equals(other.slug)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Region [id=" + id + ", name=" + name + ", slug=" + slug + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Size.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Size.java b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Size.java
new file mode 100644
index 0000000..b1c7c63
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/Size.java
@@ -0,0 +1,167 @@
+/*
+ * 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.digitalocean.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+
+import javax.inject.Named;
+
+import org.jclouds.javax.annotation.Nullable;
+
+/**
+ * A Size.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+public class Size {
+
+ private final int id;
+ private final String name;
+ private final String slug;
+ private final int memory;
+ private final int cpu;
+ private final int disk;
+ @Named("cost_per_hour")
+ private final String costPerHour;
+ @Named("cost_per_month")
+ private final String costPerMonth;
+
+ @ConstructorProperties({ "id", "name", "slug", "memory", "cpu", "disk", "cost_per_hour", "cost_per_month" })
+ public Size(int id, String name, @Nullable String slug, int memory, int cpu, int disk, String costPerHour,
+ String costPerMonth) {
+ this.id = id;
+ this.name = checkNotNull(name, "name cannot be null");
+ this.slug = slug;
+ this.memory = memory;
+ this.cpu = cpu;
+ this.disk = disk;
+ this.costPerHour = checkNotNull(costPerHour, "costPerHour cannot be null");
+ this.costPerMonth = checkNotNull(costPerMonth, "costPerMonth cannot be null");
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getSlug() {
+ return slug;
+ }
+
+ public int getMemory() {
+ return memory;
+ }
+
+ public int getCpu() {
+ return cpu;
+ }
+
+ public int getDisk() {
+ return disk;
+ }
+
+ public String getCostPerHour() {
+ return costPerHour;
+ }
+
+ public String getCostPerMonth() {
+ return costPerMonth;
+ }
+
+ @Override
+ public int hashCode() {
+ int prime = 31;
+ int result = 1;
+ result = prime * result + (costPerHour == null ? 0 : costPerHour.hashCode());
+ result = prime * result + (costPerMonth == null ? 0 : costPerMonth.hashCode());
+ result = prime * result + cpu;
+ result = prime * result + disk;
+ result = prime * result + id;
+ result = prime * result + memory;
+ result = prime * result + (name == null ? 0 : name.hashCode());
+ result = prime * result + (slug == null ? 0 : slug.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Size other = (Size) obj;
+ if (costPerHour == null) {
+ if (other.costPerHour != null) {
+ return false;
+ }
+ } else if (!costPerHour.equals(other.costPerHour)) {
+ return false;
+ }
+ if (costPerMonth == null) {
+ if (other.costPerMonth != null) {
+ return false;
+ }
+ } else if (!costPerMonth.equals(other.costPerMonth)) {
+ return false;
+ }
+ if (cpu != other.cpu) {
+ return false;
+ }
+ if (disk != other.disk) {
+ return false;
+ }
+ if (id != other.id) {
+ return false;
+ }
+ if (memory != other.memory) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ if (slug == null) {
+ if (other.slug != null) {
+ return false;
+ }
+ } else if (!slug.equals(other.slug)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Size [id=" + id + ", name=" + name + ", slug=" + slug + ", memory=" + memory + ", cpu=" + cpu + ", disk="
+ + disk + ", costPerHour=" + costPerHour + ", costPerMonth=" + costPerMonth + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/domain/SshKey.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/domain/SshKey.java b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/SshKey.java
new file mode 100644
index 0000000..3e335c0
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/SshKey.java
@@ -0,0 +1,99 @@
+/*
+ * 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.digitalocean.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+import java.security.PublicKey;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.inject.name.Named;
+
+/**
+ * A SSH Key.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+public class SshKey {
+
+ private final int id;
+ private final String name;
+ @Named("ssh_pub_key")
+ private final PublicKey publicKey;
+
+ @ConstructorProperties({ "id", "name", "ssh_pub_key" })
+ public SshKey(int id, String name, @Nullable PublicKey publicKey) {
+ this.id = id;
+ this.name = checkNotNull(name, "name cannot be null");
+ this.publicKey = publicKey;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + id;
+ result = prime * result + (name == null ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SshKey other = (SshKey) obj;
+ if (id != other.id) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "SshKey [id=" + id + ", name=" + name + ", publicKey=" + publicKey + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/domain/options/CreateDropletOptions.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/domain/options/CreateDropletOptions.java b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/options/CreateDropletOptions.java
new file mode 100644
index 0000000..aba2114
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/domain/options/CreateDropletOptions.java
@@ -0,0 +1,164 @@
+/*
+ * 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.digitalocean.domain.options;
+
+import java.util.Set;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Options to customize droplet creation.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+public class CreateDropletOptions extends BaseHttpRequestOptions {
+
+ private final Set<Integer> sshKeyIds;
+ private final Boolean privateNetworking;
+ private final Boolean backupsEnabled;
+
+ public CreateDropletOptions(Set<Integer> sshKeyIds, Boolean privateNetworking, Boolean backupsEnabled) {
+ this.sshKeyIds = sshKeyIds;
+ this.privateNetworking = privateNetworking;
+ this.backupsEnabled = backupsEnabled;
+
+ if (!sshKeyIds.isEmpty()) {
+ queryParameters.put("ssh_key_ids", Joiner.on(',').join(sshKeyIds));
+ }
+ if (privateNetworking != null) {
+ queryParameters.put("private_networking", privateNetworking.toString());
+ }
+ if (backupsEnabled != null) {
+ queryParameters.put("backups_enabled", backupsEnabled.toString());
+ }
+ }
+
+ public Iterable<Integer> getSshKeyIds() {
+ return sshKeyIds;
+ }
+
+ public Boolean getPrivateNetworking() {
+ return privateNetworking;
+ }
+
+ public Boolean getBackupsEnabled() {
+ return backupsEnabled;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + (backupsEnabled == null ? 0 : backupsEnabled.hashCode());
+ result = prime * result + (privateNetworking == null ? 0 : privateNetworking.hashCode());
+ result = prime * result + (sshKeyIds == null ? 0 : sshKeyIds.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ CreateDropletOptions other = (CreateDropletOptions) obj;
+ if (backupsEnabled == null) {
+ if (other.backupsEnabled != null) {
+ return false;
+ }
+ } else if (!backupsEnabled.equals(other.backupsEnabled)) {
+ return false;
+ }
+ if (privateNetworking == null) {
+ if (other.privateNetworking != null) {
+ return false;
+ }
+ } else if (!privateNetworking.equals(other.privateNetworking)) {
+ return false;
+ }
+ if (sshKeyIds == null) {
+ if (other.sshKeyIds != null) {
+ return false;
+ }
+ } else if (!sshKeyIds.equals(other.sshKeyIds)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "CreateDropletOptions [sshKeyIds=" + sshKeyIds + ", privateNetworking=" + privateNetworking
+ + ", backupsEnabled=" + backupsEnabled + "]";
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private ImmutableSet.Builder<Integer> sshKeyIds = ImmutableSet.builder();
+ private Boolean privateNetworking;
+ private Boolean backupsEnabled;
+
+ /**
+ * 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(), privateNetworking, backupsEnabled);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/features/DropletApi.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/features/DropletApi.java b/digitalocean/src/main/java/org/jclouds/digitalocean/features/DropletApi.java
new file mode 100644
index 0000000..4c57db2
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/features/DropletApi.java
@@ -0,0 +1,286 @@
+/*
+ * 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.digitalocean.features;
+
+import java.io.Closeable;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.digitalocean.domain.Droplet;
+import org.jclouds.digitalocean.domain.DropletCreation;
+import org.jclouds.digitalocean.domain.options.CreateDropletOptions;
+import org.jclouds.digitalocean.http.filters.AuthenticationFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.inject.name.Named;
+
+/**
+ * Provides access to the Droplet management features.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+@RequestFilters(AuthenticationFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+@Path("/droplets")
+public interface DropletApi extends Closeable {
+
+ /**
+ * Lists all existing droplets.
+ *
+ * @return The list of all existing droplets.
+ */
+ @Named("droplet:list")
+ @GET
+ @SelectJson("droplets")
+ List<Droplet> list();
+
+ /**
+ * Gets the details of the given droplet.
+ *
+ * @param id The id of the droplet to get.
+ * @return The details of the droplet or <code>null</code> if no droplet exists with the given id.
+ */
+ @Named("droplet:get")
+ @GET
+ @Path("/{id}")
+ @SelectJson("droplet")
+ @Fallback(NullOnNotFoundOr404.class)
+ Droplet get(@PathParam("id") int id);
+
+ /**
+ * Creates a new droplet.
+ *
+ * @param name The name for the new droplet.
+ * @param imageId The id of the image to use to create the droplet.
+ * @param sizeId The size to use to create the droplet.
+ * @param regionId The region where the droplet must be created.
+ * @return The created droplet.
+ */
+ @Named("droplet:create")
+ @GET
+ @Path("/new")
+ @SelectJson("droplet")
+ DropletCreation create(@QueryParam("name") String name, @QueryParam("image_id") int imageId,
+ @QueryParam("size_id") int sizeId, @QueryParam("region_id") int regionId);
+
+ /**
+ * Creates a new droplet.
+ *
+ * @param name The name for the new droplet.
+ * @param imageId The id of the image to use to create the droplet.
+ * @param sizeId The size to use to create the droplet.
+ * @param regionId The region where the droplet must be created.
+ * @param options Custom options to create the droplet.
+ * @return The created droplet.
+ */
+ @Named("droplet:create")
+ @GET
+ @Path("/new")
+ @SelectJson("droplet")
+ DropletCreation create(@QueryParam("name") String name, @QueryParam("image_id") int imageId,
+ @QueryParam("size_id") int sizeId, @QueryParam("region_id") int regionId, CreateDropletOptions options);
+
+ /**
+ * Reboots the given droplet.
+ *
+ * @param id The id of the droplet to reboot.
+ * @return The id of the event to track the reboot process.
+ */
+ @Named("droplet:reboot")
+ @GET
+ @Path("/{id}/reboot")
+ @SelectJson("event_id")
+ int reboot(@PathParam("id") int id);
+
+ /**
+ * Power cycles the given droplet.
+ *
+ * @param id The id of the droplet to power cycle.
+ * @return The id of the event to track the power cycle process.
+ */
+ @Named("droplet:powercycle")
+ @GET
+ @Path("/{id}/power_cycle")
+ @SelectJson("event_id")
+ int powerCycle(@PathParam("id") int id);
+
+ /**
+ * Shuts down the given droplet.
+ *
+ * @param id The id of the droplet to shutdown.
+ * @return The id of the event to track the shutdown process.
+ */
+ @Named("droplet:shutdown")
+ @GET
+ @Path("/{id}/shutdown")
+ @SelectJson("event_id")
+ int shutdown(@PathParam("id") int id);
+
+ /**
+ * Powers off the given droplet.
+ *
+ * @param id The id of the droplet to power off.
+ * @return The id of the event to track the power off process.
+ */
+ @Named("droplet:poweroff")
+ @GET
+ @Path("/{id}/power_off")
+ @SelectJson("event_id")
+ int powerOff(@PathParam("id") int id);
+
+ /**
+ * Powers on the given droplet.
+ *
+ * @param id The id of the droplet to power on.
+ * @return The id of the event to track the power on process.
+ */
+ @Named("droplet:poweron")
+ @GET
+ @Path("/{id}/power_on")
+ @SelectJson("event_id")
+ int powerOn(@PathParam("id") int id);
+
+ /**
+ * Resets the password for the given droplet.
+ *
+ * @param id The id of the droplet to reset the password to.
+ * @return The id of the event to track the password reset process.
+ */
+ @Named("droplet:resetpassword")
+ @GET
+ @Path("/{id}/password_reset")
+ @SelectJson("event_id")
+ int resetPassword(@PathParam("id") int id);
+
+ /**
+ * Changes the size for the given droplet.
+ *
+ * @param id The id of the droplet to change the size to.
+ * @param sizeId The id of the new size for the droplet.
+ * @return The id of the event to track the resize process.
+ */
+ @Named("droplet:resize")
+ @GET
+ @Path("/{id}/resize")
+ @SelectJson("event_id")
+ int resize(@PathParam("id") int id, @QueryParam("size_id") int sizeId);
+
+ /**
+ * Takes a snapshot of the droplet once it has been powered off.
+ *
+ * @param id The id of the droplet to take the snapshot of.
+ * @return The id of the event to track the snapshot process.
+ */
+ @Named("droplet:snapshot")
+ @GET
+ @Path("/{id}/snapshot")
+ @SelectJson("event_id")
+ int snapshot(@PathParam("id") int id);
+
+ /**
+ * Takes a snapshot of the droplet once it has been powered off.
+ *
+ * @param id The id of the droplet to take the snapshot of.
+ * @param name The name for the snapshot.
+ * @return The id of the event to track the snapshot process.
+ */
+ @Named("droplet:snapshot")
+ @GET
+ @Path("/{id}/snapshot")
+ @SelectJson("event_id")
+ int snapshot(@PathParam("id") int id, @QueryParam("name") String name);
+
+ /**
+ * Restores a droplet with a previous image or snapshot.
+ * <p>
+ * This will be a mirror copy of the image or snapshot to your droplet. Be sure you have backed up any necessary
+ * information prior to restore.
+ *
+ * @param id The id of the droplet to restore.
+ * @param imageId The id of the image or snapshot to use to restore the droplet.
+ * @return The id of the event to track the restore process.
+ */
+ @Named("droplet:restore")
+ @GET
+ @Path("/{id}/restore")
+ @SelectJson("event_id")
+ int restore(@PathParam("id") int id, @QueryParam("image_id") int imageId);
+
+ /**
+ * Rebuilds a droplet with a default image.
+ * <p>
+ * This is useful if you want to start again but retain the same IP address for your droplet.
+ *
+ * @param id The id of the droplet to rebuild.
+ * @param imageId The id of the image or snapshot to use to restore the droplet.
+ * @return The id of the event to track the restore process.
+ */
+ @Named("droplet:rebuild")
+ @GET
+ @Path("/{id}/rebuild")
+ @SelectJson("event_id")
+ int rebuild(@PathParam("id") int id, @QueryParam("image_id") int imageId);
+
+ /**
+ * Renames a droplet to the specified name.
+ *
+ * @param id The id of the droplet to rename.
+ * @param name The new name for the droplet.
+ * @return The id of the event to track the rename process.
+ */
+ @Named("droplet:rename")
+ @GET
+ @Path("/{id}/rename")
+ @SelectJson("event_id")
+ int rename(@PathParam("id") int id, @QueryParam("name") String name);
+
+ /**
+ * Destroys the given droplet.
+ *
+ * @param id The id of the droplet to destroy.
+ * @return The id of the event to track the destroy process.
+ */
+ @Named("droplet:destroy")
+ @GET
+ @Path("/{id}/destroy")
+ @SelectJson("event_id")
+ int destroy(@PathParam("id") int id);
+
+ /**
+ * Destroys the given droplet.
+ *
+ * @param id The id of the droplet to destroy.
+ * @param scrubData If true this will strictly write 0s to your prior partition to ensure that all data is completely
+ * erased.
+ * @return The id of the event to track the destroy process.
+ */
+ @Named("droplet:destroy")
+ @GET
+ @Path("/{id}/destroy")
+ @SelectJson("event_id")
+ int destroy(@PathParam("id") int id, @QueryParam("scrub_data") boolean scrubData);
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/features/EventApi.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/features/EventApi.java b/digitalocean/src/main/java/org/jclouds/digitalocean/features/EventApi.java
new file mode 100644
index 0000000..5da2685
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/features/EventApi.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.digitalocean.features;
+
+import java.io.Closeable;
+
+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.NullOnNotFoundOr404;
+import org.jclouds.digitalocean.domain.Event;
+import org.jclouds.digitalocean.http.filters.AuthenticationFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.inject.name.Named;
+
+/**
+ * Provides access to the Event API.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+@RequestFilters(AuthenticationFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+@Path("/events")
+public interface EventApi extends Closeable {
+
+ /**
+ * Gets the details of a given event.
+ *
+ * @param The id of the event to get.
+ * @return The details of the event or <code>null</code> if no event exists with the given id.
+ */
+ @Named("event:get")
+ @GET
+ @Path("/{id}")
+ @Fallback(NullOnNotFoundOr404.class)
+ @SelectJson("event")
+ Event get(@PathParam("id") int id);
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/features/ImageApi.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/features/ImageApi.java b/digitalocean/src/main/java/org/jclouds/digitalocean/features/ImageApi.java
new file mode 100644
index 0000000..53d99c5
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/features/ImageApi.java
@@ -0,0 +1,96 @@
+/*
+ * 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.digitalocean.features;
+
+import java.io.Closeable;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.digitalocean.domain.Image;
+import org.jclouds.digitalocean.http.filters.AuthenticationFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.inject.name.Named;
+
+/**
+ * Provides access to the Image management features.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+@RequestFilters(AuthenticationFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+@Path("/images")
+public interface ImageApi extends Closeable {
+
+ /**
+ * Lists all available images.
+ *
+ * @return The list of all available images.
+ */
+ @Named("image:list")
+ @GET
+ @SelectJson("images")
+ List<Image> list();
+
+ /**
+ * Gets the details of the given image.
+ *
+ * @param id The id of the image to get.
+ * @return The details of the image or <code>null</code> if no image exists
+ * with the given id.
+ */
+ @Named("image:get")
+ @GET
+ @Path("/{id}")
+ @SelectJson("image")
+ @Fallback(NullOnNotFoundOr404.class)
+ Image get(@PathParam("id") int id);
+
+ /**
+ * Deletes an existing image.
+ *
+ * @param id The id of the key pair.
+ */
+ @Named("image:delete")
+ @GET
+ @Path("/{id}/destroy")
+ void delete(@PathParam("id") int id);
+
+ /**
+ * Transfers the image to the given region.
+ *
+ * @param id The id of the image to transfer.
+ * @param regionId The id of the region to which the image will be
+ * transferred.
+ * @return The id of the event to track the transfer process.
+ */
+ @Named("image:transfer")
+ @GET
+ @Path("/{id}/transfer")
+ @SelectJson("event_id")
+ int transfer(@PathParam("id") int id, @QueryParam("region_id") int regionId);
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/features/KeyPairApi.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/features/KeyPairApi.java b/digitalocean/src/main/java/org/jclouds/digitalocean/features/KeyPairApi.java
new file mode 100644
index 0000000..b8b2dfb
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/features/KeyPairApi.java
@@ -0,0 +1,108 @@
+/*
+ * 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.digitalocean.features;
+
+import java.io.Closeable;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.digitalocean.domain.SshKey;
+import org.jclouds.digitalocean.http.filters.AuthenticationFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.inject.name.Named;
+
+/**
+ * Provides access to the SSH key pair management features.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+@RequestFilters(AuthenticationFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+@Path("/ssh_keys")
+public interface KeyPairApi extends Closeable {
+
+ /**
+ * Lists all existing SSH key pairs.
+ *
+ * @return The list of all existing SSH key pairs.
+ */
+ @Named("key:list")
+ @GET
+ @SelectJson("ssh_keys")
+ List<SshKey> list();
+
+ /**
+ * Gets the details of an existing SSH key pair.
+ *
+ * @param id The id of the SSH key pair.
+ * @return The details of the SSH key pair or <code>null</code> if no key
+ * exists with the given id.
+ */
+ @Named("key:get")
+ @GET
+ @Path("/{id}")
+ @SelectJson("ssh_key")
+ @Fallback(NullOnNotFoundOr404.class)
+ SshKey get(@PathParam("id") int id);
+
+ /**
+ * Creates a new SSH key pair.
+ *
+ * @param name The name of the key pair.
+ * @param publicKey The public key.
+ * @return The details of the created key pair.
+ */
+ @Named("key:create")
+ @GET
+ @Path("/new")
+ @SelectJson("ssh_key")
+ SshKey create(@QueryParam("name") String name, @QueryParam("ssh_pub_key") String publicKey);
+
+ /**
+ * Changes the SSH key for the given key pair.
+ *
+ * @param id The id of the key pair.
+ * @param newPublicKey The new public key.
+ * @return The details of the modified key pair.
+ */
+ @Named("key:edit")
+ @GET
+ @Path("/{id}/edit")
+ @SelectJson("ssh_key")
+ SshKey edit(@PathParam("id") int id, @QueryParam("ssh_pub_key") String newPublicKey);
+
+ /**
+ * Deletes an existing SSH key pair.
+ *
+ * @param id The id of the key pair.
+ */
+ @Named("key:delete")
+ @GET
+ @Path("/{id}/destroy")
+ void delete(@PathParam("id") int id);
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/features/RegionApi.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/features/RegionApi.java b/digitalocean/src/main/java/org/jclouds/digitalocean/features/RegionApi.java
new file mode 100644
index 0000000..ae9a1cb
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/features/RegionApi.java
@@ -0,0 +1,54 @@
+/*
+ * 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.digitalocean.features;
+
+import java.io.Closeable;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.digitalocean.domain.Region;
+import org.jclouds.digitalocean.http.filters.AuthenticationFilter;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.inject.name.Named;
+
+/**
+ * Provides access to the Region management features.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+@RequestFilters(AuthenticationFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+@Path("/regions")
+public interface RegionApi extends Closeable {
+
+ /**
+ * Lists all available regions.
+ *
+ * @return The list of all available regions.
+ */
+ @Named("region:list")
+ @GET
+ @SelectJson("regions")
+ List<Region> list();
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/features/SizesApi.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/features/SizesApi.java b/digitalocean/src/main/java/org/jclouds/digitalocean/features/SizesApi.java
new file mode 100644
index 0000000..4dd1fd6
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/features/SizesApi.java
@@ -0,0 +1,54 @@
+/*
+ * 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.digitalocean.features;
+
+import java.io.Closeable;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.digitalocean.domain.Size;
+import org.jclouds.digitalocean.http.filters.AuthenticationFilter;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.inject.name.Named;
+
+/**
+ * Provides access to the Size management features.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+@RequestFilters(AuthenticationFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+@Path("/sizes")
+public interface SizesApi extends Closeable {
+
+ /**
+ * Lists all available sizes.
+ *
+ * @return The list of all available sizes.
+ */
+ @Named("size:list")
+ @GET
+ @SelectJson("sizes")
+ List<Size> list();
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/handlers/DigitalOceanErrorHandler.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/handlers/DigitalOceanErrorHandler.java b/digitalocean/src/main/java/org/jclouds/digitalocean/handlers/DigitalOceanErrorHandler.java
new file mode 100644
index 0000000..d16eaf3
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/handlers/DigitalOceanErrorHandler.java
@@ -0,0 +1,62 @@
+/*
+ * 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.digitalocean.handlers;
+
+import javax.inject.Singleton;
+
+import org.jclouds.digitalocean.http.ResponseStatusFromPayloadHttpCommandExecutorService;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.ResourceNotFoundException;
+
+/**
+ * Parse the errors in the response and propagate an appropriate exception.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ *
+ * @see ResponseStatusFromPayloadHttpCommandExecutorService
+ */
+@Singleton
+public class DigitalOceanErrorHandler implements HttpErrorHandler {
+
+ @Override
+ public void handleError(HttpCommand command, HttpResponse response) {
+ Exception exception = null;
+
+ try {
+ // The response message is already properly populated by the
+ // ResponseStatusFromPayloadHttpCommandExecutorService
+ switch (response.getStatusCode()) {
+ case 401:
+ exception = new AuthorizationException(response.getMessage(), exception);
+ break;
+ case 404:
+ exception = new ResourceNotFoundException(response.getMessage(), exception);
+ break;
+ default:
+ exception = new HttpResponseException(response.getMessage(), command, response);
+ break;
+ }
+ } finally {
+ command.setException(exception);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/http/ResponseStatusFromPayloadHttpCommandExecutorService.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/http/ResponseStatusFromPayloadHttpCommandExecutorService.java b/digitalocean/src/main/java/org/jclouds/digitalocean/http/ResponseStatusFromPayloadHttpCommandExecutorService.java
new file mode 100644
index 0000000..40aed57
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/http/ResponseStatusFromPayloadHttpCommandExecutorService.java
@@ -0,0 +1,137 @@
+/*
+ * 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.digitalocean.http;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.io.Closeables.close;
+import static org.jclouds.io.Payloads.newInputStreamPayload;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URI;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+
+import org.jclouds.Constants;
+import org.jclouds.digitalocean.domain.BaseResponse;
+import org.jclouds.digitalocean.domain.BaseResponse.Status;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.http.IOExceptionRetryHandler;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.http.handlers.DelegatingErrorHandler;
+import org.jclouds.http.handlers.DelegatingRetryHandler;
+import org.jclouds.http.internal.HttpWire;
+import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService;
+import org.jclouds.io.ContentMetadataCodec;
+import org.jclouds.io.Payload;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+/**
+ * Custom implementation of the HTTP driver to read the response body in order to get the real response status.
+ * <p>
+ * The DigitalOcean API always return 200 codes even if a request failed due to some internal error, but populates an
+ * <code>ERROR</code> string in the response payload.
+ * <p>
+ * This class will read the body of the response and populate a 500 status code if an error is found.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+@Singleton
+public class ResponseStatusFromPayloadHttpCommandExecutorService extends JavaUrlHttpCommandExecutorService {
+
+ public static final String ACCESS_DENIED = "Access Denied";
+ public static final String NOT_FOUND = "Not Found";
+
+ private final ParseJson<BaseResponse> errorParser;
+
+ @Inject
+ ResponseStatusFromPayloadHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
+ @Named(Constants.PROPERTY_IO_WORKER_THREADS) ListeningExecutorService ioExecutor,
+ DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
+ DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier,
+ @Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider, Function<URI, Proxy> proxyForURI,
+ ParseJson<BaseResponse> errorParser) throws SecurityException, NoSuchFieldException {
+ super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire, verifier,
+ untrustedSSLContextProvider, proxyForURI);
+ this.errorParser = checkNotNull(errorParser, "errorParser cannot be null");
+ }
+
+ @Override
+ protected HttpResponse invoke(HttpURLConnection connection) throws IOException, InterruptedException {
+ HttpResponse original = super.invoke(connection);
+ HttpResponse.Builder<?> response = original.toBuilder();
+
+ if (hasPayload(original)) {
+ // As we need to read the response body to determine if there are errors, but we may need to process the body
+ // again later in the response parsers if everything is OK, we buffer the body into an InputStream we can reset
+ InputStream in = null;
+ InputStream originalInputStream = original.getPayload().openStream();
+
+ if (originalInputStream instanceof ByteArrayInputStream) {
+ in = originalInputStream;
+ } else {
+ try {
+ in = new ByteArrayInputStream(ByteStreams.toByteArray(originalInputStream));
+ } finally {
+ close(originalInputStream, true);
+ }
+ }
+
+ // Process the payload and look for errors
+ BaseResponse responseContent = errorParser.apply(in);
+ if (responseContent != null && responseContent.getStatus() == Status.ERROR) {
+ // Yes, this is ugly, but the DigitalOcean API sometimes sets the status code to 200 for these errors and
+ // the only way to know what happened is parsing the error message
+ String message = responseContent.getMessage();
+ if (ACCESS_DENIED.equals(message)) {
+ response.statusCode(401);
+ } else if (NOT_FOUND.equals(message)) {
+ response.statusCode(404);
+ } else {
+ response.statusCode(500);
+ }
+ response.message(responseContent.getDetails());
+ }
+
+ // Reset the input stream and set the payload, so it can be read again
+ // by the response and error parsers
+ in.reset();
+ Payload payload = newInputStreamPayload(in);
+ contentMetadataCodec.fromHeaders(payload.getContentMetadata(), original.getHeaders());
+ response.payload(payload);
+ }
+
+ return response.build();
+ }
+
+ private static boolean hasPayload(final HttpResponse response) {
+ return response.getPayload() != null && response.getPayload().getRawContent() != null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/http/filters/AuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/http/filters/AuthenticationFilter.java b/digitalocean/src/main/java/org/jclouds/digitalocean/http/filters/AuthenticationFilter.java
new file mode 100644
index 0000000..2998482
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/http/filters/AuthenticationFilter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.digitalocean.http.filters;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpException;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpRequestFilter;
+import org.jclouds.location.Provider;
+
+import com.google.common.base.Supplier;
+
+/**
+ * Adds the authentication query parameters to the requests.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ */
+@Singleton
+public class AuthenticationFilter implements HttpRequestFilter {
+
+ public static final String IDENTITY_PARAM = "client_id";
+ public static final String CREDENTIAL_PARAM = "api_key";
+
+ private final Supplier<Credentials> credentials;
+
+ @Inject
+ AuthenticationFilter(@Provider final Supplier<Credentials> credentials) {
+ this.credentials = checkNotNull(credentials, "credential supplier cannot be null");
+ }
+
+ @Override
+ public HttpRequest filter(HttpRequest request) throws HttpException {
+ Credentials creds = credentials.get();
+ return request.toBuilder().addQueryParam(IDENTITY_PARAM, creds.identity)
+ .addQueryParam(CREDENTIAL_PARAM, creds.credential).build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/java/org/jclouds/digitalocean/ssh/DSAKeys.java
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/java/org/jclouds/digitalocean/ssh/DSAKeys.java b/digitalocean/src/main/java/org/jclouds/digitalocean/ssh/DSAKeys.java
new file mode 100644
index 0000000..75fce0f
--- /dev/null
+++ b/digitalocean/src/main/java/org/jclouds/digitalocean/ssh/DSAKeys.java
@@ -0,0 +1,184 @@
+/*
+ * 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.digitalocean.ssh;
+
+import static com.google.common.base.Joiner.on;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Splitter.fixedLength;
+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.common.io.BaseEncoding.base16;
+import static com.google.common.io.BaseEncoding.base64;
+import static org.jclouds.util.Strings2.toStringAndClose;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+
+import org.jclouds.crypto.Pems;
+import org.jclouds.ssh.SshKeys;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.InputSupplier;
+
+/**
+ * Utility methods to work with DSA SSH keys.
+ * <p>
+ * Methods in this class should be moved to the {@link SshKeys} class.
+ *
+ * @author Sergi Castro
+ * @author Ignasi Barrera
+ *
+ * @see SshKeys
+ */
+public class DSAKeys {
+
+ public static String encodeAsOpenSSH(DSAPublicKey key) {
+ DSAParams params = key.getParams();
+ byte[] keyBlob = keyBlob(params.getP(), params.getQ(), params.getG(), key.getY());
+ return "ssh-dss " + base64().encode(keyBlob);
+ }
+
+ /**
+ * Executes {@link Pems#publicKeySpecFromOpenSSH(InputSupplier)} on the
+ * string which was OpenSSH Base64 Encoded {@code id_rsa.pub}
+ *
+ * @param idRsaPub formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
+ * @see Pems#publicKeySpecFromOpenSSH(InputSupplier)
+ */
+ public static DSAPublicKeySpec publicKeySpecFromOpenSSH(String idDsaPub) {
+ try {
+ return publicKeySpecFromOpenSSH(ByteStreams.newInputStreamSupplier(idDsaPub.getBytes(Charsets.UTF_8)));
+ } catch (IOException e) {
+ throw propagate(e);
+ }
+ }
+
+ /**
+ * Returns {@link DSAPublicKeySpec} which was OpenSSH Base64 Encoded
+ * {@code id_rsa.pub}
+ *
+ * @param supplier the input stream factory, formatted
+ * {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
+ *
+ * @return the {@link DSAPublicKeySpec} which was OpenSSH Base64 Encoded
+ * {@code id_rsa.pub}
+ * @throws IOException if an I/O error occurs
+ */
+ public static DSAPublicKeySpec publicKeySpecFromOpenSSH(InputSupplier<? extends InputStream> supplier)
+ throws IOException {
+ InputStream stream = supplier.getInput();
+ Iterable<String> parts = Splitter.on(' ').split(toStringAndClose(stream).trim());
+ checkArgument(size(parts) >= 2 && "ssh-dss".equals(get(parts, 0)), "bad format, should be: ssh-dss AAAAB3...");
+ stream = new ByteArrayInputStream(base64().decode(get(parts, 1)));
+ String marker = new String(readLengthFirst(stream));
+ checkArgument("ssh-dss".equals(marker), "looking for marker ssh-dss but got %s", marker);
+ BigInteger p = new BigInteger(readLengthFirst(stream));
+ BigInteger q = new BigInteger(readLengthFirst(stream));
+ BigInteger g = new BigInteger(readLengthFirst(stream));
+ BigInteger y = new BigInteger(readLengthFirst(stream));
+ return new DSAPublicKeySpec(y, p, q, g);
+ }
+
+ /**
+ * @param publicKeyOpenSSH RSA public key in OpenSSH format
+ * @return fingerprint ex.
+ * {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
+ */
+ public static String fingerprintPublicKey(String publicKeyOpenSSH) {
+ DSAPublicKeySpec publicKeySpec = publicKeySpecFromOpenSSH(publicKeyOpenSSH);
+ return fingerprint(publicKeySpec.getP(), publicKeySpec.getQ(), publicKeySpec.getG(), publicKeySpec.getY());
+ }
+
+ /**
+ * Create a fingerprint per the following <a
+ * href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00"
+ * >spec</a>
+ *
+ * @return hex fingerprint ex.
+ * {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
+ */
+ public static String fingerprint(BigInteger p, BigInteger q, BigInteger g, BigInteger y) {
+ byte[] keyBlob = keyBlob(p, q, g, y);
+ return hexColonDelimited(Hashing.md5().hashBytes(keyBlob));
+ }
+
+ /**
+ * @see SshKeys
+ */
+ private static String hexColonDelimited(HashCode hc) {
+ return on(':').join(fixedLength(2).split(base16().lowerCase().encode(hc.asBytes())));
+ }
+
+ /**
+ * @see SshKeys
+ */
+ private static byte[] keyBlob(BigInteger p, BigInteger q, BigInteger g, BigInteger y) {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writeLengthFirst("ssh-dss".getBytes(), out);
+ writeLengthFirst(p.toByteArray(), out);
+ writeLengthFirst(q.toByteArray(), out);
+ writeLengthFirst(g.toByteArray(), out);
+ writeLengthFirst(y.toByteArray(), out);
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw propagate(e);
+ }
+ }
+
+ /**
+ * @see SshKeys
+ */
+ // http://www.ietf.org/rfc/rfc4253.txt
+ private static byte[] readLengthFirst(InputStream in) throws IOException {
+ int byte1 = in.read();
+ int byte2 = in.read();
+ int byte3 = in.read();
+ int byte4 = in.read();
+ int length = (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0);
+ byte[] val = new byte[length];
+ in.read(val, 0, length);
+ return val;
+ }
+
+ /**
+ * @see SshKeys
+ */
+ // http://www.ietf.org/rfc/rfc4253.txt
+ private static void writeLengthFirst(byte[] array, ByteArrayOutputStream out) throws IOException {
+ out.write(array.length >>> 24 & 0xFF);
+ out.write(array.length >>> 16 & 0xFF);
+ out.write(array.length >>> 8 & 0xFF);
+ out.write(array.length >>> 0 & 0xFF);
+ if (array.length == 1 && array[0] == (byte) 0x00) {
+ out.write(new byte[0]);
+ } else {
+ out.write(array);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/224b2828/digitalocean/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata
----------------------------------------------------------------------
diff --git a/digitalocean/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata b/digitalocean/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata
new file mode 100644
index 0000000..0c26db4
--- /dev/null
+++ b/digitalocean/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata
@@ -0,0 +1 @@
+org.jclouds.digitalocean.DigitalOceanProviderMetadata
\ No newline at end of file