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 2015/10/30 11:30:56 UTC
[3/3] jclouds git commit: JCLOUDS-717: Join Enterprise and OpenSource
Chef
JCLOUDS-717: Join Enterprise and OpenSource Chef
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/9df30c5a
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/9df30c5a
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/9df30c5a
Branch: refs/heads/master
Commit: 9df30c5a091eb6e3487ca8578674b872ab77963b
Parents: c9306c1
Author: Ignasi Barrera <na...@apache.org>
Authored: Fri Oct 30 00:42:03 2015 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Fri Oct 30 01:02:53 2015 +0100
----------------------------------------------------------------------
apis/chef/src/main/clojure/org/jclouds/chef.clj | 4 +-
.../src/main/java/org/jclouds/chef/ChefApi.java | 11 +-
.../java/org/jclouds/chef/ChefApiMetadata.java | 8 +-
.../BindGroupToUpdateRequestJsonPayload.java | 79 +++
.../org/jclouds/chef/binders/GroupName.java | 38 ++
.../chef/config/BaseChefHttpApiModule.java | 204 -------
.../jclouds/chef/config/ChefHttpApiModule.java | 180 +++++-
.../java/org/jclouds/chef/domain/Group.java | 211 ++++++++
.../main/java/org/jclouds/chef/domain/User.java | 220 ++++++++
.../jclouds/chef/features/OrganizationApi.java | 130 +++++
.../jclouds/chef/filters/SignedHeaderAuth.java | 2 +-
.../chef/options/CreateClientOptions.java | 2 +-
.../org/jclouds/chef/BaseChefApiExpectTest.java | 1 +
.../org/jclouds/chef/ChefApiDelegationTest.java | 55 ++
.../org/jclouds/chef/ChefApiExpectTest.java | 3 -
.../java/org/jclouds/chef/ChefApiLiveTest.java | 522 +++++++++++++++++-
...BindGroupToUpdateRequestJsonPayloadTest.java | 82 +++
.../org/jclouds/chef/binders/GroupNameTest.java | 40 ++
.../features/OrganizationApiExpectTest.java | 207 +++++++
.../chef/features/OrganizationApiLiveTest.java | 120 ++++
.../chef/functions/ParseClientFromJsonTest.java | 2 -
.../chef/internal/BaseChefApiLiveTest.java | 542 -------------------
.../jclouds/chef/internal/BaseChefLiveTest.java | 16 +-
.../chef/internal/BaseStubbedOhaiLiveTest.java | 60 --
...CleanupStaleNodesAndClientsImplLiveTest.java | 3 +-
...PopulateAutomaticAttributesImplLiveTest.java | 3 +-
...eAndPopulateAutomaticAttributesImplTest.java | 6 +-
...DeleteAllApisAndNodesInListImplLiveTest.java | 3 +-
...okbookVersionsInEnvironmentImplLiveTest.java | 7 +-
.../internal/ListNodesImplLiveTest.java | 13 +-
.../ListNodesInEnvironmentImplLiveTest.java | 13 +-
...teAutomaticAttributesOnNodeImplLiveTest.java | 3 +-
apis/chef/src/test/resources/group-update.json | 1 +
apis/chef/src/test/resources/group.json | 14 +
apis/chef/src/test/resources/groups.json | 7 +
apis/chef/src/test/resources/user.json | 9 +
providers/enterprisechef/pom.xml | 114 ----
.../enterprisechef/EnterpriseChefApi.java | 128 -----
.../EnterpriseChefApiMetadata.java | 81 ---
.../EnterpriseChefProviderMetadata.java | 79 ---
.../BindGroupToUpdateRequestJsonPayload.java | 79 ---
.../enterprisechef/binders/GroupName.java | 38 --
.../config/EnterpriseChefHttpApiModule.java | 36 --
.../jclouds/enterprisechef/domain/Group.java | 211 --------
.../org/jclouds/enterprisechef/domain/User.java | 220 --------
.../EnterpriseChefApiExpectTest.java | 209 -------
.../EnterpriseChefApiLiveTest.java | 122 -----
.../EnterpriseChefProviderMetadataTest.java | 31 --
...BindGroupToUpdateRequestJsonPayloadTest.java | 82 ---
.../enterprisechef/binders/GroupNameTest.java | 40 --
.../src/test/resources/group-update.json | 1 -
.../src/test/resources/group.json | 14 -
.../src/test/resources/groups.json | 7 -
.../enterprisechef/src/test/resources/user.json | 9 -
providers/pom.xml | 1 -
55 files changed, 1952 insertions(+), 2371 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/clojure/org/jclouds/chef.clj
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/clojure/org/jclouds/chef.clj b/apis/chef/src/main/clojure/org/jclouds/chef.clj
index ad0f200..964cd00 100644
--- a/apis/chef/src/main/clojure/org/jclouds/chef.clj
+++ b/apis/chef/src/main/clojure/org/jclouds/chef.clj
@@ -19,7 +19,7 @@
#^{:author "Adrian Cole"
:doc "A clojure binding to the jclouds chef interface.
-Here's a quick example of how to manipulate a databag on the Opscode Platform,
+Here's a quick example of how to manipulate a databag on the Chef Platform,
which is basically Chef Server as a Service.
(use 'org.jclouds.chef)
@@ -28,7 +28,7 @@ which is basically Chef Server as a Service.
;; load the rsa key from ~/.chef/CLIENT_NAME.pem
(def credential (load-pem client))
-;; create a connection to the opscode platform
+;; create a connection to the chef platform
(def chef (chef-service \"chef\" client credential :chef.endpoint \"https://api.opscode.com/organizations/YOUR_ORG\"))
(with-chef-service [chef]
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java b/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java
index 1fe0f1b..836e1e9 100644
--- a/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java
+++ b/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java
@@ -55,10 +55,11 @@ import org.jclouds.chef.domain.Role;
import org.jclouds.chef.domain.Sandbox;
import org.jclouds.chef.domain.SearchResult;
import org.jclouds.chef.domain.UploadSandbox;
+import org.jclouds.chef.features.OrganizationApi;
import org.jclouds.chef.filters.SignedHeaderAuth;
+import org.jclouds.chef.functions.ParseCookbookDefinitionFromJson;
import org.jclouds.chef.functions.ParseCookbookDefinitionListFromJson;
import org.jclouds.chef.functions.ParseCookbookNamesFromJson;
-import org.jclouds.chef.functions.ParseCookbookDefinitionFromJson;
import org.jclouds.chef.functions.ParseCookbookVersionsFromJson;
import org.jclouds.chef.functions.ParseKeySetFromJson;
import org.jclouds.chef.functions.ParseSearchClientsFromJson;
@@ -71,6 +72,7 @@ import org.jclouds.chef.options.CreateClientOptions;
import org.jclouds.chef.options.SearchOptions;
import org.jclouds.io.Payload;
import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.Delegate;
import org.jclouds.rest.annotations.EndpointParam;
import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.Headers;
@@ -85,6 +87,7 @@ import org.jclouds.rest.annotations.SkipEncoding;
import org.jclouds.rest.annotations.WrapWith;
import org.jclouds.rest.binders.BindToJsonPayload;
+import com.google.common.base.Optional;
import com.google.inject.Provides;
/**
@@ -100,6 +103,12 @@ public interface ChefApi extends Closeable {
*/
@Provides
ChefService chefService();
+
+ /**
+ * Provides access to the organization, user and group management endpoints.
+ */
+ @Delegate
+ Optional<OrganizationApi> organizationApi();
// Clients
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/ChefApiMetadata.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/ChefApiMetadata.java b/apis/chef/src/main/java/org/jclouds/chef/ChefApiMetadata.java
index 41a46df..bbbdfe6 100644
--- a/apis/chef/src/main/java/org/jclouds/chef/ChefApiMetadata.java
+++ b/apis/chef/src/main/java/org/jclouds/chef/ChefApiMetadata.java
@@ -40,7 +40,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;
/**
- * Implementation of {@link ApiMetadata} for OpsCode's Chef api.
+ * Implementation of {@link ApiMetadata} for Chef.
*/
@AutoService(ApiMetadata.class)
public class ChefApiMetadata extends BaseHttpApiMetadata<ChefApi> {
@@ -48,7 +48,7 @@ public class ChefApiMetadata extends BaseHttpApiMetadata<ChefApi> {
/**
* The default Chef Server API version to use.
*/
- public static final String DEFAULT_API_VERSION = "0.10.8";
+ public static final String DEFAULT_API_VERSION = "12.0.2";
@Override
public Builder toBuilder() {
@@ -86,11 +86,11 @@ public class ChefApiMetadata extends BaseHttpApiMetadata<ChefApi> {
protected Builder() {
id("chef")
- .name("OpsCode Chef Api")
+ .name("Chef Api")
.identityName("User")
.credentialName("Certificate")
.version(DEFAULT_API_VERSION)
- .documentation(URI.create("http://wiki.opscode.com/display/chef/Server+API"))
+ .documentation(URI.create("https://docs.chef.io/api_chef_server.html"))
.defaultEndpoint("http://localhost:4000")
.defaultProperties(ChefApiMetadata.defaultProperties())
.defaultModules(
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/binders/BindGroupToUpdateRequestJsonPayload.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/binders/BindGroupToUpdateRequestJsonPayload.java b/apis/chef/src/main/java/org/jclouds/chef/binders/BindGroupToUpdateRequestJsonPayload.java
new file mode 100644
index 0000000..469423d
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/binders/BindGroupToUpdateRequestJsonPayload.java
@@ -0,0 +1,79 @@
+/*
+ * 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.chef.binders;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.Group;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.json.Json;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+/**
+ * Binds a group to the payload expected for the Put method in the Enterprise Chef
+ * Api.
+ */
+@Singleton
+public class BindGroupToUpdateRequestJsonPayload extends BindToJsonPayload {
+
+ @Inject
+ public BindGroupToUpdateRequestJsonPayload(Json jsonBinder) {
+ super(jsonBinder);
+ }
+
+ @Override
+ public <R extends HttpRequest> R bindToRequest(R request, Object payload) {
+ checkArgument(checkNotNull(payload, "payload") instanceof Group, "this binder is only valid for Group objects");
+ GroupUpdateRequest updateGroup = new GroupUpdateRequest((Group) payload);
+ return super.bindToRequest(request, updateGroup);
+ }
+
+ @SuppressWarnings("unused")
+ private static class GroupUpdateRequest {
+ private String name;
+ private String groupname;
+ private String orgname;
+ private ActorConfiguration actors;
+
+ public GroupUpdateRequest(Group group) {
+ this.name = group.getName();
+ this.groupname = group.getGroupname();
+ this.orgname = group.getOrgname();
+ this.actors = new ActorConfiguration(group);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class ActorConfiguration {
+ private Set<String> clients;
+ private Set<String> groups;
+ private Set<String> users;
+
+ public ActorConfiguration(Group group) {
+ this.clients = group.getClients();
+ this.groups = group.getGroups();
+ this.users = group.getUsers();
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/binders/GroupName.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/binders/GroupName.java b/apis/chef/src/main/java/org/jclouds/chef/binders/GroupName.java
new file mode 100644
index 0000000..18f1b5b
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/binders/GroupName.java
@@ -0,0 +1,38 @@
+/*
+ * 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.chef.binders;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.Group;
+
+import com.google.common.base.Function;
+
+/**
+ * Gets the name of a group.
+ */
+@Singleton
+public class GroupName implements Function<Object, String> {
+
+ @Override
+ public String apply(Object from) {
+ return ((Group) checkNotNull(from, "from")).getGroupname();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/config/BaseChefHttpApiModule.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/BaseChefHttpApiModule.java b/apis/chef/src/main/java/org/jclouds/chef/config/BaseChefHttpApiModule.java
deleted file mode 100644
index c60be06..0000000
--- a/apis/chef/src/main/java/org/jclouds/chef/config/BaseChefHttpApiModule.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * 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.chef.config;
-
-import static com.google.common.base.Suppliers.compose;
-import static com.google.common.base.Suppliers.memoizeWithExpiration;
-import static com.google.common.base.Throwables.propagate;
-import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
-import static org.jclouds.chef.config.ChefProperties.CHEF_VALIDATOR_CREDENTIAL;
-import static org.jclouds.chef.config.ChefProperties.CHEF_VALIDATOR_NAME;
-import static org.jclouds.crypto.Pems.privateKeySpec;
-
-import java.io.IOException;
-import java.security.PrivateKey;
-import java.security.spec.InvalidKeySpecException;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import org.jclouds.chef.domain.BootstrapConfig;
-import org.jclouds.chef.domain.Client;
-import org.jclouds.chef.functions.BootstrapConfigForGroup;
-import org.jclouds.chef.functions.ClientForGroup;
-import org.jclouds.chef.handlers.ChefApiErrorRetryHandler;
-import org.jclouds.chef.handlers.ChefErrorHandler;
-import org.jclouds.crypto.Crypto;
-import org.jclouds.crypto.Pems;
-import org.jclouds.date.DateService;
-import org.jclouds.date.TimeStamp;
-import org.jclouds.domain.Credentials;
-import org.jclouds.http.HttpErrorHandler;
-import org.jclouds.http.HttpRetryHandler;
-import org.jclouds.http.annotation.ClientError;
-import org.jclouds.http.annotation.Redirection;
-import org.jclouds.http.annotation.ServerError;
-import org.jclouds.rest.ConfiguresHttpApi;
-import org.jclouds.rest.config.HttpApiModule;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Charsets;
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Supplier;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.common.io.ByteSource;
-import com.google.inject.ConfigurationException;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.Provides;
-import com.google.inject.name.Names;
-
-/**
- * Configures the Chef connection.
- */
-@ConfiguresHttpApi
-public abstract class BaseChefHttpApiModule<S> extends HttpApiModule<S> {
-
- @Provides
- @TimeStamp
- protected final String guiceProvideTimeStamp(@TimeStamp Supplier<String> cache) {
- return provideTimeStamp(cache);
- }
-
- protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
- return cache.get();
- }
-
- /**
- * borrowing concurrency code to ensure that caching takes place properly
- */
- @Provides
- @TimeStamp
- final Supplier<String> provideTimeStampCache(@Named(PROPERTY_SESSION_INTERVAL) long seconds, final DateService dateService) {
- return memoizeWithExpiration(new Supplier<String>() {
- @Override
- public String get() {
- return dateService.iso8601SecondsDateFormat();
- }
- }, seconds, TimeUnit.SECONDS);
- }
-
- // TODO: potentially change this
- @Provides
- @Singleton
- public final Supplier<PrivateKey> supplyKey(final LoadingCache<Credentials, PrivateKey> keyCache,
- @org.jclouds.location.Provider final Supplier<Credentials> creds) {
- return compose(new Function<Credentials, PrivateKey>() {
- @Override
- public PrivateKey apply(Credentials in) {
- return keyCache.getUnchecked(in);
- }
- }, creds);
- }
-
- @Provides
- @Singleton
- final LoadingCache<Credentials, PrivateKey> privateKeyCache(PrivateKeyForCredentials loader) {
- // throw out the private key related to old credentials
- return CacheBuilder.newBuilder().maximumSize(2).build(loader);
- }
-
- /**
- * it is relatively expensive to extract a private key from a PEM. cache the
- * relationship between current credentials so that the private key is only
- * recalculated once.
- */
- @VisibleForTesting
- @Singleton
- private static class PrivateKeyForCredentials extends CacheLoader<Credentials, PrivateKey> {
- private final Crypto crypto;
-
- @Inject
- private PrivateKeyForCredentials(Crypto crypto) {
- this.crypto = crypto;
- }
-
- @Override
- public PrivateKey load(Credentials in) {
- try {
- return crypto.rsaKeyFactory().generatePrivate(
- privateKeySpec(ByteSource.wrap(in.credential.getBytes(Charsets.UTF_8))));
- } catch (InvalidKeySpecException e) {
- throw propagate(e);
- } catch (IOException e) {
- throw propagate(e);
- }
- }
- }
-
- @Provides
- @Singleton
- @Validator
- public final Optional<String> provideValidatorName(Injector injector) {
- // Named properties can not be injected as optional here, so let's use the
- // injector to bypass it
- Key<String> key = Key.get(String.class, Names.named(CHEF_VALIDATOR_NAME));
- try {
- return Optional.<String> of(injector.getInstance(key));
- } catch (ConfigurationException ex) {
- return Optional.<String> absent();
- }
- }
-
- @Provides
- @Singleton
- @Validator
- public final Optional<PrivateKey> provideValidatorCredential(Crypto crypto, Injector injector)
- throws InvalidKeySpecException, IOException {
- // Named properties can not be injected as optional here, so let's use the
- // injector to bypass it
- Key<String> key = Key.get(String.class, Names.named(CHEF_VALIDATOR_CREDENTIAL));
- try {
- String validatorCredential = injector.getInstance(key);
- PrivateKey validatorKey = crypto.rsaKeyFactory().generatePrivate(
- Pems.privateKeySpec(ByteSource.wrap(validatorCredential.getBytes(Charsets.UTF_8))));
- return Optional.<PrivateKey> of(validatorKey);
- } catch (ConfigurationException ex) {
- return Optional.<PrivateKey> absent();
- }
- }
-
- @Provides
- @Singleton
- final CacheLoader<String, BootstrapConfig> bootstrapConfigForGroup(BootstrapConfigForGroup bootstrapConfigForGroup) {
- return CacheLoader.from(bootstrapConfigForGroup);
- }
-
- @Provides
- @Singleton
- final CacheLoader<String, Client> groupToClient(ClientForGroup clientForGroup) {
- return CacheLoader.from(clientForGroup);
- }
-
- @Override
- protected void bindErrorHandlers() {
- bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ChefErrorHandler.class);
- bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(ChefErrorHandler.class);
- bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ChefErrorHandler.class);
- }
-
- @Override
- protected void bindRetryHandlers() {
- bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(ChefApiErrorRetryHandler.class);
- }
-
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/config/ChefHttpApiModule.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/ChefHttpApiModule.java b/apis/chef/src/main/java/org/jclouds/chef/config/ChefHttpApiModule.java
index ffd3b0b..f00bdd4 100644
--- a/apis/chef/src/main/java/org/jclouds/chef/config/ChefHttpApiModule.java
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/ChefHttpApiModule.java
@@ -16,13 +16,191 @@
*/
package org.jclouds.chef.config;
+import static com.google.common.base.Suppliers.compose;
+import static com.google.common.base.Suppliers.memoizeWithExpiration;
+import static com.google.common.base.Throwables.propagate;
+import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
+import static org.jclouds.chef.config.ChefProperties.CHEF_VALIDATOR_CREDENTIAL;
+import static org.jclouds.chef.config.ChefProperties.CHEF_VALIDATOR_NAME;
+import static org.jclouds.crypto.Pems.privateKeySpec;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.BootstrapConfig;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.functions.BootstrapConfigForGroup;
+import org.jclouds.chef.functions.ClientForGroup;
+import org.jclouds.chef.handlers.ChefApiErrorRetryHandler;
+import org.jclouds.chef.handlers.ChefErrorHandler;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.crypto.Pems;
+import org.jclouds.date.DateService;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpRetryHandler;
+import org.jclouds.http.annotation.ClientError;
+import org.jclouds.http.annotation.Redirection;
+import org.jclouds.http.annotation.ServerError;
import org.jclouds.rest.ConfiguresHttpApi;
+import org.jclouds.rest.config.HttpApiModule;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.io.ByteSource;
+import com.google.inject.ConfigurationException;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Provides;
+import com.google.inject.name.Names;
/**
* Configures the Chef connection.
*/
@ConfiguresHttpApi
-public class ChefHttpApiModule extends BaseChefHttpApiModule<ChefApi> {
+public class ChefHttpApiModule extends HttpApiModule<ChefApi> {
+
+ @Provides
+ @TimeStamp
+ protected final String guiceProvideTimeStamp(@TimeStamp Supplier<String> cache) {
+ return provideTimeStamp(cache);
+ }
+
+ protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
+ return cache.get();
+ }
+
+ /**
+ * borrowing concurrency code to ensure that caching takes place properly
+ */
+ @Provides
+ @TimeStamp
+ final Supplier<String> provideTimeStampCache(@Named(PROPERTY_SESSION_INTERVAL) long seconds,
+ final DateService dateService) {
+ return memoizeWithExpiration(new Supplier<String>() {
+ @Override
+ public String get() {
+ return dateService.iso8601SecondsDateFormat();
+ }
+ }, seconds, TimeUnit.SECONDS);
+ }
+
+ // TODO: potentially change this
+ @Provides
+ @Singleton
+ public final Supplier<PrivateKey> supplyKey(final LoadingCache<Credentials, PrivateKey> keyCache,
+ @org.jclouds.location.Provider final Supplier<Credentials> creds) {
+ return compose(new Function<Credentials, PrivateKey>() {
+ @Override
+ public PrivateKey apply(Credentials in) {
+ return keyCache.getUnchecked(in);
+ }
+ }, creds);
+ }
+
+ @Provides
+ @Singleton
+ final LoadingCache<Credentials, PrivateKey> privateKeyCache(PrivateKeyForCredentials loader) {
+ // throw out the private key related to old credentials
+ return CacheBuilder.newBuilder().maximumSize(2).build(loader);
+ }
+
+ /**
+ * it is relatively expensive to extract a private key from a PEM. cache the
+ * relationship between current credentials so that the private key is only
+ * recalculated once.
+ */
+ @VisibleForTesting
+ @Singleton
+ private static class PrivateKeyForCredentials extends CacheLoader<Credentials, PrivateKey> {
+ private final Crypto crypto;
+
+ @Inject
+ private PrivateKeyForCredentials(Crypto crypto) {
+ this.crypto = crypto;
+ }
+
+ @Override
+ public PrivateKey load(Credentials in) {
+ try {
+ return crypto.rsaKeyFactory().generatePrivate(
+ privateKeySpec(ByteSource.wrap(in.credential.getBytes(Charsets.UTF_8))));
+ } catch (InvalidKeySpecException e) {
+ throw propagate(e);
+ } catch (IOException e) {
+ throw propagate(e);
+ }
+ }
+ }
+
+ @Provides
+ @Singleton
+ @Validator
+ public final Optional<String> provideValidatorName(Injector injector) {
+ // Named properties can not be injected as optional here, so let's use the
+ // injector to bypass it
+ Key<String> key = Key.get(String.class, Names.named(CHEF_VALIDATOR_NAME));
+ try {
+ return Optional.<String> of(injector.getInstance(key));
+ } catch (ConfigurationException ex) {
+ return Optional.<String> absent();
+ }
+ }
+
+ @Provides
+ @Singleton
+ @Validator
+ public final Optional<PrivateKey> provideValidatorCredential(Crypto crypto, Injector injector)
+ throws InvalidKeySpecException, IOException {
+ // Named properties can not be injected as optional here, so let's use the
+ // injector to bypass it
+ Key<String> key = Key.get(String.class, Names.named(CHEF_VALIDATOR_CREDENTIAL));
+ try {
+ String validatorCredential = injector.getInstance(key);
+ PrivateKey validatorKey = crypto.rsaKeyFactory().generatePrivate(
+ Pems.privateKeySpec(ByteSource.wrap(validatorCredential.getBytes(Charsets.UTF_8))));
+ return Optional.<PrivateKey> of(validatorKey);
+ } catch (ConfigurationException ex) {
+ return Optional.<PrivateKey> absent();
+ }
+ }
+
+ @Provides
+ @Singleton
+ final CacheLoader<String, BootstrapConfig> bootstrapConfigForGroup(BootstrapConfigForGroup bootstrapConfigForGroup) {
+ return CacheLoader.from(bootstrapConfigForGroup);
+ }
+
+ @Provides
+ @Singleton
+ final CacheLoader<String, Client> groupToClient(ClientForGroup clientForGroup) {
+ return CacheLoader.from(clientForGroup);
+ }
+
+ @Override
+ protected void bindErrorHandlers() {
+ bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ChefErrorHandler.class);
+ bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(ChefErrorHandler.class);
+ bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ChefErrorHandler.class);
+ }
+
+ @Override
+ protected void bindRetryHandlers() {
+ bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(ChefApiErrorRetryHandler.class);
+ }
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/domain/Group.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Group.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Group.java
new file mode 100644
index 0000000..eabfcdd
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Group.java
@@ -0,0 +1,211 @@
+/*
+ * 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.chef.domain;
+
+import static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+import java.util.Set;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Group object.
+ */
+public class Group {
+ public static Builder builder(String groupname) {
+ return new Builder(groupname);
+ }
+
+ public static class Builder {
+ private String name;
+ private String groupname;
+ private String orgname;
+ private ImmutableSet.Builder<String> actors = ImmutableSet.builder();
+ private ImmutableSet.Builder<String> clients = ImmutableSet.builder();
+ private ImmutableSet.Builder<String> groups = ImmutableSet.builder();
+ private ImmutableSet.Builder<String> users = ImmutableSet.builder();
+
+ public Builder(String groupname) {
+ this.groupname = groupname;
+ }
+
+ public Builder name(String name) {
+ this.name = checkNotNull(name, "name");
+ return this;
+ }
+
+ public Builder groupname(String groupname) {
+ this.groupname = checkNotNull(groupname, "groupname");
+ return this;
+ }
+
+ public Builder orgname(String orgname) {
+ this.orgname = checkNotNull(orgname, "orgname");
+ return this;
+ }
+
+ public Builder actor(String actor) {
+ this.actors.add(checkNotNull(actor, "actor"));
+ return this;
+ }
+
+ public Builder actors(Iterable<String> actors) {
+ this.actors.addAll(checkNotNull(actors, "actors"));
+ return this;
+ }
+
+ public Builder client(String client) {
+ this.clients.add(checkNotNull(client, "client"));
+ return this;
+ }
+
+ public Builder clients(Iterable<String> clients) {
+ this.clients.addAll(checkNotNull(clients, "clients"));
+ return this;
+ }
+
+ public Builder group(String group) {
+ this.groups.add(checkNotNull(group, "group"));
+ return this;
+ }
+
+ public Builder groups(Iterable<String> groups) {
+ this.groups.addAll(checkNotNull(groups, "groups"));
+ return this;
+ }
+
+ public Builder user(String user) {
+ this.users.add(checkNotNull(user, "user"));
+ return this;
+ }
+
+ public Builder users(Iterable<String> users) {
+ this.users.addAll(checkNotNull(users, "users"));
+ return this;
+ }
+
+ public Group build() {
+ return new Group(name, checkNotNull(groupname, "groupname"), orgname, actors.build(), clients.build(),
+ groups.build(), users.build());
+ }
+ }
+
+ private final String name;
+ private final String groupname;
+ private final String orgname;
+ private final Set<String> actors;
+ private final Set<String> clients;
+ private final Set<String> groups;
+ private final Set<String> users;
+
+ @ConstructorProperties({ "name", "groupname", "orgname", "actors", "clients", "groups", "users" })
+ public Group(String name, String groupname, String orgname, @Nullable Set<String> actors,
+ @Nullable Set<String> clients, @Nullable Set<String> groups, @Nullable Set<String> users) {
+ this.name = name;
+ this.groupname = groupname;
+ this.orgname = orgname;
+ this.actors = copyOfOrEmpty(actors);
+ this.clients = copyOfOrEmpty(clients);
+ this.groups = copyOfOrEmpty(groups);
+ this.users = copyOfOrEmpty(users);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getGroupname() {
+ return groupname;
+ }
+
+ public String getOrgname() {
+ return orgname;
+ }
+
+ public Set<String> getActors() {
+ return actors;
+ }
+
+ public Set<String> getClients() {
+ return clients;
+ }
+
+ public Set<String> getGroups() {
+ return groups;
+ }
+
+ public Set<String> getUsers() {
+ return users;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (groupname == null ? 0 : groupname.hashCode());
+ result = prime * result + (name == null ? 0 : name.hashCode());
+ result = prime * result + (orgname == null ? 0 : orgname.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;
+ }
+ Group other = (Group) obj;
+ if (groupname == null) {
+ if (other.groupname != null) {
+ return false;
+ }
+ } else if (!groupname.equals(other.groupname)) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ if (orgname == null) {
+ if (other.orgname != null) {
+ return false;
+ }
+ } else if (!orgname.equals(other.orgname)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Group [name=" + name + ", groupname=" + groupname + ", orgname=" + orgname + ", actors=" + actors
+ + ", clients=" + clients + ", groups=" + groups + ", users=" + users + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/domain/User.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/User.java b/apis/chef/src/main/java/org/jclouds/chef/domain/User.java
new file mode 100644
index 0000000..935bece
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/User.java
@@ -0,0 +1,220 @@
+/*
+ * 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.chef.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+import java.security.PublicKey;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * User object.
+ */
+public class User {
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String username;
+ private String firstName;
+ private String middleName;
+ private String lastName;
+ private String displayName;
+ private String email;
+ private PublicKey publicKey;
+
+ public Builder username(String username) {
+ this.username = checkNotNull(username, "username");
+ return this;
+ }
+
+ public Builder firstName(String firstName) {
+ this.firstName = checkNotNull(firstName, "firstName");
+ return this;
+ }
+
+ public Builder middleName(String middleName) {
+ this.middleName = checkNotNull(middleName, "middleName");
+ return this;
+ }
+
+ public Builder lastName(String lastName) {
+ this.lastName = checkNotNull(lastName, "lastName");
+ return this;
+ }
+
+ public Builder displayName(String displayName) {
+ this.displayName = checkNotNull(displayName, "displayName");
+ return this;
+ }
+
+ public Builder email(String email) {
+ this.email = checkNotNull(email, "email");
+ return this;
+ }
+
+ public Builder publicKey(PublicKey publicKey) {
+ this.publicKey = checkNotNull(publicKey, "publicKey");
+ return this;
+ }
+
+ public User build() {
+ return new User(username, firstName, middleName, lastName, displayName, email, publicKey);
+ }
+ }
+
+ private final String username;
+ @SerializedName("first_name")
+ private final String firstName;
+ @SerializedName("middle_name")
+ private final String middleName;
+ @SerializedName("last_name")
+ private final String lastName;
+ @SerializedName("display_name")
+ private final String displayName;
+ private final String email;
+ @SerializedName("public_key")
+ private final PublicKey publicKey;
+
+ @ConstructorProperties({ "username", "first_name", "middle_name", "last_name", "display_name", "email", "public_key" })
+ protected User(String username, String firstName, String middleName, String lastName, String displayName,
+ String email, PublicKey publicKey) {
+ this.username = username;
+ this.firstName = firstName;
+ this.middleName = middleName;
+ this.lastName = lastName;
+ this.displayName = displayName;
+ this.email = email;
+ this.publicKey = publicKey;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getMiddleName() {
+ return middleName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (displayName == null ? 0 : displayName.hashCode());
+ result = prime * result + (email == null ? 0 : email.hashCode());
+ result = prime * result + (firstName == null ? 0 : firstName.hashCode());
+ result = prime * result + (lastName == null ? 0 : lastName.hashCode());
+ result = prime * result + (middleName == null ? 0 : middleName.hashCode());
+ result = prime * result + (publicKey == null ? 0 : publicKey.hashCode());
+ result = prime * result + (username == null ? 0 : username.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;
+ }
+ User other = (User) obj;
+ if (displayName == null) {
+ if (other.displayName != null) {
+ return false;
+ }
+ } else if (!displayName.equals(other.displayName)) {
+ return false;
+ }
+ if (email == null) {
+ if (other.email != null) {
+ return false;
+ }
+ } else if (!email.equals(other.email)) {
+ return false;
+ }
+ if (firstName == null) {
+ if (other.firstName != null) {
+ return false;
+ }
+ } else if (!firstName.equals(other.firstName)) {
+ return false;
+ }
+ if (lastName == null) {
+ if (other.lastName != null) {
+ return false;
+ }
+ } else if (!lastName.equals(other.lastName)) {
+ return false;
+ }
+ if (middleName == null) {
+ if (other.middleName != null) {
+ return false;
+ }
+ } else if (!middleName.equals(other.middleName)) {
+ return false;
+ }
+ if (publicKey == null) {
+ if (other.publicKey != null) {
+ return false;
+ }
+ } else if (!publicKey.equals(other.publicKey)) {
+ return false;
+ }
+ if (username == null) {
+ if (other.username != null) {
+ return false;
+ }
+ } else if (!username.equals(other.username)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "User [username=" + username + ", firstName=" + firstName + ", middleName=" + middleName + ", lastName="
+ + lastName + ", displayName=" + displayName + ", email=" + email + ", publicKey=" + publicKey + "]";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/features/OrganizationApi.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/features/OrganizationApi.java b/apis/chef/src/main/java/org/jclouds/chef/features/OrganizationApi.java
new file mode 100644
index 0000000..3624ddd
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/features/OrganizationApi.java
@@ -0,0 +1,130 @@
+/*
+ * 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.chef.features;
+
+import java.util.Set;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Constants;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.binders.BindGroupToUpdateRequestJsonPayload;
+import org.jclouds.chef.binders.GroupName;
+import org.jclouds.chef.domain.Group;
+import org.jclouds.chef.domain.User;
+import org.jclouds.chef.filters.SignedHeaderAuth;
+import org.jclouds.chef.functions.ParseKeySetFromJson;
+import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.Headers;
+import org.jclouds.rest.annotations.ParamParser;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SinceApiVersion;
+import org.jclouds.rest.annotations.WrapWith;
+
+/**
+ * Provides synchronous access to the Enterprise Chef Api.
+ */
+@RequestFilters(SignedHeaderAuth.class)
+@Consumes(MediaType.APPLICATION_JSON)
+@Headers(keys = "X-Chef-Version", values = "{" + Constants.PROPERTY_API_VERSION + "}")
+@SinceApiVersion("12.0.0")
+public interface OrganizationApi extends ChefApi {
+ /**
+ * Retrieves an existing user.
+ *
+ * @param name
+ * The name of the user to get.
+ * @return The details of the user or <code>null</code> if not found.
+ */
+ @Named("user:get")
+ @GET
+ @Path("/users/{name}")
+ @Fallback(NullOnNotFoundOr404.class)
+ User getUser(@PathParam("name") String name);
+
+ /**
+ * List all existing groups.
+ *
+ * @return The list of groups.
+ */
+ @Named("group:list")
+ @GET
+ @Path("/groups")
+ @ResponseParser(ParseKeySetFromJson.class)
+ Set<String> listGroups();
+
+ /**
+ * Retrieves an existing group.
+ *
+ * @param name
+ * The name of the group to get.
+ * @return The details of the group or <code>null</code> if not found.
+ */
+ @Named("group:get")
+ @GET
+ @Path("/groups/{name}")
+ @Fallback(NullOnNotFoundOr404.class)
+ Group getGroup(@PathParam("name") String name);
+
+ /**
+ * Creates a new group.
+ *
+ * @param name
+ * The name of the group to create.
+ */
+ @Named("group:create")
+ @POST
+ @Path("/groups")
+ void createGroup(@WrapWith("groupname") String name);
+
+ /**
+ * Updates a group.
+ * <p>
+ * This method can be used to add actors (clients, groups) to the group.
+ *
+ * @param group
+ * The group with the updated information.
+ */
+ @Named("group:update")
+ @PUT
+ @Path("/groups/{name}")
+ void updateGroup(
+ @PathParam("name") @ParamParser(GroupName.class) @BinderParam(BindGroupToUpdateRequestJsonPayload.class) Group group);
+
+ /**
+ * Deletes a group.
+ *
+ * @param name
+ * The name of the group to delete.
+ */
+ @Named("group:delete")
+ @DELETE
+ @Path("/groups/{name}")
+ void deleteGroup(@PathParam("name") String name);
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java b/apis/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java
index 50d26a1..ab2cf3c 100644
--- a/apis/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java
+++ b/apis/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java
@@ -63,7 +63,7 @@ import com.google.common.io.ByteSource;
/**
* Ported from mixlib-authentication in order to sign Chef requests.
*
- * @see <a href= "http://github.com/opscode/mixlib-authentication" />
+ * @see <a href= "https://github.com/chef/mixlib-authentication" />
*/
@Singleton
public class SignedHeaderAuth implements HttpRequestFilter {
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/main/java/org/jclouds/chef/options/CreateClientOptions.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/options/CreateClientOptions.java b/apis/chef/src/main/java/org/jclouds/chef/options/CreateClientOptions.java
index 776d7bb..45d94ee 100644
--- a/apis/chef/src/main/java/org/jclouds/chef/options/CreateClientOptions.java
+++ b/apis/chef/src/main/java/org/jclouds/chef/options/CreateClientOptions.java
@@ -20,7 +20,7 @@ package org.jclouds.chef.options;
* Options for the create client method.
*/
public class CreateClientOptions implements Cloneable {
- /** Administrator flag. This flag will be ignored in Opscode Hosted Chef. */
+ /** Administrator flag. This flag will be ignored in Chef 12. */
private boolean admin;
public CreateClientOptions() {
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java b/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java
index 23013a9..8cb3288 100644
--- a/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java
+++ b/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java
@@ -33,6 +33,7 @@ public abstract class BaseChefApiExpectTest<S> extends BaseRestApiExpectTest<S>
protected SignedHeaderAuth signedHeaderAuth;
public BaseChefApiExpectTest() {
+ provider = "chef";
credential = PRIVATE_KEY;
signedHeaderAuth = createInjector(Functions.forMap(ImmutableMap.<HttpRequest, HttpResponse> of()),
createModule(), setupProperties()).getInstance(SignedHeaderAuth.class);
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/test/java/org/jclouds/chef/ChefApiDelegationTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiDelegationTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiDelegationTest.java
new file mode 100644
index 0000000..893d9d3
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiDelegationTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.chef;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.jclouds.ContextBuilder;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "ChefApiDelegationTest")
+public class ChefApiDelegationTest {
+
+ public void testOrganizationApiNotAvailableInOldVersions() throws IOException {
+ ChefApi chef = ContextBuilder.newBuilder(new ChefApiMetadata())
+ .credentials("foo", "bar")
+ .apiVersion("11.0.4")
+ .buildApi(ChefApi.class);
+
+ try {
+ assertFalse(chef.organizationApi().isPresent());
+ } finally {
+ chef.close();
+ }
+ }
+
+ public void testOrganizationApiPresentInRecentVersions() throws IOException {
+ ChefApi chef = ContextBuilder.newBuilder(new ChefApiMetadata())
+ .credentials("foo", "bar")
+ .apiVersion("12.0.4")
+ .buildApi(ChefApi.class);
+
+ try {
+ assertTrue(chef.organizationApi().isPresent());
+ } finally {
+ chef.close();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java
index 478959b..1bf308b 100644
--- a/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java
@@ -42,9 +42,6 @@ import com.google.inject.Module;
*/
@Test(groups = "unit", testName = "ChefApiExpectTest")
public class ChefApiExpectTest extends BaseChefApiExpectTest<ChefApi> {
- public ChefApiExpectTest() {
- provider = "chef";
- }
private HttpRequest.Builder<?> getHttpRequestBuilder(String method, String endPoint) {
return HttpRequest.builder() //
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java
index e4ad583..c00e0d9 100644
--- a/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java
@@ -16,17 +16,527 @@
*/
package org.jclouds.chef;
-import org.jclouds.chef.internal.BaseChefApiLiveTest;
+import static com.google.common.collect.Iterables.any;
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.hash.Hashing.md5;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.jclouds.util.Closeables2.closeQuietly;
+import static org.jclouds.util.Predicates2.retry;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import org.jclouds.chef.domain.ChecksumStatus;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.chef.domain.Environment;
+import org.jclouds.chef.domain.Metadata;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.domain.Resource;
+import org.jclouds.chef.domain.Role;
+import org.jclouds.chef.domain.Sandbox;
+import org.jclouds.chef.domain.SearchResult;
+import org.jclouds.chef.domain.UploadSandbox;
+import org.jclouds.chef.internal.BaseChefLiveTest;
+import org.jclouds.chef.options.CreateClientOptions;
+import org.jclouds.chef.options.SearchOptions;
+import org.jclouds.crypto.Pems;
+import org.jclouds.io.ByteStreams2;
+import org.jclouds.io.Payloads;
+import org.jclouds.io.payloads.FilePayload;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Files;
+import com.google.common.primitives.Bytes;
+
/**
- * Tests behavior of {@code ChefApi} against a Chef Server <= 0.9.8.
+ * Tests behavior of {@code ChefApi}
*/
-@Test(groups = { "live" })
-public class ChefApiLiveTest extends BaseChefApiLiveTest<ChefApi> {
+@Test(groups = { "live", "integration" })
+public class ChefApiLiveTest extends BaseChefLiveTest {
+ public static final String PREFIX = "jcloudstest-" + System.getProperty("user.name");
+ public static final String ADMIN_PREFIX = "jcloudstest-adm-" + System.getProperty("user.name");
+ public static final String ENV_NODE = PREFIX + "-env-node";
+
+ // It may take a bit until the search index is populated
+ protected int maxWaitForIndexInMs = 60000;
+
+ // The id of the data bag item used in search tests
+ private String databagitemId;
+
+ public void testCreateNewCookbook() throws Exception {
+ // Define the file you want in the cookbook
+ File file = new File(System.getProperty("user.dir"), "pom.xml");
+ FilePayload content = uploadAndGetFilePayload(file);
+
+ // Create the metadata of the cookbook
+ Metadata metadata = Metadata.builder() //
+ .name(PREFIX) //
+ .version("0.0.0") //
+ .description("Jclouds test uploaded cookbook") //
+ .maintainer("jclouds") //
+ .maintainerEmail("someone@jclouds.org") //
+ .license("Apache 2.0") //
+ .build();
+
+ // Create a new cookbook
+ CookbookVersion cookbook = CookbookVersion.builder(PREFIX, "0.0.0") //
+ .metadata(metadata) //
+ .rootFile(Resource.builder().fromPayload(content).build()) //
+ .attribute(Resource.builder().fromPayload(content).name("default.rb").build())
+ .build();
+
+ // upload the cookbook to the remote server
+ api.updateCookbook(PREFIX, "0.0.0", cookbook);
+ }
+
+ private FilePayload uploadAndGetFilePayload(File file) throws IOException {
+ FilePayload content = Payloads.newFilePayload(file);
+ content.getContentMetadata().setContentType("application/x-binary");
+
+ // Get an md5 so that you can see if the server already has it or not
+ content.getContentMetadata().setContentMD5(Files.asByteSource(file).hash(Hashing.md5()).asBytes());
+
+ // Note that java collections cannot effectively do equals or hashcodes on
+ // byte arrays, so let's convert to a list of bytes.
+ List<Byte> md5 = Bytes.asList(content.getContentMetadata().getContentMD5());
+
+ // Request an upload site for this file
+ UploadSandbox site = api.createUploadSandboxForChecksums(ImmutableSet.of(md5));
+ assertTrue(site.getChecksums().containsKey(md5), md5 + " not in " + site.getChecksums());
+
+ try {
+ // Upload the file contents, if still not uploaded
+ ChecksumStatus status = site.getChecksums().get(md5);
+ if (status.needsUpload()) {
+ api.uploadContent(status.getUrl(), content);
+ }
+ Sandbox sandbox = api.commitSandbox(site.getSandboxId(), true);
+ assertTrue(sandbox.isCompleted(), "Sandbox should be completed after uploading");
+ } catch (RuntimeException e) {
+ api.commitSandbox(site.getSandboxId(), false);
+ fail("Could not upload content");
+ }
+ return content;
+ }
+
+ public void testListCookbooks() throws Exception {
+ Set<String> cookbookNames = api.listCookbooks();
+ assertFalse(cookbookNames.isEmpty(), "No cookbooks were found");
+
+ for (String cookbookName : cookbookNames) {
+ Set<String> versions = api.listVersionsOfCookbook(cookbookName);
+ assertFalse(versions.isEmpty(), "There are no versions of the cookbook: " + cookbookName);
+
+ for (String version : api.listVersionsOfCookbook(cookbookName)) {
+ CookbookVersion cookbook = api.getCookbook(cookbookName, version);
+ assertNotNull(cookbook, "Could not get cookbook: " + cookbookName);
+ }
+ }
+ }
+
+ @Test(dependsOnMethods = "testListCookbooks")
+ public void testListCookbookVersionsWithChefService() throws Exception {
+ Iterable<? extends CookbookVersion> cookbooks = chefService.listCookbookVersions();
+ assertFalse(isEmpty(cookbooks), "No cookbooks were found");
+ }
+
+ @Test(dependsOnMethods = "testListCookbookVersionsWithChefService")
+ public void testDownloadCookbooks() throws Exception {
+ Iterable<? extends CookbookVersion> cookbooks = chefService.listCookbookVersions();
+ for (CookbookVersion cookbook : cookbooks) {
+ for (Resource resource : ImmutableList.<Resource> builder().addAll(cookbook.getDefinitions())
+ .addAll(cookbook.getFiles()).addAll(cookbook.getLibraries()).addAll(cookbook.getSuppliers())
+ .addAll(cookbook.getRecipes()).addAll(cookbook.getResources()).addAll(cookbook.getRootFiles())
+ .addAll(cookbook.getTemplates()).addAll(cookbook.getAttributes()).build()) {
+
+ InputStream stream = api.getResourceContents(resource);
+ assertNotNull(stream, "Resource contents are null for resource: " + resource.getName());
+
+ byte[] md5 = ByteStreams2.hashAndClose(stream, md5()).asBytes();
+ assertEquals(md5, resource.getChecksum());
+ }
+ }
+ }
+
+ @Test(dependsOnMethods = "testCreateNewCookbook")
+ public void testUpdateCookbook() throws Exception {
+ CookbookVersion cookbook = api.getCookbook(PREFIX, "0.0.0");
+ assertNotNull(cookbook, "Cookbook not found: " + PREFIX);
+ assertNotNull(api.updateCookbook(PREFIX, "0.0.0", cookbook), "Updated cookbook was null");
+ }
+
+ @Test(dependsOnMethods = { "testCreateNewCookbook", "testUpdateCookbook" })
+ public void testDeleteCookbook() throws Exception {
+ assertNotNull(api.deleteCookbook(PREFIX, "0.0.0"), "Deleted cookbook was null");
+ }
+
+ @Test
+ public void testCreateClient() throws Exception {
+ api.deleteClient(PREFIX);
+ String credential = Pems.pem(api.createClient(PREFIX).getPrivateKey());
+ assertClientCreated(PREFIX, credential);
+ }
+
+ @Test
+ public void testCreateAdminClient() throws Exception {
+ api.deleteClient(ADMIN_PREFIX);
+ String credential = Pems.pem(api.createClient(ADMIN_PREFIX, CreateClientOptions.Builder.admin()).getPrivateKey());
+ assertClientCreated(ADMIN_PREFIX, credential);
+ }
+
+ @Test(dependsOnMethods = "testCreateClient")
+ public void testGenerateKeyForClient() throws Exception {
+ String credential = Pems.pem(api.generateKeyForClient(PREFIX).getPrivateKey());
+ assertClientCreated(PREFIX, credential);
+ }
+
+ @Test
+ public void testListNodes() throws Exception {
+ Set<String> nodes = api.listNodes();
+ assertNotNull(nodes, "No nodes were found");
+ }
+
+ @Test(dependsOnMethods = "testCreateRole")
+ public void testCreateNode() throws Exception {
+ api.deleteNode(PREFIX);
+ api.createNode(Node.builder().name(PREFIX).runListElement("role[" + PREFIX + "]").environment("_default").build());
+ Node node = api.getNode(PREFIX);
+ // TODO check recipes
+ assertNotNull(node, "Created node should not be null");
+ Set<String> nodes = api.listNodes();
+ assertTrue(nodes.contains(PREFIX), String.format("node %s not in %s", PREFIX, nodes));
+ }
+
+ @Test(dependsOnMethods = "testCreateNode")
+ public void testUpdateNode() throws Exception {
+ for (String nodename : api.listNodes()) {
+ Node node = api.getNode(nodename);
+ api.updateNode(node);
+ }
+ }
+
+ @Test
+ public void testListRoles() throws Exception {
+ Set<String> roles = api.listRoles();
+ assertNotNull(roles, "Role list was null");
+ }
+
+ @Test
+ public void testCreateRole() throws Exception {
+ api.deleteRole(PREFIX);
+ api.createRole(Role.builder().name(PREFIX).runListElement("recipe[java]").build());
+ Role role = api.getRole(PREFIX);
+ assertNotNull(role, "Created role should not be null");
+ assertEquals(role.getName(), PREFIX);
+ assertEquals(role.getRunList(), Collections.singleton("recipe[java]"));
+ }
+
+ @Test(dependsOnMethods = "testCreateRole")
+ public void testUpdateRole() throws Exception {
+ for (String rolename : api.listRoles()) {
+ Role role = api.getRole(rolename);
+ api.updateRole(role);
+ }
+ }
+
+ @Test
+ public void testListDatabags() throws Exception {
+ Set<String> databags = api.listDatabags();
+ assertNotNull(databags, "Data bag list was null");
+ }
+
+ @Test
+ public void testCreateDatabag() throws Exception {
+ api.deleteDatabag(PREFIX);
+ api.createDatabag(PREFIX);
+ }
- protected ChefApiLiveTest() {
- provider = "chef";
+ @Test(dependsOnMethods = "testCreateDatabagItem")
+ public void testListDatabagItems() throws Exception {
+ Set<String> databagItems = api.listDatabagItems(PREFIX);
+ assertNotNull(databagItems, "Data bag item list was null");
}
+ @Test(dependsOnMethods = "testCreateDatabag")
+ public void testCreateDatabagItem() throws Exception {
+ Properties config = new Properties();
+ config.setProperty("foo", "bar");
+ api.deleteDatabagItem(PREFIX, PREFIX);
+ DatabagItem databagItem = api.createDatabagItem(PREFIX, new DatabagItem("config", json.toJson(config)));
+ databagitemId = databagItem.getId();
+ assertNotNull(databagItem, "Created data bag item should not be null");
+ assertEquals(databagItem.getId(), "config");
+
+ // The databagItem json contains extra keys: (the name and the type if the
+ // item)
+ Properties props = json.fromJson(databagItem.toString(), Properties.class);
+ for (Object key : config.keySet()) {
+ assertTrue(props.containsKey(key));
+ assertEquals(config.get(key), props.get(key));
+ }
+ }
+
+ @Test(dependsOnMethods = "testCreateDatabagItem")
+ public void testUpdateDatabagItem() throws Exception {
+ for (String databagItemId : api.listDatabagItems(PREFIX)) {
+ DatabagItem databagItem = api.getDatabagItem(PREFIX, databagItemId);
+ api.updateDatabagItem(PREFIX, databagItem);
+ }
+ }
+
+ @Test(dependsOnMethods = "testSearchDatabagWithOptions")
+ public void testDeleteDatabagItem() throws Exception {
+ for (String databagItemId : api.listDatabagItems(PREFIX)) {
+ DatabagItem databagItem = api.deleteDatabagItem(PREFIX, databagItemId);
+ assertNotNull(databagItem, "Deleted data bag item should not be null");
+ assertEquals(databagItem.getId(), databagItemId, "Deleted data bag item id must match the original id");
+ assertNull(api.getDatabagItem(PREFIX, databagItemId), "Data bag item should not exist");
+ }
+ }
+
+ @Test
+ public void testListSearchIndexes() throws Exception {
+ Set<String> indexes = api.listSearchIndexes();
+ assertNotNull(indexes, "The index list should not be null");
+ assertTrue(indexes.contains("node"));
+ assertTrue(indexes.contains("client"));
+ assertTrue(indexes.contains("role"));
+ }
+
+ @Test
+ public void testSearchNodes() throws Exception {
+ SearchResult<? extends Node> results = api.searchNodes();
+ assertNotNull(results, "Node result list should not be null");
+ }
+
+ @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateNode" })
+ public void testSearchNodesWithOptions() throws Exception {
+ Predicate<SearchOptions> waitForIndex = retry(new Predicate<SearchOptions>() {
+ @Override
+ public boolean apply(SearchOptions input) {
+ SearchResult<? extends Node> results = api.searchNodes(input);
+ assertNotNull(results);
+ if (results.size() > 0) {
+ assertEquals(results.size(), 1);
+ assertEquals(results.iterator().next().getName(), PREFIX);
+ return true;
+ } else {
+ // The index may still not be populated
+ return false;
+ }
+ }
+ }, maxWaitForIndexInMs, 5000L, MILLISECONDS);
+
+ SearchOptions options = SearchOptions.Builder.query("name:" + PREFIX);
+ assertTrue(waitForIndex.apply(options));
+ }
+
+ @Test
+ public void testSearchClients() throws Exception {
+ SearchResult<? extends Client> results = api.searchClients();
+ assertNotNull(results, "Client result list should not be null");
+ }
+
+ @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateClient" })
+ public void testSearchClientsWithOptions() throws Exception {
+ Predicate<SearchOptions> waitForIndex = retry(new Predicate<SearchOptions>() {
+ @Override
+ public boolean apply(SearchOptions input) {
+ SearchResult<? extends Client> results = api.searchClients(input);
+ assertNotNull(results);
+ if (results.size() > 0) {
+ assertEquals(results.size(), 1);
+ assertEquals(results.iterator().next().getName(), PREFIX);
+ return true;
+ } else {
+ // The index may still not be populated
+ return false;
+ }
+ }
+ }, maxWaitForIndexInMs, 5000L, MILLISECONDS);
+
+ SearchOptions options = SearchOptions.Builder.query("name:" + PREFIX);
+ assertTrue(waitForIndex.apply(options));
+ }
+
+ @Test
+ public void testSearchRoles() throws Exception {
+ SearchResult<? extends Role> results = api.searchRoles();
+ assertNotNull(results, "Role result list should not be null");
+ }
+
+ @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateRole" })
+ public void testSearchRolesWithOptions() throws Exception {
+ Predicate<SearchOptions> waitForIndex = retry(new Predicate<SearchOptions>() {
+ @Override
+ public boolean apply(SearchOptions input) {
+ SearchResult<? extends Role> results = api.searchRoles(input);
+ assertNotNull(results);
+ if (results.size() > 0) {
+ assertEquals(results.size(), 1);
+ assertEquals(results.iterator().next().getName(), PREFIX);
+ return true;
+ } else {
+ // The index may still not be populated
+ return false;
+ }
+ }
+ }, maxWaitForIndexInMs, 5000L, MILLISECONDS);
+
+ SearchOptions options = SearchOptions.Builder.query("name:" + PREFIX);
+ assertTrue(waitForIndex.apply(options));
+ }
+
+ @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateDatabagItem" })
+ public void testSearchDatabag() throws Exception {
+ SearchResult<? extends DatabagItem> results = api.searchDatabagItems(PREFIX);
+ assertNotNull(results, "Data bag item result list should not be null");
+ }
+
+ @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateDatabagItem" })
+ public void testSearchDatabagWithOptions() throws Exception {
+ Predicate<SearchOptions> waitForIndex = retry(new Predicate<SearchOptions>() {
+ @Override
+ public boolean apply(SearchOptions input) {
+ SearchResult<? extends DatabagItem> results = api.searchDatabagItems(PREFIX, input);
+ assertNotNull(results);
+ if (results.size() > 0) {
+ assertEquals(results.size(), 1);
+ assertEquals(results.iterator().next().getId(), databagitemId);
+ return true;
+ } else {
+ // The index may still not be populated
+ return false;
+ }
+ }
+ }, maxWaitForIndexInMs, 5000L, MILLISECONDS);
+
+ SearchOptions options = SearchOptions.Builder.query("id:" + databagitemId);
+ assertTrue(waitForIndex.apply(options));
+ }
+
+ @Test(expectedExceptions = ResourceNotFoundException.class, dependsOnMethods = "testListSearchIndexes")
+ public void testSearchDatabagNotFound() throws Exception {
+ SearchResult<? extends DatabagItem> results = api.searchDatabagItems("whoopie");
+ assertNotNull(results, "Data bag item result list should not be null");
+ }
+
+ @Test
+ public void testCreateEnvironment() {
+ api.deleteEnvironment(PREFIX);
+ api.createEnvironment(Environment.builder().name(PREFIX).description(PREFIX).build());
+ Environment env = api.getEnvironment(PREFIX);
+ assertNotNull(env, "Created environment should not be null");
+ assertEquals(env.getName(), PREFIX);
+ assertEquals(env.getDescription(), PREFIX);
+ }
+
+ @Test(dependsOnMethods = "testCreateEnvironment")
+ public void testListEnvironment() {
+ Set<String> envList = api.listEnvironments();
+ assertNotNull(envList, "Environment list was null");
+ assertTrue(envList.contains(PREFIX));
+ }
+
+ @Test(dependsOnMethods = "testCreateEnvironment")
+ public void testSearchEnvironments() throws Exception {
+ SearchResult<? extends Environment> results = api.searchEnvironments();
+ assertNotNull(results, "Environment result list was null");
+ }
+
+ @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateEnvironment" })
+ public void testSearchEnvironmentsWithOptions() throws Exception {
+ Predicate<SearchOptions> waitForIndex = retry(new Predicate<SearchOptions>() {
+ @Override
+ public boolean apply(SearchOptions input) {
+ SearchResult<? extends Environment> results = api.searchEnvironments(input);
+ assertNotNull(results);
+ if (results.size() > 0) {
+ assertEquals(results.size(), 1);
+ assertEquals(results.iterator().next().getName(), PREFIX);
+ return true;
+ } else {
+ // The index may still not be populated
+ return false;
+ }
+ }
+ }, maxWaitForIndexInMs, 5000L, MILLISECONDS);
+
+ SearchOptions options = SearchOptions.Builder.query("name:" + PREFIX);
+ assertTrue(waitForIndex.apply(options));
+ }
+
+ @Test(dependsOnMethods = "testCreateEnvironment")
+ public void testListRecipesInEnvironment() {
+ Set<String> recipeList = api.listRecipesInEnvironment(PREFIX);
+ assertTrue(!recipeList.isEmpty());
+ }
+
+ @Test(dependsOnMethods = "testCreateEnvironment")
+ public void testListNodesInEnvironment() {
+ api.deleteNode(ENV_NODE);
+ api.createNode(Node.builder().name(ENV_NODE).runListElement("role[" + PREFIX + "]").environment(PREFIX).build());
+ Node node = api.getNode(ENV_NODE);
+ assertNotNull(node, "Created node should not be null");
+ Set<String> nodeList = api.listNodesInEnvironment(PREFIX);
+ assertTrue(!nodeList.isEmpty());
+ }
+
+ @Test(dependsOnMethods = "testCreateNewCookbook")
+ public void testListCookbooksInEnvironment() throws Exception {
+ Set<CookbookDefinition> cookbooks = api.listCookbooksInEnvironment("_default");
+ assertTrue(any(cookbooks, new Predicate<CookbookDefinition>() {
+ @Override
+ public boolean apply(CookbookDefinition input) {
+ return PREFIX.equals(input.getName());
+ }}), String.format("Cookbook %s not in %s", PREFIX, cookbooks));
+ }
+
+ @AfterClass(groups = { "live", "integration" })
+ @Override
+ public void tearDown() {
+ api.deleteClient(PREFIX);
+ api.deleteClient(ADMIN_PREFIX);
+ api.deleteNode(PREFIX);
+ api.deleteNode(ENV_NODE);
+ api.deleteRole(PREFIX);
+ api.deleteDatabag(PREFIX);
+ api.deleteEnvironment(PREFIX);
+ super.tearDown();
+ }
+
+ private void assertClientCreated(String identity, String credential) {
+ Properties overrides = super.setupProperties();
+ overrides.setProperty(provider + ".identity", identity);
+ overrides.setProperty(provider + ".credential", credential);
+
+ ChefApi clientApi = create(overrides, setupModules());
+
+ try {
+ Client client = clientApi.getClient(identity);
+ assertNotNull(client, "Client not found: " + identity);
+ } finally {
+ closeQuietly(clientApi);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/test/java/org/jclouds/chef/binders/BindGroupToUpdateRequestJsonPayloadTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/binders/BindGroupToUpdateRequestJsonPayloadTest.java b/apis/chef/src/test/java/org/jclouds/chef/binders/BindGroupToUpdateRequestJsonPayloadTest.java
new file mode 100644
index 0000000..f8d7b2b
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/binders/BindGroupToUpdateRequestJsonPayloadTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.chef.binders;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.Group;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.jclouds.util.Strings2;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Unit tests for the {@link BindGroupToUpdateRequestJsonPayload} class.
+ */
+@Test(groups = "unit", testName = "BindGroupToUpdateRequestJsonPayloadTest")
+public class BindGroupToUpdateRequestJsonPayloadTest {
+
+ private Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+ }
+ }, new ChefParserModule(), new GsonModule());
+
+ private BindGroupToUpdateRequestJsonPayload binder = injector.getInstance(BindGroupToUpdateRequestJsonPayload.class);
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testInvalidNullInput() {
+ HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build();
+ binder.bindToRequest(request, null);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidTypeInput() {
+ HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build();
+ binder.bindToRequest(request, new Object());
+ }
+
+ public void testBindOnlyName() throws IOException {
+ HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build();
+ HttpRequest newRequest = binder.bindToRequest(request, Group.builder("foo").build());
+
+ String payload = Strings2.toStringAndClose(newRequest.getPayload().getInput());
+ assertEquals(payload, "{\"groupname\":\"foo\",\"actors\":{\"clients\":[],\"groups\":[],\"users\":[]}}");
+ }
+
+ public void testBindNameAndLists() throws IOException {
+ Group group = Group.builder("foo").client("nacx-validator").group("admins").user("nacx").build();
+
+ HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build();
+ HttpRequest newRequest = binder.bindToRequest(request, group);
+
+ String payload = Strings2.toStringAndClose(newRequest.getPayload().getInput());
+ assertEquals(payload,
+ "{\"groupname\":\"foo\",\"actors\":{\"clients\":[\"nacx-validator\"],\"groups\":[\"admins\"],\"users\":[\"nacx\"]}}");
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/9df30c5a/apis/chef/src/test/java/org/jclouds/chef/binders/GroupNameTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/binders/GroupNameTest.java b/apis/chef/src/test/java/org/jclouds/chef/binders/GroupNameTest.java
new file mode 100644
index 0000000..34303e8
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/binders/GroupNameTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.chef.binders;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.domain.Group;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for the {@link GroupName} class.
+ */
+@Test(groups = "unit", testName = "GroupNameTest")
+public class GroupNameTest {
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testInvalidNullInput() {
+ new GroupName().apply(null);
+ }
+
+ public void testApplyGroupName() throws IOException {
+ assertEquals(new GroupName().apply(Group.builder("foo").build()), "foo");
+ }
+}