JCLOUDS-613: Implement the DigitalOcean v2 API Thanks to [~nacx] for pagination, many tests, fixes, and improvements to help push this over the finish line!
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/057be8df Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/057be8df Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/057be8df Branch: refs/heads/master Commit: 057be8df999bce950b672f9181547482e4a9b849 Parents: Author: Chris Custine <[email protected]> Authored: Sat Jun 27 19:52:05 2015 -0600 Committer: Ignasi Barrera <[email protected]> Committed: Sun Jun 28 22:12:03 2015 +0200 ---------------------------------------------------------------------- providers/digitalocean2/pom.xml | 152 +++++++ .../jclouds/digitalocean2/DigitalOcean2Api.java | 73 ++++ .../digitalocean2/DigitalOcean2ApiMetadata.java | 103 +++++ .../DigitalOcean2ProviderMetadata.java | 78 ++++ .../DigitalOcean2ComputeServiceAdapter.java | 191 +++++++++ ...igitalOcean2ComputeServiceContextModule.java | 205 ++++++++++ .../extensions/DigitalOcean2ImageExtension.java | 132 ++++++ .../functions/DropletStatusToStatus.java | 46 +++ .../functions/DropletToNodeMetadata.java | 176 ++++++++ .../compute/functions/ImageToImage.java | 65 +++ .../compute/functions/RegionToLocation.java | 57 +++ .../compute/functions/SizeToHardware.java | 58 +++ ...plateOptionsToStatementWithoutPublicKey.java | 59 +++ .../options/DigitalOcean2TemplateOptions.java | 174 ++++++++ .../strategy/CreateKeyPairsThenCreateNodes.java | 217 ++++++++++ .../config/DigitalOcean2HttpApiModule.java | 57 +++ .../config/DigitalOceanParserModule.java | 144 +++++++ .../jclouds/digitalocean2/domain/Action.java | 71 ++++ .../jclouds/digitalocean2/domain/Backup.java | 43 ++ .../digitalocean2/domain/Distribution.java | 69 ++++ .../jclouds/digitalocean2/domain/Droplet.java | 92 +++++ .../digitalocean2/domain/DropletCreate.java | 66 +++ .../org/jclouds/digitalocean2/domain/Image.java | 48 +++ .../jclouds/digitalocean2/domain/Kernel.java | 35 ++ .../org/jclouds/digitalocean2/domain/Key.java | 39 ++ .../jclouds/digitalocean2/domain/Networks.java | 77 ++++ .../digitalocean2/domain/OperatingSystem.java | 60 +++ .../jclouds/digitalocean2/domain/Region.java | 39 ++ .../org/jclouds/digitalocean2/domain/Size.java | 46 +++ .../jclouds/digitalocean2/domain/Snapshot.java | 47 +++ .../domain/internal/PaginatedCollection.java | 111 +++++ .../domain/options/CreateDropletOptions.java | 155 +++++++ .../domain/options/ImageListOptions.java | 74 ++++ .../domain/options/ListOptions.java | 60 +++ .../digitalocean2/features/ActionApi.java | 113 ++++++ .../digitalocean2/features/DropletApi.java | 350 ++++++++++++++++ .../digitalocean2/features/ImageApi.java | 131 ++++++ .../jclouds/digitalocean2/features/KeyApi.java | 164 ++++++++ .../digitalocean2/features/RegionApi.java | 107 +++++ .../jclouds/digitalocean2/features/SizeApi.java | 100 +++++ .../functions/BaseToPagedIterable.java | 59 +++ .../functions/LinkToImageListOptions.java | 67 ++++ .../functions/LinkToListOptions.java | 61 +++ .../handlers/DigitalOcean2ErrorHandler.java | 67 ++++ .../org/jclouds/digitalocean2/ssh/DSAKeys.java | 172 ++++++++ .../jclouds/digitalocean2/ssh/ECDSAKeys.java | 343 ++++++++++++++++ .../services/org.jclouds.apis.ApiMetadata | 18 + .../DigitalOcean2ProviderMetadataTest.java | 29 ++ .../DigitalOcean2ComputeServiceLiveTest.java | 58 +++ .../DigitalOcean2TemplateBuilderLiveTest.java | 55 +++ .../compute/config/ActionDonePredicateTest.java | 74 ++++ .../config/DropletTerminatedPredicateTest.java | 57 +++ .../DigitalOcean2ImageExtensionLiveTest.java | 40 ++ .../functions/DropletStatusToStatusTest.java | 36 ++ .../functions/DropletToNodeMetadataTest.java | 237 +++++++++++ .../compute/functions/ImageToImageTest.java | 57 +++ .../compute/functions/RegionToLocationTest.java | 52 +++ .../compute/functions/SizeToHardwareTest.java | 49 +++ ...eOptionsToStatementWithoutPublicKeyTest.java | 75 ++++ .../DigitalOcean2TemplateOptionsTest.java | 52 +++ .../domain/OperatingSystemTest.java | 104 +++++ .../features/ActionApiLiveTest.java | 70 ++++ .../features/ActionApiMockTest.java | 110 +++++ .../features/DropletApiLiveTest.java | 195 +++++++++ .../features/DropletApiMockTest.java | 401 +++++++++++++++++++ .../features/ImageApiLiveTest.java | 97 +++++ .../features/ImageApiMockTest.java | 150 +++++++ .../digitalocean2/features/KeyApiLiveTest.java | 101 +++++ .../digitalocean2/features/KeyApiMockTest.java | 203 ++++++++++ .../features/RegionApiLiveTest.java | 62 +++ .../features/RegionApiMockTest.java | 77 ++++ .../digitalocean2/features/SizeApiLiveTest.java | 62 +++ .../digitalocean2/features/SizeApiMockTest.java | 77 ++++ .../functions/LinkToImageListOptionsTest.java | 65 +++ .../functions/LinkToListOptionsTest.java | 58 +++ .../internal/BaseDigitalOcean2ApiLiveTest.java | 120 ++++++ .../internal/BaseDigitalOcean2ApiMockTest.java | 137 +++++++ .../jclouds/digitalocean2/ssh/DSAKeysTest.java | 54 +++ .../digitalocean2/ssh/ECDSAKeysTest.java | 55 +++ .../src/test/resources/action.json | 33 ++ .../src/test/resources/actions-first.json | 168 ++++++++ .../src/test/resources/actions-last.json | 106 +++++ .../src/test/resources/backups-first.json | 26 ++ .../src/test/resources/backups-last.json | 26 ++ .../src/test/resources/droplet-create-req.json | 12 + .../src/test/resources/droplet-create-res.json | 35 ++ .../src/test/resources/droplet.json | 105 +++++ .../src/test/resources/droplets-first.json | 115 ++++++ .../src/test/resources/droplets-last.json | 115 ++++++ .../digitalocean2/src/test/resources/image.json | 24 ++ .../src/test/resources/images-first.json | 108 +++++ .../src/test/resources/images-last.json | 123 ++++++ .../src/test/resources/kernels-first.json | 38 ++ .../src/test/resources/kernels-last.json | 38 ++ .../digitalocean2/src/test/resources/key.json | 8 + .../src/test/resources/keys-first.json | 43 ++ .../src/test/resources/keys-last.json | 25 ++ .../src/test/resources/logback-test.xml | 42 ++ .../src/test/resources/power-cycle.json | 33 ++ .../src/test/resources/power-off.json | 33 ++ .../src/test/resources/power-on.json | 33 ++ .../src/test/resources/reboot.json | 33 ++ .../src/test/resources/regions-first.json | 111 +++++ .../src/test/resources/regions-last.json | 128 ++++++ .../src/test/resources/shutdown.json | 33 ++ .../src/test/resources/sizes-first.json | 123 ++++++ .../src/test/resources/sizes-last.json | 98 +++++ .../src/test/resources/snapshot.json | 33 ++ .../src/test/resources/snapshots-first.json | 27 ++ .../src/test/resources/snapshots-last.json | 27 ++ .../src/test/resources/ssh-dsa.pub | 1 + .../src/test/resources/ssh-ecdsa.pub | 1 + 112 files changed, 9844 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/pom.xml ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/pom.xml b/providers/digitalocean2/pom.xml new file mode 100644 index 0000000..db5139f --- /dev/null +++ b/providers/digitalocean2/pom.xml @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.jclouds.labs</groupId> + <artifactId>jclouds-labs</artifactId> + <version>2.0.0-SNAPSHOT</version> + </parent> + + <groupId>org.apache.jclouds.labs</groupId> + <artifactId>digitalocean2</artifactId> + <name>jclouds DigitalOcean v2 API Provider</name> + <description>jclouds provider for Digital Ocean v2 Compute API</description> + + <properties> + <test.digitalocean2.endpoint>https://api.digitalocean.com/v2/</test.digitalocean2.endpoint> + <test.digitalocean2.api-version>2</test.digitalocean2.api-version> + <test.digitalocean2.identity>FIXME</test.digitalocean2.identity> + <test.digitalocean2.credential>FIXME</test.digitalocean2.credential> + <test.digitalocean2.template>imageId=ubuntu-14-04-x64</test.digitalocean2.template> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-core</artifactId> + <version>${jclouds.version}</version> + </dependency> + <dependency> + <groupId>org.apache.jclouds.api</groupId> + <artifactId>oauth</artifactId> + <version>${jclouds.version}</version> + <type>jar</type> + </dependency> + <dependency> + <groupId>org.apache.jclouds.api</groupId> + <artifactId>oauth</artifactId> + <version>${jclouds.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-compute</artifactId> + <version>${jclouds.version}</version> + </dependency> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-compute</artifactId> + <version>${jclouds.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-core</artifactId> + <version>${jclouds.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds.driver</groupId> + <artifactId>jclouds-slf4j</artifactId> + <version>${jclouds.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds.driver</groupId> + <artifactId>jclouds-sshj</artifactId> + <version>${jclouds.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.squareup.okhttp</groupId> + <artifactId>mockwebserver</artifactId> + <scope>test</scope> + <exclusions> + <!-- Already provided by jclouds-sshj --> + <exclusion> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.auto.service</groupId> + <artifactId>auto-service</artifactId> + <scope>provided</scope> + </dependency> + </dependencies> + <profiles> + <profile> + <id>live</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <executions> + <execution> + <id>integration</id> + <phase>integration-test</phase> + <goals> + <goal>test</goal> + </goals> + <configuration> + <systemPropertyVariables> + <test.digitalocean2.endpoint>${test.digitalocean2.endpoint}</test.digitalocean2.endpoint> + <test.digitalocean2.api-version>${test.digitalocean2.api-version}</test.digitalocean2.api-version> + <test.digitalocean2.build-version>${test.digitalocean2.build-version}</test.digitalocean2.build-version> + <test.digitalocean2.identity>${test.digitalocean2.identity}</test.digitalocean2.identity> + <test.digitalocean2.credential>${test.digitalocean2.credential}</test.digitalocean2.credential> + <test.digitalocean2.template>${test.digitalocean2.template}</test.digitalocean2.template> + </systemPropertyVariables> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2Api.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2Api.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2Api.java new file mode 100644 index 0000000..773fa59 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2Api.java @@ -0,0 +1,73 @@ +/* + * 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; + +import java.io.Closeable; + +import org.jclouds.digitalocean2.features.ActionApi; +import org.jclouds.digitalocean2.features.DropletApi; +import org.jclouds.digitalocean2.features.ImageApi; +import org.jclouds.digitalocean2.features.KeyApi; +import org.jclouds.digitalocean2.features.RegionApi; +import org.jclouds.digitalocean2.features.SizeApi; +import org.jclouds.rest.annotations.Delegate; + +import com.google.common.annotations.Beta; + +/** + * Provides access to DigitalOcean. + */ +@Beta +public interface DigitalOcean2Api extends Closeable { + + /** + * Provides access to Droplet features + */ + @Delegate + DropletApi dropletApi(); + + /** + * Provides access to SSH Key features + */ + @Delegate + KeyApi keyApi(); + + /** + * Provides access to Images + */ + @Delegate + ImageApi imageApi(); + + /** + * Provides access to Actions + */ + @Delegate + ActionApi actionApi(); + + /** + * Provides access to Sizes + */ + @Delegate + SizeApi sizeApi(); + + /** + * Provides access to Regions + */ + @Delegate + RegionApi regionApi(); + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java new file mode 100644 index 0000000..0b20b96 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java @@ -0,0 +1,103 @@ +/* + * 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; + +import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; +import static org.jclouds.compute.config.ComputeServiceProperties.POLL_INITIAL_PERIOD; +import static org.jclouds.compute.config.ComputeServiceProperties.POLL_MAX_PERIOD; +import static org.jclouds.compute.config.ComputeServiceProperties.TEMPLATE; +import static org.jclouds.oauth.v2.config.CredentialType.BEARER_TOKEN_CREDENTIALS; +import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE; +import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE; +import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG; +import static org.jclouds.reflect.Reflection2.typeToken; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.apis.ApiMetadata; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.digitalocean2.compute.config.DigitalOcean2ComputeServiceContextModule; +import org.jclouds.digitalocean2.config.DigitalOcean2HttpApiModule; +import org.jclouds.digitalocean2.config.DigitalOceanParserModule; +import org.jclouds.oauth.v2.config.OAuthModule; +import org.jclouds.rest.internal.BaseHttpApiMetadata; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Module; + +/** + * Implementation of {@link ApiMetadata} for DigitalOcean v2 API + */ +public class DigitalOcean2ApiMetadata extends BaseHttpApiMetadata<DigitalOcean2Api> { + + @Override + public Builder toBuilder() { + return new Builder().fromApiMetadata(this); + } + + public DigitalOcean2ApiMetadata() { + this(new Builder()); + } + + protected DigitalOcean2ApiMetadata(Builder builder) { + super(builder); + } + + public static Properties defaultProperties() { + Properties properties = BaseHttpApiMetadata.defaultProperties(); + properties.put("oauth.endpoint", "https://cloud.digitalocean.com/v1/oauth/token"); + properties.put(JWS_ALG, "RS256"); + properties.put(AUDIENCE, "https://cloud.digitalocean.com/v1/oauth/token"); + properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString()); + properties.put(PROPERTY_SESSION_INTERVAL, 3600); + properties.put(TEMPLATE, "imageId=ubuntu-14-04-x64"); + properties.put(POLL_INITIAL_PERIOD, 5000); + properties.put(POLL_MAX_PERIOD, 20000); + return properties; + } + + public static class Builder extends BaseHttpApiMetadata.Builder<DigitalOcean2Api, Builder> { + + protected Builder() { + id("digitalocean2") + .name("Digital Ocean v2 API") + .identityName("Not used for OAuth") + .credentialName("Must be oauth2 Bearer Token") + .documentation(URI.create("https://developers.digitalocean.com/v2/")) + .defaultEndpoint("https://api.digitalocean.com/v2") + .defaultProperties(DigitalOcean2ApiMetadata.defaultProperties()) + .view(typeToken(ComputeServiceContext.class)) + .defaultModules(ImmutableSet.<Class<? extends Module>>builder() + .add(DigitalOcean2HttpApiModule.class) + .add(OAuthModule.class) + .add(DigitalOceanParserModule.class) + .add(DigitalOcean2ComputeServiceContextModule.class) + .build()); + } + + @Override + public DigitalOcean2ApiMetadata build() { + return new DigitalOcean2ApiMetadata(this); + } + + @Override + protected Builder self() { + return this; + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadata.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadata.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadata.java new file mode 100644 index 0000000..0f64f78 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadata.java @@ -0,0 +1,78 @@ +/* + * 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; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.providers.ProviderMetadata; +import org.jclouds.providers.internal.BaseProviderMetadata; + +import com.google.auto.service.AutoService; + +/** + * Implementation of {@link org.jclouds.providers.ProviderMetadata} for DigitalOcean. + */ +@AutoService(ProviderMetadata.class) +public class DigitalOcean2ProviderMetadata extends BaseProviderMetadata { + + public static Builder builder() { + return new Builder(); + } + + @Override + public Builder toBuilder() { + return builder().fromProviderMetadata(this); + } + + public DigitalOcean2ProviderMetadata() { + super(builder()); + } + + public DigitalOcean2ProviderMetadata(Builder builder) { + super(builder); + } + + public static Properties defaultProperties() { + Properties properties = DigitalOcean2ApiMetadata.defaultProperties(); + return properties; + } + + public static class Builder extends BaseProviderMetadata.Builder { + + protected Builder() { + id("digitalocean2") + .name("DigitalOcean Compute Services") + .apiMetadata(new DigitalOcean2ApiMetadata()) + .homepage(URI.create("https://www.digitalocean.com/")) + .console(URI.create("https://cloud.digitalocean.com/")) + .endpoint("https://api.digitalocean.com/v2") + .defaultProperties(DigitalOcean2ProviderMetadata.defaultProperties()); + } + + @Override + public DigitalOcean2ProviderMetadata build() { + return new DigitalOcean2ProviderMetadata(this); + } + + @Override + public Builder fromProviderMetadata(ProviderMetadata in) { + super.fromProviderMetadata(in); + return this; + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java new file mode 100644 index 0000000..aa4f656 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java @@ -0,0 +1,191 @@ +/* + * 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; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.contains; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.getOnlyElement; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; + +import org.jclouds.compute.ComputeServiceAdapter; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.digitalocean2.DigitalOcean2Api; +import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions; +import org.jclouds.digitalocean2.domain.Action; +import org.jclouds.digitalocean2.domain.Droplet; +import org.jclouds.digitalocean2.domain.DropletCreate; +import org.jclouds.digitalocean2.domain.Image; +import org.jclouds.digitalocean2.domain.Region; +import org.jclouds.digitalocean2.domain.Size; +import org.jclouds.digitalocean2.domain.options.CreateDropletOptions; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.logging.Logger; + +import com.google.common.base.Predicate; +import com.google.common.primitives.Ints; + +/** + * Implementation of the Compute Service for the DigitalOcean API. + */ +public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter<Droplet, Size, Image, Region> { + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + private final DigitalOcean2Api api; + private final Predicate<Integer> nodeRunningPredicate; + private final Predicate<Integer> nodeStoppedPredicate; + private final Predicate<Integer> nodeTerminatedPredicate; + + @Inject DigitalOcean2ComputeServiceAdapter(DigitalOcean2Api api, + @Named(TIMEOUT_NODE_RUNNING) Predicate<Integer> nodeRunningPredicate, + @Named(TIMEOUT_NODE_SUSPENDED) Predicate<Integer> nodeStoppedPredicate, + @Named(TIMEOUT_NODE_TERMINATED) Predicate<Integer> nodeTerminatedPredicate) { + this.api = api; + this.nodeRunningPredicate = nodeRunningPredicate; + this.nodeStoppedPredicate = nodeStoppedPredicate; + this.nodeTerminatedPredicate = nodeTerminatedPredicate; + } + + @Override + public NodeAndInitialCredentials<Droplet> createNodeWithGroupEncodedIntoName(String group, final String name, + Template template) { + DigitalOcean2TemplateOptions templateOptions = template.getOptions().as(DigitalOcean2TemplateOptions.class); + CreateDropletOptions.Builder options = CreateDropletOptions.builder(); + + // DigitalOcean specific options + options.privateNetworking(templateOptions.getPrivateNetworking()); + options.backupsEnabled(templateOptions.getBackupsEnabled()); + if (!templateOptions.getSshKeyIds().isEmpty()) { + options.addSshKeyIds(templateOptions.getSshKeyIds()); + } + + DropletCreate dropletCreated = api.dropletApi().create(name, + template.getLocation().getId(), + template.getHardware().getProviderId(), + template.getImage().getProviderId(), + options.build()); + + // We have to actively wait until the droplet has been provisioned until + // we can build the entire Droplet object we want to return + nodeRunningPredicate.apply(getOnlyElement(dropletCreated.links().actions()).id()); + Droplet droplet = api.dropletApi().get(dropletCreated.droplet().id()); + + LoginCredentials defaultCredentials = LoginCredentials.builder().user("root") + .privateKey(templateOptions.getLoginPrivateKey()).build(); + + return new NodeAndInitialCredentials<Droplet>(droplet, String.valueOf(droplet.id()), defaultCredentials); + } + + @Override + public Iterable<Image> listImages() { + return api.imageApi().list().concat(); + } + + @Override + public Iterable<Size> listHardwareProfiles() { + return filter(api.sizeApi().list().concat(), new Predicate<Size>() { + @Override + public boolean apply(Size size) { + return size.available(); + } + }); + } + + @Override + public Iterable<Region> listLocations() { + // DigitalOcean lists regions that are unavailable for droplet creation + return filter(api.regionApi().list().concat(), new Predicate<Region>() { + @Override + public boolean apply(Region region) { + return region.available(); + } + }); + } + + @Override + public Iterable<Droplet> listNodes() { + return api.dropletApi().list().concat(); + } + + @Override + public Iterable<Droplet> listNodesByIds(final Iterable<String> ids) { + return filter(listNodes(), new Predicate<Droplet>() { + @Override + public boolean apply(Droplet droplet) { + return contains(ids, String.valueOf(droplet.id())); + } + }); + } + + @Override + public Image getImage(String id) { + // The id of the image can be an id or a slug. Use the corresponding method of the API depending on what is + // provided. If it can be parsed as a number, use the method to get by ID. Otherwise, get by slug. + Integer imageId = Ints.tryParse(id); + return imageId != null ? api.imageApi().get(imageId) : api.imageApi().get(id); + } + + @Override + public Droplet getNode(String id) { + return api.dropletApi().get(Integer.parseInt(id)); + } + + @Override + public void destroyNode(String id) { + // We have to wait here, as the api does not properly populate the state + // but fails if there is a pending event + int dropletId = Integer.parseInt(id); + api.dropletApi().delete(dropletId); + checkState(nodeTerminatedPredicate.apply(dropletId), "node was not destroyed in the configured timeout"); + } + + @Override + public void rebootNode(String id) { + // We have to wait here, as the api does not properly populate the state + // but fails if there is a pending event + Action action = api.dropletApi().reboot(Integer.parseInt(id)); + checkState(nodeRunningPredicate.apply(action.id()), "node did not restart in the configured timeout"); + } + + @Override + public void resumeNode(String id) { + // We have to wait here, as the api does not properly populate the state + // but fails if there is a pending event + Action action = api.dropletApi().powerOn(Integer.parseInt(id)); + checkState(nodeRunningPredicate.apply(action.id()), "node did not started in the configured timeout"); + } + + @Override + public void suspendNode(String id) { + int dropletId = Integer.parseInt(id); + // We have to wait here, as the api does not properly populate the state + // but fails if there is a pending event + Action action = api.dropletApi().powerOff(dropletId); + checkState(nodeStoppedPredicate.apply(action.id()), "node did not stop in the configured timeout"); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java new file mode 100644 index 0000000..7809f9d --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java @@ -0,0 +1,205 @@ +/* + * 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.config; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; +import static org.jclouds.util.Predicates2.retry; + +import javax.inject.Singleton; + +import org.jclouds.compute.ComputeServiceAdapter; +import org.jclouds.compute.config.ComputeServiceAdapterContextModule; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadata.Status; +import org.jclouds.compute.extensions.ImageExtension; +import org.jclouds.compute.functions.TemplateOptionsToStatement; +import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.compute.reference.ComputeServiceConstants.PollPeriod; +import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts; +import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet; +import org.jclouds.digitalocean2.DigitalOcean2Api; +import org.jclouds.digitalocean2.compute.DigitalOcean2ComputeServiceAdapter; +import org.jclouds.digitalocean2.compute.extensions.DigitalOcean2ImageExtension; +import org.jclouds.digitalocean2.compute.functions.DropletStatusToStatus; +import org.jclouds.digitalocean2.compute.functions.DropletToNodeMetadata; +import org.jclouds.digitalocean2.compute.functions.ImageToImage; +import org.jclouds.digitalocean2.compute.functions.RegionToLocation; +import org.jclouds.digitalocean2.compute.functions.SizeToHardware; +import org.jclouds.digitalocean2.compute.functions.TemplateOptionsToStatementWithoutPublicKey; +import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions; +import org.jclouds.digitalocean2.compute.strategy.CreateKeyPairsThenCreateNodes; +import org.jclouds.digitalocean2.domain.Action; +import org.jclouds.digitalocean2.domain.Droplet; +import org.jclouds.digitalocean2.domain.Image; +import org.jclouds.digitalocean2.domain.Region; +import org.jclouds.digitalocean2.domain.Size; +import org.jclouds.domain.Location; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.inject.Injector; +import com.google.inject.Provides; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Named; + +/** + * Configures the compute service classes for the DigitalOcean API. + */ +public class DigitalOcean2ComputeServiceContextModule extends + ComputeServiceAdapterContextModule<Droplet, Size, Image, Region> { + + @Override + protected void configure() { + super.configure(); + + bind(new TypeLiteral<ComputeServiceAdapter<Droplet, Size, Image, Region>>() { + }).to(DigitalOcean2ComputeServiceAdapter.class); + + bind(new TypeLiteral<Function<Droplet, NodeMetadata>>() { + }).to(DropletToNodeMetadata.class); + bind(new TypeLiteral<Function<Image, org.jclouds.compute.domain.Image>>() { + }).to(ImageToImage.class); + bind(new TypeLiteral<Function<Region, Location>>() { + }).to(RegionToLocation.class); + bind(new TypeLiteral<Function<Size, Hardware>>() { + }).to(SizeToHardware.class); + bind(new TypeLiteral<Function<Droplet.Status, Status>>() { + }).to(DropletStatusToStatus.class); + + install(new LocationsFromComputeServiceAdapterModule<Droplet, Size, Image, Region>() { + }); + + bind(CreateNodesInGroupThenAddToSet.class).to(CreateKeyPairsThenCreateNodes.class); + bind(TemplateOptions.class).to(DigitalOcean2TemplateOptions.class); + bind(TemplateOptionsToStatement.class).to(TemplateOptionsToStatementWithoutPublicKey.class); + + bind(new TypeLiteral<ImageExtension>() { + }).to(DigitalOcean2ImageExtension.class); + } + + @Override + protected Optional<ImageExtension> provideImageExtension(Injector i) { + return Optional.of(i.getInstance(ImageExtension.class)); + } + + @Provides + @Named(TIMEOUT_NODE_RUNNING) + protected Predicate<Integer> provideDropletRunningPredicate(final DigitalOcean2Api api, Timeouts timeouts, + PollPeriod pollPeriod) { + return retry(new ActionDonePredicate(api), timeouts.nodeRunning, pollPeriod.pollInitialPeriod, + pollPeriod.pollMaxPeriod); + } + + @Provides + @Named(TIMEOUT_NODE_SUSPENDED) + protected Predicate<Integer> provideDropletSuspendedPredicate(final DigitalOcean2Api api, Timeouts timeouts, + PollPeriod pollPeriod) { + return retry(new ActionDonePredicate(api), timeouts.nodeSuspended, pollPeriod.pollInitialPeriod, + pollPeriod.pollMaxPeriod); + } + + @Provides + @Named(TIMEOUT_NODE_TERMINATED) + protected Predicate<Integer> provideDropletTerminatedPredicate(final DigitalOcean2Api api, Timeouts timeouts, + PollPeriod pollPeriod) { + return retry(new DropletTerminatedPredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod, + pollPeriod.pollMaxPeriod); + } + + @Provides + @Named(TIMEOUT_IMAGE_AVAILABLE) + protected Predicate<Integer> provideImageAvailablePredicate(final DigitalOcean2Api api, Timeouts timeouts, + PollPeriod pollPeriod) { + return retry(new ActionDonePredicate(api), timeouts.imageAvailable, pollPeriod.pollInitialPeriod, + pollPeriod.pollMaxPeriod); + } + + @Provides + @Singleton + protected Predicate<Region> provideRegionAvailablePredicate(final DigitalOcean2Api api, Timeouts timeouts, + PollPeriod pollPeriod) { + return retry(new RegionAvailablePredicate(), timeouts.imageAvailable, pollPeriod.pollInitialPeriod, + pollPeriod.pollMaxPeriod); + } + + @Provides + protected Predicate<Integer> provideActionCompletedPredicate(final DigitalOcean2Api api, Timeouts timeouts, + PollPeriod pollPeriod) { + return retry(new ActionDonePredicate(api), timeouts.imageAvailable, pollPeriod.pollInitialPeriod, + pollPeriod.pollMaxPeriod); + } + + @VisibleForTesting + static class ActionDonePredicate implements Predicate<Integer> { + + private final DigitalOcean2Api api; + + public ActionDonePredicate(DigitalOcean2Api api) { + this.api = checkNotNull(api, "api must not be null"); + } + + @Override + public boolean apply(Integer input) { + checkNotNull(input, "action id cannot be null"); + Action current = api.actionApi().get(input); + switch (current.status()) { + case COMPLETED: + return true; + case IN_PROGRESS: + return false; + case ERRORED: + default: + throw new IllegalStateException("Resource is in invalid status: " + current.status().name()); + } + } + + } + + @VisibleForTesting + static class DropletTerminatedPredicate implements Predicate<Integer> { + + private final DigitalOcean2Api api; + + public DropletTerminatedPredicate(DigitalOcean2Api api) { + this.api = checkNotNull(api, "api must not be null"); + } + + @Override + public boolean apply(Integer input) { + checkNotNull(input, "droplet"); + Droplet droplet = api.dropletApi().get(input); + return droplet == null; + } + } + + @VisibleForTesting + static class RegionAvailablePredicate implements Predicate<Region> { + @Override + public boolean apply(Region input) { + return input.available(); + } + + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java new file mode 100644 index 0000000..41e3270 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java @@ -0,0 +1,132 @@ +/* + * 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.extensions; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; + +import java.util.NoSuchElementException; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.jclouds.compute.domain.CloneImageTemplate; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.ImageTemplate; +import org.jclouds.compute.domain.ImageTemplateBuilder; +import org.jclouds.compute.extensions.ImageExtension; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.digitalocean2.DigitalOcean2Api; +import org.jclouds.digitalocean2.domain.Action; +import org.jclouds.digitalocean2.domain.Droplet; +import org.jclouds.digitalocean2.domain.Droplet.Status; +import org.jclouds.logging.Logger; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.primitives.Ints; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * The {@link org.jclouds.compute.extensions.ImageExtension} implementation for the DigitalOcean provider. + */ +@Singleton +public class DigitalOcean2ImageExtension implements ImageExtension { + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + private final DigitalOcean2Api api; + private final Predicate<Integer> imageAvailablePredicate; + private final Predicate<Integer> nodeStoppedPredicate; + private final Function<org.jclouds.digitalocean2.domain.Image, Image> imageTransformer; + + @Inject DigitalOcean2ImageExtension(DigitalOcean2Api api, + @Named(TIMEOUT_IMAGE_AVAILABLE) Predicate<Integer> imageAvailablePredicate, + @Named(TIMEOUT_NODE_SUSPENDED) Predicate<Integer> nodeStoppedPredicate, + Function<org.jclouds.digitalocean2.domain.Image, Image> imageTransformer) { + this.api = api; + this.imageAvailablePredicate = imageAvailablePredicate; + this.nodeStoppedPredicate = nodeStoppedPredicate; + this.imageTransformer = imageTransformer; + } + + @Override + public ImageTemplate buildImageTemplateFromNode(String name, String id) { + Droplet droplet = api.dropletApi().get(Integer.parseInt(id)); + + if (droplet == null) { + throw new NoSuchElementException("Cannot find droplet with id: " + id); + } + + return new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name).build(); + } + + @Override + public ListenableFuture<Image> createImage(ImageTemplate template) { + checkState(template instanceof CloneImageTemplate, "DigitalOcean only supports creating images through cloning."); + final CloneImageTemplate cloneTemplate = (CloneImageTemplate) template; + + // Droplet needs to be stopped + int dropletId = Integer.parseInt(cloneTemplate.getSourceNodeId()); + Action powerOffEvent = api.dropletApi().powerOff(dropletId); + checkState(nodeStoppedPredicate.apply(powerOffEvent.id()), "node was not powered off in the configured timeout"); + + Droplet droplet = api.dropletApi().get(dropletId); + checkState(droplet.status() == Status.OFF, "node was not powered off in the configured timeout"); + + Action snapshotEvent = api.dropletApi().snapshot(Integer.parseInt(cloneTemplate.getSourceNodeId()), + cloneTemplate.getName()); + + logger.info(">> registered new Image, waiting for it to become available"); + + // Until the process completes we don't have enough information to build an image to return + checkState(imageAvailablePredicate.apply(snapshotEvent.id()), + "snapshot failed to complete in the configured timeout"); + + org.jclouds.digitalocean2.domain.Image snapshot = api.imageApi().list().concat().firstMatch( + new Predicate<org.jclouds.digitalocean2.domain.Image>() { + @Override + public boolean apply(org.jclouds.digitalocean2.domain.Image input) { + return input.name().equals(cloneTemplate.getName()); + } + }).get(); + + return immediateFuture(imageTransformer.apply(snapshot)); + } + + @Override + public boolean deleteImage(String id) { + try { + // The id of the image can be an id or a slug. Use the corresponding method of the API depending on what is + // provided. If it can be parsed as a number, use the method to destroy by ID. Otherwise, destroy by slug. + Integer imageId = Ints.tryParse(id); + if (imageId != null) { + logger.debug(">> image does not have a slug. Using the id to delete the image..."); + api.imageApi().delete(imageId); + } + return true; + } catch (Exception ex) { + return false; + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatus.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatus.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatus.java new file mode 100644 index 0000000..6edadb9 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatus.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.compute.functions; + +import javax.inject.Singleton; + +import org.jclouds.compute.domain.NodeMetadata.Status; +import org.jclouds.digitalocean2.domain.Droplet; +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableMap; + +/** + * Transforms an {@link org.jclouds.compute.domain.NodeMetadata.Status} to the jclouds portable model. + */ +@Singleton +public class DropletStatusToStatus implements Function<Droplet.Status, Status> { + + private static final Function<Droplet.Status, Status> toPortableStatus = Functions.forMap( + ImmutableMap.<Droplet.Status, Status> builder() + .put(Droplet.Status.NEW, Status.PENDING) + .put(Droplet.Status.ACTIVE, Status.RUNNING) + .put(Droplet.Status.ARCHIVE, Status.TERMINATED) + .put(Droplet.Status.OFF, Status.SUSPENDED) + .build(), + Status.UNRECOGNIZED); + + @Override + public Status apply(final Droplet.Status input) { + return toPortableStatus.apply(input); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java new file mode 100644 index 0000000..eebc121 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java @@ -0,0 +1,176 @@ +/* + * 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.functions; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.find; +import static com.google.common.collect.Iterables.tryFind; + +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.collect.Memoized; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadata.Status; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.jclouds.compute.functions.GroupNamingConvention; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.digitalocean2.domain.Droplet; +import org.jclouds.digitalocean2.domain.Networks; +import org.jclouds.digitalocean2.domain.Region; +import org.jclouds.domain.Credentials; +import org.jclouds.domain.Location; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.logging.Logger; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Iterables; + +/** + * Transforms an {@link Droplet} to the jclouds portable model. + */ +@Singleton +public class DropletToNodeMetadata implements Function<Droplet, NodeMetadata> { + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + private final Supplier<Map<String, ? extends Image>> images; + private final Supplier<Map<String, ? extends Hardware>> hardwares; + private final Supplier<Set<? extends Location>> locations; + private final Function<Droplet.Status, Status> toPortableStatus; + private final GroupNamingConvention groupNamingConvention; + private final Map<String, Credentials> credentialStore; + + @Inject + DropletToNodeMetadata(Supplier<Map<String, ? extends Image>> images, + Supplier<Map<String, ? extends Hardware>> hardwares, @Memoized Supplier<Set<? extends Location>> locations, + Function<Droplet.Status, Status> toPortableStatus, GroupNamingConvention.Factory groupNamingConvention, + Map<String, Credentials> credentialStore) { + this.images = checkNotNull(images, "images cannot be null"); + this.hardwares = checkNotNull(hardwares, "hardwares cannot be null"); + this.locations = checkNotNull(locations, "locations cannot be null"); + this.toPortableStatus = checkNotNull(toPortableStatus, "toPortableStatus cannot be null"); + this.groupNamingConvention = checkNotNull(groupNamingConvention, "groupNamingConvention cannot be null") + .createWithoutPrefix(); + this.credentialStore = checkNotNull(credentialStore, "credentialStore cannot be null"); + } + + @Override + public NodeMetadata apply(Droplet input) { + NodeMetadataBuilder builder = new NodeMetadataBuilder(); + builder.ids(String.valueOf(input.id())); + builder.name(input.name()); + builder.hostname(input.name()); + builder.group(groupNamingConvention.extractGroup(input.name())); + + builder.hardware(getHardware(input.sizeSlug())); + builder.location(getLocation(input.region())); + + Optional<? extends Image> image = findImage(input.image().id()); + if (image.isPresent()) { + builder.imageId(image.get().getId()); + builder.operatingSystem(image.get().getOperatingSystem()); + } else { + logger.info(">> image with id %s for droplet %s was not found. " + + "This might be because the image that was used to create the droplet has a new id.", + input.image().id(), input.id()); + } + + builder.status(toPortableStatus.apply(input.status())); + builder.backendStatus(input.status().name()); + + if (!input.getPublicAddresses().isEmpty()) { + builder.publicAddresses(FluentIterable + .from(input.getPublicAddresses()) + .transform(new Function<Networks.Address, String>() { + @Override + public String apply(final Networks.Address input) { + return input.ip(); + } + }) + ); + } + + if (!input.getPrivateAddresses().isEmpty()) { + builder.privateAddresses(FluentIterable + .from(input.getPrivateAddresses()) + .transform(new Function<Networks.Address, String>() { + @Override + public String apply(final Networks.Address input) { + return input.ip(); + } + }) + ); + } + + // DigitalOcean does not provide a way to get the credentials. + // Try to return them from the credential store + Credentials credentials = credentialStore.get("node#" + input.id()); + if (credentials instanceof LoginCredentials) { + builder.credentials(LoginCredentials.class.cast(credentials)); + } + + return builder.build(); + } + + protected Optional<? extends Image> findImage(Integer id) { + // Try to find the image by ID in the cache. The cache is indexed by slug (for public images) and by id (for + // private ones). + final String imageId = String.valueOf(id); + Optional<? extends Image> image = Optional.fromNullable(images.get().get(imageId)); + if (!image.isPresent()) { + // If it is a public image (indexed by slug) but the "int" form of the id was provided, try to find it in the + // whole list of cached images + image = tryFind(images.get().values(), new Predicate<Image>() { + @Override + public boolean apply(Image input) { + return input.getProviderId().equals(imageId); + } + }); + } + return image; + } + + protected Hardware getHardware(final String slug) { + return Iterables.find(hardwares.get().values(), new Predicate<Hardware>() { + @Override + public boolean apply(Hardware input) { + return input.getId().equals(slug); + } + }); + } + + protected Location getLocation(final Region region) { + return find(locations.get(), new Predicate<Location>() { + @Override + public boolean apply(Location location) { + return region != null && region.slug().equals(location.getId()); + } + }, null); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java new file mode 100644 index 0000000..8f9ad92 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.digitalocean2.compute.functions; + +import static org.jclouds.compute.domain.OperatingSystem.builder; + +import javax.inject.Singleton; + +import org.jclouds.compute.domain.Image.Status; +import org.jclouds.compute.domain.ImageBuilder; +import org.jclouds.digitalocean2.domain.Image; +import org.jclouds.digitalocean2.domain.OperatingSystem; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; + +/** + * Transforms an {@link Image} to the jclouds portable model. + */ +@Singleton +public class ImageToImage implements Function<Image, org.jclouds.compute.domain.Image> { + + @Override + public org.jclouds.compute.domain.Image apply(final Image input) { + String description = input.distribution() + " " + input.name(); + ImageBuilder builder = new ImageBuilder(); + // Private images don't have a slug + builder.id(input.slug() != null ? input.slug() : String.valueOf(input.id())); + builder.providerId(String.valueOf(input.id())); + builder.name(input.name()); + builder.description(description); + builder.status(Status.AVAILABLE); + + OperatingSystem os = OperatingSystem.create(input.name(), input.distribution()); + + builder.operatingSystem(builder() + .name(os.distribution().value()) + .family(os.distribution().osFamily()) + .description(description) + .arch(os.arch()) + .version(os.version()) + .is64Bit(os.is64bit()) + .build()); + + ImmutableMap.Builder<String, String> metadata = ImmutableMap.builder(); + metadata.put("publicImage", String.valueOf(input.isPublic())); + builder.userMetadata(metadata.build()); + + return builder.build(); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java new file mode 100644 index 0000000..4adf240 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.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.compute.functions; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.digitalocean2.domain.Region; +import org.jclouds.domain.Location; +import org.jclouds.domain.LocationBuilder; +import org.jclouds.domain.LocationScope; +import org.jclouds.location.suppliers.all.JustProvider; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableSet; + +/** + * Transforms an {@link Region} to the jclouds portable model. + */ +@Singleton +public class RegionToLocation implements Function<Region, Location> { + + private final JustProvider justProvider; + + @Inject + RegionToLocation(JustProvider justProvider) { + this.justProvider = checkNotNull(justProvider, "justProvider cannot be null"); + } + + @Override + public Location apply(Region input) { + LocationBuilder builder = new LocationBuilder(); + builder.id(input.slug()); + builder.description(input.name()); + builder.scope(LocationScope.REGION); + builder.parent(getOnlyElement(justProvider.get())); + builder.iso3166Codes(ImmutableSet.<String> of()); + return builder.build(); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/SizeToHardware.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/SizeToHardware.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/SizeToHardware.java new file mode 100644 index 0000000..5645d3b --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/SizeToHardware.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.digitalocean2.compute.functions; + +import javax.inject.Singleton; + +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.HardwareBuilder; +import org.jclouds.compute.domain.Processor; +import org.jclouds.compute.domain.Volume.Type; +import org.jclouds.compute.domain.VolumeBuilder; +import org.jclouds.digitalocean2.domain.Size; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; + +/** + * Transforms an {@link Size} to the jclouds portable model. + */ +@Singleton +public class SizeToHardware implements Function<Size, Hardware> { + + @Override + public Hardware apply(Size input) { + HardwareBuilder builder = new HardwareBuilder(); + builder.id(input.slug()); + builder.providerId(input.slug()); + builder.name(input.slug()); + builder.ram(input.memory()); + // No cpu speed from DigitalOcean API, so assume more cores == faster + builder.processor(new Processor(input.vcpus(), input.vcpus())); + + builder.volume(new VolumeBuilder() + .size(Float.valueOf(input.disk())) + .type(Type.LOCAL) + .build()); + + ImmutableMap.Builder<String, String> metadata = ImmutableMap.builder(); + metadata.put("costPerHour", String.valueOf(input.priceHourly())); + metadata.put("costPerMonth", String.valueOf(input.priceMonthly())); + builder.userMetadata(metadata.build()); + + return builder.build(); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java new file mode 100644 index 0000000..52dcb0e --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.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.digitalocean2.compute.functions; + +import javax.inject.Singleton; + +import org.jclouds.compute.functions.TemplateOptionsToStatement; +import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.scriptbuilder.InitScript; +import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.scriptbuilder.domain.StatementList; +import org.jclouds.scriptbuilder.statements.ssh.InstallRSAPrivateKey; +import com.google.common.collect.ImmutableList; + +/** + * Convert the template options into a statement, but ignoring the public key. + * <p> + * The {@link org.jclouds.DigitalOcean2ComputeServiceAdapter.compute.strategy.DigitalOceanComputeServiceAdapter} already takes care of + * installing it using the {@link org.jclouds.digitalocean.features.KeyPairApi}. + */ +@Singleton +public class TemplateOptionsToStatementWithoutPublicKey extends TemplateOptionsToStatement { + + @Override + public Statement apply(TemplateOptions options) { + ImmutableList.Builder<Statement> builder = ImmutableList.builder(); + if (options.getRunScript() != null) { + builder.add(options.getRunScript()); + } + if (options.getPrivateKey() != null) { + builder.add(new InstallRSAPrivateKey(options.getPrivateKey())); + } + + ImmutableList<Statement> bootstrap = builder.build(); + if (!bootstrap.isEmpty()) { + if (options.getTaskName() == null && !(options.getRunScript() instanceof InitScript)) { + options.nameTask("bootstrap"); + } + return bootstrap.size() == 1 ? bootstrap.get(0) : new StatementList(bootstrap); + } + + return null; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptions.java ---------------------------------------------------------------------- diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptions.java new file mode 100644 index 0000000..cafcdb1 --- /dev/null +++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptions.java @@ -0,0 +1,174 @@ +/* + * 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.options; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Set; + +import org.jclouds.compute.options.TemplateOptions; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; + +/** + * Custom options for the DigitalOcean API. + */ +public class DigitalOcean2TemplateOptions extends TemplateOptions implements Cloneable { + + private Set<Integer> sshKeyIds = ImmutableSet.of(); + private boolean privateNetworking = false; + private boolean backupsEnabled = false; + private boolean autoCreateKeyPair = true; + + /** + * Enables a private network interface if the region supports private networking. + */ + public DigitalOcean2TemplateOptions privateNetworking(boolean privateNetworking) { + this.privateNetworking = privateNetworking; + return this; + } + + /** + * Enabled backups for the droplet. + */ + public DigitalOcean2TemplateOptions backupsEnabled(boolean backupsEnabled) { + this.backupsEnabled = backupsEnabled; + return this; + } + + /** + * Sets the ssh key ids to be added to the droplet. + */ + public DigitalOcean2TemplateOptions sshKeyIds(Iterable<Integer> sshKeyIds) { + this.sshKeyIds = ImmutableSet.copyOf(checkNotNull(sshKeyIds, "sshKeyIds cannot be null")); + return this; + } + + /** + * Sets whether an SSH key pair should be created automatically. + */ + public DigitalOcean2TemplateOptions autoCreateKeyPair(boolean autoCreateKeyPair) { + this.autoCreateKeyPair = autoCreateKeyPair; + return this; + } + + public Set<Integer> getSshKeyIds() { + return sshKeyIds; + } + + public boolean getPrivateNetworking() { + return privateNetworking; + } + + public boolean getBackupsEnabled() { + return backupsEnabled; + } + + public boolean getAutoCreateKeyPair() { + return autoCreateKeyPair; + } + + @Override + public DigitalOcean2TemplateOptions clone() { + DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions(); + copyTo(options); + return options; + } + + @Override + public void copyTo(TemplateOptions to) { + super.copyTo(to); + if (to instanceof DigitalOcean2TemplateOptions) { + DigitalOcean2TemplateOptions eTo = DigitalOcean2TemplateOptions.class.cast(to); + eTo.privateNetworking(privateNetworking); + eTo.backupsEnabled(backupsEnabled); + eTo.autoCreateKeyPair(autoCreateKeyPair); + eTo.sshKeyIds(sshKeyIds); + } + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), backupsEnabled, privateNetworking, autoCreateKeyPair, sshKeyIds); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DigitalOcean2TemplateOptions other = (DigitalOcean2TemplateOptions) obj; + return super.equals(other) && equal(this.backupsEnabled, other.backupsEnabled) + && equal(this.privateNetworking, other.privateNetworking) + && equal(this.autoCreateKeyPair, other.autoCreateKeyPair) && equal(this.sshKeyIds, other.sshKeyIds); + } + + @Override + public ToStringHelper string() { + ToStringHelper toString = super.string().omitNullValues(); + toString.add("privateNetworking", privateNetworking); + toString.add("backupsEnabled", backupsEnabled); + if (!sshKeyIds.isEmpty()) { + toString.add("sshKeyIds", sshKeyIds); + } + toString.add("autoCreateKeyPair", autoCreateKeyPair); + return toString; + } + + public static class Builder { + + /** + * @see DigitalOcean2TemplateOptions#privateNetworking + */ + public static DigitalOcean2TemplateOptions privateNetworking(boolean privateNetworking) { + DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions(); + return options.privateNetworking(privateNetworking); + } + + /** + * @see DigitalOcean2TemplateOptions#backupsEnabled + */ + public static DigitalOcean2TemplateOptions backupsEnabled(boolean backupsEnabled) { + DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions(); + return options.backupsEnabled(backupsEnabled); + } + + /** + * @see DigitalOcean2TemplateOptions#sshKeyIds + */ + public static DigitalOcean2TemplateOptions sshKeyIds(Iterable<Integer> sshKeyIds) { + DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions(); + return options.sshKeyIds(sshKeyIds); + } + + /** + * @see DigitalOcean2TemplateOptions#autoCreateKeyPair + */ + public static DigitalOcean2TemplateOptions autoCreateKeyPair(boolean autoCreateKeyPair) { + DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions(); + return options.autoCreateKeyPair(autoCreateKeyPair); + } + } +}
