You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2014/07/28 22:02:52 UTC

[01/10] Move jclouds-chef to the main jclouds repo

Repository: jclouds
Updated Branches:
  refs/heads/master eba727fef -> 867c7a407


http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/pom.xml
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/pom.xml b/providers/enterprise-chef/pom.xml
new file mode 100644
index 0000000..944701b
--- /dev/null
+++ b/providers/enterprise-chef/pom.xml
@@ -0,0 +1,110 @@
+<?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</groupId>
+    <artifactId>jclouds-project</artifactId>
+    <version>1.8.0-SNAPSHOT</version>
+    <relativePath>../../project/pom.xml</relativePath>
+  </parent>
+  <groupId>org.apache.jclouds.provider</groupId>
+  <artifactId>enterprisechef</artifactId>
+  <name>jclouds Enterprise Chef provider</name>
+  <description>jclouds components to access Enterprise Chef</description>
+
+  <properties>
+    <test.enterprisechef.org>YOUR_ORG</test.enterprisechef.org>
+    <test.enterprisechef.endpoint>https://api.opscode.com/organizations/${test.enterprisechef.org}</test.enterprisechef.endpoint>
+    <test.enterprisechef.api-version />
+    <test.enterprisechef.build-version />
+    <test.enterprisechef.identity>YOUR_USER</test.enterprisechef.identity>
+    <test.enterprisechef.credential>${user.home}/.chef/${test.enterprisechef.org}/${test.enterprisechef.identity}.pem</test.enterprisechef.credential>
+    <jclouds.osgi.export>org.jclouds.enterprisechef*;version="${project.version}"</jclouds.osgi.export>
+    <jclouds.osgi.import>org.jclouds*;version="${project.version}",*</jclouds.osgi.import>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.jclouds.api</groupId>
+      <artifactId>chef</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds.api</groupId>
+      <artifactId>chef</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-core</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds.driver</groupId>
+      <artifactId>jclouds-slf4j</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <version>1.0.9</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <profiles>
+    <profile>
+      <id>live</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>integration</id>
+                <phase>integration-test</phase>
+                <goals>
+                  <goal>test</goal>
+                </goals>
+                <configuration>
+                  <systemPropertyVariables>
+                    <test.enterprisechef.org>${test.enterprisechef.org}</test.enterprisechef.org>
+                    <test.enterprisechef.endpoint>${test.enterprisechef.endpoint}</test.enterprisechef.endpoint>
+                    <test.enterprisechef.api-version>${test.enterprisechef.api-version}</test.enterprisechef.api-version>
+                    <test.enterprisechef.build-version>${test.enterprisechef.build-version}</test.enterprisechef.build-version>
+                    <test.enterprisechef.identity>${test.enterprisechef.identity}</test.enterprisechef.identity>
+                    <test.enterprisechef.credential>${test.enterprisechef.credential}</test.enterprisechef.credential>
+                  </systemPropertyVariables>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java
new file mode 100644
index 0000000..8f1a0ed
--- /dev/null
+++ b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java
@@ -0,0 +1,124 @@
+/*
+ * 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.enterprisechef;
+
+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.filters.SignedHeaderAuth;
+import org.jclouds.chef.functions.ParseKeySetFromJson;
+import org.jclouds.enterprisechef.binders.BindGroupToUpdateRequestJsonPayload;
+import org.jclouds.enterprisechef.binders.GroupName;
+import org.jclouds.enterprisechef.domain.Group;
+import org.jclouds.enterprisechef.domain.User;
+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.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 + "}")
+public interface EnterpriseChefApi 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/867c7a40/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApiMetadata.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApiMetadata.java b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApiMetadata.java
new file mode 100644
index 0000000..fdccf46
--- /dev/null
+++ b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApiMetadata.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.enterprisechef;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.ChefContext;
+import org.jclouds.chef.config.ChefBootstrapModule;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.enterprisechef.config.EnterpriseChefHttpApiModule;
+import org.jclouds.ohai.config.JMXOhaiModule;
+import org.jclouds.rest.internal.BaseHttpApiMetadata;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+/**
+ * Implementation of {@link ApiMetadata} for the Enterprise Chef api.
+ */
+public class EnterpriseChefApiMetadata extends BaseHttpApiMetadata<EnterpriseChefApi> {
+
+   @Override
+   public Builder toBuilder() {
+      return new Builder().fromApiMetadata(this);
+   }
+
+   public EnterpriseChefApiMetadata() {
+      this(new Builder());
+   }
+
+   protected EnterpriseChefApiMetadata(Builder builder) {
+      super(builder);
+   }
+
+   public static Properties defaultProperties() {
+      return ChefApiMetadata.defaultProperties();
+   }
+
+   public static class Builder extends BaseHttpApiMetadata.Builder<EnterpriseChefApi, Builder> {
+
+      protected Builder() {
+         id("enterprisechef")
+               .name("Enterprise Chef Api")
+               .identityName("User")
+               .credentialName("Certificate")
+               .version(ChefApiMetadata.DEFAULT_API_VERSION)
+               .documentation(URI.create("http://www.opscode.com/support"))
+               .defaultEndpoint("https://api.opscode.com")
+               .view(ChefContext.class)
+               .defaultProperties(EnterpriseChefApiMetadata.defaultProperties())
+               .defaultModules(
+                     ImmutableSet.<Class<? extends Module>> of(EnterpriseChefHttpApiModule.class,
+                           ChefParserModule.class, ChefBootstrapModule.class, JMXOhaiModule.class));
+      }
+
+      @Override
+      public EnterpriseChefApiMetadata build() {
+         return new EnterpriseChefApiMetadata(this);
+      }
+
+      @Override
+      protected Builder self() {
+         return this;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadata.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadata.java b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadata.java
new file mode 100644
index 0000000..336ea7a
--- /dev/null
+++ b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadata.java
@@ -0,0 +1,86 @@
+/*
+ * 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.enterprisechef;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.providers.ProviderMetadata;
+import org.jclouds.providers.internal.BaseProviderMetadata;
+
+/**
+ * Implementation of @ link org.jclouds.types.ProviderMetadata} for Enterprise Chef
+ */
+public class EnterpriseChefProviderMetadata extends BaseProviderMetadata
+{
+
+    public static Builder builder()
+    {
+        return new Builder();
+    }
+
+    @Override
+    public Builder toBuilder()
+    {
+        return builder().fromProviderMetadata(this);
+    }
+
+    public EnterpriseChefProviderMetadata()
+    {
+        super(builder());
+    }
+
+    public EnterpriseChefProviderMetadata(Builder builder)
+    {
+        super(builder);
+    }
+
+    public static Properties defaultProperties()
+    {
+        Properties properties = new Properties();
+        return properties;
+    }
+
+    public static class Builder extends BaseProviderMetadata.Builder
+    {
+
+        protected Builder()
+        {
+            id("enterprisechef") //
+                .name("OpsCode Enterprise Chef") //
+                .endpoint("https://api.opscode.com") //
+                .homepage(URI.create("https://manage.opscode.com")) //
+                .console(URI.create("https://manage.opscode.com")) //
+                .apiMetadata(new EnterpriseChefApiMetadata()) //
+                .defaultProperties(EnterpriseChefProviderMetadata.defaultProperties());
+        }
+
+        @Override
+        public EnterpriseChefProviderMetadata build()
+        {
+            return new EnterpriseChefProviderMetadata(this);
+        }
+
+        @Override
+        public Builder fromProviderMetadata(ProviderMetadata in)
+        {
+            super.fromProviderMetadata(in);
+            return this;
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayload.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayload.java b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayload.java
new file mode 100644
index 0000000..2573b9d
--- /dev/null
+++ b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/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.enterprisechef.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.enterprisechef.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/867c7a40/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/binders/GroupName.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/binders/GroupName.java b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/binders/GroupName.java
new file mode 100644
index 0000000..12286a6
--- /dev/null
+++ b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/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.enterprisechef.binders;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.inject.Singleton;
+
+import org.jclouds.enterprisechef.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/867c7a40/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/config/EnterpriseChefHttpApiModule.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/config/EnterpriseChefHttpApiModule.java b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/config/EnterpriseChefHttpApiModule.java
new file mode 100644
index 0000000..ed0a49f
--- /dev/null
+++ b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/config/EnterpriseChefHttpApiModule.java
@@ -0,0 +1,36 @@
+/*
+ * 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.enterprisechef.config;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.BaseChefHttpApiModule;
+import org.jclouds.enterprisechef.EnterpriseChefApi;
+import org.jclouds.rest.ConfiguresHttpApi;
+
+/**
+ * Configures the Enterprise Chef connection.
+ */
+@ConfiguresHttpApi
+public class EnterpriseChefHttpApiModule extends BaseChefHttpApiModule<EnterpriseChefApi> {
+
+   @Override
+   protected void configure() {
+      super.configure();
+      bind(ChefApi.class).to(EnterpriseChefApi.class);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/domain/Group.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/domain/Group.java b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/domain/Group.java
new file mode 100644
index 0000000..8535ca2
--- /dev/null
+++ b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/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.enterprisechef.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/867c7a40/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/domain/User.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/domain/User.java b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/domain/User.java
new file mode 100644
index 0000000..da4e9e5
--- /dev/null
+++ b/providers/enterprise-chef/src/main/java/org/jclouds/enterprisechef/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.enterprisechef.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/867c7a40/providers/enterprise-chef/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata b/providers/enterprise-chef/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata
new file mode 100644
index 0000000..7861cbb
--- /dev/null
+++ b/providers/enterprise-chef/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata
@@ -0,0 +1 @@
+org.jclouds.enterprisechef.EnterpriseChefProviderMetadata

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiExpectTest.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiExpectTest.java b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiExpectTest.java
new file mode 100644
index 0000000..3785cf6
--- /dev/null
+++ b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiExpectTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.enterprisechef;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.chef.BaseChefApiExpectTest;
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.enterprisechef.config.EnterpriseChefHttpApiModule;
+import org.jclouds.enterprisechef.domain.Group;
+import org.jclouds.enterprisechef.domain.User;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.providers.ProviderMetadata;
+import org.jclouds.rest.ConfiguresRestClient;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.inject.Module;
+
+/**
+ * Expect tests for the {@link EnterpriseChefApi} class.
+ */
+@Test(groups = "unit", testName = "EnterpriseChefApiExpectTest")
+public class EnterpriseChefApiExpectTest extends BaseChefApiExpectTest<EnterpriseChefApi> {
+   public EnterpriseChefApiExpectTest() {
+      provider = "enterprisechef";
+   }
+
+   public void testGetUserReturns2xx() {
+      EnterpriseChefApi api = requestSendsResponse(
+            signed(HttpRequest.builder() //
+                  .method("GET") //
+                  .endpoint("https://api.opscode.com/users/nacx") //
+                  .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+                  .addHeader("Accept", MediaType.APPLICATION_JSON).build()), //
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/user.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+
+      User user = api.getUser("nacx");
+      assertEquals(user.getUsername(), "nacx");
+      assertEquals(user.getDisplayName(), "Ignasi Barrera");
+   }
+
+   public void testGetUserReturns404() {
+      EnterpriseChefApi api = requestSendsResponse(signed(HttpRequest.builder() //
+            .method("GET") //
+            .endpoint("https://api.opscode.com/users/foo") //
+            .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+            .addHeader("Accept", MediaType.APPLICATION_JSON) //
+            .build()), //
+            HttpResponse.builder().statusCode(404).build());
+
+      assertNull(api.getUser("foo"));
+   }
+
+   public void testListGroups() {
+      EnterpriseChefApi api = requestSendsResponse(
+            signed(HttpRequest.builder() //
+                  .method("GET") //
+                  .endpoint("https://api.opscode.com/groups") //
+                  .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+                  .addHeader("Accept", MediaType.APPLICATION_JSON).build()), //
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/groups.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+
+      Set<String> groups = api.listGroups();
+      assertEquals(groups.size(), 5);
+      assertTrue(groups.contains("admins"));
+   }
+
+   public void testGetGroupReturns2xx() {
+      EnterpriseChefApi api = requestSendsResponse(
+            signed(HttpRequest.builder() //
+                  .method("GET") //
+                  .endpoint("https://api.opscode.com/groups/admins") //
+                  .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+                  .addHeader("Accept", MediaType.APPLICATION_JSON).build()), //
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/group.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+
+      Group group = api.getGroup("admins");
+      assertEquals(group.getName(), "admins");
+      assertEquals(group.getGroupname(), "admins");
+   }
+
+   public void testGetGroupReturns404() {
+      EnterpriseChefApi api = requestSendsResponse(signed(HttpRequest.builder() //
+            .method("GET") //
+            .endpoint("https://api.opscode.com/groups/foo") //
+            .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+            .addHeader("Accept", MediaType.APPLICATION_JSON) //
+            .build()), //
+            HttpResponse.builder().statusCode(404).build());
+
+      assertNull(api.getGroup("foo"));
+   }
+
+   public void testCreateGroupReturns2xx() {
+      EnterpriseChefApi api = requestSendsResponse(signed(HttpRequest.builder() //
+            .method("POST") //
+            .endpoint("https://api.opscode.com/groups") //
+            .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+            .addHeader("Accept", MediaType.APPLICATION_JSON) //
+            .payload(payloadFromStringWithContentType("{\"groupname\":\"foo\"}", MediaType.APPLICATION_JSON)) //
+            .build()), //
+            HttpResponse.builder().statusCode(201).build());
+
+      api.createGroup("foo");
+   }
+
+   public void testDeleteGroupReturns2xx() {
+      EnterpriseChefApi api = requestSendsResponse(signed(HttpRequest.builder() //
+            .method("DELETE") //
+            .endpoint("https://api.opscode.com/groups/foo") //
+            .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+            .addHeader("Accept", MediaType.APPLICATION_JSON) //
+            .build()), //
+            HttpResponse.builder().statusCode(200).build());
+
+      api.deleteGroup("foo");
+   }
+
+   @Test(expectedExceptions = ResourceNotFoundException.class)
+   public void testDeleteGroupFailsOn404() {
+      EnterpriseChefApi api = requestSendsResponse(signed(HttpRequest.builder() //
+            .method("DELETE") //
+            .endpoint("https://api.opscode.com/groups/foo") //
+            .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+            .addHeader("Accept", MediaType.APPLICATION_JSON) //
+            .build()), //
+            HttpResponse.builder().statusCode(404).build());
+
+      api.deleteGroup("foo");
+   }
+
+   public void testUpdateGroupReturns2xx() {
+      EnterpriseChefApi api = requestSendsResponse(signed(HttpRequest.builder() //
+            .method("PUT") //
+            .endpoint("https://api.opscode.com/groups/admins") //
+            .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+            .addHeader("Accept", MediaType.APPLICATION_JSON) //
+            .payload(payloadFromResourceWithContentType("/group-update.json", MediaType.APPLICATION_JSON)) //
+            .build()), //
+            HttpResponse.builder().statusCode(200).build());
+
+      Group group = Group.builder("admins").client("abiquo").group("admins").user("nacx").build();
+      api.updateGroup(group);
+   }
+
+   @Test(expectedExceptions = ResourceNotFoundException.class)
+   public void testUpdateGroupFailsOn404() {
+      EnterpriseChefApi api = requestSendsResponse(signed(HttpRequest.builder() //
+            .method("PUT") //
+            .endpoint("https://api.opscode.com/groups/admins") //
+            .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+            .addHeader("Accept", MediaType.APPLICATION_JSON) //
+            .payload(payloadFromResourceWithContentType("/group-update.json", MediaType.APPLICATION_JSON)) //
+            .build()), //
+            HttpResponse.builder().statusCode(404).build());
+
+      Group group = Group.builder("admins").client("abiquo").group("admins").user("nacx").build();
+      api.updateGroup(group);
+   }
+
+   @Override
+   protected Module createModule() {
+      return new TestEnterpriseChefRestClientModule();
+   }
+
+   @ConfiguresRestClient
+   static class TestEnterpriseChefRestClientModule extends EnterpriseChefHttpApiModule {
+      @Override
+      protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
+         return "timestamp";
+      }
+   }
+
+   @Override
+   protected ProviderMetadata createProviderMetadata() {
+      return new EnterpriseChefProviderMetadata();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiLiveTest.java b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiLiveTest.java
new file mode 100644
index 0000000..6ac99c1
--- /dev/null
+++ b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiLiveTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.enterprisechef;
+
+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 java.util.Set;
+import java.util.UUID;
+
+import org.jclouds.chef.internal.BaseChefApiLiveTest;
+import org.jclouds.enterprisechef.domain.Group;
+import org.jclouds.enterprisechef.domain.User;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.testng.annotations.Test;
+
+/**
+ * Tests behavior of the EnterpriseChefApi.
+ */
+@Test(groups = "live", singleThreaded = true, testName = "EnterpriseChefApiLiveTest")
+public class EnterpriseChefApiLiveTest extends BaseChefApiLiveTest<EnterpriseChefApi> {
+
+   private static final String GROUP_NAME = System.getProperty("user.name") + "-jcloudstest";
+   private static final String ORG_NAME = System.getProperty("test.enterprisechef.org");
+
+   public EnterpriseChefApiLiveTest() {
+      provider = "enterprisechef";
+   }
+
+   @Override
+   @Test
+   public void testSearchClientsWithOptions() throws Exception {
+      // This test will fail because Enterprise Chef does not index client name.
+      // Once it is fixes, the test should succeed.
+      // See: http://tickets.opscode.com/browse/CHEF-2477
+      super.testSearchClientsWithOptions();
+   }
+
+   public void testGetUser() {
+      User user = api.getUser(identity);
+      assertEquals(user.getUsername(), identity);
+      assertNotNull(user.getPublicKey());
+   }
+
+   public void testGetUnexistingUser() {
+      User user = api.getUser(UUID.randomUUID().toString());
+      assertNull(user);
+   }
+
+   public void testListGroups() {
+      Set<String> groups = api.listGroups();
+      assertNotNull(groups);
+      assertFalse(groups.isEmpty());
+   }
+
+   public void testGetUnexistingGroup() {
+      Group group = api.getGroup(UUID.randomUUID().toString());
+      assertNull(group);
+   }
+
+   public void testCreateGroup() {
+      api.createGroup(GROUP_NAME);
+      Group group = api.getGroup(GROUP_NAME);
+      assertNotNull(group);
+      assertEquals(group.getGroupname(), GROUP_NAME);
+   }
+
+   @Test(dependsOnMethods = "testCreateGroup")
+   public void testUpdateGroup() {
+      Group group = api.getGroup(GROUP_NAME);
+      Group updated = Group.builder(group.getGroupname()) //
+            .actors(group.getActors()) //
+            .orgname(group.getOrgname()) //
+            .name(group.getName()) //
+            .groups(group.getGroups()) //
+            .client(ORG_NAME + "-validator") //
+            .user(identity) //
+            .build();
+
+      api.updateGroup(updated);
+      group = api.getGroup(GROUP_NAME);
+
+      assertNotNull(group);
+      assertTrue(group.getUsers().contains(identity));
+      assertTrue(group.getClients().contains(ORG_NAME + "-validator"));
+   }
+
+   @Test(expectedExceptions = ResourceNotFoundException.class)
+   public void testUpdateUnexistingGroup() {
+      api.updateGroup(Group.builder(UUID.randomUUID().toString()).build());
+   }
+
+   @Test(dependsOnMethods = "testUpdateGroup")
+   public void testDeleteGroup() {
+      api.deleteGroup(GROUP_NAME);
+      Group group = api.getGroup(GROUP_NAME);
+      assertNull(group);
+   }
+
+   @Test(expectedExceptions = ResourceNotFoundException.class)
+   public void testDeleteUnexistingGroup() {
+      api.deleteGroup(UUID.randomUUID().toString());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadataTest.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadataTest.java b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadataTest.java
new file mode 100644
index 0000000..9675af8
--- /dev/null
+++ b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadataTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.enterprisechef;
+
+import org.jclouds.providers.internal.BaseProviderMetadataTest;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for the {@link EnterpriseChefProviderMetadata} class.
+ */
+@Test(groups = "unit", testName = "EnterpriseChefProviderTest")
+public class EnterpriseChefProviderMetadataTest extends BaseProviderMetadataTest {
+
+   public EnterpriseChefProviderMetadataTest() {
+      super(new EnterpriseChefProviderMetadata(), new EnterpriseChefApiMetadata());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayloadTest.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayloadTest.java b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayloadTest.java
new file mode 100644
index 0000000..4e9a3c2
--- /dev/null
+++ b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/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.enterprisechef.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.enterprisechef.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/867c7a40/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/binders/GroupNameTest.java
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/binders/GroupNameTest.java b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/binders/GroupNameTest.java
new file mode 100644
index 0000000..98ea715
--- /dev/null
+++ b/providers/enterprise-chef/src/test/java/org/jclouds/enterprisechef/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.enterprisechef.binders;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.enterprisechef.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");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/test/resources/group-update.json
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/test/resources/group-update.json b/providers/enterprise-chef/src/test/resources/group-update.json
new file mode 100644
index 0000000..cc7bf6a
--- /dev/null
+++ b/providers/enterprise-chef/src/test/resources/group-update.json
@@ -0,0 +1 @@
+{"groupname":"admins","actors":{"clients":["abiquo"],"groups":["admins"],"users":["nacx"]}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/test/resources/group.json
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/test/resources/group.json b/providers/enterprise-chef/src/test/resources/group.json
new file mode 100644
index 0000000..df3eb28
--- /dev/null
+++ b/providers/enterprise-chef/src/test/resources/group.json
@@ -0,0 +1,14 @@
+{
+  "actors" : [ "abiquo",
+      "nacx",
+      "pivotal"
+    ],
+  "clients" : [ "abiquo" ],
+  "groupname" : "admins",
+  "groups" : [ "billing-admins" ],
+  "name" : "admins",
+  "orgname" : "nacx",
+  "users" : [ "nacx",
+      "pivotal"
+  ]
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/test/resources/groups.json
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/test/resources/groups.json b/providers/enterprise-chef/src/test/resources/groups.json
new file mode 100644
index 0000000..30c8aa6
--- /dev/null
+++ b/providers/enterprise-chef/src/test/resources/groups.json
@@ -0,0 +1,7 @@
+{
+  "965f2db33d302ed4625d172e0bc36920" : "https://api.opscode.com/organizations/nacx/groups/965f2db33d302ed4625d172e0bc36920",
+  "admins" : "https://api.opscode.com/organizations/nacx/groups/admins",
+  "billing-admins" : "https://api.opscode.com/organizations/nacx/groups/billing-admins",
+  "clients" : "https://api.opscode.com/organizations/nacx/groups/clients",
+  "users" : "https://api.opscode.com/organizations/nacx/groups/users"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/enterprise-chef/src/test/resources/user.json
----------------------------------------------------------------------
diff --git a/providers/enterprise-chef/src/test/resources/user.json b/providers/enterprise-chef/src/test/resources/user.json
new file mode 100644
index 0000000..ee35a95
--- /dev/null
+++ b/providers/enterprise-chef/src/test/resources/user.json
@@ -0,0 +1,9 @@
+{
+  "display_name" : "Ignasi Barrera",
+  "email" : "myemail@enterprisechef.org",
+  "first_name" : "Ignasi",
+  "last_name" : "Barrera",
+  "middle_name" : "",
+  "public_key" : "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAp0ytgXbPzqJwOOixn7bTa6VAiNvVIOn+yDPoWbyEfc0li93BHIwv\n01KW/mn55IXnSbMw86rdxisvwPHFfb7URuKuTzME6yrphBiancmNjushZZeBWb8j\nqJhnFIKbaaOqew0LZSyG9ycYODB/HDK/pWTV4Bd1OtLHBNFrnIf+r3HOjJsa4rmK\nWXgSQIQO7be/iRHysApV9tfVH8lo1ETnA08JTrQwDgo9St9YNbydb5V0CiLiQsOa\nIbY09buUK9lXthh/rrRVbGbSwQM6OYdXIEZTN2BFvQ0p5pH8AiTwFqb0ICO46a0S\njfGcXNjC/QfHljAPY3T5xyIOODM8afHCnwIDAQAB\n-----END RSA PUBLIC KEY-----\n",
+  "username" : "nacx"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/providers/pom.xml
----------------------------------------------------------------------
diff --git a/providers/pom.xml b/providers/pom.xml
index 2f64ce5..2fec399 100644
--- a/providers/pom.xml
+++ b/providers/pom.xml
@@ -79,5 +79,6 @@
     <module>aws-route53</module>
     <module>ultradns-ws</module>
     <module>dynect</module>
+    <module>enterprise-chef</module>
   </modules>
 </project>


[03/10] Move jclouds-chef to the main jclouds repo

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSearchDataBagItemFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSearchDataBagItemFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSearchDataBagItemFromJsonTest.java
new file mode 100644
index 0000000..3daf3a8
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSearchDataBagItemFromJsonTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.chef.domain.SearchResult;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+@Test(groups = { "unit" })
+public class ParseSearchDataBagItemFromJsonTest {
+   private ParseSearchDatabagFromJson handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(ParseSearchDatabagFromJson.class);
+   }
+
+   public void test1() {
+      String itemJson = "{\"my_key\":\"my_data\"}";
+      String searchJson = "{\"rows\":[{\"raw_data\": {\"id\":\"item1\",\"my_key\":\"my_data\"}}]}";
+      DatabagItem item = new DatabagItem("item1", itemJson);
+      SearchResult<DatabagItem> result = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(searchJson).build());
+      assertEquals(result.size(), 1);
+      assertEquals(result.iterator().next(), item);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseUploadSandboxFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseUploadSandboxFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseUploadSandboxFromJsonTest.java
new file mode 100644
index 0000000..b92fb3a
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseUploadSandboxFromJsonTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.functions;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.asList;
+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.ChecksumStatus;
+import org.jclouds.chef.domain.UploadSandbox;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code ParseUploadSiteFromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseUploadSandboxFromJsonTest {
+
+   private ParseJson<UploadSandbox> handler;
+   private Injector injector;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<UploadSandbox>>() {
+      }));
+   }
+
+   public void test() {
+      assertEquals(
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+                  .payload(ParseUploadSandboxFromJsonTest.class.getResourceAsStream("/upload-site.json")).build()),
+            UploadSandbox
+                  .builder()
+                  .uri(URI
+                        .create("https://api.opscode.com/organizations/jclouds/sandboxes/d454f71e2a5f400c808d0c5d04c2c88c"))
+                  .checksum(
+                        asList(base16().lowerCase().decode("0c5ecd7788cf4f6c7de2a57193897a6c")),
+                        ChecksumStatus
+                              .builder()
+                              .url(URI
+                                    .create("https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/sandbox-d454f71e2a5f400c808d0c5d04c2c88c/checksum-0c5ecd7788cf4f6c7de2a57193897a6c?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277344702&Signature=FtKyqvYEjhhEKmRY%2B0M8aGPMM7g%3D"))
+                              .needsUpload(true).build())
+                  .checksum(asList(base16().lowerCase().decode("0189e76ccc476701d6b374e5a1a27347")),
+                        ChecksumStatus.builder().build())
+                  .checksum(asList(base16().lowerCase().decode("1dda05ed139664f1f89b9dec482b77c0")),
+                        ChecksumStatus.builder().build()).sandboxId("d454f71e2a5f400c808d0c5d04c2c88c").build()
+
+      );
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/RunListForGroupTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/RunListForGroupTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/RunListForGroupTest.java
new file mode 100644
index 0000000..28b8833
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/RunListForGroupTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.Api;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+@Test(groups = "unit", testName = "RunListForGroupTest")
+public class RunListForGroupTest {
+   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 Json json = injector.getInstance(Json.class);
+
+   @Test(expectedExceptions = IllegalStateException.class)
+   public void testWhenNoDatabagItem() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Client client = createMock(Client.class);
+
+      RunListForGroup fn = new RunListForGroup(new BootstrapConfigForGroup("jclouds", chefApi), json);
+
+      expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(null);
+
+      replay(client);
+      replay(chefApi);
+
+      fn.apply("foo");
+
+      verify(client);
+      verify(chefApi);
+   }
+
+   @Test
+   public void testReadRunList() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Api api = createMock(Api.class);
+
+      RunListForGroup fn = new RunListForGroup(new BootstrapConfigForGroup("jclouds", chefApi), json);
+      DatabagItem config = new DatabagItem("foo",
+            "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
+
+      expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(config);
+
+      replay(api);
+      replay(chefApi);
+
+      assertEquals(fn.apply("foo"), ImmutableList.of("recipe[apache2]", "role[webserver]"));
+
+      verify(api);
+      verify(chefApi);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/UriForResourceTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/UriForResourceTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/UriForResourceTest.java
new file mode 100644
index 0000000..3285a1c
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/UriForResourceTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+
+import org.jclouds.chef.domain.Resource;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+
+/**
+ * Tests behavior of {@code UriForResource}
+ */
+@Test(groups = { "unit" })
+public class UriForResourceTest {
+
+   @Test(expectedExceptions = NullPointerException.class)
+   public void testWithNullInput() {
+      Function<Object, URI> function = new UriForResource();
+      function.apply(null);
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testWithInvalidInput() {
+      Function<Object, URI> function = new UriForResource();
+      function.apply(new Object());
+   }
+
+   @Test
+   public void testWithValidResource() {
+      Function<Object, URI> function = new UriForResource();
+      Resource res = Resource.builder().name("test").url(URI.create("http://foo/bar")).build();
+      URI result = function.apply(res);
+      assertEquals(res.getUrl().toString(), result.toString());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/handlers/ChefApiErrorRetryHandlerTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/handlers/ChefApiErrorRetryHandlerTest.java b/apis/chef/src/test/java/org/jclouds/chef/handlers/ChefApiErrorRetryHandlerTest.java
new file mode 100644
index 0000000..5e99675
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/handlers/ChefApiErrorRetryHandlerTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.handlers;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
+import org.testng.annotations.Test;
+
+/**
+ * Tests behavior of {@code ChefClientErrorRetryHandler}
+ */
+@Test(groups = { "unit" })
+public class ChefApiErrorRetryHandlerTest {
+   @Test
+   public void test401DoesNotRetry() {
+
+      HttpCommand command = createMock(HttpCommand.class);
+      HttpResponse response = createMock(HttpResponse.class);
+      BackoffLimitedRetryHandler retry = createMock(BackoffLimitedRetryHandler.class);
+
+      expect(command.getFailureCount()).andReturn(0);
+      expect(response.getStatusCode()).andReturn(401).atLeastOnce();
+
+      replay(response);
+      replay(retry);
+      replay(command);
+
+      ChefApiErrorRetryHandler handler = new ChefApiErrorRetryHandler(retry);
+
+      assert !handler.shouldRetryRequest(command, response);
+
+      verify(retry);
+      verify(command);
+      verify(response);
+
+   }
+
+   @Test
+   public void test400DoesNotRetry() {
+
+      HttpCommand command = createMock(HttpCommand.class);
+      HttpResponse response = createMock(HttpResponse.class);
+      BackoffLimitedRetryHandler retry = createMock(BackoffLimitedRetryHandler.class);
+
+      expect(command.getFailureCount()).andReturn(0);
+      expect(response.getStatusCode()).andReturn(401).atLeastOnce();
+
+      replay(response);
+      replay(retry);
+      replay(command);
+
+      ChefApiErrorRetryHandler handler = new ChefApiErrorRetryHandler(retry);
+
+      assert !handler.shouldRetryRequest(command, response);
+
+      verify(retry);
+      verify(command);
+      verify(response);
+
+   }
+
+   @Test
+   public void testRetryOn400PutSandbox() {
+
+      HttpCommand command = createMock(HttpCommand.class);
+      BackoffLimitedRetryHandler retry = createMock(BackoffLimitedRetryHandler.class);
+
+      HttpRequest request = HttpRequest.builder().method("PUT")
+            .endpoint("https://api.opscode.com/organizations/jclouds/sandboxes/bfd68d4052f44053b2e593a33b5e1cd5")
+            .build();
+      HttpResponse response = HttpResponse
+            .builder()
+            .statusCode(400)
+            .message("400 Bad Request")
+            .payload(
+                  "{\"error\":[\"Cannot update sandbox bfd68d4052f44053b2e593a33b5e1cd5: checksum 9b7c23369f4b576451216c39f214af6c was not uploaded\"]}")
+            .build();
+
+      expect(command.getFailureCount()).andReturn(0);
+      expect(command.getCurrentRequest()).andReturn(request).atLeastOnce();
+      expect(retry.shouldRetryRequest(command, response)).andReturn(true);
+
+      replay(retry);
+      replay(command);
+
+      ChefApiErrorRetryHandler handler = new ChefApiErrorRetryHandler(retry);
+
+      assert handler.shouldRetryRequest(command, response);
+
+      verify(retry);
+      verify(command);
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefApiLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefApiLiveTest.java
new file mode 100644
index 0000000..225e593
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefApiLiveTest.java
@@ -0,0 +1,542 @@
+/*
+ * 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.internal;
+
+import static com.google.common.base.Throwables.propagate;
+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.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.ChefApi;
+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.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.Closeables;
+import com.google.common.io.Files;
+import com.google.common.primitives.Bytes;
+
+/**
+ * Tests behavior of {@code ChefApi}
+ */
+@Test(groups = { "live", "integration" })
+public abstract class BaseChefApiLiveTest<A extends ChefApi> extends BaseChefLiveTest<A> {
+   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 = 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");
+      }
+
+      // 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()) //
+            .build();
+
+      // upload the cookbook to the remote server
+      api.updateCookbook(PREFIX, "0.0.0", cookbook);
+   }
+
+   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()).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);
+   }
+
+   @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);
+
+      A clientApi = create(overrides, setupModules());
+
+      try {
+         Client client = clientApi.getClient(identity);
+         assertNotNull(client, "Client not found: " + identity);
+      } finally {
+         try {
+            Closeables.close(clientApi, true);
+         } catch (IOException e) {
+            throw propagate(e);
+         }
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefLiveTest.java
new file mode 100644
index 0000000..e314418
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefLiveTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.internal;
+
+import static org.jclouds.reflect.Types2.checkBound;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+
+import org.jclouds.apis.BaseApiLiveTest;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.ChefService;
+import org.jclouds.json.Json;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import com.google.common.io.Files;
+import com.google.common.reflect.TypeToken;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+@Test(groups = "live")
+public abstract class BaseChefLiveTest<A extends ChefApi> extends BaseApiLiveTest<A> {
+
+   protected Injector injector;
+   protected ChefService chefService;
+   protected Json json;
+
+   protected BaseChefLiveTest() {
+      provider = "chef";
+   }
+
+   /**
+    * the credential is a path to the pem file.
+    */
+   @Override
+   protected Properties setupProperties() {
+      Properties overrides = super.setupProperties();
+      credential = setCredentialFromPemFile(overrides, identity, provider + ".credential");
+      return overrides;
+   }
+
+   @Override
+   protected void initialize() {
+      super.initialize();
+      chefService = injector.getInstance(ChefService.class);
+      json = injector.getInstance(Json.class);
+   }
+
+   @Override
+   protected A create(Properties props, Iterable<Module> modules) {
+      injector = newBuilder().modules(modules).overrides(props).buildInjector();
+      return injector.getInstance(resolveApiClass());
+   }
+
+   protected String setCredentialFromPemFile(Properties overrides, String identity, String key) {
+      String val = null;
+      String credentialFromFile = null;
+      if (System.getProperties().containsKey("test." + key)) {
+         val = System.getProperty("test." + key);
+      } else {
+         val = System.getProperty("user.home") + "/.chef/" + identity + ".pem";
+      }
+      try {
+         credentialFromFile = Files.toString(new File(val), Charsets.UTF_8);
+      } catch (IOException e) {
+         throw Throwables.propagate(e);
+      }
+      overrides.setProperty(key, credentialFromFile);
+      return credentialFromFile;
+   }
+
+   @SuppressWarnings("unchecked")
+   private Class<A> resolveApiClass() {
+      return Class.class.cast(checkBound(new TypeToken<A>(getClass()) {
+         private static final long serialVersionUID = 1L;
+      }).getRawType());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefServiceTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefServiceTest.java b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefServiceTest.java
new file mode 100644
index 0000000..e763d8f
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefServiceTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.internal;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.List;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.domain.BootstrapConfig;
+import org.jclouds.chef.filters.SignedHeaderAuthTest;
+import org.jclouds.chef.util.RunListBuilder;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.logging.config.NullLoggingModule;
+import org.jclouds.rest.internal.BaseRestApiTest.MockModule;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+/**
+ * Unit tests for the <code>BaseChefService</code> class.
+ */
+@Test(groups = "unit", testName = "BaseChefServiceTest")
+public class BaseChefServiceTest {
+
+   private BaseChefService chefService;
+
+   @BeforeClass
+   public void setup() {
+      Injector injector = ContextBuilder.newBuilder(new ChefApiMetadata())
+            .credentials(SignedHeaderAuthTest.USER_ID, SignedHeaderAuthTest.PRIVATE_KEY)
+            .modules(ImmutableSet.<Module> of(new MockModule(), new NullLoggingModule())).buildInjector();
+
+      chefService = injector.getInstance(BaseChefService.class);
+   }
+
+   @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "bootstrapConfig must not be null")
+   public void testBuildBootstrapConfigurationWithNullConfig() {
+      chefService.buildBootstrapConfiguration(null);
+   }
+
+   public void testBuildBootstrapConfigurationWithEmptyRunlist() {
+      BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(ImmutableList.<String> of()).build();
+      String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
+      assertEquals(config, "{\"run_list\":[]}");
+   }
+
+   public void testBuildBootstrapConfigurationWithRunlist() {
+      List<String> runlist = new RunListBuilder().addRecipe("apache2").addRole("webserver").build();
+      BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist).build();
+      String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
+      assertEquals(config, "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
+   }
+
+   public void testBuildBootstrapConfigurationWithRunlistAndEmptyAttributes() {
+      List<String> runlist = new RunListBuilder().addRecipe("apache2").addRole("webserver").build();
+      BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist).attributes(new JsonBall("{}"))
+            .build();
+      String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
+      assertEquals(config, "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
+   }
+
+   public void testBuildBootstrapConfigurationWithRunlistAndAttributes() {
+      List<String> runlist = new RunListBuilder().addRecipe("apache2").addRole("webserver").build();
+      BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist)
+            .attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")).build();
+      String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
+      assertEquals(config, "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
+   }
+
+   public void testBuildBootstrapConfigurationWithRunlistAndAttributesAndEnvironment() {
+      List<String> runlist = new RunListBuilder().addRecipe("apache2").addRole("webserver").build();
+      BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist)
+            .attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")).environment("env").build();
+      String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
+      assertEquals(config,
+            "{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\",\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/internal/BaseStubbedOhaiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/internal/BaseStubbedOhaiLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseStubbedOhaiLiveTest.java
new file mode 100644
index 0000000..8779017
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseStubbedOhaiLiveTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.internal;
+
+import java.util.Map;
+
+import org.jclouds.apis.ApiMetadata;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefBootstrapModule;
+import org.jclouds.chef.config.ChefHttpApiModule;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.ohai.AutomaticSupplier;
+import org.jclouds.ohai.config.ConfiguresOhai;
+import org.jclouds.ohai.config.OhaiModule;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+@Test(groups = "live")
+@Deprecated
+public class BaseStubbedOhaiLiveTest extends BaseChefLiveTest<ChefApi> {
+
+   @ConfiguresOhai
+   static class TestOhaiModule extends OhaiModule {
+
+      @Override
+      protected Supplier<Map<String, JsonBall>> provideAutomatic(AutomaticSupplier in) {
+         return Suppliers.<Map<String, JsonBall>> ofInstance(ImmutableMap.of("foo", new JsonBall("bar")));
+      }
+   }
+
+   @Override
+   protected ApiMetadata createApiMetadata() {
+      return new ChefApiMetadata()
+            .toBuilder()
+            .defaultModules(
+                  ImmutableSet.<Class<? extends Module>> of(ChefHttpApiModule.class, ChefParserModule.class,
+                        ChefBootstrapModule.class, TestOhaiModule.class)).build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImplLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImplLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImplLiveTest.java
new file mode 100644
index 0000000..e8b249b
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImplLiveTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.strategy.internal;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.internal.BaseChefLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Tests behavior of {@code CleanupStaleNodesAndClientsImpl} strategies
+ */
+@Test(groups = "live", testName = "CleanupStaleNodesAndClientsImplLiveTest")
+public class CleanupStaleNodesAndClientsImplLiveTest extends BaseChefLiveTest<ChefApi> {
+
+   private CreateNodeAndPopulateAutomaticAttributesImpl creator;
+   private CleanupStaleNodesAndClientsImpl strategy;
+
+   @Override
+   protected void initialize() {
+      super.initialize();
+      this.creator = injector.getInstance(CreateNodeAndPopulateAutomaticAttributesImpl.class);
+      this.strategy = injector.getInstance(CleanupStaleNodesAndClientsImpl.class);
+   }
+
+   @Test
+   public void testExecute() throws InterruptedException {
+      try {
+         creator.execute(prefix, ImmutableSet.<String> of());
+         // http://tickets.corp.opscode.com/browse/PL-522
+         // assert chef.nodeExists(prefix);
+         assertNotNull(api.getNode(prefix));
+         strategy.execute(prefix, 10);
+         assertNotNull(api.getNode(prefix));
+         Thread.sleep(1000);
+         strategy.execute(prefix, 1);
+         assertNull(api.getNode(prefix));
+      } finally {
+         api.deleteNode(prefix);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java
new file mode 100644
index 0000000..60f2661
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Set;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.internal.BaseChefLiveTest;
+import org.jclouds.ohai.config.OhaiModule.CurrentUserProvider;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Tests behavior of {@code CreateNodeAndPopulateAutomaticAttributesImpl}
+ * strategies
+ */
+@Test(groups = "live", testName = "CreateNodeAndPopulateAutomaticAttributesImplLiveTest")
+public class CreateNodeAndPopulateAutomaticAttributesImplLiveTest extends BaseChefLiveTest<ChefApi> {
+
+   private CurrentUserProvider currentUserProvider;
+   private CreateNodeAndPopulateAutomaticAttributesImpl strategy;
+
+   @Override
+   protected void initialize() {
+      super.initialize();
+      this.currentUserProvider = injector.getInstance(CurrentUserProvider.class);
+      this.strategy = injector.getInstance(CreateNodeAndPopulateAutomaticAttributesImpl.class);
+   }
+
+   @Test
+   public void testExecute() {
+      Set<String> runList = ImmutableSet.of("role[" + prefix + "]");
+      try {
+         strategy.execute(prefix, runList);
+         Node node = api.getNode(prefix);
+         assertEquals(node.getName(), prefix);
+         assertEquals(node.getRunList(), runList);
+         assertEquals(node.getAutomaticAttributes().get("current_user").toString(), currentUserProvider.get().toString());
+      } finally {
+         api.deleteNode(prefix);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplTest.java b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplTest.java
new file mode 100644
index 0000000..3ba7110
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.strategy.internal;
+
+import static org.easymock.classextension.EasyMock.createMock;
+import static org.easymock.classextension.EasyMock.replay;
+import static org.easymock.classextension.EasyMock.verify;
+
+import java.util.Map;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.domain.JsonBall;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Tests behavior of {@code CreateNodeAndPopulateAutomaticAttributesImpl}
+ */
+@Test(groups = "unit", testName = "CreateNodeAndPopulateAutomaticAttributesImplTest")
+public class CreateNodeAndPopulateAutomaticAttributesImplTest {
+
+   @Test
+   public void testWithNoRunlist() {
+      ChefApi chef = createMock(ChefApi.class);
+
+      Supplier<Map<String, JsonBall>> automaticSupplier = Suppliers.<Map<String, JsonBall>> ofInstance(ImmutableMap.<String, JsonBall> of());
+
+      Node nodeWithAutomatic = Node.builder().name("name").environment("_default")
+            .automaticAttributes(automaticSupplier.get()).build();
+
+      chef.createNode(nodeWithAutomatic);
+
+      replay(chef);
+
+      CreateNodeAndPopulateAutomaticAttributesImpl updater = new CreateNodeAndPopulateAutomaticAttributesImpl(chef,
+            automaticSupplier);
+
+      updater.execute("name", ImmutableSet.<String> of());
+      verify(chef);
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/DeleteAllApisAndNodesInListImplLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/DeleteAllApisAndNodesInListImplLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/DeleteAllApisAndNodesInListImplLiveTest.java
new file mode 100644
index 0000000..6f0e595
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/DeleteAllApisAndNodesInListImplLiveTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.strategy.internal;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.internal.BaseChefLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Tests behavior of {@code DeleteAllApisAndNodesInListImpl} strategies
+ */
+@Test(groups = "live", testName = "DeleteAllApisAndNodesInListImplLiveTest")
+public class DeleteAllApisAndNodesInListImplLiveTest extends BaseChefLiveTest<ChefApi> {
+
+   private DeleteAllNodesInListImpl strategy;
+   private CreateNodeAndPopulateAutomaticAttributesImpl creator;
+
+   @Override
+   protected void initialize() {
+      super.initialize();
+      this.creator = injector.getInstance(CreateNodeAndPopulateAutomaticAttributesImpl.class);
+      this.strategy = injector.getInstance(DeleteAllNodesInListImpl.class);
+   }
+
+   @Test
+   public void testExecute() throws InterruptedException {
+      try {
+         creator.execute(prefix, ImmutableSet.<String> of());
+         creator.execute(prefix + 1, ImmutableSet.<String> of());
+
+         // http://tickets.corp.opscode.com/browse/PL-522
+         // assert api.nodeExists(prefix);
+         assertNotNull(api.getNode(prefix));
+         assertNotNull(api.getNode(prefix + 1));
+
+         strategy.execute(ImmutableSet.of(prefix, prefix + 1));
+         assertNull(api.getNode(prefix));
+         assertNull(api.getNode(prefix + 1));
+      } finally {
+         api.deleteNode(prefix);
+         api.deleteNode(prefix + 1);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImplLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImplLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImplLiveTest.java
new file mode 100644
index 0000000..5f68fcf
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImplLiveTest.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import static com.google.common.collect.Iterables.size;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.ChecksumStatus;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.domain.Metadata;
+import org.jclouds.chef.domain.Resource;
+import org.jclouds.chef.domain.Sandbox;
+import org.jclouds.chef.domain.UploadSandbox;
+import org.jclouds.chef.internal.BaseChefLiveTest;
+import org.jclouds.io.Payloads;
+import org.jclouds.io.payloads.FilePayload;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+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 ListCookbookVersionsInEnvironmentImpl} strategies
+ */
+@Test(groups = "live", testName = "ListCookbookVersionsInEnvironmentImplLiveTest")
+public class ListCookbookVersionsInEnvironmentImplLiveTest extends BaseChefLiveTest<ChefApi> {
+   public static final String PREFIX = "jcloudstest-strategy-" + System.getProperty("user.name");
+
+   private ListCookbookVersionsInEnvironmentImpl strategy;
+   private CreateNodeAndPopulateAutomaticAttributesImpl creator;
+
+   private ExecutorService testExecutorService;
+   private ListeningExecutorService testListeningExecutorService;
+
+   @Override
+   protected void initialize() {
+      super.initialize();
+
+      try {
+         createCookbooksWithMultipleVersions(PREFIX);
+         createCookbooksWithMultipleVersions(PREFIX + 1);
+      } catch (Exception e) {
+         fail("Could not create cookbooks", e);
+      }
+
+      this.strategy = injector.getInstance(ListCookbookVersionsInEnvironmentImpl.class);
+      this.testExecutorService = Executors.newFixedThreadPool(5);
+      this.testListeningExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
+   }
+
+   @AfterClass(groups = { "integration", "live" })
+   @Override
+   protected void tearDown() {
+      api.deleteCookbook(PREFIX, "0.0.0");
+      api.deleteCookbook(PREFIX, "1.0.0");
+      api.deleteCookbook(PREFIX + 1, "0.0.0");
+      api.deleteCookbook(PREFIX + 1, "1.0.0");
+
+      this.testExecutorService.shutdown();
+      this.testListeningExecutorService.shutdown();
+
+      super.tearDown();
+   }
+
+   @Test
+   public void testExecute() {
+      assertTrue(size(strategy.execute("_default")) > 0, "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService, "_default")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService, "_default")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteWithNumVersions() {
+      assertTrue(size(strategy.execute("_default", "2")) > 0, "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithNumVersionsAndExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService, "_default", "2")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithNumVersionsAndListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService, "_default", "2")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteWithNumVersionsAll() {
+      assertTrue(size(strategy.execute("_default", "all")) > 0, "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithNumVersionsAllAndExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService, "_default", "all")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithNumVersionsAllAndListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService, "_default", "all")) > 0,
+            "Expected one or more elements");
+   }
+
+   private FilePayload uploadContent(String fileName) throws Exception {
+      // Define the file you want in the cookbook
+      File file = new File(System.getProperty("user.dir"), fileName);
+      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", e);
+      }
+
+      return content;
+   }
+
+   private void createCookbooksWithMultipleVersions(String cookbookName) throws Exception {
+      FilePayload v0content = uploadContent("pom.xml");
+      FilePayload v1content = uploadContent("../README.md");
+
+      // Create the metadata of the cookbook
+      Metadata metadata = Metadata.builder() //
+            .name(cookbookName) //
+            .version("0.0.0") //
+            .description("Jclouds test uploaded cookbook") //
+            .maintainer("jclouds") //
+            .maintainerEmail("someone@jclouds.org") //
+            .license("Apache 2.0") //
+            .build();
+
+      // Create new cookbook version
+      CookbookVersion cookbook = CookbookVersion.builder(cookbookName, "0.0.0") //
+            .metadata(metadata) //
+            .rootFile(Resource.builder().fromPayload(v0content).build()) //
+            .build();
+
+      // upload the cookbook to the remote server
+      api.updateCookbook(cookbookName, "0.0.0", cookbook);
+
+      // Create the metadata of the cookbook
+      metadata = Metadata.builder() //
+            .name(cookbookName) //
+            .version("1.0.0") //
+            .description("Jclouds test uploaded cookbook") //
+            .maintainer("jclouds") //
+            .maintainerEmail("someone@jclouds.org") //
+            .license("Apache 2.0") //
+            .build();
+
+      // Create a new cookbook version
+      cookbook = CookbookVersion.builder(cookbookName, "1.0.0") //
+            .metadata(metadata) //
+            .rootFile(Resource.builder().fromPayload(v1content).build()) //
+            .build();
+
+      // upload the cookbook to the remote server
+      api.updateCookbook(cookbookName, "1.0.0", cookbook);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListNodesImplLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListNodesImplLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListNodesImplLiveTest.java
new file mode 100644
index 0000000..903b998
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListNodesImplLiveTest.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.strategy.internal;
+
+import static com.google.common.collect.Iterables.size;
+import static org.testng.Assert.assertTrue;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.internal.BaseChefLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Tests behavior of {@code ListNodesImpl} strategies
+ */
+@Test(groups = "live", testName = "ListNodesImplLiveTest")
+public class ListNodesImplLiveTest extends BaseChefLiveTest<ChefApi> {
+
+   private ListNodesImpl strategy;
+   private CreateNodeAndPopulateAutomaticAttributesImpl creator;
+
+   private ExecutorService testExecutorService;
+   private ListeningExecutorService testListeningExecutorService;
+
+   @Override
+   protected void initialize() {
+      super.initialize();
+      this.creator = injector.getInstance(CreateNodeAndPopulateAutomaticAttributesImpl.class);
+      this.strategy = injector.getInstance(ListNodesImpl.class);
+      creator.execute(prefix, ImmutableSet.<String> of());
+      creator.execute(prefix + 1, ImmutableSet.<String> of());
+
+      this.testExecutorService = Executors.newFixedThreadPool(5);
+      this.testListeningExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
+   }
+
+   @AfterClass(groups = { "integration", "live" })
+   @Override
+   protected void tearDown() {
+      api.deleteNode(prefix);
+      api.deleteNode(prefix + 1);
+
+      this.testExecutorService.shutdown();
+      this.testListeningExecutorService.shutdown();
+
+      super.tearDown();
+   }
+
+   @Test
+   public void testExecute() {
+      assertTrue(size(strategy.execute()) > 0, "Expected one or more elements");
+   }
+
+   public void testExecuteConcurrentlyWithExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService)) > 0, "Expected one or more elements");
+   }
+
+   public void testExecuteConcurrentlyWithListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService)) > 0, "Expected one or more elements");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImplLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImplLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImplLiveTest.java
new file mode 100644
index 0000000..f655d52
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImplLiveTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.strategy.internal;
+
+import static com.google.common.collect.Iterables.size;
+import static org.testng.Assert.assertTrue;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.internal.BaseChefLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Tests behavior of {@code ListNodesInEnvironmentImpl} strategies
+ */
+@Test(groups = "live", testName = "ListNodesInEnvironmentImplLiveTest")
+public class ListNodesInEnvironmentImplLiveTest extends BaseChefLiveTest<ChefApi> {
+
+   private ListNodesInEnvironmentImpl strategy;
+   private CreateNodeAndPopulateAutomaticAttributesImpl creator;
+
+   private ExecutorService testExecutorService;
+   private ListeningExecutorService testListeningExecutorService;
+
+   @Override
+   protected void initialize() {
+      super.initialize();
+      this.creator = injector.getInstance(CreateNodeAndPopulateAutomaticAttributesImpl.class);
+      this.strategy = injector.getInstance(ListNodesInEnvironmentImpl.class);
+      creator.execute(prefix, ImmutableSet.<String>of());
+      creator.execute(prefix + 1, ImmutableSet.<String>of());
+
+      this.testExecutorService = Executors.newFixedThreadPool(5);
+      this.testListeningExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
+   }
+
+   @AfterClass(groups = { "integration", "live" })
+   @Override
+   protected void tearDown() {
+      api.deleteNode(prefix);
+      api.deleteNode(prefix + 1);
+
+      this.testExecutorService.shutdown();
+      this.testListeningExecutorService.shutdown();
+
+      super.tearDown();
+   }
+
+   @Test
+   public void testExecute() {
+      assertTrue(size(strategy.execute("_default")) > 0, "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService, "_default")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService, "_default")) > 0,
+            "Expected one or more elements");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java
new file mode 100644
index 0000000..a8f7438
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.strategy.internal;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Set;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.internal.BaseChefLiveTest;
+import org.jclouds.ohai.config.OhaiModule.CurrentUserProvider;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Tests behavior of {@code UpdateAutomaticAttributesOnNodeImpl} strategies
+ */
+@Test(groups = "live", testName = "UpdateAutomaticAttributesOnNodeImplLiveTest")
+public class UpdateAutomaticAttributesOnNodeImplLiveTest extends BaseChefLiveTest<ChefApi> {
+
+   private CurrentUserProvider currentUserProvider;
+   private UpdateAutomaticAttributesOnNodeImpl strategy;
+
+   @Override
+   protected void initialize() {
+      super.initialize();
+      this.currentUserProvider = injector.getInstance(CurrentUserProvider.class);
+      this.strategy = injector.getInstance(UpdateAutomaticAttributesOnNodeImpl.class);
+   }
+
+   @Test
+   public void testExecute() {
+      Set<String> runList = ImmutableSet.of("role[" + prefix + "]");
+      try {
+         api.createNode(Node.builder().name(prefix).runList(runList).environment("_default").build());
+         strategy.execute(prefix);
+         Node node = api.getNode(prefix);
+         assertEquals(node.getName(), prefix);
+         assertEquals(node.getRunList(), runList);
+         assertEquals(node.getAutomaticAttributes().get("current_user").toString(), currentUserProvider.get().toString());
+      } finally {
+         api.deleteNode(prefix);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplTest.java b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplTest.java
new file mode 100644
index 0000000..2e0d198
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.strategy.internal;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.util.Map;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.domain.JsonBall;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests behavior of {@code UpdateAutomaticAttributesOnNodeImpl}
+ */
+@Test(groups = { "unit" })
+public class UpdateAutomaticAttributesOnNodeImplTest {
+
+   @Test
+   public void test() {
+      ChefApi chef = createMock(ChefApi.class);
+
+      Map<String, JsonBall> automatic = ImmutableMap.<String, JsonBall> of();
+      Supplier<Map<String, JsonBall>> automaticSupplier = Suppliers.<Map<String, JsonBall>> ofInstance(automatic);
+
+      Node node = Node.builder().name("name").environment("_default").build();
+      Node nodeWithAutomatic = Node.builder().name("name").environment("_default").automaticAttributes(automatic)
+            .build();
+
+      expect(chef.getNode("name")).andReturn(node);
+      expect(chef.updateNode(nodeWithAutomatic)).andReturn(null);
+
+      replay(chef);
+
+      UpdateAutomaticAttributesOnNodeImpl updater = new UpdateAutomaticAttributesOnNodeImpl(chef, automaticSupplier);
+
+      updater.execute("name");
+      verify(chef);
+
+   }
+}


[10/10] git commit: Move jclouds-chef to the main jclouds repo

Posted by na...@apache.org.
Move jclouds-chef to the main jclouds repo


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/867c7a40
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/867c7a40
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/867c7a40

Branch: refs/heads/master
Commit: 867c7a407c1db8f56ff3211d70481755cf71440f
Parents: eba727f
Author: Ignasi Barrera <na...@apache.org>
Authored: Mon Jul 28 17:34:30 2014 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Mon Jul 28 21:08:54 2014 +0200

----------------------------------------------------------------------
 apis/chef/pom.xml                               | 131 +++
 apis/chef/src/main/clojure/org/jclouds/chef.clj | 261 ++++++
 .../src/main/java/org/jclouds/chef/ChefApi.java | 853 +++++++++++++++++++
 .../java/org/jclouds/chef/ChefApiMetadata.java  | 110 +++
 .../main/java/org/jclouds/chef/ChefContext.java |  37 +
 .../main/java/org/jclouds/chef/ChefService.java | 263 ++++++
 .../binders/BindChecksumsToJsonPayload.java     |  54 ++
 .../BindCreateClientOptionsToJsonPayload.java   |  71 ++
 .../BindGenerateKeyForClientToJsonPayload.java  |  35 +
 .../org/jclouds/chef/binders/DatabagItemId.java |  32 +
 .../jclouds/chef/binders/EnvironmentName.java   |  31 +
 .../java/org/jclouds/chef/binders/NodeName.java |  32 +
 .../java/org/jclouds/chef/binders/RoleName.java |  32 +
 .../chef/config/BaseChefHttpApiModule.java      | 208 +++++
 .../chef/config/ChefBootstrapModule.java        | 121 +++
 .../jclouds/chef/config/ChefHttpApiModule.java  |  28 +
 .../jclouds/chef/config/ChefParserModule.java   | 321 +++++++
 .../org/jclouds/chef/config/ChefProperties.java | 110 +++
 .../org/jclouds/chef/config/CookbookParser.java |  41 +
 .../chef/config/CookbookVersionsParser.java     |  41 +
 .../org/jclouds/chef/config/InstallChef.java    |  37 +
 .../java/org/jclouds/chef/config/Validator.java |  40 +
 .../java/org/jclouds/chef/domain/Attribute.java | 235 +++++
 .../jclouds/chef/domain/BootstrapConfig.java    |  95 +++
 .../org/jclouds/chef/domain/ChecksumStatus.java | 102 +++
 .../java/org/jclouds/chef/domain/Client.java    | 182 ++++
 .../jclouds/chef/domain/CookbookDefinition.java | 217 +++++
 .../jclouds/chef/domain/CookbookVersion.java    | 369 ++++++++
 .../org/jclouds/chef/domain/DatabagItem.java    |  63 ++
 .../org/jclouds/chef/domain/Environment.java    | 178 ++++
 .../java/org/jclouds/chef/domain/Metadata.java  | 447 ++++++++++
 .../main/java/org/jclouds/chef/domain/Node.java | 263 ++++++
 .../java/org/jclouds/chef/domain/Resource.java  | 169 ++++
 .../main/java/org/jclouds/chef/domain/Role.java | 205 +++++
 .../java/org/jclouds/chef/domain/Sandbox.java   | 195 +++++
 .../org/jclouds/chef/domain/SearchResult.java   |  46 +
 .../org/jclouds/chef/domain/UploadSandbox.java  | 136 +++
 .../jclouds/chef/filters/SignedHeaderAuth.java  | 200 +++++
 .../chef/functions/BootstrapConfigForGroup.java |  61 ++
 .../jclouds/chef/functions/ClientForGroup.java  |  69 ++
 .../chef/functions/GroupToBootScript.java       | 130 +++
 ...seCookbookDefinitionCheckingChefVersion.java |  49 ++
 .../ParseCookbookDefinitionFromJson.java        |  50 ++
 .../ParseCookbookDefinitionFromJsonv10.java     |  52 ++
 .../ParseCookbookDefinitionListFromJsonv10.java |  63 ++
 ...arseCookbookVersionsCheckingChefVersion.java |  49 ++
 .../ParseCookbookVersionsV09FromJson.java       |  49 ++
 .../ParseCookbookVersionsV10FromJson.java       |  59 ++
 .../ParseErrorFromJsonOrReturnBody.java         |  55 ++
 .../chef/functions/ParseKeySetFromJson.java     |  45 +
 .../functions/ParseSearchClientsFromJson.java   |  35 +
 .../functions/ParseSearchDatabagFromJson.java   |  77 ++
 .../ParseSearchEnvironmentsFromJson.java        |  35 +
 .../functions/ParseSearchNodesFromJson.java     |  35 +
 .../functions/ParseSearchResultFromJson.java    |  50 ++
 .../functions/ParseSearchRolesFromJson.java     |  35 +
 .../jclouds/chef/functions/RunListForGroup.java |  61 ++
 .../jclouds/chef/functions/UriForResource.java  |  42 +
 .../chef/handlers/ChefApiErrorRetryHandler.java |  67 ++
 .../jclouds/chef/handlers/ChefErrorHandler.java |  81 ++
 .../jclouds/chef/internal/BaseChefService.java  | 299 +++++++
 .../jclouds/chef/internal/ChefContextImpl.java  |  55 ++
 .../chef/options/CreateClientOptions.java       |  64 ++
 .../org/jclouds/chef/options/SearchOptions.java |  95 +++
 .../predicates/CookbookVersionPredicates.java   |  93 ++
 .../strategy/CleanupStaleNodesAndClients.java   |  32 +
 ...reateNodeAndPopulateAutomaticAttributes.java |  33 +
 .../chef/strategy/DeleteAllClientsInList.java   |  34 +
 .../chef/strategy/DeleteAllNodesInList.java     |  31 +
 .../org/jclouds/chef/strategy/ListClients.java  |  32 +
 .../chef/strategy/ListCookbookVersions.java     |  32 +
 .../ListCookbookVersionsInEnvironment.java      |  37 +
 .../jclouds/chef/strategy/ListEnvironments.java |  31 +
 .../org/jclouds/chef/strategy/ListNodes.java    |  32 +
 .../chef/strategy/ListNodesInEnvironment.java   |  32 +
 .../UpdateAutomaticAttributesOnNode.java        |  31 +
 .../internal/BaseListCookbookVersionsImpl.java  |  97 +++
 .../strategy/internal/BaseListNodesImpl.java    |  77 ++
 .../CleanupStaleNodesAndClientsImpl.java        | 102 +++
 ...eNodeAndPopulateAutomaticAttributesImpl.java |  83 ++
 .../internal/DeleteAllClientsInListImpl.java    |  85 ++
 .../internal/DeleteAllNodesInListImpl.java      |  81 ++
 .../chef/strategy/internal/ListClientsImpl.java | 109 +++
 .../internal/ListCookbookVersionsImpl.java      |  60 ++
 .../ListCookbookVersionsInEnvironmentImpl.java  | 117 +++
 .../strategy/internal/ListEnvironmentsImpl.java |  96 +++
 .../chef/strategy/internal/ListNodesImpl.java   |  62 ++
 .../internal/ListNodesInEnvironmentImpl.java    |  62 ++
 .../UpdateAutomaticAttributesOnNodeImpl.java    |  75 ++
 .../chef/suppliers/ChefVersionSupplier.java     |  73 ++
 .../org/jclouds/chef/test/TransientChefApi.java | 387 +++++++++
 .../chef/test/TransientChefApiMetadata.java     |  75 ++
 .../test/config/TransientChefApiModule.java     | 116 +++
 .../java/org/jclouds/chef/util/ChefUtils.java   |  71 ++
 .../org/jclouds/chef/util/CollectionUtils.java  |  69 ++
 .../org/jclouds/chef/util/RunListBuilder.java   |  83 ++
 .../main/java/org/jclouds/ohai/Automatic.java   |  33 +
 .../org/jclouds/ohai/AutomaticSupplier.java     |  48 ++
 .../org/jclouds/ohai/config/ConfiguresOhai.java |  28 +
 .../org/jclouds/ohai/config/JMXOhaiModule.java  |  48 ++
 .../org/jclouds/ohai/config/OhaiModule.java     | 183 ++++
 .../ohai/functions/ByteArrayToMacAddress.java   |  51 ++
 .../ohai/functions/MapSetToMultimap.java        |  42 +
 .../jclouds/ohai/functions/NestSlashKeys.java   | 156 ++++
 .../ohai/suppliers/UptimeSecondsSupplier.java   |  44 +
 .../services/org.jclouds.apis.ApiMetadata       |   2 +
 .../src/test/clojure/org/jclouds/chef_test.clj  |  70 ++
 .../org/jclouds/chef/BaseChefApiExpectTest.java |  44 +
 .../org/jclouds/chef/ChefApiExpectTest.java     | 279 ++++++
 .../java/org/jclouds/chef/ChefApiLiveTest.java  |  32 +
 .../org/jclouds/chef/ChefApiMetadataTest.java   |  33 +
 .../test/java/org/jclouds/chef/ChefApiTest.java | 741 ++++++++++++++++
 .../BindHexEncodedMD5sToJsonPayloadTest.java    |  70 ++
 .../chef/config/ChefParserModuleTest.java       |  93 ++
 .../chef/filters/SignedHeaderAuthTest.java      | 213 +++++
 .../functions/BootstrapConfigForGroupTest.java  |  74 ++
 .../chef/functions/ClientForGroupTest.java      | 106 +++
 .../chef/functions/GroupToBootScriptTest.java   | 230 +++++
 .../chef/functions/ParseClientFromJsonTest.java |  98 +++
 ...okbookDefinitionCheckingChefVersionTest.java |  62 ++
 .../ParseCookbookDefinitionFromJsonTest.java    |  72 ++
 .../ParseCookbookDefinitionFromJsonv10Test.java |  74 ++
 ...seCookbookDefinitionListFromJsonv10Test.java |  92 ++
 .../ParseCookbookVersionFromJsonTest.java       | 129 +++
 ...CookbookVersionsCheckingChefVersionTest.java |  62 ++
 .../ParseCookbookVersionsV09FromJsonTest.java   |  61 ++
 .../ParseCookbookVersionsV10FromJsonTest.java   |  68 ++
 .../functions/ParseDataBagItemFromJsonTest.java |  65 ++
 .../ParseErrorFromJsonOrReturnBodyTest.java     |  42 +
 .../chef/functions/ParseKeySetFromJsonTest.java |  66 ++
 .../chef/functions/ParseNodeFromJsonTest.java   |  73 ++
 .../functions/ParseSandboxFromJsonTest.java     |  71 ++
 .../ParseSearchDataBagItemFromJsonTest.java     |  62 ++
 .../ParseUploadSandboxFromJsonTest.java         |  87 ++
 .../chef/functions/RunListForGroupTest.java     |  92 ++
 .../chef/functions/UriForResourceTest.java      |  54 ++
 .../handlers/ChefApiErrorRetryHandlerTest.java  | 115 +++
 .../chef/internal/BaseChefApiLiveTest.java      | 542 ++++++++++++
 .../jclouds/chef/internal/BaseChefLiveTest.java |  96 +++
 .../chef/internal/BaseChefServiceTest.java      |  99 +++
 .../chef/internal/BaseStubbedOhaiLiveTest.java  |  60 ++
 ...CleanupStaleNodesAndClientsImplLiveTest.java |  61 ++
 ...PopulateAutomaticAttributesImplLiveTest.java |  62 ++
 ...eAndPopulateAutomaticAttributesImplTest.java |  61 ++
 ...DeleteAllApisAndNodesInListImplLiveTest.java |  64 ++
 ...okbookVersionsInEnvironmentImplLiveTest.java | 217 +++++
 .../internal/ListNodesImplLiveTest.java         |  82 ++
 .../ListNodesInEnvironmentImplLiveTest.java     |  86 ++
 ...teAutomaticAttributesOnNodeImplLiveTest.java |  61 ++
 ...UpdateAutomaticAttributesOnNodeImplTest.java |  63 ++
 .../chef/suppliers/ChefVersionSupplierTest.java |  47 +
 .../test/TransientChefApiIntegrationTest.java   |  69 ++
 .../chef/test/TransientChefApiMetadataTest.java |  33 +
 .../org/jclouds/chef/util/ChefUtilsTest.java    |  57 ++
 .../jclouds/chef/util/RunListBuilderTest.java   |  71 ++
 .../java/org/jclouds/ohai/config/JMXTest.java   |  82 ++
 .../org/jclouds/ohai/config/OhaiModuleTest.java | 147 ++++
 .../functions/ByteArrayToMacAddressTest.java    |  33 +
 .../ohai/functions/NestSlashKeysTest.java       | 117 +++
 .../resources/apache-chef-demo-cookbook.json    |  46 +
 apis/chef/src/test/resources/bootstrap-env.sh   |  56 ++
 apis/chef/src/test/resources/bootstrap.sh       |  56 ++
 apis/chef/src/test/resources/brew-cookbook.json |  48 ++
 apis/chef/src/test/resources/client.json        |   8 +
 apis/chef/src/test/resources/clients_list.json  |   5 +
 apis/chef/src/test/resources/data_list.json     |   4 +
 apis/chef/src/test/resources/env_cookbooks.json |  20 +
 .../src/test/resources/environment_recipes.json |   6 +
 .../chef/src/test/resources/mysql-cookbook.json | 268 ++++++
 apis/chef/src/test/resources/node.json          |  10 +
 apis/chef/src/test/resources/nodes_list.json    |   5 +
 apis/chef/src/test/resources/privkey.txt        |  27 +
 apis/chef/src/test/resources/pubkey.txt         |   9 +
 apis/chef/src/test/resources/roles_list.json    |   4 +
 apis/chef/src/test/resources/sandbox.json       |  12 +
 apis/chef/src/test/resources/search_role.json   |  34 +
 .../src/test/resources/search_role_empty.json   |   5 +
 .../src/test/resources/tomcat-cookbook.json     | 121 +++
 apis/chef/src/test/resources/upload-site.json   |  13 +
 apis/pom.xml                                    |   1 +
 project/pom.xml                                 |   2 +
 providers/enterprise-chef/pom.xml               | 110 +++
 .../enterprisechef/EnterpriseChefApi.java       | 124 +++
 .../EnterpriseChefApiMetadata.java              |  82 ++
 .../EnterpriseChefProviderMetadata.java         |  86 ++
 .../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 +++++
 .../org.jclouds.providers.ProviderMetadata      |   1 +
 .../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 +
 .../src/test/resources/user.json                |   9 +
 providers/pom.xml                               |   1 +
 201 files changed, 18721 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/pom.xml
----------------------------------------------------------------------
diff --git a/apis/chef/pom.xml b/apis/chef/pom.xml
new file mode 100644
index 0000000..6d0d889
--- /dev/null
+++ b/apis/chef/pom.xml
@@ -0,0 +1,131 @@
+<?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</groupId>
+    <artifactId>jclouds-project</artifactId>
+    <version>1.8.0-SNAPSHOT</version>
+    <relativePath>../../project/pom.xml</relativePath>
+  </parent>
+  <groupId>org.apache.jclouds.api</groupId>
+  <artifactId>chef</artifactId>
+  <packaging>bundle</packaging>
+  <name>jclouds Chef api</name>
+  <description>jclouds components to access Chef</description>
+
+  <properties>
+    <test.chef.endpoint>http://localhost:4000</test.chef.endpoint>
+    <test.chef.api-version />
+    <test.chef.build-version />
+    <test.chef.identity>chef-webui</test.chef.identity>
+    <test.chef.credential>${user.home}/.chef/webui.pem</test.chef.credential>
+    <jclouds.osgi.import>org.jclouds*;version=${project.version},*</jclouds.osgi.import>
+    <jclouds.osgi.export>
+      org.jclouds.chef*;version=${project.version};-noimport:=true,
+      org.jclouds.ohai*;version=${project.version};-noimport:=true,
+    </jclouds.osgi.export>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-core</artifactId>
+      <version>${project.version}</version>
+      <type>jar</type>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-core</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-scriptbuilder</artifactId>
+      <version>${project.version}</version>
+      <type>jar</type>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-scriptbuilder</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <!--  for ohai -->
+    <dependency>
+        <groupId>com.google.inject.extensions</groupId>
+        <artifactId>guice-multibindings</artifactId>
+        <version>3.0</version>
+    </dependency>
+    <!--  for transient chef provider -->
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-blobstore</artifactId>
+      <version>${project.version}</version>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds.driver</groupId>
+      <artifactId>jclouds-slf4j</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <profiles>
+    <profile>
+      <id>live</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>integration</id>
+                <phase>integration-test</phase>
+                <goals>
+                  <goal>test</goal>
+                </goals>
+                <configuration>
+                  <systemPropertyVariables>
+                    <test.chef.endpoint>${test.chef.endpoint}</test.chef.endpoint>
+                    <test.chef.api-version>${test.chef.api-version}</test.chef.api-version>
+                    <test.chef.build-version>${test.chef.build-version}</test.chef.build-version>
+                    <test.chef.identity>${test.chef.identity}</test.chef.identity>
+                    <test.chef.credential>${test.chef.credential}</test.chef.credential>
+                  </systemPropertyVariables>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..ad0f200
--- /dev/null
+++ b/apis/chef/src/main/clojure/org/jclouds/chef.clj
@@ -0,0 +1,261 @@
+;
+; 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.
+;
+
+(ns 
+  #^{: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, 
+which is basically Chef Server as a Service.
+
+(use 'org.jclouds.chef)
+
+(def client \"YOUR_CLIENT\")
+;; load the rsa key from ~/.chef/CLIENT_NAME.pem
+(def credential (load-pem client))
+
+;; create a connection to the opscode platform
+(def chef (chef-service \"chef\" client credential :chef.endpoint \"https://api.opscode.com/organizations/YOUR_ORG\"))
+
+(with-chef-service [chef]
+  (create-databag \"cluster-config\")
+  (update-databag-item \"cluster-config\" {:id \"master\" :name \"myhost.com\"})) 
+
+;; note that you can create your chef connection like this to do in-memory testing
+(def chef (chef-service \"transientchef\" \"\" \"\"))
+
+See http://code.google.com/p/jclouds for details."}
+  org.jclouds.chef
+  (:use  [org.jclouds.core])
+  (:require (org.danlarkin [json :as json]))
+  (:import 
+        java.util.Properties
+        [org.jclouds ContextBuilder]
+        [org.jclouds.chef ChefClient
+          ChefService ChefContext]
+        [org.jclouds.chef.domain DatabagItem]))
+(try
+ (use '[clojure.contrib.reflect :only [get-field]])
+ (catch Exception e
+   (use '[clojure.contrib.java-utils
+          :only [wall-hack-field]
+          :rename {wall-hack-field get-field}])))
+
+(defn load-pem
+  "get the pem associated with the supplied identity"
+  ([#^String identity]
+     (slurp (str (. System getProperty "user.home") "/.chef/" identity ".pem"))))
+
+;; TODO find a way to pass the chef provider by default
+
+(defn chef-service
+  "Create a logged in context to a chef server.
+
+provider \"chef\" is a remote connection, and you can pass the option
+   :chef.endpoint \"https://url\" to override the endpoint
+
+provider \"transientchef\" is for in-memory when you are looking to do 
+unit testing"
+  ([#^String provider #^String identity #^String credential & options]
+    (let [module-keys (set (keys module-lookup))
+          ext-modules (filter #(module-keys %) options)
+          opts (apply hash-map (filter #(not (module-keys %)) options))]
+      (.. (ContextBuilder/newBuilder provider)
+          (credentials provider-identity provider-credential)
+          (modules (apply modules (concat ext-modules (opts :extensions))))
+          (overrides (reduce #(do (.put %1 (name (first %2)) (second %2)) %1)
+            (Properties.) (dissoc opts :extensions)))
+          (build ChefContext)
+          (getChefService)))))
+
+(defn chef-context
+  "Returns a chef context from a chef service."
+  [#^ChefService chef]
+  (.getContext chef))
+
+(defn chef-service?
+  [object]
+  (instance? ChefService object))
+
+(defn chef-context?
+  [object]
+  (instance? ChefContext object))
+
+(defn as-chef-service
+  "Tries hard to produce a chef service from its input arguments"
+  [& args]
+  (cond
+   (chef-service? (first args)) (first args)
+   (chef-context? (first args)) (.getChefService (first args))
+   :else (apply chef-service args)))
+
+(defn as-chef-api
+  "Tries hard to produce a chef client from its input arguments"
+  [& args]
+  (cond
+   (chef-service? (first args)) (.getApi (.getContext (first args)))
+   (chef-context? (first args)) (.getApi (first args))
+   :else (.getApi (.getContext (apply chef-service args)))))
+
+(def *chef*)
+
+(defmacro with-chef-service
+  "Specify the default chef service"
+  [[& chef-or-args] & body]
+  `(binding [*chef* (as-chef-service ~@chef-or-args)]
+     ~@body))
+
+(defn nodes
+  "Retrieve the names of the existing nodes in your chef server."
+  ([] (nodes *chef*))
+  ([#^ChefService chef]
+    (seq (.listNodes (as-chef-api chef)))))
+
+(defn nodes-with-details
+  "Retrieve the existing nodes in your chef server including all details."
+  ([] (nodes *chef*))
+  ([#^ChefService chef]
+    (seq (.listNodes chef))))
+
+(defn clients
+  "Retrieve the names of the existing clients in your chef server."
+  ([] (clients *chef*))
+  ([#^ChefService chef]
+    (seq (.listClients (as-chef-api chef)))))
+
+(defn clients-with-details
+  "Retrieve the existing clients in your chef server including all details."
+  ([] (clients *chef*))
+  ([#^ChefService chef]
+    (seq (.listClients chef))))
+
+(defn cookbooks
+  "Retrieve the names of the existing cookbooks in your chef server."
+  ([] (cookbooks *chef*))
+  ([#^ChefService chef]
+    (seq (.listCookbooks (as-chef-api chef)))))
+
+(defn cookbook-versions
+  "Retrieve the versions of an existing cookbook in your chef server."
+  ([name] (cookbook-versions *chef*))
+  ([#^ChefService name chef]
+    (seq (.getVersionsOfCookbook (as-chef-api chef) name))))
+
+(defn cookbook-versions-with-details
+  "Retrieve the existing cookbook versions in your chef server including all details."
+  ([] (cookbook-versions *chef*))
+  ([#^ChefService chef]
+    (seq (.listCookbookVersions chef))))
+
+(defn update-run-list
+  "Updates the run-list associated with a tag"
+  ([run-list tag] (update-run-list run-list tag *chef*))
+  ([run-list tag #^ChefService chef]
+    (.updateRunListForTag chef run-list tag)))
+
+(defn run-list
+  "Retrieves the run-list associated with a tag"
+  ([tag] (run-list tag *chef*))
+  ([tag #^ChefService chef]
+    (seq (.getRunListForTag chef tag))))
+
+(defn create-bootstrap
+  "creates a client and bootstrap script associated with a tag"
+  ([tag] (create-bootstrap tag *chef*))
+  ([tag #^ChefService chef]
+    (.createClientAndBootstrapScriptForTag chef tag)))
+
+(defn databags
+  "Retrieve the names of the existing data bags in your chef server."
+  ([] (databags *chef*))
+  ([#^ChefService chef]
+    (seq (.listDatabags (as-chef-api chef)))))
+
+(defn databag-exists?
+  "Predicate to check presence of a databag"
+  ([databag-name]
+     (databag-exists? databag-name *chef*))
+  ([databag-name #^ChefService chef]
+     (.databagExists (as-chef-api chef) databag-name)))
+
+(defn delete-databag
+  "Delete a data bag, including its items"
+  ([databag]
+    (delete-databag databag *chef*))
+  ([databag chef]
+    (.deleteDatabag (as-chef-api chef) databag)))
+
+(defn create-databag
+  "create a data bag"
+  ([databag]
+    (create-databag databag *chef*))
+  ([databag chef]
+    (.createDatabag (as-chef-api chef) databag)))
+
+(defn databag-items
+  "Retrieve the names of the existing items in a data bag in your chef server."
+  ([databag]
+    (databag-items databag *chef*))
+  ([databag chef]
+    (seq (.listDatabagItems (as-chef-api chef) databag))))
+
+(defn databag-item-exists?
+  "Predicate to check presence of a databag item"
+  ([databag-name item-id]
+     (databag-item-exists? databag-name item-id *chef*))
+  ([databag-name item-id #^ChefService chef]
+     (.databagExists (as-chef-api chef) databag-name item-id)))
+
+(defn databag-item
+  "Get an item from the data bag"
+  ([databag item-id]
+    (databag-item databag item-id *chef*))
+  ([databag item-id chef]
+    (json/decode-from-str (str (.getDatabagItem (as-chef-api chef) databag item-id)))))
+
+(defn delete-databag-item
+  "delete an item from the data bag"
+  ([databag item-id]
+    (delete-databag-item databag item-id *chef*))
+  ([databag item-id chef]
+    (.deleteDatabagItem (as-chef-api chef) databag item-id)))
+
+(defn create-databag-item
+  "put a new item in the data bag.  Note the Map you pass must have an :id key:
+
+ex.
+  (create-databag-item \"cluster-config\" {:id \"master\" :name \"myhost.com\"}))"
+  ([databag value]
+    (create-databag-item databag value *chef*))
+  ([databag value chef]
+    (let [value-str (json/encode-to-str value)]
+      (let [value-json (json/decode-from-str value-str)]
+        (.createDatabagItem  (as-chef-api chef) databag 
+          (DatabagItem. (get value-json :id) value-str))))))
+
+(defn update-databag-item
+  "updates an existing item in the data bag.  Note the Map you pass must have an :id key:
+
+ex.
+  (update-databag-item \"cluster-config\" {:id \"master\" :name \"myhost.com\"}))"
+  ([databag value]
+    (update-databag-item databag value *chef*))
+  ([databag value chef]
+    (let [value-str (json/encode-to-str value)]
+      (let [value-json (json/decode-from-str value-str)]
+        (.updateDatabagItem  (as-chef-api chef) databag 
+          (DatabagItem. (get value-json :id) value-str))))))

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..13f4971
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java
@@ -0,0 +1,853 @@
+/*
+ * 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 java.io.Closeable;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.List;
+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.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Constants;
+import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.chef.binders.BindChecksumsToJsonPayload;
+import org.jclouds.chef.binders.BindCreateClientOptionsToJsonPayload;
+import org.jclouds.chef.binders.BindGenerateKeyForClientToJsonPayload;
+import org.jclouds.chef.binders.DatabagItemId;
+import org.jclouds.chef.binders.EnvironmentName;
+import org.jclouds.chef.binders.NodeName;
+import org.jclouds.chef.binders.RoleName;
+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.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.filters.SignedHeaderAuth;
+import org.jclouds.chef.functions.ParseCookbookDefinitionCheckingChefVersion;
+import org.jclouds.chef.functions.ParseCookbookDefinitionFromJsonv10;
+import org.jclouds.chef.functions.ParseCookbookDefinitionListFromJsonv10;
+import org.jclouds.chef.functions.ParseCookbookVersionsCheckingChefVersion;
+import org.jclouds.chef.functions.ParseKeySetFromJson;
+import org.jclouds.chef.functions.ParseSearchClientsFromJson;
+import org.jclouds.chef.functions.ParseSearchDatabagFromJson;
+import org.jclouds.chef.functions.ParseSearchEnvironmentsFromJson;
+import org.jclouds.chef.functions.ParseSearchNodesFromJson;
+import org.jclouds.chef.functions.ParseSearchRolesFromJson;
+import org.jclouds.chef.functions.UriForResource;
+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.EndpointParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.Headers;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.ParamParser;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.annotations.SinceApiVersion;
+import org.jclouds.rest.annotations.SkipEncoding;
+import org.jclouds.rest.annotations.WrapWith;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+/**
+ * Provides synchronous access to Chef.
+ */
+@RequestFilters(SignedHeaderAuth.class)
+@Headers(keys = "X-Chef-Version", values = "{" + Constants.PROPERTY_API_VERSION + "}")
+@Consumes(MediaType.APPLICATION_JSON)
+public interface ChefApi extends Closeable {
+
+   // Clients
+
+   /**
+    * Lists the names of the existing clients.
+    * 
+    * @return The names of the existing clients.
+    */
+   @Named("client:list")
+   @GET
+   @Path("/clients")
+   @ResponseParser(ParseKeySetFromJson.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listClients();
+
+   /**
+    * Gets the details of existing client.
+    * 
+    * @param clientname The name of the client to get.
+    * @return The details of the given client.
+    */
+   @Named("client:get")
+   @GET
+   @Path("/clients/{clientname}")
+   @Fallback(NullOnNotFoundOr404.class)
+   Client getClient(@PathParam("clientname") String clientName);
+
+   /**
+    * Creates a new client.
+    * 
+    * @param clientname The name of the new client
+    * @return The client with the generated private key. This key should be
+    *         stored so client can be properly authenticated .
+    */
+   @Named("client:create")
+   @POST
+   @Path("/clients")
+   @MapBinder(BindToJsonPayload.class)
+   Client createClient(@PayloadParam("name") String clientName);
+
+   /**
+    * Creates a new client with custom options.
+    * 
+    * @param clientname The name of the new client
+    * @param options The options to customize the client creation.
+    * @return The client with the generated private key. This key should be
+    *         stored so client can be properly authenticated .
+    */
+   @Named("client:create")
+   @POST
+   @Path("/clients")
+   @MapBinder(BindCreateClientOptionsToJsonPayload.class)
+   Client createClient(@PayloadParam("name") String clientName, CreateClientOptions options);
+
+   /**
+    * Generates a new key-pair for this client, and return the new private key in
+    * the response body.
+    * 
+    * @param clientname The name of the client.
+    * @return The details of the client with the new private key.
+    */
+   @Named("client:generatekey")
+   @PUT
+   @Path("/clients/{clientname}")
+   Client generateKeyForClient(
+         @PathParam("clientname") @BinderParam(BindGenerateKeyForClientToJsonPayload.class) String clientName);
+
+   /**
+    * Deletes the given client.
+    * 
+    * @param clientname The name of the client to delete.
+    * @return The deleted client.
+    */
+   @Named("client:delete")
+   @DELETE
+   @Path("/clients/{clientname}")
+   @Fallback(NullOnNotFoundOr404.class)
+   Client deleteClient(@PathParam("clientname") String clientName);
+
+   // Cookbooks
+
+   /**
+    * Lists the names of the existing cookbooks.
+    * 
+    * @return The names of the exsisting cookbooks.
+    */
+   @Named("cookbook:list")
+   @GET
+   @Path("/cookbooks")
+   @ResponseParser(ParseCookbookDefinitionCheckingChefVersion.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listCookbooks();
+
+   /**
+    * Lists the cookbooks that are available in the given environment.
+    * 
+    * @param environmentname The name of the environment to get the cookbooks
+    *        from.
+    * @return The definitions of the cookbooks (name, URL and versions) available in
+    *         the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("cookbook:list")
+   @GET
+   @ResponseParser(ParseCookbookDefinitionListFromJsonv10.class)
+   @Path("/environments/{environmentname}/cookbooks")
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<CookbookDefinition> listCookbooksInEnvironment(@PathParam("environmentname") String environmentName);
+
+   /**
+    * Lists the cookbooks that are available in the given environment, limiting
+    * the number of versions returned for each cookbook.
+    * 
+    * @param environmentname The name of the environment to get the cookbooks
+    *        from.
+    * @param numversions The number of cookbook versions to include in the
+    *        response, where n is the number of cookbook versions.
+    * @return The definitions of the cookbooks (name, URL and versions) available in
+    *         the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("cookbook:list")
+   @GET
+   @ResponseParser(ParseCookbookDefinitionListFromJsonv10.class)
+   @Path("/environments/{environmentname}/cookbooks?num_versions={numversions}")
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<CookbookDefinition> listCookbooksInEnvironment(@PathParam("environmentname") String environmentName,
+         @PathParam("numversions") String numVersions);
+
+   /**
+    * Lists the available versions of the given cookbook.
+    * 
+    * @param cookbookName The name of the cookbook.
+    * @return The available versions of the given cookbook.
+    */
+   @Named("cookbook:versions")
+   @GET
+   @Path("/cookbooks/{cookbookname}")
+   @ResponseParser(ParseCookbookVersionsCheckingChefVersion.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listVersionsOfCookbook(@PathParam("cookbookname") String cookbookName);
+
+   /**
+    * Gets the details of the given cookbook, with the links to each resource
+    * such as recipe files, attributes, etc.
+    * 
+    * @param cookbookName The name of the cookbook.
+    * @param version The version of the cookbook to get.
+    * @return The details of the given cookbook.
+    */
+   @Named("cookbook:get")
+   @GET
+   @Path("/cookbooks/{cookbookname}/{version}")
+   @Fallback(NullOnNotFoundOr404.class)
+   CookbookVersion getCookbook(@PathParam("cookbookname") String cookbookName, @PathParam("version") String version);
+
+   /**
+    * Gets the definition of the cookbook in the given environment.
+    * 
+    * @param environmentname The name of the environment.
+    * @param cookbookname The name of the cookbook.
+    * @return The definition of the cookbook (URL and versions) of the cookbook
+    *         in the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("environment:cookbook")
+   @GET
+   @ResponseParser(ParseCookbookDefinitionFromJsonv10.class)
+   @Path("/environments/{environmentname}/cookbooks/{cookbookname}")
+   CookbookDefinition getCookbookInEnvironment(@PathParam("environmentname") String environmentName,
+         @PathParam("cookbookname") String cookbookName);
+
+   /**
+    * Gets the definition of the cookbook in the given environment.
+    * 
+    * @param environmentname The name of the environment.
+    * @param cookbookname The name of the cookbook.
+    * @param numversions The number of cookbook versions to include in the
+    *        response, where n is the number of cookbook versions.
+    * @return The definition of the cookbook (URL and versions) of the cookbook
+    *         in the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("environment:cookbook")
+   @GET
+   @ResponseParser(ParseCookbookDefinitionFromJsonv10.class)
+   @Path("/environments/{environmentname}/cookbooks/{cookbookname}?num_versions={numversions}")
+   CookbookDefinition getCookbookInEnvironment(@PathParam("environmentname") String environmentName,
+         @PathParam("cookbookname") String cookbookName, @PathParam("numversions") String numVersions);
+
+   /**
+    * Lists the names of the recipes in the given environment.
+    * 
+    * @param environmentname The name of the environment.
+    * @return The names of the recipes in the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("environment:recipelist")
+   @GET
+   @Path("/environments/{environmentname}/recipes")
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listRecipesInEnvironment(@PathParam("environmentname") String environmentName);
+
+   /**
+    * Creates or updates the given cookbook.
+    * 
+    * @param cookbookName The name of the cookbook to create or update.
+    * @param version The version of the cookbook to create or update.
+    * @param cookbook The contents of the cookbook to create or update.
+    * @return The details of the created or updated cookbook.
+    */
+   @Named("cookbook:update")
+   @PUT
+   @Path("/cookbooks/{cookbookname}/{version}")
+   CookbookVersion updateCookbook(@PathParam("cookbookname") String cookbookName, @PathParam("version") String version,
+         @BinderParam(BindToJsonPayload.class) CookbookVersion cookbook);
+
+   /**
+    * Deletes the given cookbook.
+    * 
+    * @param cookbookName The name of the cookbook to delete.
+    * @param version The version of the cookbook to delete.
+    * @return The details of the deleted cookbook.
+    */
+   @Named("cookbook:delete")
+   @DELETE
+   @Path("/cookbooks/{cookbookname}/{version}")
+   @Fallback(NullOnNotFoundOr404.class)
+   CookbookVersion deleteCookbook(@PathParam("cookbookname") String cookbookName, @PathParam("version") String version);
+
+   // Data bags
+
+   /**
+    * Lists the names of the existing data bags.
+    * 
+    * @return The names of the existing data bags.
+    */
+   @Named("databag:list")
+   @GET
+   @Path("/data")
+   @ResponseParser(ParseKeySetFromJson.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listDatabags();
+
+   /**
+    * Creates a new data bag.
+    * 
+    * @param databagName The name for the new data bag.
+    */
+   @Named("databag:create")
+   @POST
+   @Path("/data")
+   void createDatabag(@WrapWith("name") String databagName);
+
+   /**
+    * Deletes a data bag, including its items.
+    * 
+    * @param databagName The name of the data bag to delete.
+    */
+   @Named("databag:delete")
+   @DELETE
+   @Path("/data/{name}")
+   @Fallback(VoidOnNotFoundOr404.class)
+   void deleteDatabag(@PathParam("name") String databagName);
+
+   /**
+    * Lists the names of the items in a data bag.
+    * 
+    * @param databagName The name of the data bag.
+    * @return The names of the items in the given data bag.
+    */
+   @Named("databag:listitems")
+   @GET
+   @Path("/data/{name}")
+   @ResponseParser(ParseKeySetFromJson.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listDatabagItems(@PathParam("name") String databagName);
+
+   /**
+    * Gets an item in a data bag.
+    * 
+    * @param databagName The name of the data bag.
+    * @param databagItemId The identifier of the item to get.
+    * @return The details of the item in the given data bag.
+    */
+   @Named("databag:getitem")
+   @GET
+   @Path("/data/{databagName}/{databagItemId}")
+   @Fallback(NullOnNotFoundOr404.class)
+   DatabagItem getDatabagItem(@PathParam("databagName") String databagName,
+         @PathParam("databagItemId") String databagItemId);
+
+   /**
+    * Adds an item in a data bag.
+    * 
+    * @param databagName The name of the data bag.
+    * @param The item to add to the data bag.
+    * @param The item just added to the data bag.
+    */
+   @Named("databag:createitem")
+   @POST
+   @Path("/data/{databagName}")
+   DatabagItem createDatabagItem(@PathParam("databagName") String databagName,
+         @BinderParam(BindToJsonPayload.class) DatabagItem databagItem);
+
+   /**
+    * Updates an item in a data bag.
+    * 
+    * @param databagName The name of the data bag.
+    * @param item The new contents for the item in the data bag.
+    * @return The details for the updated item in the data bag.
+    */
+   @Named("databag:updateitem")
+   @PUT
+   @Path("/data/{databagName}/{databagItemId}")
+   DatabagItem updateDatabagItem(
+         @PathParam("databagName") String databagName,
+         @PathParam("databagItemId") @ParamParser(DatabagItemId.class) @BinderParam(BindToJsonPayload.class) DatabagItem item);
+
+   /**
+    * Deletes an item from a data bag.
+    * 
+    * @param databagName The name of the data bag.
+    * @param databagItemId The identifier of the item to delete.
+    * @return The item deleted from the data bag.
+    */
+   @Named("databag:deleteitem")
+   @DELETE
+   @Path("/data/{databagName}/{databagItemId}")
+   @Fallback(NullOnNotFoundOr404.class)
+   @SelectJson("raw_data")
+   DatabagItem deleteDatabagItem(@PathParam("databagName") String databagName,
+         @PathParam("databagItemId") String databagItemId);
+
+   // Environments
+
+   /**
+    * Lists the names of the existing environments.
+    * 
+    * @return The names of the existing environments.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("environment:list")
+   @GET
+   @Path("/environments")
+   @ResponseParser(ParseKeySetFromJson.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listEnvironments();
+
+   /**
+    * Gets the details of an existing environment.
+    * 
+    * @param environmentname The name of the environment to get.
+    * @return The details of the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("environment:get")
+   @GET
+   @Path("/environments/{environmentname}")
+   @Fallback(NullOnNotFoundOr404.class)
+   Environment getEnvironment(@PathParam("environmentname") String environmentName);
+
+   /**
+    * Creates a new environment.
+    * 
+    * @param environment The environment to create.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("environment:create")
+   @POST
+   @Path("/environments")
+   void createEnvironment(@BinderParam(BindToJsonPayload.class) Environment environment);
+
+   /**
+    * Updates the given environment.
+    * 
+    * @param environment The new details for the environment.
+    * @return The details of the updated environment.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("environment:update")
+   @PUT
+   @Path("/environments/{environmentname}")
+   Environment updateEnvironment(
+         @PathParam("environmentname") @ParamParser(EnvironmentName.class) @BinderParam(BindToJsonPayload.class) Environment environment);
+
+   /**
+    * Deletes the given environment.
+    * 
+    * @param environmentname The name of the environment to delete.
+    * @return The details of the deleted environment.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("environment:delete")
+   @DELETE
+   @Path("/environments/{environmentname}")
+   @Fallback(NullOnNotFoundOr404.class)
+   Environment deleteEnvironment(@PathParam("environmentname") String environmentName);
+
+   // Nodes
+
+   /**
+    * Lists the names of the existing nodes.
+    * 
+    * @return The names of the existing nodes.
+    */
+   @Named("node:list")
+   @GET
+   @Path("/nodes")
+   @ResponseParser(ParseKeySetFromJson.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listNodes();
+
+   /**
+    * Lists the names of the nodes in the given environment.
+    * 
+    * @param environmentname The name of the environment.
+    * @return The names of the existing nodes in the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("environment:nodelist")
+   @GET
+   @Path("/environments/{environmentname}/nodes")
+   @ResponseParser(ParseKeySetFromJson.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listNodesInEnvironment(@PathParam("environmentname") String environmentName);
+
+   /**
+    * Gets the details of the given node.
+    * 
+    * @param nodename The name of the node to get.
+    * @return The details of the given node.
+    */
+   @Named("node:get")
+   @GET
+   @Path("/nodes/{nodename}")
+   @Fallback(NullOnNotFoundOr404.class)
+   Node getNode(@PathParam("nodename") String nodeName);
+
+   /**
+    * Creates a new node.
+    * 
+    * @param node The details of the node to create.
+    */
+   @Named("node:create")
+   @POST
+   @Path("/nodes")
+   void createNode(@BinderParam(BindToJsonPayload.class) Node node);
+
+   /**
+    * Updates an existing node.
+    * 
+    * @param node The new details for the node.
+    * @return The details of the updated node.
+    */
+   @Named("node:update")
+   @PUT
+   @Path("/nodes/{nodename}")
+   Node updateNode(@PathParam("nodename") @ParamParser(NodeName.class) @BinderParam(BindToJsonPayload.class) Node node);
+
+   /**
+    * Deletes the given node.
+    * 
+    * @param nodename The name of the node to delete.
+    * @return The details of the deleted node.
+    */
+   @Named("node:delete")
+   @DELETE
+   @Path("/nodes/{nodename}")
+   @Fallback(NullOnNotFoundOr404.class)
+   Node deleteNode(@PathParam("nodename") String nodeName);
+
+   // Roles
+
+   /**
+    * Lists the names of the existing roles.
+    * 
+    * @return The names of the existing roles.
+    */
+   @Named("role:list")
+   @GET
+   @Path("/roles")
+   @ResponseParser(ParseKeySetFromJson.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listRoles();
+
+   /**
+    * Gets the details of the given role.
+    * 
+    * @param rolename The name of the role to get.
+    * @return The details of the given role.
+    */
+   @Named("role:get")
+   @GET
+   @Path("/roles/{rolename}")
+   @Fallback(NullOnNotFoundOr404.class)
+   Role getRole(@PathParam("rolename") String roleName);
+
+   /**
+    * Creates a new role.
+    * 
+    * @param role The details for the new role.
+    */
+   @Named("role:create")
+   @POST
+   @Path("/roles")
+   void createRole(@BinderParam(BindToJsonPayload.class) Role role);
+
+   /**
+    * Updates the given role.
+    * 
+    * @param role The new details for the role.
+    * @return The details of the updated role.
+    */
+   @Named("role:update")
+   @PUT
+   @Path("/roles/{rolename}")
+   Role updateRole(@PathParam("rolename") @ParamParser(RoleName.class) @BinderParam(BindToJsonPayload.class) Role role);
+
+   /**
+    * Deletes the given role.
+    * 
+    * @param rolename The name of the role to delete.
+    * @return The details of the deleted role.
+    */
+   @Named("role:delete")
+   @DELETE
+   @Path("/roles/{rolename}")
+   @Fallback(NullOnNotFoundOr404.class)
+   Role deleteRole(@PathParam("rolename") String roleName);
+
+   // Sandboxes
+
+   /**
+    * Creates a new sandbox.
+    * <p>
+    * It accepts a list of checksums as input and returns the URLs against which
+    * to PUT files that need to be uploaded.
+    * 
+    * @param md5s The raw md5 sums. Uses {@code Bytes.asList()} and
+    *        {@code Bytes.toByteArray()} as necessary
+    * @return The upload sandbox with the URLs against which to PUT files that
+    *         need to be uploaded.
+    */
+   @Named("sandbox:upload")
+   @POST
+   @Path("/sandboxes")
+   UploadSandbox createUploadSandboxForChecksums(@BinderParam(BindChecksumsToJsonPayload.class) Set<List<Byte>> md5s);
+
+   /**
+    * Uploads the given content to the sandbox at the given URI.
+    * <p>
+    * The URI must be obtained, after uploading a sandbox, from the
+    * {@link UploadSandbox#getUri()}.
+    * 
+    * @param location The URI where the upload must be performed.
+    * @param content The contents to upload.
+    */
+   @Named("content:upload")
+   @PUT
+   @Produces("application/x-binary")
+   void uploadContent(@EndpointParam URI location, Payload content);
+
+   /**
+    * Gets the contents of the given resource.
+    * 
+    * @param resource The resource to get.
+    * @return An input stream for the content of the requested resource.
+    */
+   @Named("content:get")
+   @GET
+   @Fallback(NullOnNotFoundOr404.class)
+   @SkipEncoding({ '+', ' ', '/', '=', ':', ';' })
+   InputStream getResourceContents(@EndpointParam(parser = UriForResource.class) Resource resource);
+
+   /**
+    * Confirms if the sandbox is completed or not.
+    * <p>
+    * This method should be used after uploading contents to the sandbox.
+    * 
+    * @param id The id of the sandbox to commit.
+    * @param isCompleted Flag to set if the sandbox is completed or not.
+    * @return The details of the sandbox.
+    */
+   @Named("sandbox:commit")
+   @PUT
+   @Path("/sandboxes/{id}")
+   Sandbox commitSandbox(@PathParam("id") String id, @WrapWith("is_completed") boolean isCompleted);
+
+   // Search
+
+   /**
+    * Lists the names of the available search indexes.
+    * <p>
+    * By default, the "role", "node" and "api" indexes will always be available.
+    * <p>
+    * Note that the search indexes may lag behind the most current data by at
+    * least 10 seconds at any given time - so if you need to write data and
+    * immediately query it, you likely need to produce an artificial delay (or
+    * simply retry until the data is available).
+    * 
+    * @return The names of the available search indexes.
+    */
+   @Named("search:indexes")
+   @GET
+   @Path("/search")
+   @ResponseParser(ParseKeySetFromJson.class)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<String> listSearchIndexes();
+
+   /**
+    * Searches all clients.
+    * <p>
+    * Note that without any request parameters this will return all of the data
+    * within the index.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @Named("search:clients")
+   @GET
+   @Path("/search/client")
+   @ResponseParser(ParseSearchClientsFromJson.class)
+   SearchResult<? extends Client> searchClients();
+
+   /**
+    * Searches all clients that match the given options.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @Named("search:clients")
+   @GET
+   @Path("/search/client")
+   @ResponseParser(ParseSearchClientsFromJson.class)
+   SearchResult<? extends Client> searchClients(SearchOptions options);
+
+   /**
+    * Searches all items in a data bag.
+    * <p>
+    * Note that without any request parameters this will return all of the data
+    * within the index.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @Named("search:databag")
+   @GET
+   @Path("/search/{databagName}")
+   @ResponseParser(ParseSearchDatabagFromJson.class)
+   SearchResult<? extends DatabagItem> searchDatabagItems(@PathParam("databagName") String databagName);
+
+   /**
+    * Searches all items in a data bag that match the given options.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @Named("search:databag")
+   @GET
+   @Path("/search/{databagName}")
+   @ResponseParser(ParseSearchDatabagFromJson.class)
+   SearchResult<? extends DatabagItem> searchDatabagItems(@PathParam("databagName") String databagName,
+         SearchOptions options);
+
+   /**
+    * Searches all environments.
+    * <p>
+    * Note that without any request parameters this will return all of the data
+    * within the index.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("search:environments")
+   @GET
+   @Path("/search/environment")
+   @ResponseParser(ParseSearchEnvironmentsFromJson.class)
+   SearchResult<? extends Environment> searchEnvironments();
+
+   /**
+    * Searches all environments that match the given options.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @SinceApiVersion("0.10.0")
+   @Named("search:environments")
+   @GET
+   @Path("/search/environment")
+   @ResponseParser(ParseSearchEnvironmentsFromJson.class)
+   SearchResult<? extends Environment> searchEnvironments(SearchOptions options);
+
+   /**
+    * Searches all nodes.
+    * <p>
+    * Note that without any request parameters this will return all of the data
+    * within the index.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @Named("search:nodes")
+   @GET
+   @Path("/search/node")
+   @ResponseParser(ParseSearchNodesFromJson.class)
+   SearchResult<? extends Node> searchNodes();
+
+   /**
+    * Searches all nodes that match the given options.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @Named("search:nodes")
+   @GET
+   @Path("/search/node")
+   @ResponseParser(ParseSearchNodesFromJson.class)
+   SearchResult<? extends Node> searchNodes(SearchOptions options);
+
+   /**
+    * Searches all roles.
+    * <p>
+    * Note that without any request parameters this will return all of the data
+    * within the index.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @Named("search:roles")
+   @GET
+   @Path("/search/role")
+   @ResponseParser(ParseSearchRolesFromJson.class)
+   SearchResult<? extends Role> searchRoles();
+
+   /**
+    * Searches all roles that match the given options.
+    * 
+    * @return The response contains the total number of rows that matched the
+    *         request, the position this result set returns (useful for paging)
+    *         and the rows themselves.
+    */
+   @Named("search:roles")
+   @GET
+   @Path("/search/role")
+   @ResponseParser(ParseSearchRolesFromJson.class)
+   SearchResult<? extends Role> searchRoles(SearchOptions options);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..ede03e9
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/ChefApiMetadata.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
+import static org.jclouds.Constants.PROPERTY_TIMEOUTS_PREFIX;
+import static org.jclouds.chef.config.ChefProperties.CHEF_BOOTSTRAP_DATABAG;
+import static org.jclouds.chef.config.ChefProperties.CHEF_UPDATE_GEMS;
+import static org.jclouds.chef.config.ChefProperties.CHEF_UPDATE_GEM_SYSTEM;
+import static org.jclouds.chef.config.ChefProperties.CHEF_USE_OMNIBUS;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.chef.config.ChefBootstrapModule;
+import org.jclouds.chef.config.ChefHttpApiModule;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.ohai.config.JMXOhaiModule;
+import org.jclouds.rest.internal.BaseHttpApiMetadata;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+/**
+ * Implementation of {@link ApiMetadata} for OpsCode's Chef api.
+ */
+public class ChefApiMetadata extends BaseHttpApiMetadata<ChefApi> {
+
+   /**
+    * The default Chef Server API version to use.
+    */
+   public static final String DEFAULT_API_VERSION = "0.10.8";
+
+   @Override
+   public Builder toBuilder() {
+      return new Builder().fromApiMetadata(this);
+   }
+
+   public ChefApiMetadata() {
+      this(new Builder());
+   }
+
+   protected ChefApiMetadata(Builder builder) {
+      super(builder);
+   }
+
+   public static Properties defaultProperties() {
+      Properties properties = BaseHttpApiMetadata.defaultProperties();
+      properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "default", SECONDS.toMillis(30) + "");
+      properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "ChefApi.updateCookbook", MINUTES.toMillis(10) + "");
+      properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "ChefApi.createClient", MINUTES.toMillis(2) + "");
+      properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "ChefApi.generateKeyForClient", MINUTES.toMillis(2) + "");
+      properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "ChefApi.createNode", MINUTES.toMillis(2) + "");
+      properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "ChefApi.updateNode", MINUTES.toMillis(10) + "");
+      properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "ChefApi.createRole", MINUTES.toMillis(2) + "");
+      properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "ChefApi.updateRole", MINUTES.toMillis(10) + "");
+      properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "ChefApi.createEnvironment", MINUTES.toMillis(2) + "");
+      properties.setProperty(PROPERTY_SESSION_INTERVAL, "1");
+      properties.setProperty(CHEF_BOOTSTRAP_DATABAG, "bootstrap");
+      properties.setProperty(CHEF_UPDATE_GEM_SYSTEM, "false");
+      properties.setProperty(CHEF_UPDATE_GEMS, "false");
+      properties.setProperty(CHEF_USE_OMNIBUS, "true");
+      return properties;
+   }
+
+   public static class Builder extends BaseHttpApiMetadata.Builder<ChefApi, Builder> {
+
+      protected Builder() {
+         id("chef")
+               .name("OpsCode Chef Api")
+               .identityName("User")
+               .credentialName("Certificate")
+               .version(DEFAULT_API_VERSION)
+               .documentation(URI.create("http://wiki.opscode.com/display/chef/Server+API"))
+               .defaultEndpoint("http://localhost:4000")
+               .defaultProperties(ChefApiMetadata.defaultProperties())
+               .view(ChefContext.class)
+               .defaultModules(
+                     ImmutableSet.<Class<? extends Module>> of(ChefHttpApiModule.class, ChefParserModule.class,
+                           ChefBootstrapModule.class, JMXOhaiModule.class));
+      }
+
+      @Override
+      public ChefApiMetadata build() {
+         return new ChefApiMetadata(this);
+      }
+
+      @Override
+      protected Builder self() {
+         return this;
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/ChefContext.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/ChefContext.java b/apis/chef/src/main/java/org/jclouds/chef/ChefContext.java
new file mode 100644
index 0000000..7809634
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/ChefContext.java
@@ -0,0 +1,37 @@
+/*
+ * 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 java.io.Closeable;
+
+import org.jclouds.View;
+import org.jclouds.chef.internal.ChefContextImpl;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * Provides an entry point to Chef features.
+ */
+@ImplementedBy(ChefContextImpl.class)
+public interface ChefContext extends View, Closeable {
+
+   /**
+    * Provides access to high level Chef features.
+    */
+   ChefService getChefService();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/ChefService.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/ChefService.java b/apis/chef/src/main/java/org/jclouds/chef/ChefService.java
new file mode 100644
index 0000000..040107a
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/ChefService.java
@@ -0,0 +1,263 @@
+/*
+ * 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 com.google.common.io.InputSupplier;
+import com.google.inject.ImplementedBy;
+import org.jclouds.chef.domain.BootstrapConfig;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.domain.Environment;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.internal.BaseChefService;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.rest.annotations.SinceApiVersion;
+import org.jclouds.scriptbuilder.domain.Statement;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Provides high level Chef operations.
+ */
+@ImplementedBy(BaseChefService.class)
+public interface ChefService {
+
+   /**
+    * Gets the context that created this service.
+    *
+    * @return The context that created the service.
+    */
+   ChefContext getContext();
+
+   // Crypto
+
+   /**
+    * Encrypts the given input stream.
+    *
+    * @param supplier The input stream to encrypt.
+    * @return The encrypted bytes for the given input stream.
+    * @throws IOException If there is an error reading from the input stream.
+    */
+   byte[] encrypt(InputSupplier<? extends InputStream> supplier) throws IOException;
+
+   /**
+    * Decrypts the given input stream.
+    *
+    * @param supplier The input stream to decrypt.
+    * @return The decrypted bytes for the given input stream.
+    * @throws IOException If there is an error reading from the input stream.
+    */
+   byte[] decrypt(InputSupplier<? extends InputStream> supplier) throws IOException;
+
+   // Bootstrap
+
+   /**
+    * Creates all steps necessary to bootstrap the node.
+    *
+    * @param group corresponds to a configured
+    *              {@link ChefProperties#CHEF_BOOTSTRAP_DATABAG} data bag where
+    *              run_list and other information are stored.
+    * @return The script used to bootstrap the node.
+    */
+   Statement createBootstrapScriptForGroup(String group);
+
+   /**
+    * Configures how the nodes of a certain group will be bootstrapped
+    *
+    * @param group           The group where the given bootstrap configuration will be
+    *                        applied.
+    * @param bootstrapConfig The configuration to be applied to the nodes in the
+    *                        group.
+    */
+   void updateBootstrapConfigForGroup(String group, BootstrapConfig bootstrapConfig);
+
+   /**
+    * Gets the run list for the given group.
+    *
+    * @param The group to get the configured run list for.
+    * @return run list for all nodes bootstrapped with a certain group
+    */
+   List<String> getRunListForGroup(String group);
+
+   /**
+    * Gets the bootstrap configuration for a given group.
+    * <p/>
+    * The bootstrap configuration is a Json object containing the run list and
+    * the configured attributes.
+    *
+    * @param group The name of the group.
+    * @return The bootstrap configuration for the given group.
+    */
+   JsonBall getBootstrapConfigForGroup(String group);
+
+   // Nodes / Clients
+
+   /**
+    * Creates a new node and populates the automatic attributes.
+    *
+    * @param nodeName The name of the node to create.
+    * @param runList  The run list for the created node.
+    * @return The created node with the automatic attributes populated.
+    * @see OhaiModule
+    * @see ChefUtils#ohaiAutomaticAttributeBinder(com.google.inject.Binder)
+    */
+   Node createNodeAndPopulateAutomaticAttributes(String nodeName, Iterable<String> runList);
+
+   /**
+    * Updates and populate the automatic attributes of the given node.
+    *
+    * @param nodeName The node to update.
+    */
+   void updateAutomaticAttributesOnNode(String nodeName);
+
+   /**
+    * Removes the nodes and clients that have been inactive for a given amount of
+    * time.
+    *
+    * @param prefix       The prefix for the nodes and clients to delete.
+    * @param secondsStale The seconds of inactivity to consider a node and
+    *                     client obsolete.
+    */
+   void cleanupStaleNodesAndClients(String prefix, int secondsStale);
+
+   /**
+    * Deletes the given nodes.
+    *
+    * @param names The names of the nodes to delete.
+    */
+   void deleteAllNodesInList(Iterable<String> names);
+
+   /**
+    * Deletes the given clients.
+    *
+    * @param names The names of the client to delete.
+    */
+   void deleteAllClientsInList(Iterable<String> names);
+
+   /**
+    * Lists the details of all existing nodes.
+    *
+    * @return The details of all existing nodes.
+    */
+   Iterable<? extends Node> listNodes();
+
+   /**
+    * Lists the details of all existing nodes, executing concurrently using the executorService.
+    *
+    * @return The details of all existing nodes.
+    */
+   Iterable<? extends Node> listNodes(ExecutorService executorService);
+
+   /**
+    * Lists the details of all existing nodes in the given environment.
+    *
+    * @param environmentName The name fo the environment.
+    * @return The details of all existing nodes in the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   Iterable<? extends Node> listNodesInEnvironment(String environmentName);
+
+   /**
+    * Lists the details of all existing nodes in the given environment, using the ExecutorService to paralleling the execution.
+    *
+    * @param executorService The thread pool used in this operation
+    * @param environmentName The name fo the environment.
+    * @return The details of all existing nodes in the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   Iterable<? extends Node> listNodesInEnvironment(String environmentName, ExecutorService executorService);
+
+   /**
+    * Lists the details of all existing clients.
+    *
+    * @return The details of all existing clients.
+    */
+   Iterable<? extends Client> listClients();
+
+   /**
+    * Lists the details of all existing clients, but executing concurrently using the threads available in the ExecutorService.
+    *
+    * @return The details of all existing clients.
+    */
+   Iterable<? extends Client> listClients(ExecutorService executorService);
+
+   /**
+    * Lists the details of all existing cookbooks.
+    *
+    * @return The details of all existing cookbooks.
+    */
+   Iterable<? extends CookbookVersion> listCookbookVersions();
+
+   /**
+    * Lists the details of all existing cookbooks. This method is executed concurrently, using the threads available in the ExecutorService.
+    *
+    * @return The details of all existing cookbooks.
+    */
+   Iterable<? extends CookbookVersion> listCookbookVersions(ExecutorService executorService);
+
+   /**
+    * Lists the details of all existing cookbooks in an environment.
+    *
+    * @param environmentName The environment name.
+    * @return The details of all existing cookbooks in an environment.
+    */
+   Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName);
+
+   /**
+    * Lists the details of all existing cookbooks in an environment.
+
+    * @param executorService The thread pool to do the concurrent execution.
+    * @param environmentName The environment name.
+    * @return The details of all existing cookbooks in an environment.
+    */
+   Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName, ExecutorService executorService);
+
+   /**
+    * Lists the details of all existing cookbooks in an environment
+    * limiting number of versions.
+    *
+    * @param environmentName The environment name.
+    * @param numVersions     The number of cookbook versions to include.
+    *                        Use 'all' to return all cookbook versions.
+    * @return The details of all existing cookbooks in environment.
+    */
+   Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName, String numVersions);
+
+   /**
+    * Lists the details of all existing cookbooks in an environment
+    * limiting number of versions.
+    *
+    * @param executorService The executorService used to do this operation concurrently.
+    * @param environmentName The environment name.
+    * @param numVersions     The number of cookbook versions to include.
+    *                        Use 'all' to return all cookbook versions.
+    * @return The details of all existing cookbooks in environment.
+    */
+   Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName, String numVersions, ExecutorService executorService);
+
+   /**
+    * Lists the details of all existing environments.
+    *
+    * @return The details of all existing environments.
+    */
+   @SinceApiVersion("0.10.0")
+   Iterable<? extends Environment> listEnvironments();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/binders/BindChecksumsToJsonPayload.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/binders/BindChecksumsToJsonPayload.java b/apis/chef/src/main/java/org/jclouds/chef/binders/BindChecksumsToJsonPayload.java
new file mode 100644
index 0000000..9a744a3
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/binders/BindChecksumsToJsonPayload.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.binders;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.toArray;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Singleton;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.binders.BindToStringPayload;
+
+@Singleton
+public class BindChecksumsToJsonPayload extends BindToStringPayload {
+
+   @SuppressWarnings("unchecked")
+   public HttpRequest bindToRequest(HttpRequest request, Object input) {
+      checkArgument(checkNotNull(input, "input") instanceof Set, "this binder is only valid for Set!");
+
+      Set<List<Byte>> md5s = (Set<List<Byte>>) input;
+
+      StringBuilder builder = new StringBuilder();
+      builder.append("{\"checksums\":{");
+
+      for (List<Byte> md5 : md5s)
+         builder.append(String.format("\"%s\":null,", base16().lowerCase().encode(toArray(md5))));
+      builder.deleteCharAt(builder.length() - 1);
+      builder.append("}}");
+      super.bindToRequest(request, builder.toString());
+      request.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_JSON);
+      return request;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/binders/BindCreateClientOptionsToJsonPayload.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/binders/BindCreateClientOptionsToJsonPayload.java b/apis/chef/src/main/java/org/jclouds/chef/binders/BindCreateClientOptionsToJsonPayload.java
new file mode 100644
index 0000000..b719c66
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/binders/BindCreateClientOptionsToJsonPayload.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.binders;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.jclouds.chef.options.CreateClientOptions;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.json.Json;
+import org.jclouds.rest.binders.BindToJsonPayload;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+/**
+ * Bind the parameters of a {@link CreateClientOptions} to the payload.
+ */
+public class BindCreateClientOptionsToJsonPayload extends BindToJsonPayload {
+   @Inject
+   public BindCreateClientOptionsToJsonPayload(Json jsonBinder) {
+      super(jsonBinder);
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+      checkArgument(checkNotNull(request, "request") instanceof GeneratedHttpRequest,
+            "this binder is only valid for GeneratedHttpRequests");
+      GeneratedHttpRequest gRequest = (GeneratedHttpRequest) request;
+      checkState(gRequest.getInvocation().getArgs() != null, "args should be initialized at this point");
+
+      String name = checkNotNull(postParams.remove("name"), "name").toString();
+      CreateClientOptions options = (CreateClientOptions) Iterables.find(gRequest.getInvocation().getArgs(),
+            Predicates.instanceOf(CreateClientOptions.class));
+
+      return bindToRequest(request, new CreateClientParams(name, options));
+   }
+
+   @SuppressWarnings("unused")
+   private static class CreateClientParams {
+      private String name;
+
+      private boolean admin;
+
+      public CreateClientParams(String name, CreateClientOptions options) {
+         this.name = name;
+         this.admin = options.isAdmin();
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/binders/BindGenerateKeyForClientToJsonPayload.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/binders/BindGenerateKeyForClientToJsonPayload.java b/apis/chef/src/main/java/org/jclouds/chef/binders/BindGenerateKeyForClientToJsonPayload.java
new file mode 100644
index 0000000..4bd5821
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/binders/BindGenerateKeyForClientToJsonPayload.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.binders;
+
+import javax.inject.Singleton;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.binders.BindToStringPayload;
+
+@Singleton
+public class BindGenerateKeyForClientToJsonPayload extends BindToStringPayload {
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object payload) {
+      super.bindToRequest(request, String.format("{\"name\":\"%s\", \"private_key\": true}", payload));
+      request.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_JSON);
+      return request;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/binders/DatabagItemId.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/binders/DatabagItemId.java b/apis/chef/src/main/java/org/jclouds/chef/binders/DatabagItemId.java
new file mode 100644
index 0000000..5dd5a62
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/binders/DatabagItemId.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.inject.Singleton;
+
+import org.jclouds.chef.domain.DatabagItem;
+
+import com.google.common.base.Function;
+
+@Singleton
+public class DatabagItemId implements Function<Object, String> {
+
+   public String apply(Object from) {
+      return ((DatabagItem) from).getId();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/binders/EnvironmentName.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/binders/EnvironmentName.java b/apis/chef/src/main/java/org/jclouds/chef/binders/EnvironmentName.java
new file mode 100644
index 0000000..1650521
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/binders/EnvironmentName.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.google.common.base.Function;
+import org.jclouds.chef.domain.Environment;
+
+import javax.inject.Singleton;
+
+@Singleton
+public class EnvironmentName implements Function<Object, String> {
+
+   @Override
+   public String apply(Object input) {
+      return ((Environment) input).getName();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/binders/NodeName.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/binders/NodeName.java b/apis/chef/src/main/java/org/jclouds/chef/binders/NodeName.java
new file mode 100644
index 0000000..5277895
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/binders/NodeName.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.inject.Singleton;
+
+import org.jclouds.chef.domain.Node;
+
+import com.google.common.base.Function;
+
+@Singleton
+public class NodeName implements Function<Object, String> {
+
+   public String apply(Object from) {
+      return ((Node) from).getName();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/binders/RoleName.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/binders/RoleName.java b/apis/chef/src/main/java/org/jclouds/chef/binders/RoleName.java
new file mode 100644
index 0000000..aa78a06
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/binders/RoleName.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.inject.Singleton;
+
+import org.jclouds.chef.domain.Role;
+
+import com.google.common.base.Function;
+
+@Singleton
+public class RoleName implements Function<Object, String> {
+
+   public String apply(Object from) {
+      return ((Role) from).getName();
+   }
+
+}


[02/10] Move jclouds-chef to the main jclouds repo

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/suppliers/ChefVersionSupplierTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/suppliers/ChefVersionSupplierTest.java b/apis/chef/src/test/java/org/jclouds/chef/suppliers/ChefVersionSupplierTest.java
new file mode 100644
index 0000000..915f5cf
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/suppliers/ChefVersionSupplierTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.suppliers;
+
+import static org.jclouds.chef.suppliers.ChefVersionSupplier.FALLBACK_VERSION;
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for the {@link ChefVersionSupplier} class.
+ */
+@Test(groups = "unit", testName = "ChefVersionSupplierTest")
+public class ChefVersionSupplierTest {
+
+   public void testReturnsDefaultVersion() {
+      assertEquals(new ChefVersionSupplier("15").get(), FALLBACK_VERSION);
+      assertEquals(new ChefVersionSupplier("0").get(), FALLBACK_VERSION);
+      assertEquals(new ChefVersionSupplier("0.").get(), FALLBACK_VERSION);
+   }
+
+   public void testReturnsMajorVersionIfNotZero() {
+      assertEquals(new ChefVersionSupplier("11.6").get().intValue(), 11);
+      assertEquals(new ChefVersionSupplier("11.6.0").get().intValue(), 11);
+      assertEquals(new ChefVersionSupplier("11.6.0.1").get().intValue(), 11);
+   }
+
+   public void testReturnsMinorVersionIfMajorIsZero() {
+      assertEquals(new ChefVersionSupplier("0.9").get().intValue(), 9);
+      assertEquals(new ChefVersionSupplier("0.9.8").get().intValue(), 9);
+      assertEquals(new ChefVersionSupplier("0.9.8.2").get().intValue(), 9);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/test/TransientChefApiIntegrationTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/test/TransientChefApiIntegrationTest.java b/apis/chef/src/test/java/org/jclouds/chef/test/TransientChefApiIntegrationTest.java
new file mode 100644
index 0000000..ecd5d16
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/test/TransientChefApiIntegrationTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.Properties;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.chef.internal.BaseChefLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ * Tests behavior of {@code TransientChefApi}
+ */
+@Test(groups = { "integration" })
+public class TransientChefApiIntegrationTest extends BaseChefLiveTest<ChefApi> {
+   public static final String PREFIX = System.getProperty("user.name") + "-jcloudstest";
+   private DatabagItem databagItem;
+
+   public TransientChefApiIntegrationTest() {
+      provider = "transientchef";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      return new Properties();
+   }
+
+   public void testCreateDatabag() {
+      api.deleteDatabag(PREFIX);
+      api.createDatabag(PREFIX);
+   }
+
+   @Test(dependsOnMethods = { "testCreateDatabag" })
+   public void testCreateDatabagItem() {
+      Properties config = new Properties();
+      config.setProperty("foo", "bar");
+      databagItem = api.createDatabagItem(PREFIX, new DatabagItem("config", json.toJson(config)));
+      assertNotNull(databagItem);
+      assertEquals(databagItem.getId(), "config");
+      assertEquals(config, json.fromJson(databagItem.toString(), Properties.class));
+   }
+
+   @Test(dependsOnMethods = "testCreateDatabagItem")
+   public void testUpdateDatabagItem() {
+      for (String databagItemId : api.listDatabagItems(PREFIX)) {
+         DatabagItem databagItem = api.getDatabagItem(PREFIX, databagItemId);
+         api.updateDatabagItem(PREFIX, databagItem);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/test/TransientChefApiMetadataTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/test/TransientChefApiMetadataTest.java b/apis/chef/src/test/java/org/jclouds/chef/test/TransientChefApiMetadataTest.java
new file mode 100644
index 0000000..eac33f3
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/test/TransientChefApiMetadataTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.test;
+
+import org.jclouds.View;
+import org.jclouds.rest.internal.BaseHttpApiMetadataTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "TransientChefApiMetadataTest")
+public class TransientChefApiMetadataTest extends BaseHttpApiMetadataTest {
+
+   // no config management abstraction, yet
+   public TransientChefApiMetadataTest() {
+      super(new TransientChefApiMetadata(), ImmutableSet.<TypeToken<? extends View>> of());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/util/ChefUtilsTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/util/ChefUtilsTest.java b/apis/chef/src/test/java/org/jclouds/chef/util/ChefUtilsTest.java
new file mode 100644
index 0000000..40645e7
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/util/ChefUtilsTest.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.chef.util;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Date;
+import java.util.NoSuchElementException;
+
+import org.jclouds.domain.JsonBall;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Tests behavior of {@code ChefUtils}
+ */
+@Test(groups = { "unit" }, sequential = true)
+public class ChefUtilsTest {
+   public static long millis = 1280251180727l;
+   public static String millisString = "1280251180727";
+   public static Date now = new Date(1280251180727l);
+
+   public void testToOhaiTime() {
+      assertEquals(ChefUtils.toOhaiTime(millis).toString(), millisString);
+   }
+
+   public void testFromOhaiTime() {
+      assertEquals(ChefUtils.fromOhaiTime(new JsonBall(millisString)), now);
+
+   }
+
+   @Test(expectedExceptions = NoSuchElementException.class)
+   public void testFindRoleInRunListThrowsNoSuchElementOnRecipe() {
+      ChefUtils.findRoleInRunList(ImmutableList.of("recipe[java]"));
+   }
+
+   public void testFindRoleInRunList() {
+      assertEquals(ChefUtils.findRoleInRunList(ImmutableList.of("role[prod]")), "prod");
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/util/RunListBuilderTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/util/RunListBuilderTest.java b/apis/chef/src/test/java/org/jclouds/chef/util/RunListBuilderTest.java
new file mode 100644
index 0000000..674be80
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/util/RunListBuilderTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.util;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Tests possible uses of RunListBuilder
+ */
+public class RunListBuilderTest {
+
+   @Test
+   public void testRecipeAndRole() {
+      RunListBuilder options = new RunListBuilder();
+      options.addRecipe("recipe").addRole("role");
+      assertEquals(options.build(), ImmutableList.of("recipe[recipe]", "role[role]"));
+   }
+
+   @Test
+   public void testRecipe() {
+      RunListBuilder options = new RunListBuilder();
+      options.addRecipe("test");
+      assertEquals(options.build(), ImmutableList.of("recipe[test]"));
+   }
+
+   @Test
+   public void testRecipes() {
+      RunListBuilder options = new RunListBuilder();
+      options.addRecipes("test", "test2");
+      assertEquals(options.build(), ImmutableList.of("recipe[test]", "recipe[test2]"));
+   }
+
+   @Test
+   public void testRole() {
+      RunListBuilder options = new RunListBuilder();
+      options.addRole("test");
+      assertEquals(options.build(), ImmutableList.of("role[test]"));
+   }
+
+   @Test
+   public void testRoles() {
+      RunListBuilder options = new RunListBuilder();
+      options.addRoles("test", "test2");
+      assertEquals(options.build(), ImmutableList.of("role[test]", "role[test2]"));
+   }
+
+   @Test
+   public void testNoneRecipe() {
+      RunListBuilder options = new RunListBuilder();
+      assertEquals(options.build(), ImmutableList.<String> of());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/ohai/config/JMXTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/ohai/config/JMXTest.java b/apis/chef/src/test/java/org/jclouds/ohai/config/JMXTest.java
new file mode 100644
index 0000000..bcda2e9
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/ohai/config/JMXTest.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.ohai.config;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.testng.Assert.assertEquals;
+
+import java.lang.management.RuntimeMXBean;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.ohai.Automatic;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code JMX}
+ */
+@Test(groups = { "unit" })
+public class JMXTest {
+
+   @Test
+   public void test() {
+
+      final RuntimeMXBean runtime = createMock(RuntimeMXBean.class);
+
+      expect(runtime.getUptime()).andReturn(69876000l);
+
+      replay(runtime);
+
+      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(), new JMXOhaiModule() {
+         @Override
+         protected RuntimeMXBean provideRuntimeMXBean() {
+            return runtime;
+         }
+      });
+      Json json = injector.getInstance(Json.class);
+      Ohai ohai = injector.getInstance(Ohai.class);
+      assertEquals(json.toJson(ohai.ohai.get().get("uptime_seconds")), "69876");
+   }
+
+   static class Ohai {
+      private Supplier<Map<String, JsonBall>> ohai;
+
+      @Inject
+      public Ohai(@Automatic Supplier<Map<String, JsonBall>> ohai) {
+         this.ohai = ohai;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/ohai/config/OhaiModuleTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/ohai/config/OhaiModuleTest.java b/apis/chef/src/test/java/org/jclouds/ohai/config/OhaiModuleTest.java
new file mode 100644
index 0000000..ecc4562
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/ohai/config/OhaiModuleTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.ohai.config;
+
+import static org.jclouds.chef.util.ChefUtils.ohaiAutomaticAttributeBinder;
+import static org.testng.Assert.assertEquals;
+
+import java.net.SocketException;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.inject.Inject;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.ohai.Automatic;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.TypeLiteral;
+import com.google.inject.multibindings.MapBinder;
+import com.google.inject.util.Providers;
+
+/**
+ * Tests behavior of {@code OhaiModule}
+ */
+@Test(groups = { "unit" })
+public class OhaiModuleTest {
+
+   @Test
+   public void test() throws SocketException {
+
+      final Properties sysProperties = new Properties();
+
+      sysProperties.setProperty("os.name", "Mac OS X");
+      sysProperties.setProperty("os.version", "10.3.0");
+      sysProperties.setProperty("user.name", "user");
+
+      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(), new OhaiModule() {
+         @Override
+         protected Long millis() {
+            return 127999291932529l;
+         }
+
+         @Override
+         protected Properties systemProperties() {
+            return sysProperties;
+         }
+
+      });
+      Ohai ohai = injector.getInstance(Ohai.class);
+      Json json = injector.getInstance(Json.class);
+
+      assertEquals(
+            json.toJson(ohai.ohai.get(), new TypeLiteral<Map<String, JsonBall>>() {
+            }.getType()),
+            "{\"ohai_time\":127999291932529,\"platform\":\"macosx\",\"platform_version\":\"10.3.0\",\"current_user\":\"user\",\"jvm\":{\"system\":{\"user.name\":\"user\",\"os.version\":\"10.3.0\",\"os.name\":\"Mac OS X\"}}}");
+   }
+
+   public void test2modules() throws SocketException {
+
+      final Properties sysProperties = new Properties();
+
+      sysProperties.setProperty("os.name", "Mac OS X");
+      sysProperties.setProperty("os.version", "10.3.0");
+      sysProperties.setProperty("user.name", "user");
+
+      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(), new OhaiModule() {
+         @Override
+         protected Long millis() {
+            return 1279992919l;
+         }
+
+         @Override
+         protected Properties systemProperties() {
+            return sysProperties;
+         }
+
+      }, new AbstractModule() {
+
+         @Override
+         protected void configure() {
+            MapBinder<String, Supplier<JsonBall>> mapbinder = ohaiAutomaticAttributeBinder(binder());
+            mapbinder.addBinding("test").toProvider(
+                  Providers.of(Suppliers.ofInstance(new JsonBall("{\"prop1\":\"test1\"}"))));
+         }
+
+      }, new AbstractModule() {
+
+         @Override
+         protected void configure() {
+            MapBinder<String, Supplier<JsonBall>> mapbinder = ohaiAutomaticAttributeBinder(binder());
+            mapbinder.addBinding("test").toProvider(
+                  Providers.of(Suppliers.ofInstance(new JsonBall("{\"prop2\":\"test2\"}"))));
+         }
+
+      });
+      Ohai ohai = injector.getInstance(Ohai.class);
+      Json json = injector.getInstance(Json.class);
+
+      assertEquals(
+            json.toJson(ohai.ohai.get(), new TypeLiteral<Map<String, JsonBall>>() {
+            }.getType()),
+            "{\"ohai_time\":1279992919,\"platform\":\"macosx\",\"platform_version\":\"10.3.0\",\"current_user\":\"user\",\"test\":{\"prop2\":\"test2\",\"prop1\":\"test1\"},\"jvm\":{\"system\":{\"user.name\":\"user\",\"os.version\":\"10.3.0\",\"os.name\":\"Mac OS X\"}}}");
+   }
+
+   static class Ohai {
+      private Supplier<Map<String, JsonBall>> ohai;
+
+      @Inject
+      public Ohai(@Automatic Supplier<Map<String, JsonBall>> ohai) {
+         this.ohai = ohai;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/ohai/functions/ByteArrayToMacAddressTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/ohai/functions/ByteArrayToMacAddressTest.java b/apis/chef/src/test/java/org/jclouds/ohai/functions/ByteArrayToMacAddressTest.java
new file mode 100644
index 0000000..f736dad
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/ohai/functions/ByteArrayToMacAddressTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ohai.functions;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+/**
+ * Tests behavior of {@code ByteArrayToMacAddress}
+ */
+@Test(groups = { "unit" }, sequential = true)
+public class ByteArrayToMacAddressTest {
+
+   public void test() {
+      assertEquals(new ByteArrayToMacAddress().apply(base16().lowerCase().decode("0026bb09e6c4")), "00:26:bb:09:e6:c4");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/ohai/functions/NestSlashKeysTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/ohai/functions/NestSlashKeysTest.java b/apis/chef/src/test/java/org/jclouds/ohai/functions/NestSlashKeysTest.java
new file mode 100644
index 0000000..594ab58
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/ohai/functions/NestSlashKeysTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.ohai.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code NestSlashKeys}
+ */
+@Test(groups = { "unit" }, sequential = true)
+public class NestSlashKeysTest {
+
+   private NestSlashKeys converter;
+   private Json json;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+      converter = injector.getInstance(NestSlashKeys.class);
+      json = injector.getInstance(Json.class);
+   }
+
+   @Test
+   public void testBase() {
+      assertEquals(
+            json.toJson(converter.apply(ImmutableMultimap.<String, Supplier<JsonBall>> of("java",
+                  Suppliers.ofInstance(new JsonBall("java"))))), "{\"java\":\"java\"}");
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testIllegal() {
+      json.toJson(converter.apply(ImmutableMultimap.<String, Supplier<JsonBall>> of("java",
+            Suppliers.ofInstance(new JsonBall("java")), "java/system", Suppliers.ofInstance(new JsonBall("system")))));
+   }
+
+   @Test
+   public void testOne() {
+      assertEquals(
+            json.toJson(converter.apply(ImmutableMultimap.<String, Supplier<JsonBall>> of("java",
+                  Suppliers.ofInstance(new JsonBall("{\"time\":\"time\"}")), "java/system",
+                  Suppliers.ofInstance(new JsonBall("system"))))),
+            "{\"java\":{\"system\":\"system\",\"time\":\"time\"}}");
+   }
+
+   @Test
+   public void testOneDuplicate() {
+      assertEquals(
+            json.toJson(converter.apply(ImmutableMultimap.<String, Supplier<JsonBall>> of("java",
+                  Suppliers.ofInstance(new JsonBall("{\"time\":\"time\"}")), "java",
+                  Suppliers.ofInstance(new JsonBall("{\"system\":\"system\"}"))))),
+            "{\"java\":{\"system\":\"system\",\"time\":\"time\"}}");
+   }
+
+   @Test
+   public void testMerge() {
+      assertEquals(
+            json.toJson(converter.apply(ImmutableMultimap.<String, Supplier<JsonBall>> of("java",
+                  Suppliers.ofInstance(new JsonBall("{\"time\":{\"1\":\"hello\"}}")), "java/time",
+                  Suppliers.ofInstance(new JsonBall("{\"2\":\"goodbye\"}"))))),
+            "{\"java\":{\"time\":{\"2\":\"goodbye\",\"1\":\"hello\"}}}");
+   }
+
+   @Test
+   public void testMergeNestedTwice() {
+      assertEquals(
+            json.toJson(converter.apply(ImmutableMultimap.<String, Supplier<JsonBall>> of("java",
+                  Suppliers.ofInstance(new JsonBall("{\"time\":{\"1\":\"hello\"}}")), "java",
+                  Suppliers.ofInstance(new JsonBall("{\"time\":{\"2\":\"goodbye\"}}"))))),
+            "{\"java\":{\"time\":{\"2\":\"goodbye\",\"1\":\"hello\"}}}");
+   }
+
+   @Test
+   public void testReplaceList() {
+      assertEquals(
+            json.toJson(converter.apply(ImmutableMultimap.<String, Supplier<JsonBall>> of("java",
+                  Suppliers.ofInstance(new JsonBall("{\"time\":{\"1\":[\"hello\"]}}")), "java/time",
+                  Suppliers.ofInstance(new JsonBall("{\"1\":[\"goodbye\"]}"))))),
+            "{\"java\":{\"time\":{\"1\":[\"goodbye\"]}}}");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/apache-chef-demo-cookbook.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/apache-chef-demo-cookbook.json b/apis/chef/src/test/resources/apache-chef-demo-cookbook.json
new file mode 100644
index 0000000..228a3c0
--- /dev/null
+++ b/apis/chef/src/test/resources/apache-chef-demo-cookbook.json
@@ -0,0 +1,46 @@
+{
+    "definitions": [],
+    "name": "apache-chef-demo-0.0.0",
+    "attributes": [],
+    "files": [],
+    "json_class": "Chef::CookbookVersion",
+    "providers": [],
+    "metadata": {
+        "dependencies": {},
+        "name": "apache-chef-demo",
+        "maintainer_email": "youremail@example.com",
+        "attributes": {},
+        "license": "Apache v2.0",
+        "maintainer": "Your Name",
+        "suggestions": {},
+        "platforms": {},
+        "long_description": "",
+        "recommendations": {},
+        "version": "0.0.0",
+        "groupings": {},
+        "recipes": {},
+        "conflicting": {},
+        "description": "A fabulous new cookbook",
+        "replacing": {},
+        "providing": {}
+    }, "libraries": [],
+    "resources": [],
+    "templates": [],
+    "cookbook_name": "apache-chef-demo",
+    "version": "0.0.0",
+    "recipes": [],
+    "root_files": [{
+        "name": "README",
+        "url": "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-11637f98942eafbf49c71b7f2f048b78?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277766181&Signature=zgpNl6wSxjTNovqZu2nJq0JztU8%3D",
+        "checksum": "11637f98942eafbf49c71b7f2f048b78",
+        "path": "README",
+        "specificity": "default"
+    }, {
+        "name": "Rakefile",
+        "url": "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-ebcf925a1651b4e04b9cd8aac2bc54eb?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277766181&Signature=EFzzDSKKytTl7b%2FxrCeNLh05zj4%3D",
+        "checksum": "ebcf925a1651b4e04b9cd8aac2bc54eb",
+        "path": "Rakefile",
+        "specificity": "default"
+    }],
+    "chef_type": "cookbook_version"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/bootstrap-env.sh
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/bootstrap-env.sh b/apis/chef/src/test/resources/bootstrap-env.sh
new file mode 100755
index 0000000..315e248
--- /dev/null
+++ b/apis/chef/src/test/resources/bootstrap-env.sh
@@ -0,0 +1,56 @@
+mkdir -p /etc/chef
+cat >> /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE'
+	require 'rubygems'
+	require 'ohai'
+	o = Ohai::System.new
+	o.all_plugins
+	node_name "foo-" + o[:ipaddress]
+	log_level :info
+	log_location STDOUT
+	validation_client_name "chef-validator"
+	chef_server_url "http://localhost:4000"
+END_OF_JCLOUDS_FILE
+cat >> /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE'
+	-----BEGIN PRIVATE KEY-----
+	LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVB
+	eWIyWkpKcUdtMEtLUis4bmZRSk5zU2QrRjl0WE5NVjdDZk9jVzZqc3FzOEVaZ2lW
+	ClIwOWhEMUlZT2o0WXFNMHFKT05sZ3lnNHhSV2V3ZFNHN1FUUGoxbEpwVkFpZGE5
+	c1h5MitrenlhZ1pBMUFtME8KWmNicWI1aG9lSURnY1grZURhNzlzMHUwRG9tamNm
+	TzlFS2h2SExCeit6TSszUXFQUmtQVjhuWVRiZnMrSGpWegp6T1U2RDFCMFhSMytJ
+	UFpabDJBbldzMmQwcWhuU3RIY0RVdm5SVlEwUDQ4Mll3TjlWZ2NlT1p0cFB6MERD
+	S0VKCjVUeDVTVHViOGswL3p0L1ZBTUhRYWZMU3VRTUxkMnM0Wkx1T1pwdE4vL3VB
+	c1RteGlyZXFkMzd6KzhaVGRCYkoKOExFcEoraUNYdVNmbTVhVWg3aXc2b3h2VG9Z
+	MkFMNTMraksyVVFJREFRQUJBb0lCQVFEQTg4QjNpL3hXbjB2WApCVnhGYW1DWW9l
+	Y3VOakd3WFhrU3laZXc2MTZBK0VPQ3U0N2JoNGFUdXJkRmJZTDBZRmFBdGFXdnps
+	YU4yZUhnCkRiK0hEdVRlZkUyOStXa2NHazZTc2hQbWl6NVQwWE9DQUlDV3c2d1NW
+	RGtIbUd3UzRqWnZiQUZtN1c4bndHazkKWWh4Z3hGaVJuZ3N3SlpGb3BPTG9GNVdY
+	czJ0ZDhndUlZTnNsTXBvN3R1NTBpRm5CSHdLTzJac1BBazh0OW5uUwp4bERhdkty
+	dXltRW1xSENyMytkdGlvNWVhZW5KY3AzZmpvWEJRT0tVazNpcElJMjlYUkI4TnFl
+	Q1ZWLzdLeHdxCmNrcU9CRWJSd0JjbGNreUliRCtSaUFnS3ZPZWxPUmpFaUU5UjQy
+	dnVxdnhSQTZrOWtkOW83dXRsWDBBVXRwRW4KM2daYzZMZXBBb0dCQVA5YWVsNVk3
+	NStzSzJKSlVOT09oTzhhZTQ1Y2RzaWxwMnlJMFgrVUJhU3VRczIrZHlQcAprcEVI
+	QXhkNHBtbVN2bi84YzlUbEVaaHIrcVliQUJYVlBsRG5jeHBJdXcyQWpiazdzL1M0
+	WGFTS3NScXBYTDU3CnpqL1FPcUxrUms4K09WVjlxNmxNZVFOcUx0RWoxdTZKUHZp
+	WDcwUm8rRlF0UnR0Tk9ZYmZkUC9mQW9HQkFNcEEKWGpSNXdvVjVzVWIrUkVnOXZF
+	dVlvOFJTeU9hcnhxS0ZDSVhWVU5zTE94KzIyK0FLNCtDUXBidWVXTjdqb3RybApZ
+	RDZ1VDZzdldpM0FBQzdraVkwVUkvZmpWUFJDVWk4dFZvUVVFMFRhVTVWTElUYVlP
+	QitXL2JCYURFNE05NTYwCjFOdURXTzkwYmFBNWRmVTQ0aXV6dmEwMnJHSlhLOStu
+	UzNvOG5rL1BBb0dCQUxPTDZkam5EZTRtd0FhRzZKY28KY2Q0eHI4amt5UHpDUlp1
+	eUJDU0Jid3BoSVVYTGM3aERwclBreTA2NG5jSkQxVURtd0lka1hkL2ZwTWtnMlFt
+	QQovQ1VrNkxFRmpNaXNxSG9qT2FDTDlnUVpKUGhMTjVRVU4yeDFQSldHanMxdlFo
+	OFRreDBpVVVDT2E4YlFQWE5SCiszNE9Uc1c2VFVuYTRDU1pBeWNMZmhmZkFvR0JB
+	SWdnVnNlZkJDdnVRa0YwTmVVaG1EQ1JaZmhuZDh5NTVSSFIKMUhDdnFLSWxwdity
+	aGNYL3pteUJMdXRlb3BZeVJKUnNPaUUyRlcwMGk4K3JJUFJ1NFozUTVueWJ4N3cz
+	UHpWOQpvSE41UjViYUU5T3lJNEtwWld6dHBZWWl0WkY2N05jbkF2VlVMSEhPdlZK
+	UUduS1lmTEhKWW1ySkY3R0Exb2pNCkF1TWRGYmpGQW9HQVB4VWh4d0Z5OGdhcUJh
+	aEtVRVpuNEY4MUhGUDVpaEdoa1Q0UUw2QUZQTzJlK0poSUdqdVIKMjcrODVoY0Zx
+	UStISFZ0RnNtODFiL2ErUjdQNFV1Q1JnYzhlQ2p4UU1vSjFYbDRuN1ZialBiSE1u
+	SU4wUnl2ZApPNFpwV0RXWW5DTzAyMUpUT1VVT0o0Si95MDQxNkJ2a3cwejU5eTdz
+	Tlg3d0RCQkhIYksvWENjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
+	-----END PRIVATE KEY-----
+	
+END_OF_JCLOUDS_FILE
+cat >> /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE'
+	{"tomcat6":{"ssl_port":8433},"environment":"env","run_list":["recipe[apache2]","role[webserver]"]}
+END_OF_JCLOUDS_FILE
+chef-client -j /etc/chef/first-boot.json -E "env"

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/bootstrap.sh
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/bootstrap.sh b/apis/chef/src/test/resources/bootstrap.sh
new file mode 100755
index 0000000..0eb402e
--- /dev/null
+++ b/apis/chef/src/test/resources/bootstrap.sh
@@ -0,0 +1,56 @@
+mkdir -p /etc/chef
+cat >> /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE'
+	require 'rubygems'
+	require 'ohai'
+	o = Ohai::System.new
+	o.all_plugins
+	node_name "foo-" + o[:ipaddress]
+	log_level :info
+	log_location STDOUT
+	validation_client_name "chef-validator"
+	chef_server_url "http://localhost:4000"
+END_OF_JCLOUDS_FILE
+cat >> /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE'
+	-----BEGIN PRIVATE KEY-----
+	LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVB
+	eWIyWkpKcUdtMEtLUis4bmZRSk5zU2QrRjl0WE5NVjdDZk9jVzZqc3FzOEVaZ2lW
+	ClIwOWhEMUlZT2o0WXFNMHFKT05sZ3lnNHhSV2V3ZFNHN1FUUGoxbEpwVkFpZGE5
+	c1h5MitrenlhZ1pBMUFtME8KWmNicWI1aG9lSURnY1grZURhNzlzMHUwRG9tamNm
+	TzlFS2h2SExCeit6TSszUXFQUmtQVjhuWVRiZnMrSGpWegp6T1U2RDFCMFhSMytJ
+	UFpabDJBbldzMmQwcWhuU3RIY0RVdm5SVlEwUDQ4Mll3TjlWZ2NlT1p0cFB6MERD
+	S0VKCjVUeDVTVHViOGswL3p0L1ZBTUhRYWZMU3VRTUxkMnM0Wkx1T1pwdE4vL3VB
+	c1RteGlyZXFkMzd6KzhaVGRCYkoKOExFcEoraUNYdVNmbTVhVWg3aXc2b3h2VG9Z
+	MkFMNTMraksyVVFJREFRQUJBb0lCQVFEQTg4QjNpL3hXbjB2WApCVnhGYW1DWW9l
+	Y3VOakd3WFhrU3laZXc2MTZBK0VPQ3U0N2JoNGFUdXJkRmJZTDBZRmFBdGFXdnps
+	YU4yZUhnCkRiK0hEdVRlZkUyOStXa2NHazZTc2hQbWl6NVQwWE9DQUlDV3c2d1NW
+	RGtIbUd3UzRqWnZiQUZtN1c4bndHazkKWWh4Z3hGaVJuZ3N3SlpGb3BPTG9GNVdY
+	czJ0ZDhndUlZTnNsTXBvN3R1NTBpRm5CSHdLTzJac1BBazh0OW5uUwp4bERhdkty
+	dXltRW1xSENyMytkdGlvNWVhZW5KY3AzZmpvWEJRT0tVazNpcElJMjlYUkI4TnFl
+	Q1ZWLzdLeHdxCmNrcU9CRWJSd0JjbGNreUliRCtSaUFnS3ZPZWxPUmpFaUU5UjQy
+	dnVxdnhSQTZrOWtkOW83dXRsWDBBVXRwRW4KM2daYzZMZXBBb0dCQVA5YWVsNVk3
+	NStzSzJKSlVOT09oTzhhZTQ1Y2RzaWxwMnlJMFgrVUJhU3VRczIrZHlQcAprcEVI
+	QXhkNHBtbVN2bi84YzlUbEVaaHIrcVliQUJYVlBsRG5jeHBJdXcyQWpiazdzL1M0
+	WGFTS3NScXBYTDU3CnpqL1FPcUxrUms4K09WVjlxNmxNZVFOcUx0RWoxdTZKUHZp
+	WDcwUm8rRlF0UnR0Tk9ZYmZkUC9mQW9HQkFNcEEKWGpSNXdvVjVzVWIrUkVnOXZF
+	dVlvOFJTeU9hcnhxS0ZDSVhWVU5zTE94KzIyK0FLNCtDUXBidWVXTjdqb3RybApZ
+	RDZ1VDZzdldpM0FBQzdraVkwVUkvZmpWUFJDVWk4dFZvUVVFMFRhVTVWTElUYVlP
+	QitXL2JCYURFNE05NTYwCjFOdURXTzkwYmFBNWRmVTQ0aXV6dmEwMnJHSlhLOStu
+	UzNvOG5rL1BBb0dCQUxPTDZkam5EZTRtd0FhRzZKY28KY2Q0eHI4amt5UHpDUlp1
+	eUJDU0Jid3BoSVVYTGM3aERwclBreTA2NG5jSkQxVURtd0lka1hkL2ZwTWtnMlFt
+	QQovQ1VrNkxFRmpNaXNxSG9qT2FDTDlnUVpKUGhMTjVRVU4yeDFQSldHanMxdlFo
+	OFRreDBpVVVDT2E4YlFQWE5SCiszNE9Uc1c2VFVuYTRDU1pBeWNMZmhmZkFvR0JB
+	SWdnVnNlZkJDdnVRa0YwTmVVaG1EQ1JaZmhuZDh5NTVSSFIKMUhDdnFLSWxwdity
+	aGNYL3pteUJMdXRlb3BZeVJKUnNPaUUyRlcwMGk4K3JJUFJ1NFozUTVueWJ4N3cz
+	UHpWOQpvSE41UjViYUU5T3lJNEtwWld6dHBZWWl0WkY2N05jbkF2VlVMSEhPdlZK
+	UUduS1lmTEhKWW1ySkY3R0Exb2pNCkF1TWRGYmpGQW9HQVB4VWh4d0Z5OGdhcUJh
+	aEtVRVpuNEY4MUhGUDVpaEdoa1Q0UUw2QUZQTzJlK0poSUdqdVIKMjcrODVoY0Zx
+	UStISFZ0RnNtODFiL2ErUjdQNFV1Q1JnYzhlQ2p4UU1vSjFYbDRuN1ZialBiSE1u
+	SU4wUnl2ZApPNFpwV0RXWW5DTzAyMUpUT1VVT0o0Si95MDQxNkJ2a3cwejU5eTdz
+	Tlg3d0RCQkhIYksvWENjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
+	-----END PRIVATE KEY-----
+	
+END_OF_JCLOUDS_FILE
+cat >> /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE'
+	{"tomcat6":{"ssl_port":8433},"run_list":["recipe[apache2]","role[webserver]"]}
+END_OF_JCLOUDS_FILE
+chef-client -j /etc/chef/first-boot.json

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/brew-cookbook.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/brew-cookbook.json b/apis/chef/src/test/resources/brew-cookbook.json
new file mode 100644
index 0000000..dcf7aca
--- /dev/null
+++ b/apis/chef/src/test/resources/brew-cookbook.json
@@ -0,0 +1,48 @@
+{ "attributes" : [  ],
+  "chef_type" : "cookbook_version",
+  "cookbook_name" : "brew",
+  "definitions" : [  ],
+  "files" : [  ],
+  "json_class" : "Chef::CookbookVersion",
+  "libraries" : [  ],
+  "metadata" : { "attributes" : {  },
+      "conflicting" : {  },
+      "dependencies" : {  },
+      "description" : "A fabulous new cookbook",
+      "groupings" : {  },
+      "license" : "Apache v2.0",
+      "long_description" : "",
+      "maintainer" : "Your Name",
+      "maintainer_email" : "youremail@example.com",
+      "name" : "brew",
+      "platforms" : {  },
+      "providing" : { "brew" : "0.0.0" },
+      "recipes" : { "brew" : "" },
+      "recommendations" : {  },
+      "replacing" : {  },
+      "suggestions" : {  },
+      "version" : "0.0.0"
+    },
+  "name" : "brew-0.0.0",
+  "providers" : [ { "checksum" : "0c5ecd7788cf4f6c7de2a57193897a6c",
+        "name" : "brew.rb",
+        "path" : "providers/brew.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-0c5ecd7788cf4f6c7de2a57193897a6c?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774465&Signature=brTA3YkBF7iDnjPGCCHxgm7AHko%3D"
+      } ],
+  "recipes" : [ { "checksum" : "1dda05ed139664f1f89b9dec482b77c0",
+        "name" : "default.rb",
+        "path" : "recipes/default.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-1dda05ed139664f1f89b9dec482b77c0?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774465&Signature=dOzPk64at92zOfZlxt1suDpGuPs%3D"
+      } ],
+  "resources" : [ { "checksum" : "0189e76ccc476701d6b374e5a1a27347",
+        "name" : "brew.rb",
+        "path" : "resources/brew.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-0189e76ccc476701d6b374e5a1a27347?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774465&Signature=ufrI1k6pKJ1%2FBRMAaIGr6icJlpc%3D"
+      } ],
+  "root_files" : [  ],
+  "templates" : [  ],
+  "version" : "0.0.0"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/client.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/client.json b/apis/chef/src/test/resources/client.json
new file mode 100644
index 0000000..eef7c71
--- /dev/null
+++ b/apis/chef/src/test/resources/client.json
@@ -0,0 +1,8 @@
+{ "certificate" : "-----BEGIN CERTIFICATE-----\nMIIClzCCAgCgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxFjAUBgNVBAoM\nDU9wc2NvZGUsIEluYy4xHDAaBgNVBAsME0NlcnRpZmljYXRlIFNlcnZpY2UxMjAw\nBgNVBAMMKW9wc2NvZGUuY29tL2VtYWlsQWRkcmVzcz1hdXRoQG9wc2NvZGUuY29t\nMB4XDTEwMDczMDIwNDEzMFoXDTIwMDcyNzIwNDEzMFowADCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAMm9mSSahptCikfvJ30CTbEnfhfbVzTFewnznFuo\n7KrPBGYIlUdPYQ9SGDo+GKjNKiTjZYMoOMUVnsHUhu0Ez49ZSaVQInWvbF8tvpM8\nmoGQNQJtDmXG6m+YaHiA4HF/ng2u/bNLtA6Jo3HzvRCobxywc/szPt0Kj0ZD1fJ2\nE237Ph41c8zlOg9QdF0d/iD2WZdgJ1rNndKoZ0rR3A1L50VUND+PNmMDfVYHHjmb\naT89AwihCeU8eUk7m/JNP87f1QDB0Gny0rkDC3drOGS7jmabTf/7gLE5sYq3qnd+\n8/vGU3QWyfCxKSfogl7kn5uWlIe4sOqMb06GNgC+d/oytlECAwEAATANBgkqhkiG\n9w0BAQUFAAOBgQBftzSZxstWw60GqRTDNN/F2GnrdtnKBoXzHww3r6jtGEylYq20\n5KfKpEx+sPX0gyZuYJiXC2CkEjImAluWKcdN9ZF6VD541sheAjbiaU7q7ZsztTxF\nWUH2tCvHeDXYKPKek3QzL7bYpUhLnCN/XxEv6ibeMDwtI7f5qpk2Aspzcw==\n-----END CERTIFICATE-----\n",
+  "clientname" : "adriancole-jcloudstest",
+  "name" : "adriancole-jcloudstest",
+  "orgname" : "jclouds",
+  "private_key" : "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAyb2ZJJqGm0KKR+8nfQJNsSd+F9tXNMV7CfOcW6jsqs8EZgiV\nR09hD1IYOj4YqM0qJONlgyg4xRWewdSG7QTPj1lJpVAida9sXy2+kzyagZA1Am0O\nZcbqb5hoeIDgcX+eDa79s0u0DomjcfO9EKhvHLBz+zM+3QqPRkPV8nYTbfs+HjVz\nzOU6D1B0XR3+IPZZl2AnWs2d0qhnStHcDUvnRVQ0P482YwN9VgceOZtpPz0DCKEJ\n5Tx5STub8k0/zt/VAMHQafLSuQMLd2s4ZLuOZptN//uAsTmxireqd37z+8ZTdBbJ\n8LEpJ+iCXuSfm5aUh7iw6oxvToY2AL53+jK2UQIDAQABAoIBAQDA88B3i/xWn0vX\nBVxFamCYoecuNjGwXXkSyZew616A+EOCu47bh4aTurdFbYL0YFaAtaWvzlaN2eHg\nDb+HDuTefE29+WkcGk6SshPmiz5T0XOCAICWw6wSVDkHmGwS4jZvbAFm7W8nwGk9\nYhxgxFiRngswJZFopOLoF5WXs2td8guIYNslMpo7tu50iFnBHwKO2ZsPAk8t9nnS\nxlDavKruymEmqHCr3+dtio5eaenJcp3fjoXBQOKUk3ipII29XRB8NqeCVV/7Kxwq\nckqOBEbRwBclckyIbD+RiAgKvOelORjEiE9R42vuqvxRA6k9kd9o7utlX0AUtpEn\n3gZc6LepAoGBAP9ael5Y75+sK2JJUNOOhO8ae45cdsilp2yI0X+UBaSuQs2+dyPp\nkpEHAxd4pmmSvn/8c9TlEZhr+qYbABXVPlDncxpIuw2Ajbk7s/S4XaSKsRqpXL57\nzj/QOqLkRk8+OVV9q6lMeQNqLtEj1u6JPviX70Ro+FQtRttNOYbfdP/fAoGBAMpA\nXjR5woV5sUb+REg9vEuYo
 8RSyOarxqKFCIXVUNsLOx+22+AK4+CQpbueWN7jotrl\nYD6uT6svWi3AAC7kiY0UI/fjVPRCUi8tVoQUE0TaU5VLITaYOB+W/bBaDE4M9560\n1NuDWO90baA5dfU44iuzva02rGJXK9+nS3o8nk/PAoGBALOL6djnDe4mwAaG6Jco\ncd4xr8jkyPzCRZuyBCSBbwphIUXLc7hDprPky064ncJD1UDmwIdkXd/fpMkg2QmA\n/CUk6LEFjMisqHojOaCL9gQZJPhLN5QUN2x1PJWGjs1vQh8Tkx0iUUCOa8bQPXNR\n+34OTsW6TUna4CSZAycLfhffAoGBAIggVsefBCvuQkF0NeUhmDCRZfhnd8y55RHR\n1HCvqKIlpv+rhcX/zmyBLuteopYyRJRsOiE2FW00i8+rIPRu4Z3Q5nybx7w3PzV9\noHN5R5baE9OyI4KpZWztpYYitZF67NcnAvVULHHOvVJQGnKYfLHJYmrJF7GA1ojM\nAuMdFbjFAoGAPxUhxwFy8gaqBahKUEZn4F81HFP5ihGhkT4QL6AFPO2e+JhIGjuR\n27+85hcFqQ+HHVtFsm81b/a+R7P4UuCRgc8eCjxQMoJ1Xl4n7VbjPbHMnIN0Ryvd\nO4ZpWDWYnCO021JTOUUOJ4J/y0416Bvkw0z59y7sNX7wDBBHHbK/XCc=\n-----END RSA PRIVATE KEY-----\n",
+  "uri" : "https://api.opscode.com/organizations/jclouds/clients/adriancole-jcloudstest",
+  "validator" : false
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/clients_list.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/clients_list.json b/apis/chef/src/test/resources/clients_list.json
new file mode 100644
index 0000000..000110b
--- /dev/null
+++ b/apis/chef/src/test/resources/clients_list.json
@@ -0,0 +1,5 @@
+{
+  "chef-webui": "http://localhost:4000/clients/chef-webui",
+  "chef-validator": "http://localhost:4000/clients/chef-validator",
+  "adam": "http://localhost:4000/clients/adam"
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/data_list.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/data_list.json b/apis/chef/src/test/resources/data_list.json
new file mode 100644
index 0000000..de9205d
--- /dev/null
+++ b/apis/chef/src/test/resources/data_list.json
@@ -0,0 +1,4 @@
+{
+    "users": "http://localhost:4000/data/users",
+    "applications": "http://localhost:4000/data/applications"
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/env_cookbooks.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/env_cookbooks.json b/apis/chef/src/test/resources/env_cookbooks.json
new file mode 100644
index 0000000..ee7114f
--- /dev/null
+++ b/apis/chef/src/test/resources/env_cookbooks.json
@@ -0,0 +1,20 @@
+{
+  "apache2" => {
+    "url" => "http://localhost:4000/cookbooks/apache2",
+    "versions" => [
+      {"url" => "http://localhost:4000/cookbooks/apache2/5.1.0",
+       "version" => "5.1.0"},
+      {"url" => "http://localhost:4000/cookbooks/apache2/4.2.0",
+       "version" => "4.2.0"}
+    ]
+  },
+  "nginx" => {
+    "url" => "http://localhost:4000/cookbooks/nginx",
+    "versions" => [
+      {"url" => "http://localhost:4000/cookbooks/nginx/1.0.0",
+       "version" => "1.0.0"},
+      {"url" => "http://localhost:4000/cookbooks/nginx/0.3.0",
+       "version" => "0.3.0"}
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/environment_recipes.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/environment_recipes.json b/apis/chef/src/test/resources/environment_recipes.json
new file mode 100644
index 0000000..cca3a11
--- /dev/null
+++ b/apis/chef/src/test/resources/environment_recipes.json
@@ -0,0 +1,6 @@
+[
+  "ant",
+  "apache2",
+  "apache2::mod_auth_openid"
+]
+ 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/mysql-cookbook.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/mysql-cookbook.json b/apis/chef/src/test/resources/mysql-cookbook.json
new file mode 100644
index 0000000..b0450ae
--- /dev/null
+++ b/apis/chef/src/test/resources/mysql-cookbook.json
@@ -0,0 +1,268 @@
+{ "attributes" : [ { "checksum" : "548fa4bc548b8b59ac98fffee8e81f4a",
+        "name" : "server.rb",
+        "path" : "attributes/server.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-548fa4bc548b8b59ac98fffee8e81f4a?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=MsgggSKS0T1q1Lc72nJpHORBQX0%3D"
+      } ],
+  "chef_type" : "cookbook_version",
+  "cookbook_name" : "mysql",
+  "definitions" : [  ],
+  "files" : [  ],
+  "json_class" : "Chef::CookbookVersion",
+  "libraries" : [ { "checksum" : "b2eb0760c07734be9c637dcffc86175a",
+        "name" : "database.rb",
+        "path" : "libraries/database.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-b2eb0760c07734be9c637dcffc86175a?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=CvVbxzrA%2Fuxc59ZjLR5BsMozfxk%3D"
+      } ],
+  "metadata" : { "attributes" : { "mysql/bind_address" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "ipaddress",
+              "description" : "Address that mysqld should listen on",
+              "display_name" : "MySQL Bind Address",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/datadir" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "/var/lib/mysql",
+              "description" : "Location of mysql databases",
+              "display_name" : "MySQL Data Directory",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/ec2_path" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "/mnt/mysql",
+              "description" : "Location of mysql directory on EC2 instance EBS volumes",
+              "display_name" : "MySQL EC2 Path",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/server_root_password" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "randomly generated",
+              "description" : "Randomly generated password for the mysqld root user",
+              "display_name" : "MySQL Server Root Password",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/tunable" : { "calculated" : false,
+              "choice" : [  ],
+              "description" : "Hash of MySQL tunable attributes",
+              "display_name" : "MySQL Tunables",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "hash"
+            },
+          "mysql/tunable/back_log" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "128",
+              "display_name" : "MySQL Tunable Back Log",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/tunable/key_buffer" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "250M",
+              "display_name" : "MySQL Tuntable Key Buffer",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/tunable/max_connections" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "800",
+              "display_name" : "MySQL Tunable Max Connections",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/tunable/max_heap_table_size" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "32M",
+              "display_name" : "MySQL Tunable Max Heap Table Size",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/tunable/net_read_timeout" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "30",
+              "display_name" : "MySQL Tunable Net Read Timeout",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/tunable/net_write_timeout" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "30",
+              "display_name" : "MySQL Tunable Net Write Timeout",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/tunable/table_cache" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "128",
+              "display_name" : "MySQL Tunable Table Cache",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            },
+          "mysql/tunable/wait_timeout" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "180",
+              "display_name" : "MySQL Tunable Wait Timeout",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            }
+        },
+      "conflicting" : {  },
+      "dependencies" : { "openssl" : "" },
+      "description" : "Installs and configures mysql for client or server",
+      "groupings" : {  },
+      "license" : "Apache 2.0",
+      "long_description" : "= DESCRIPTION:\n\nInstalls and configures MySQL client or server.\n\n= REQUIREMENTS:\n\n== Platform:\n\nBest tested on Ubuntu 9.04,9.10. On EC2, requires platform that supports -o bind option for the 'mount' command.\n\n== Cookbooks:\n\nRequires Opscode's openssl cookbook for secure password generation.\n\n= ATTRIBUTES: \n\n* mysql[:server_root_password] - Set the server's root password with this, default is a randomly generated password with OpenSSL::Random.random_bytes.\n* mysql[:server_repl_password] - Set the replication user 'repl' password with this, default is a randomly generated password with OpenSSL::Random.random_bytes.\n* mysql[:server_debian_password] - Set the debian-sys-maint user password with this, default is a randomly generated password with OpenSSL::Random.random_bytes.\n* mysql[:bind_address] - Listen address for MySQLd, default is node's ipaddress.\n* mysql[:datadir] - Location for mysql data directory, default is \"/var/lib/mysql\" 
 \n* mysql[:ec2_path] - location of mysql datadir on EC2 nodes, default \"/mnt/mysql\" \n\nPerformance tuning attributes, each corresponds to the same-named parameter in my.cnf; default values listed\n\n* mysql[:tunable][:key_buffer]          = \"250M\"\n* mysql[:tunable][:max_connections]     = \"800\" \n* mysql[:tunable][:wait_timeout]        = \"180\" \n* mysql[:tunable][:net_write_timeout]   = \"30\" \n* mysql[:tunable][:net_write_timeout]   = \"30\" \n* mysql[:tunable][:back_log]            = \"128\" \n* mysql[:tunable][:table_cache]         = \"128\" \n* mysql[:tunable][:max_heap_table_size] = \"32M\" \n\n= USAGE:\n\nOn client nodes,\n\n  include_recipe \"mysql::client\"\n  \nAs the common use case is on systems with Ruby, we also install the MySQL RubyGem. Because we may want to be able to use the gem within another Chef recipe, we make sure the mysql development package and gem are installed first. The key is this:\n\n  r = package ... do\n    action :nothing\n  end\n  \n  r.
 run_action(:install)\n  \nThis creates a resource object for the package and does the installation before other recipes are parsed. You'll need to have the C compiler and such (ie, build-essential on Ubuntu) before running the recipes, but we already do that when installing Chef :-). If you want to be able to access a MySQL database via Ruby within another recipe, you could do so, like so:\n\n  Gem.clear_paths # needed for Chef to find the gem...\n  require 'mysql' # requires the mysql gem\n\n  execute \"create #{node[:railsapp][:db][:database]} database\" do\n    command \"/usr/bin/mysqladmin -u root -p#{node[:mysql][:server_root_password]} create #{node[:railsapp][:db][:database]}\"\n    not_if do\n      m = Mysql.new(\"localhost\", \"root\", @node[:mysql][:server_root_password])\n      m.list_dbs.include?(@node[:railsapp][:db][:database])\n    end\n  end\n\nOn server nodes, \n\n  include_recipe \"mysql::server\"\n  \nOn Debian/Ubuntu this will preseed the MySQL package with the r
 andomly generated root password. You can of course change the password afterward, but this makes sure that there's a good password set. You can view it in the node data in the Chef Server webui. Sets a new password for debian-sys-maint user as well.\n\nAlso sets up 'repl' user grants for replication slaves.\n\nOn EC2 nodes,\n\n  include_recipe \"mysql::server_ec2\"\n  \nWhen the ec2_path doesn't exist we look for a mounted filesystem (eg, EBS) and move the datadir there.\n\nThe client recipe is already included by server and 'default' recipes.\n\n= LICENSE and AUTHOR:\n      \nAuthor:: Joshua Timberman (<jo...@opscode.com>)\nAuthor:: AJ Christensen (<aj...@opscode.com>)\n\nCopyright:: 2009, Opscode, Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing
 , software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
+      "maintainer" : "Opscode, Inc.",
+      "maintainer_email" : "cookbooks@opscode.com",
+      "name" : "mysql",
+      "platforms" : { "debian" : "",
+          "ubuntu" : ""
+        },
+      "providing" : {  },
+      "recipes" : { "mysql::client" : "Installs packages required for mysql clients using run_action magic",
+          "mysql::server" : "Installs packages required for mysql servers w/o manual intervention",
+          "mysql::server_ec2" : "Performs EC2-specific mountpoint manipulation"
+        },
+      "recommendations" : {  },
+      "replacing" : {  },
+      "suggestions" : {  },
+      "version" : "0.21.2"
+    },
+  "name" : "mysql-0.21.2",
+  "providers" : [ { "checksum" : "b994881a2aba60e32c4b6408ffba993d",
+        "name" : "database.rb",
+        "path" : "providers/database.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-b994881a2aba60e32c4b6408ffba993d?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=2XMRbryCmEqirCWLCvrXoenYubw%3D"
+      } ],
+  "recipes" : [ { "checksum" : "f51bd8122b7dccc9f4656319fef3252a",
+        "name" : "server_ec2.rb",
+        "path" : "recipes/server_ec2.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-f51bd8122b7dccc9f4656319fef3252a?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=BUR2mosCvmoOKq4Mkh3JUG0MY38%3D"
+      },
+      { "checksum" : "80daa897597560372d017c58c4df0e3c",
+        "name" : "server.rb",
+        "path" : "recipes/server.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-80daa897597560372d017c58c4df0e3c?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=bU3j9Nw%2BnuIroXKrlJZe7tjaugA%3D"
+      },
+      { "checksum" : "bd3ba2d05dea6a8cf0dc2a45f540cc32",
+        "name" : "default.rb",
+        "path" : "recipes/default.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-bd3ba2d05dea6a8cf0dc2a45f540cc32?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=7haumT5EtEM2h8l32efqV%2Fik%2BdY%3D"
+      },
+      { "checksum" : "a1d679c7480267cd9b69e3194c7e45ab",
+        "name" : "client.rb",
+        "path" : "recipes/client.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-a1d679c7480267cd9b69e3194c7e45ab?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=ZhfTiwv1aXC7HZMnW8A7i4vkMCM%3D"
+      }
+    ],
+  "resources" : [ { "checksum" : "8aa8e2cafe54c2932c7aa65d62ec2695",
+        "name" : "database.rb",
+        "path" : "resources/database.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-8aa8e2cafe54c2932c7aa65d62ec2695?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=6URS94f1HpRibHC%2FhBkr7Eg3dVA%3D"
+      } ],
+  "root_files" : [ { "checksum" : "e9278fc99fd668bdce33d72dc71fade9",
+        "name" : "README.rdoc",
+        "path" : "README.rdoc",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-e9278fc99fd668bdce33d72dc71fade9?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=KPgAGqShEO5SGzz8oRdwIInPUOc%3D"
+      },
+      { "checksum" : "8d2f9635f4817ff905a4124e09ec6c59",
+        "name" : "metadata.rb",
+        "path" : "metadata.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-8d2f9635f4817ff905a4124e09ec6c59?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=fPF2iY7tNrq%2FuNrCjRLImP9vRbA%3D"
+      },
+      { "checksum" : "e6804b8f3e6dfdbbece9d319537ffea1",
+        "name" : "metadata.json",
+        "path" : "metadata.json",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-e6804b8f3e6dfdbbece9d319537ffea1?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=EVsALLreeAA41%2BiPAfPt%2FxFEIYI%3D"
+      }
+    ],
+  "templates" : [ { "checksum" : "689c1b6fbb242b6c508384e56646341d",
+        "name" : "my.cnf.erb",
+        "path" : "templates/ubuntu-9.10/my.cnf.erb",
+        "specificity" : "ubuntu-9.10",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-689c1b6fbb242b6c508384e56646341d?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=2ugp0XVvvUktYdBxfC9bCZBjOs4%3D"
+      },
+      { "checksum" : "16b036a0bb31957a77e9b825cf616cc5",
+        "name" : "mysql-server.seed.erb",
+        "path" : "templates/default/mysql-server.seed.erb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-16b036a0bb31957a77e9b825cf616cc5?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=50bRvt6GQqFEcfYtaIsq1d4a4c8%3D"
+      },
+      { "checksum" : "932b51ddddcbd24ee10a76ecae33b8ba",
+        "name" : "grants.sql.erb",
+        "path" : "templates/default/grants.sql.erb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-932b51ddddcbd24ee10a76ecae33b8ba?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=RHR0obLFcsN1M9tL28IH4Tcur5g%3D"
+      },
+      { "checksum" : "7746560b37ac8d4a0cf68befbecbd8a3",
+        "name" : "my.cnf.erb",
+        "path" : "templates/default/my.cnf.erb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-7746560b37ac8d4a0cf68befbecbd8a3?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=W7Nqkvhw2TBrlDvcM2rlA4Oj5%2Bk%3D"
+      },
+      { "checksum" : "63bd67fae6d297e8f658e9c0ad01a411",
+        "name" : "my.cnf.erb",
+        "path" : "templates/centos/my.cnf.erb",
+        "specificity" : "centos",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-63bd67fae6d297e8f658e9c0ad01a411?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=oFcRya56f%2F4oJBGrIvk4XcWQFm4%3D"
+      },
+      { "checksum" : "689c1b6fbb242b6c508384e56646341d",
+        "name" : "my.cnf.erb",
+        "path" : "templates/ubuntu-10.04/my.cnf.erb",
+        "specificity" : "ubuntu-10.04",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-689c1b6fbb242b6c508384e56646341d?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=2ugp0XVvvUktYdBxfC9bCZBjOs4%3D"
+      },
+      { "checksum" : "63bd67fae6d297e8f658e9c0ad01a411",
+        "name" : "my.cnf.erb",
+        "path" : "templates/redhat/my.cnf.erb",
+        "specificity" : "redhat",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-63bd67fae6d297e8f658e9c0ad01a411?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=oFcRya56f%2F4oJBGrIvk4XcWQFm4%3D"
+      },
+      { "checksum" : "d2244150a145b3f658cd37c13269fafc",
+        "name" : "port_mysql.erb",
+        "path" : "templates/default/port_mysql.erb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-d2244150a145b3f658cd37c13269fafc?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=23iHcjwpNOvlNf%2BlrKcV2pkU7uo%3D"
+      },
+      { "checksum" : "2e08553db526f5f80c28b343f6a616cb",
+        "name" : "debian.cnf.erb",
+        "path" : "templates/default/debian.cnf.erb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-2e08553db526f5f80c28b343f6a616cb?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=VdcFYxnLUkj1tS3k8DovrzZEA7E%3D"
+      },
+      { "checksum" : "1e5068eec65b51f5a327580fb0af4677",
+        "name" : "my.cnf.erb",
+        "path" : "templates/ubuntu-8.04/my.cnf.erb",
+        "specificity" : "ubuntu-8.04",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-1e5068eec65b51f5a327580fb0af4677?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=D5lPeu1UQvA9pEXZm5nVOwj3WIo%3D"
+      }
+    ],
+  "version" : "0.21.2"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/node.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/node.json b/apis/chef/src/test/resources/node.json
new file mode 100644
index 0000000..c8ddaef
--- /dev/null
+++ b/apis/chef/src/test/resources/node.json
@@ -0,0 +1,10 @@
+{ "automatic" : {  },
+  "chef_environment" : "prod",
+  "chef_type" : "node",
+  "default" : {  },
+  "json_class" : "Chef::Node",
+  "name" : "adrian-jcloudstest",
+  "normal" : { "tomcat6" : { "ssl_port" : 8433 } },
+  "override" : {  },
+  "run_list" : [ "recipe[java]" ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/nodes_list.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/nodes_list.json b/apis/chef/src/test/resources/nodes_list.json
new file mode 100644
index 0000000..92ef95a
--- /dev/null
+++ b/apis/chef/src/test/resources/nodes_list.json
@@ -0,0 +1,5 @@
+{
+  "blah": "https://api.opscode.com/org/directory/nodes/blah",
+  "boxer": "https://api.opscode.com/org/directory/nodes/boxer",
+  "blarrrrgh": "https://api.opscode.com/org/directory/nodes/blarrrrgh"
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/privkey.txt
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/privkey.txt b/apis/chef/src/test/resources/privkey.txt
new file mode 100644
index 0000000..43be3f7
--- /dev/null
+++ b/apis/chef/src/test/resources/privkey.txt
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA0ueqo76MXuP6XqZBILFziH/9AI7C6PaN5W0dSvkr9yInyGHS
+z/IR1+4tqvP2qlfKVKI4CP6BFH251Ft9qMUBuAsnlAVQ1z0exDtIFFOyQCdR7iXm
+jBIWMSS4buBwRQXwDK7id1OxtU23qVJv+xwEV0IzaaSJmaGLIbvRBD+qatfUuQJB
+MU/04DdJIwvLtZBYdC2219m5dUBQaa4bimL+YN9EcsDzD9h9UxQo5ReK7b3cNMzJ
+BKJWLzFBcJuePMzAnLFktr/RufX4wpXe6XJxoVPaHo72GorLkwnQ0HYMTY8rehT4
+mDi1FI969LHCFFaFHSAaRnwdXaQkJmSfcxzCYQIDAQABAoIBAQCW3I4sKN5B9jOe
+xq/pkeWBq4OvhW8Ys1yW0zFT8t6nHbB1XrwscQygd8gE9BPqj3e0iIEqtdphbPmj
+VHqTYbC0FI6QDClifV7noTwTBjeIOlgZ0NSUN0/WgVzIOxUz2mZ2vBZUovKILPqG
+TOi7J7RXMoySMdcXpP1f+PgvYNcnKsT72UcWaSXEV8/zo+Zm/qdGPVWwJonri5Mp
+DVm5EQSENBiRyt028rU6ElXORNmoQpVjDVqZ1gipzXkifdjGyENw2rt4V/iKYD7V
+5iqXOsvP6Cemf4gbrjunAgDG08S00kiUgvVWcdXW+dlsR2nCvH4DOEe3AYYh/aH8
+DxEE7FbtAoGBAPcNO8fJ56mNw0ow4Qg38C+Zss/afhBOCfX4O/SZKv/roRn5+gRM
+KRJYSVXNnsjPI1plzqR4OCyOrjAhtuvL4a0DinDzf1+fiztyNohwYsW1vYmqn3ti
+EN0GhSgE7ppZjqvLQ3f3LUTxynhA0U+k9wflb4irIlViTUlCsOPkrNJDAoGBANqL
+Q+vvuGSsmRLU/Cenjy+Mjj6+QENg51dz34o8JKuVKIPKU8pNnyeLa5fat0qD2MHm
+OB9opeQOcw0dStodxr6DB3wi83bpjeU6BWUGITNiWEaZEBrQ0aiqNJJKrrHm8fAZ
+9o4l4oHc4hI0kYVYYDuxtKuVJrzZiEapTwoOcYiLAoGBAI/EWbeIHZIj9zOjgjEA
+LHvm25HtulLOtyk2jd1njQhlHNk7CW2azIPqcLLH99EwCYi/miNH+pijZ2aHGCXb
+/bZrSxM0ADmrZKDxdB6uGCyp+GS2sBxjEyEsfCyvwhJ8b3Q100tqwiNO+d5FCglp
+HICx2dgUjuRVUliBwOK93nx1AoGAUI8RhIEjOYkeDAESyhNMBr0LGjnLOosX+/as
+qiotYkpjWuFULbibOFp+WMW41vDvD9qrSXir3fstkeIAW5KqVkO6mJnRoT3Knnra
+zjiKOITCAZQeiaP8BO5o3pxE9TMqb9VCO3ffnPstIoTaN4syPg7tiGo8k1SklVeH
+2S8lzq0CgYAKG2fljIYWQvGH628rp4ZcXS4hWmYohOxsnl1YrszbJ+hzR+IQOhGl
+YlkUQYXhy9JixmUUKtH+NXkKX7Lyc8XYw5ETr7JBT3ifs+G7HruDjVG78EJVojbd
+8uLA+DdQm5mg4vd1GTiSK65q/3EeoBlUaVor3HhLFki+i9qpT8CBsg==
+-----END RSA PRIVATE KEY-----
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/pubkey.txt
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/pubkey.txt b/apis/chef/src/test/resources/pubkey.txt
new file mode 100644
index 0000000..886a471
--- /dev/null
+++ b/apis/chef/src/test/resources/pubkey.txt
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ueqo76MXuP6XqZBILFz
+iH/9AI7C6PaN5W0dSvkr9yInyGHSz/IR1+4tqvP2qlfKVKI4CP6BFH251Ft9qMUB
+uAsnlAVQ1z0exDtIFFOyQCdR7iXmjBIWMSS4buBwRQXwDK7id1OxtU23qVJv+xwE
+V0IzaaSJmaGLIbvRBD+qatfUuQJBMU/04DdJIwvLtZBYdC2219m5dUBQaa4bimL+
+YN9EcsDzD9h9UxQo5ReK7b3cNMzJBKJWLzFBcJuePMzAnLFktr/RufX4wpXe6XJx
+oVPaHo72GorLkwnQ0HYMTY8rehT4mDi1FI969LHCFFaFHSAaRnwdXaQkJmSfcxzC
+YQIDAQAB
+-----END PUBLIC KEY-----
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/roles_list.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/roles_list.json b/apis/chef/src/test/resources/roles_list.json
new file mode 100644
index 0000000..1f75bc1
--- /dev/null
+++ b/apis/chef/src/test/resources/roles_list.json
@@ -0,0 +1,4 @@
+{
+    "webserver": "http://localhost:4000/roles/webserver",
+    "smtpserver": "http://localhost:4000/roles/smtpserver"
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/sandbox.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/sandbox.json b/apis/chef/src/test/resources/sandbox.json
new file mode 100644
index 0000000..640f2a1
--- /dev/null
+++ b/apis/chef/src/test/resources/sandbox.json
@@ -0,0 +1,12 @@
+{
+         "_rev": "1-8c27b0ea4c2b7aaedbb44cfbdfcc11b2",
+         "json_class": "Chef::Sandbox",
+         "is_completed": false,
+         "create_time": "2010-07-07T03:36:00+00:00",
+         "chef_type": "sandbox",
+         "checksums": [],
+         "name": "f9d6d9b72bae465890aae87969f98a9c",
+         "guid": "f9d6d9b72bae465890aae87969f98a9c"
+     }
+
+

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/search_role.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/search_role.json b/apis/chef/src/test/resources/search_role.json
new file mode 100644
index 0000000..5e51663
--- /dev/null
+++ b/apis/chef/src/test/resources/search_role.json
@@ -0,0 +1,34 @@
+{
+    "total": 1,
+    "start": 0,
+    "rows": [
+        {
+            "name": "webserver",
+            "description": "The base role for systems that serve HTTP traffic",
+            "json_class": "Chef::Role",
+            "default_attributes": {
+                "apache2": {
+                    "listen_ports": [
+                        "80",
+                        "443"
+                    ]
+                }
+            },
+            "override_attributes": {
+                "apache2": {
+                    "max_children": "50"
+                }
+            },
+            "chef_type": "role",
+            "run_list": [],
+            "env_run_lists": {
+                "prod": [
+                    "recipe[apache2]"
+                ],
+                "staging": [
+                    "recipe[apache2::staging]"
+                ]
+            }
+        }
+    ]
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/search_role_empty.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/search_role_empty.json b/apis/chef/src/test/resources/search_role_empty.json
new file mode 100644
index 0000000..ab859ca
--- /dev/null
+++ b/apis/chef/src/test/resources/search_role_empty.json
@@ -0,0 +1,5 @@
+{
+    "total": 0,
+    "start": 0,
+    "rows": []
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/tomcat-cookbook.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/tomcat-cookbook.json b/apis/chef/src/test/resources/tomcat-cookbook.json
new file mode 100644
index 0000000..992f401
--- /dev/null
+++ b/apis/chef/src/test/resources/tomcat-cookbook.json
@@ -0,0 +1,121 @@
+{ "attributes" : [ { "checksum" : "6e3fd0d16a87a55c569da108194ecb29",
+        "name" : "tomcat6.rb",
+        "path" : "attributes/tomcat6.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-6e3fd0d16a87a55c569da108194ecb29?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=zvYjsgUXIC7Xj3MT8Wd93esDGJM%3D"
+      } ],
+  "chef_type" : "cookbook_version",
+  "cookbook_name" : "tomcat6",
+  "definitions" : [  ],
+  "files" : [ { "checksum" : "18e534a72652f3d53b197ca4e5027009",
+        "name" : "org.apache.tomcat.tomcat6.plist",
+        "path" : "files/default/org.apache.tomcat.tomcat6.plist",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-18e534a72652f3d53b197ca4e5027009?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=M3fBL4t7uuYVXVah2PyZ8eL1QCc%3D"
+      },
+      { "checksum" : "6a35ce92050296862ea63b784529d2e0",
+        "name" : "logging.properties",
+        "path" : "files/default/logging.properties",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-6a35ce92050296862ea63b784529d2e0?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=vRlKoye2%2Fwz8mdQI%2F3Ht916sllE%3D"
+      }
+    ],
+  "json_class" : "Chef::CookbookVersion",
+  "libraries" : [ { "checksum" : "2b6f7847142bb36823c570899669c54b",
+        "name" : "tomcat_manager.rb",
+        "path" : "libraries/tomcat_manager.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-2b6f7847142bb36823c570899669c54b?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=zxIoUKcGYWo9ir6qf1tTy3wvKZ4%3D"
+      },
+      { "checksum" : "24db7b7dd6f04f8da5fa2b282910ac08",
+        "name" : "tomcat.rb",
+        "path" : "libraries/tomcat.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-24db7b7dd6f04f8da5fa2b282910ac08?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=c4Gbn5kX0ZaPbWvk5LAcR77sITg%3D"
+      }
+    ],
+  "metadata" : { "attributes" : { "tomcat6/with_native" : { "calculated" : false,
+              "choice" : [  ],
+              "default" : "false",
+              "description" : "works for centos, install tomcat-native libraries",
+              "display_name" : "Tomcat native support",
+              "recipes" : [  ],
+              "required" : "optional",
+              "type" : "string"
+            } },
+      "conflicting" : {  },
+      "dependencies" : {  },
+      "description" : "Installs and configures all aspects of tomcat6 using custom local installation",
+      "groupings" : {  },
+      "license" : "Apache 2.0",
+      "long_description" : "= DESCRIPTION:\n\nInstalls Tomcat6\n\n= REQUIREMENTS:\n\n== Platform and Application Environment:\n\nTested on Centos 5.2 8.10. May work on other platforms, esp Redhat.\nNeeds Java at least Java 5\n\n== Cookbooks:\n\nOpscode cookbooks, http://github.com/opscode/cookbooks/tree/master:\n\n* java\n\n= ATTRIBUTES: \n\n= USAGE:\n\n\n= LICENSE and AUTHOR:\n      \nAuthor:: Edmund Haselwanter (<ed...@haselwanter.com>)\nCopyright:: 2009, Edmund Haselwanter\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimit
 ations under the License.\n",
+      "maintainer" : "Opscode, Inc.",
+      "maintainer_email" : "cookbooks@opscode.com",
+      "name" : "tomcat6",
+      "platforms" : { "centos" : "",
+          "debian" : "",
+          "redhat" : "",
+          "ubuntu" : ""
+        },
+      "providing" : {  },
+      "recipes" : { "tomcat6" : "Main Tomcat 6 configuration" },
+      "recommendations" : {  },
+      "replacing" : {  },
+      "suggestions" : {  },
+      "version" : "0.1.0"
+    },
+  "name" : "tomcat6-0.1.0",
+  "providers" : [  ],
+  "recipes" : [ { "checksum" : "d45661e4b50f9677de7b8684af26ff9d",
+        "name" : "default.rb",
+        "path" : "recipes/default.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-d45661e4b50f9677de7b8684af26ff9d?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=r3CvyVe7dcOzB%2F0fNAun5ldGwr8%3D"
+      } ],
+  "resources" : [  ],
+  "root_files" : [ { "checksum" : "14f6977f68c3674484e8289e361fb5a4",
+        "name" : "README.rdoc",
+        "path" : "README.rdoc",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-14f6977f68c3674484e8289e361fb5a4?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=VNZxN%2B7CxO7ZbDHJOS%2FaTtpkPaE%3D"
+      },
+      { "checksum" : "abc416ffba9ea64ca71635191cb87af6",
+        "name" : "metadata.rb",
+        "path" : "metadata.rb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-abc416ffba9ea64ca71635191cb87af6?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=pemynt9Q1F%2BxlS26kLaz%2F4NDGO4%3D"
+      },
+      { "checksum" : "dd8473a8a7f2b446250ecdefb1882a5e",
+        "name" : "metadata.json",
+        "path" : "metadata.json",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-dd8473a8a7f2b446250ecdefb1882a5e?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=sHpayqP%2Fe4Luv20EMa3q%2FaMN4ms%3D"
+      }
+    ],
+  "templates" : [ { "checksum" : "107263b81e4700cf0adad7af2a133bbd",
+        "name" : "tomcat-users.xml.erb",
+        "path" : "templates/default/tomcat-users.xml.erb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-107263b81e4700cf0adad7af2a133bbd?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=QC7MNIauR2ox5MVlohf2i73uM1s%3D"
+      },
+      { "checksum" : "fa4432b353fa57b9da26a4bff44285f2",
+        "name" : "sv-tomcat6-run.erb",
+        "path" : "templates/default/sv-tomcat6-run.erb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-fa4432b353fa57b9da26a4bff44285f2?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=q2rDeJFeh4oyhfv9ExMifGB0wxo%3D"
+      },
+      { "checksum" : "33fd6f63133e7ebe28bc62e58773c408",
+        "name" : "manager.xml.erb",
+        "path" : "templates/default/manager.xml.erb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-33fd6f63133e7ebe28bc62e58773c408?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=rIijEhwLPf5PWTJOg%2BElbOquPBM%3D"
+      },
+      { "checksum" : "09f2bf988663175cd1b7973198dfb5eb",
+        "name" : "sv-tomcat6-log-run.erb",
+        "path" : "templates/default/sv-tomcat6-log-run.erb",
+        "specificity" : "default",
+        "url" : "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-09f2bf988663175cd1b7973198dfb5eb?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=UnfNDP4pDzPM3PoLcLWyTnTa%2FKI%3D"
+      }
+    ],
+  "version" : "0.1.0"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/resources/upload-site.json
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/resources/upload-site.json b/apis/chef/src/test/resources/upload-site.json
new file mode 100644
index 0000000..58bf165
--- /dev/null
+++ b/apis/chef/src/test/resources/upload-site.json
@@ -0,0 +1,13 @@
+{
+    "uri": "https://api.opscode.com/organizations/jclouds/sandboxes/d454f71e2a5f400c808d0c5d04c2c88c",
+    "checksums": {
+        "0c5ecd7788cf4f6c7de2a57193897a6c": {
+            "url": "https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/sandbox-d454f71e2a5f400c808d0c5d04c2c88c/checksum-0c5ecd7788cf4f6c7de2a57193897a6c?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277344702&Signature=FtKyqvYEjhhEKmRY%2B0M8aGPMM7g%3D",
+            "needs_upload": true
+        }, "0189e76ccc476701d6b374e5a1a27347": {
+            "needs_upload": false
+        }, "1dda05ed139664f1f89b9dec482b77c0": {
+            "needs_upload": false
+        }
+    }, "sandbox_id": "d454f71e2a5f400c808d0c5d04c2c88c"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/pom.xml
----------------------------------------------------------------------
diff --git a/apis/pom.xml b/apis/pom.xml
index f467c3a..1b37a13 100644
--- a/apis/pom.xml
+++ b/apis/pom.xml
@@ -54,5 +54,6 @@
     <module>rackspace-clouddns</module>
     <module>sts</module>
     <module>route53</module>
+    <module>chef</module>
   </modules>
 </project>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/project/pom.xml
----------------------------------------------------------------------
diff --git a/project/pom.xml b/project/pom.xml
index 2279e7b..21a55db 100644
--- a/project/pom.xml
+++ b/project/pom.xml
@@ -494,9 +494,11 @@
             <ignoredResource>virtualhardwaresection.xml</ignoredResource>
             <ignoredResource>logback.xml</ignoredResource>
             <ignoredResource>amzn_images.xml</ignoredResource>
+            <ignoredResource>test</ignoredResource>
             <ignoredResource>test.jks</ignoredResource>
             <ignoredResource>CreateInternetService-options-test.xml</ignoredResource>
             <ignoredResource>.gitattributes</ignoredResource>
+            <ignoredResource>functions/.gitattributes</ignoredResource>
             <ignoredResource>OSGI-OPT/bnd.bnd</ignoredResource>
             <!-- For bouncycastle -->
             <ignoredResource>META-INF/BCKEY.DSA</ignoredResource>


[06/10] Move jclouds-chef to the main jclouds repo

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/BaseListNodesImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/BaseListNodesImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/BaseListNodesImpl.java
new file mode 100644
index 0000000..a426b8d
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/BaseListNodesImpl.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.logging.Logger;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+
+public abstract class BaseListNodesImpl {
+
+   protected final ChefApi api;
+
+   protected Logger logger = Logger.NULL;
+
+   BaseListNodesImpl(ChefApi api) {
+      this.api = checkNotNull(api, "api");
+   }
+
+   protected Iterable<? extends Node> execute(Iterable<String> toGet) {
+      Iterable<? extends Node> nodes = transform(toGet, new Function<String, Node>() {
+               @Override
+               public Node apply(final String input) {
+                  return api.getNode(input);
+               }
+            }
+      );
+
+      logger.trace(String.format("getting nodes: %s", Joiner.on(',').join(toGet)));
+      return nodes;
+
+   }
+
+   protected Iterable<? extends Node> executeConcurrently(final ListeningExecutorService executor,
+         Iterable<String> toGet) {
+      ListenableFuture<List<Node>> futures = allAsList(transform(toGet, new Function<String, ListenableFuture<Node>>() {
+         @Override
+         public ListenableFuture<Node> apply(final String input) {
+            return executor.submit(new Callable<Node>() {
+               @Override
+               public Node call() throws Exception {
+                  return api.getNode(input);
+               }
+            });
+         }
+      }));
+
+      logger.trace(String.format("getting nodes: %s", Joiner.on(',').join(toGet)));
+      return getUnchecked(futures);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java
new file mode 100644
index 0000000..46c765b
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java
@@ -0,0 +1,102 @@
+/*
+ * 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.strategy.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.and;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+import static org.jclouds.chef.util.ChefUtils.fromOhaiTime;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.CleanupStaleNodesAndClients;
+import org.jclouds.chef.strategy.DeleteAllClientsInList;
+import org.jclouds.chef.strategy.DeleteAllNodesInList;
+import org.jclouds.chef.strategy.ListNodes;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+/**
+ * 
+ * Cleans up nodes and apis who have been hanging around too long.
+ */
+@Singleton
+public class CleanupStaleNodesAndClientsImpl implements CleanupStaleNodesAndClients {
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final ListNodes nodeLister;
+   private final DeleteAllNodesInList nodeDeleter;
+   private final DeleteAllClientsInList clientDeleter;
+
+   @Inject
+   public CleanupStaleNodesAndClientsImpl(DeleteAllNodesInList nodeDeleter, DeleteAllClientsInList clientDeleter,
+         ListNodes nodeLister) {
+      this.nodeLister = checkNotNull(nodeLister, "nodeLister");
+      this.nodeDeleter = checkNotNull(nodeDeleter, "nodeDeleter");
+      this.clientDeleter = checkNotNull(clientDeleter, "clientDeleter");
+   }
+
+   @Override
+   public void execute(final String prefix, int secondsStale) {
+      final Calendar expired = Calendar.getInstance();
+      expired.setTime(new Date());
+      expired.add(Calendar.SECOND, -secondsStale);
+      Iterable<? extends Node> staleNodes = filter(
+         nodeLister.execute(), and(notNull(), new Predicate<Node>() {
+               @Override
+               public boolean apply(Node input) {
+                  return input.getName().startsWith(prefix);
+               }
+         },
+         new Predicate<Node>() {
+             @Override
+             public boolean apply(Node input) {
+                JsonBall dateLong = input.getAutomaticAttributes().get("ohai_time");
+                if (dateLong == null)
+                   return true;
+                Calendar nodeUpdate = Calendar.getInstance();
+                nodeUpdate.setTime(fromOhaiTime(dateLong));
+                return expired.after(nodeUpdate);
+             }
+         }));
+      Iterable<String> nodeNames = transform(staleNodes, new Function<Node, String>() {
+
+         @Override
+         public String apply(Node from) {
+            return from.getName();
+         }
+
+      });
+      nodeDeleter.execute(nodeNames);
+      clientDeleter.execute(nodeNames);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java
new file mode 100644
index 0000000..7dabd06
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java
@@ -0,0 +1,83 @@
+/*
+ * 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.strategy.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.CreateNodeAndPopulateAutomaticAttributes;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.logging.Logger;
+import org.jclouds.ohai.Automatic;
+
+import com.google.common.base.Supplier;
+
+/**
+ * 
+ * Updates node with new automatic attributes.
+ */
+@Singleton
+public class CreateNodeAndPopulateAutomaticAttributesImpl implements CreateNodeAndPopulateAutomaticAttributes {
+
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final ChefApi chef;
+   private final Supplier<Map<String, JsonBall>> automaticSupplier;
+
+   @Inject
+   public CreateNodeAndPopulateAutomaticAttributesImpl(ChefApi chef,
+         @Automatic Supplier<Map<String, JsonBall>> automaticSupplier) {
+      this.chef = checkNotNull(chef, "chef");
+      this.automaticSupplier = checkNotNull(automaticSupplier, "automaticSupplier");
+   }
+
+   @Override
+   public Node execute(Node node) {
+      logger.trace("creating node %s", node.getName());
+      Node withAutomatic = Node.builder() //
+            .name(node.getName()) //
+            .normalAttributes(node.getNormalAttributes()) //
+            .overrideAttributes(node.getOverrideAttributes()) //
+            .defaultAttributes(node.getDefaultAttributes()) //
+            .automaticAttributes(node.getAutomaticAttributes()) //
+            .automaticAttributes(automaticSupplier.get()) //
+            .runList(node.getRunList()) //
+            .environment(node.getEnvironment()) //
+            .build();
+      
+      
+      chef.createNode(withAutomatic);
+      logger.debug("created node %s", withAutomatic.getName());
+      return node;
+   }
+
+   @Override
+   public Node execute(String nodeName, Iterable<String> runList) {
+      return execute(Node.builder().name(nodeName).runList(runList).environment("_default").build());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/DeleteAllClientsInListImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/DeleteAllClientsInListImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/DeleteAllClientsInListImpl.java
new file mode 100644
index 0000000..ffdd3ec
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/DeleteAllClientsInListImpl.java
@@ -0,0 +1,85 @@
+/*
+ * 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.strategy.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.strategy.DeleteAllClientsInList;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
+
+/**
+ * Concurrently delete all given clients.
+ */
+@Singleton
+public class DeleteAllClientsInListImpl implements DeleteAllClientsInList {
+
+   protected final ChefApi api;
+   protected final ListeningExecutorService userExecutor;
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   DeleteAllClientsInListImpl(@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, ChefApi api) {
+      this.userExecutor = checkNotNull(userExecutor, "userExecuor");
+      this.api = checkNotNull(api, "api");
+   }
+
+   @Override
+   public void execute(Iterable<String> names) {
+      execute(userExecutor, names);
+   }
+
+   @Override
+   public void execute(final ListeningExecutorService executor, Iterable<String> names) {
+      ListenableFuture<List<Client>> futures = allAsList(transform(names,
+            new Function<String, ListenableFuture<Client>>() {
+               @Override
+               public ListenableFuture<Client> apply(final String input) {
+                  return executor.submit(new Callable<Client>() {
+                     @Override
+                     public Client call() throws Exception {
+                        return api.deleteClient(input);
+                     }
+                  });
+               }
+            }));
+
+      logger.trace(String.format("deleting clients: %s", Joiner.on(',').join(names)));
+      getUnchecked(futures);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/DeleteAllNodesInListImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/DeleteAllNodesInListImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/DeleteAllNodesInListImpl.java
new file mode 100644
index 0000000..212d8b7
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/DeleteAllNodesInListImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.DeleteAllNodesInList;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
+
+@Singleton
+public class DeleteAllNodesInListImpl implements DeleteAllNodesInList {
+
+   protected final ChefApi api;
+   protected final ListeningExecutorService userExecutor;
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   DeleteAllNodesInListImpl(@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, ChefApi api) {
+      this.userExecutor = checkNotNull(userExecutor, "userExecuor");
+      this.api = checkNotNull(api, "api");
+   }
+
+   @Override
+   public void execute(Iterable<String> names) {
+      execute(userExecutor, names);
+   }
+
+   @Override
+   public void execute(final ListeningExecutorService executor, Iterable<String> names) {
+      ListenableFuture<List<Node>> futures = allAsList(transform(names, new Function<String, ListenableFuture<Node>>() {
+         @Override
+         public ListenableFuture<Node> apply(final String input) {
+            return executor.submit(new Callable<Node>() {
+               @Override
+               public Node call() throws Exception {
+                  return api.deleteNode(input);
+               }
+            });
+         }
+      }));
+
+      logger.trace(String.format("deleting nodes: %s", Joiner.on(',').join(names)));
+      getUnchecked(futures);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListClientsImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListClientsImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListClientsImpl.java
new file mode 100644
index 0000000..105be2f
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListClientsImpl.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.strategy.ListClients;
+import org.jclouds.logging.Logger;
+
+import java.util.concurrent.ExecutorService;
+
+
+@Singleton
+public class ListClientsImpl implements ListClients {
+
+   protected final ChefApi api;
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   ListClientsImpl(ChefApi api) {
+      this.api = checkNotNull(api, "api");
+   }
+
+   @Override
+   public Iterable<? extends Client> execute() {
+
+      Iterable<String> toGet = api.listClients();
+      Iterable<? extends Client> clients = transform(toGet,
+            new Function<String, Client>() {
+               @Override
+               public Client apply(final String input) {
+
+                  return api.getClient(input);
+               }
+
+            }
+      );
+
+      logger.trace(String.format("getting clients: %s", Joiner.on(',').join(toGet)));
+      return clients;
+
+   }
+
+   @Override
+   public Iterable<? extends Client> execute(ExecutorService executorService) {
+      return this.execute(MoreExecutors.listeningDecorator(executorService));
+   }
+
+
+   private Iterable<? extends Client> execute(ListeningExecutorService listeningExecutor) {
+      return executeConcurrently(listeningExecutor, api.listClients());
+   }
+
+   private Iterable<? extends Client> executeConcurrently(final ListeningExecutorService executor,
+         Iterable<String> toGet) {
+      ListenableFuture<List<Client>> futures = allAsList(transform(toGet,
+            new Function<String, ListenableFuture<Client>>() {
+               @Override
+               public ListenableFuture<Client> apply(final String input) {
+                  return executor.submit(new Callable<Client>() {
+                     @Override
+                     public Client call() throws Exception {
+                        return api.getClient(input);
+                     }
+                  });
+               }
+            }
+      ));
+
+      logger.trace(String.format("getting clients: %s", Joiner.on(',').join(toGet)));
+      return getUnchecked(futures);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsImpl.java
new file mode 100644
index 0000000..d109038
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsImpl.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.strategy.ListCookbookVersions;
+import org.jclouds.logging.Logger;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.concurrent.ExecutorService;
+
+@Singleton
+public class ListCookbookVersionsImpl extends BaseListCookbookVersionsImpl implements ListCookbookVersions {
+
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   ListCookbookVersionsImpl(ChefApi api) {
+      super(api);
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> execute() {
+      return super.execute(api.listCookbooks());
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> execute(ExecutorService executor) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor));
+   }
+
+
+   private Iterable<? extends CookbookVersion> executeConcurrently(ListeningExecutorService executor) {
+      return super.executeConcurrently(executor, api.listCookbooks());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImpl.java
new file mode 100644
index 0000000..a7142dc
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImpl.java
@@ -0,0 +1,117 @@
+/*
+ * 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.strategy.internal;
+
+import static com.google.common.collect.Iterables.transform;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.strategy.ListCookbookVersionsInEnvironment;
+import org.jclouds.logging.Logger;
+
+import java.util.concurrent.ExecutorService;
+
+@Singleton
+public class ListCookbookVersionsInEnvironmentImpl extends BaseListCookbookVersionsImpl
+      implements ListCookbookVersionsInEnvironment {
+
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   ListCookbookVersionsInEnvironmentImpl(ChefApi api) {
+      super(api);
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> execute(String environmentName) {
+      return super.execute(transform(api.listCookbooksInEnvironment(environmentName),
+            new Function<CookbookDefinition, String>() {
+
+               @Override
+               public String apply(CookbookDefinition cookbookDefinition) {
+                  return cookbookDefinition.getName();
+               }
+            }
+      ));
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> execute(String environmentName, String numVersions) {
+      return super.execute(transform(api.listCookbooksInEnvironment(environmentName, numVersions),
+            new Function<CookbookDefinition, String>() {
+
+               @Override
+               public String apply(CookbookDefinition cookbookDefinition) {
+                  return cookbookDefinition.getName();
+               }
+            }
+      ));
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> execute(ExecutorService executor,
+         String environmentName) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor), environmentName);
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> execute(ExecutorService executor,
+         String environmentName, String numVersions) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor), environmentName, numVersions);
+   }
+
+
+   private Iterable<? extends CookbookVersion> executeConcurrently(ListeningExecutorService executor,
+         String environmentName) {
+      return super.execute(
+            transform(api.listCookbooksInEnvironment(environmentName), new Function<CookbookDefinition, String>() {
+
+               @Override
+               public String apply(CookbookDefinition cookbookDefinition) {
+                  return cookbookDefinition.getName();
+               }
+            })
+      );
+   }
+
+
+   private Iterable<? extends CookbookVersion> executeConcurrently(ListeningExecutorService executor,
+         String environmentName, String numVersions) {
+      return super.execute(transform(api.listCookbooksInEnvironment(environmentName, numVersions),
+            new Function<CookbookDefinition, String>() {
+
+               @Override
+               public String apply(CookbookDefinition cookbookDefinition) {
+                  return cookbookDefinition.getName();
+               }
+            }
+      ));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListEnvironmentsImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListEnvironmentsImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListEnvironmentsImpl.java
new file mode 100644
index 0000000..0ed792e
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListEnvironmentsImpl.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Environment;
+import org.jclouds.chef.strategy.ListEnvironments;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
+import com.google.common.util.concurrent.MoreExecutors;
+
+
+import java.util.concurrent.ExecutorService;
+
+
+
+@Singleton
+public class ListEnvironmentsImpl implements ListEnvironments {
+
+   protected final ChefApi api;
+   protected final ListeningExecutorService userExecutor;
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   ListEnvironmentsImpl(@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, ChefApi api) {
+      this.userExecutor = checkNotNull(userExecutor, "userExecuor");
+      this.api = checkNotNull(api, "api");
+   }
+
+   @Override
+   public Iterable<? extends Environment> execute() {
+      return execute(userExecutor);
+   }
+
+   @Override
+   public Iterable<? extends Environment> execute(ExecutorService executor) {
+      return this.execute(MoreExecutors.listeningDecorator(executor));
+   }
+
+   private Iterable<? extends Environment> execute(ListeningExecutorService executor) {
+      return execute(executor, api.listEnvironments());
+   }
+
+   private Iterable<? extends Environment> execute(final ListeningExecutorService executor, Iterable<String> toGet) {
+      ListenableFuture<List<Environment>> futures = allAsList(transform(toGet,
+            new Function<String, ListenableFuture<Environment>>() {
+               @Override
+               public ListenableFuture<Environment> apply(final String input) {
+                  return executor.submit(new Callable<Environment>() {
+                     @Override
+                     public Environment call() throws Exception {
+                        return api.getEnvironment(input);
+                     }
+                  });
+               }
+            }));
+
+      logger.trace(String.format("deleting environments: %s", Joiner.on(',').join(toGet)));
+      return getUnchecked(futures);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListNodesImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListNodesImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListNodesImpl.java
new file mode 100644
index 0000000..8d95965
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListNodesImpl.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.ListNodes;
+import org.jclouds.logging.Logger;
+
+import java.util.concurrent.ExecutorService;
+
+@Singleton
+public class ListNodesImpl extends BaseListNodesImpl implements ListNodes {
+
+
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   ListNodesImpl(ChefApi api) {
+      super(api);
+   }
+
+   @Override
+   public Iterable<? extends Node> execute() {
+      return super.execute(api.listNodes());
+   }
+
+   @Override
+   public Iterable<? extends Node> execute(ExecutorService executor) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor));
+   }
+
+
+   private Iterable<? extends Node> executeConcurrently(ListeningExecutorService executor) {
+      return super.executeConcurrently(executor, api.listNodes());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImpl.java
new file mode 100644
index 0000000..58ecaaa
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImpl.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.ListNodesInEnvironment;
+import org.jclouds.logging.Logger;
+
+import java.util.concurrent.ExecutorService;
+
+@Singleton
+public class ListNodesInEnvironmentImpl extends BaseListNodesImpl implements ListNodesInEnvironment {
+
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   ListNodesInEnvironmentImpl(ChefApi api) {
+      super(api);
+   }
+
+   @Override
+   public Iterable<? extends Node> execute(String environmentName) {
+      return super.execute(api.listNodesInEnvironment(environmentName));
+   }
+
+   @Override
+   public Iterable<? extends Node> execute(ExecutorService executor, String environmentName) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor), environmentName);
+   }
+
+
+   private Iterable<? extends Node> executeConcurrently(ListeningExecutorService executor,
+         String environmentName) {
+      return super.executeConcurrently(executor, api.listNodesInEnvironment(environmentName));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java
new file mode 100644
index 0000000..a6dcac2
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.UpdateAutomaticAttributesOnNode;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.logging.Logger;
+import org.jclouds.ohai.Automatic;
+
+import com.google.common.base.Supplier;
+
+/**
+ * 
+ * Updates node with new automatic attributes.
+ */
+@Singleton
+public class UpdateAutomaticAttributesOnNodeImpl implements UpdateAutomaticAttributesOnNode {
+
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final ChefApi chef;
+   private final Supplier<Map<String, JsonBall>> automaticSupplier;
+
+   @Inject
+   public UpdateAutomaticAttributesOnNodeImpl(ChefApi chef, @Automatic Supplier<Map<String, JsonBall>> automaticSupplier) {
+      this.chef = checkNotNull(chef, "chef");
+      this.automaticSupplier = checkNotNull(automaticSupplier, "automaticSupplier");
+   }
+
+   @Override
+   public void execute(String nodeName) {
+      logger.trace("updating node %s", nodeName);
+      Node node = chef.getNode(nodeName);
+      Node updated = Node.builder() //
+            .name(node.getName()) //
+            .normalAttributes(node.getNormalAttributes()) //
+            .overrideAttributes(node.getOverrideAttributes()) //
+            .defaultAttributes(node.getDefaultAttributes()) //
+            .automaticAttributes(automaticSupplier.get()) //
+            .runList(node.getRunList()) //
+            .environment(node.getEnvironment()) //
+            .build();
+
+      chef.updateNode(updated);
+      logger.debug("updated node %s", nodeName);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/suppliers/ChefVersionSupplier.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/suppliers/ChefVersionSupplier.java b/apis/chef/src/main/java/org/jclouds/chef/suppliers/ChefVersionSupplier.java
new file mode 100644
index 0000000..a0d8b04
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/suppliers/ChefVersionSupplier.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.chef.suppliers;
+
+import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.logging.Logger;
+import org.jclouds.rest.annotations.ApiVersion;
+
+import com.google.common.base.Supplier;
+
+/**
+ * Properly supply the version of the Chef Server.
+ */
+@Singleton
+public class ChefVersionSupplier implements Supplier<Integer> {
+
+   /** The default version to assume in case we can not parse it. */
+   public static final Integer FALLBACK_VERSION = 10;
+
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   private Logger logger = Logger.NULL;
+
+   /** The configured version of the Chef Server API. */
+   private final String apiVersion;
+
+   @Inject
+   ChefVersionSupplier(@ApiVersion String apiVersion) {
+      this.apiVersion = checkNotNull(apiVersion, "apiVersion must not be null");
+   }
+
+   @Override
+   public Integer get() {
+      // Old versions of Chef have versions like 0.9.x, 0.10.x, but newer
+      // versions are in the format 10.x.y, 11.x.y
+      Pattern versionPattern = Pattern.compile("(?:0\\.(\\d+)|(\\d+)\\.\\d+)(?:\\.\\d)*");
+
+      Matcher m = versionPattern.matcher(apiVersion);
+      if (!m.matches()) {
+         logger.warn("Configured version does not match the standard version pattern. Assuming version %s",
+               FALLBACK_VERSION);
+         return FALLBACK_VERSION;
+      }
+
+      return Integer.valueOf(firstNonNull(m.group(1), m.group(2)));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApi.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApi.java b/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApi.java
new file mode 100644
index 0000000..c2c5f10
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApi.java
@@ -0,0 +1,387 @@
+/*
+ * 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.test;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Sets.newLinkedHashSet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.blobstore.config.LocalBlobStore;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.domain.PageSet;
+import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.chef.ChefApi;
+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.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.options.CreateClientOptions;
+import org.jclouds.chef.options.SearchOptions;
+import org.jclouds.io.Payload;
+import org.jclouds.lifecycle.Closer;
+import org.jclouds.util.Strings2;
+
+import com.google.common.base.Function;
+
+/**
+ * In-memory chef simulator.
+ */
+public class TransientChefApi implements ChefApi {
+   @Singleton
+   private static class StorageMetadataToName implements Function<PageSet<? extends StorageMetadata>, Set<String>> {
+      @Override
+      public Set<String> apply(PageSet<? extends StorageMetadata> from) {
+         return newLinkedHashSet(transform(from, new Function<StorageMetadata, String>() {
+
+            @Override
+            public String apply(StorageMetadata from) {
+               return from.getName();
+            }
+         }));
+      }
+   }
+
+   @Singleton
+   private static class BlobToDatabagItem implements Function<Blob, DatabagItem> {
+      @Override
+      public DatabagItem apply(Blob from) {
+         try {
+            return from == null ? null : new DatabagItem(from.getMetadata().getName(), Strings2.toStringAndClose(from
+                  .getPayload().getInput()));
+         } catch (IOException e) {
+            propagate(e);
+            return null;
+         }
+      }
+   }
+
+   private final LocalBlobStore databags;
+   private final BlobToDatabagItem blobToDatabagItem;
+   private final StorageMetadataToName storageMetadataToName;
+   private final Closer closer;
+
+   @Inject
+   TransientChefApi(@Named("databags") LocalBlobStore databags, StorageMetadataToName storageMetadataToName,
+         BlobToDatabagItem blobToDatabagItem, Closer closer) {
+      this.databags = checkNotNull(databags, "databags");
+      this.storageMetadataToName = checkNotNull(storageMetadataToName, "storageMetadataToName");
+      this.blobToDatabagItem = checkNotNull(blobToDatabagItem, "blobToDatabagItem");
+      this.closer = checkNotNull(closer, "closer");
+   }
+
+   @Override
+   public Sandbox commitSandbox(String id, boolean isCompleted) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client createClient(String clientName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client createClient(String clientName, CreateClientOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void createDatabag(String databagName) {
+      databags.createContainerInLocation(null, databagName);
+   }
+
+   @Override
+   public DatabagItem createDatabagItem(String databagName, DatabagItem databagItem) {
+      Blob blob = databags.blobBuilder(databagItem.getId()).payload(databagItem.toString()).build();
+      databags.putBlob(databagName, blob);
+      return databagItem;
+   }
+
+   @Override
+   public void createNode(Node node) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void createRole(Role role) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client deleteClient(String clientName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookVersion deleteCookbook(String cookbookName, String version) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void deleteDatabag(String databagName) {
+      databags.deleteContainer(databagName);
+   }
+
+   @Override
+   public DatabagItem deleteDatabagItem(String databagName, String databagItemId) {
+      DatabagItem item = blobToDatabagItem.apply(databags.getBlob(databagName, databagItemId));
+      databags.removeBlob(databagName, databagItemId);
+      return item;
+   }
+
+   @Override
+   public Node deleteNode(String nodeName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Role deleteRole(String rolename) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client generateKeyForClient(String clientName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client getClient(String clientName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookVersion getCookbook(String cookbookName, String version) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public DatabagItem getDatabagItem(String databagName, String databagItemId) {
+      return blobToDatabagItem.apply(databags.getBlob(databagName, databagItemId));
+   }
+
+   @Override
+   public Node getNode(String nodeName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Role getRole(String roleName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public UploadSandbox createUploadSandboxForChecksums(Set<List<Byte>> md5s) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listVersionsOfCookbook(String cookbookName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listClients() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listCookbooks() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listDatabagItems(String databagName) {
+      return storageMetadataToName.apply(databags.list(databagName));
+   }
+
+   @Override
+   public Set<String> listDatabags() {
+      return storageMetadataToName.apply(databags.list());
+   }
+
+   @Override
+   public Set<String> listNodes() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listRoles() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listSearchIndexes() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Client> searchClients() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Client> searchClients(SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends DatabagItem> searchDatabagItems(String databagName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends DatabagItem> searchDatabagItems(String databagName, SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Node> searchNodes() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Node> searchNodes(SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Role> searchRoles() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Role> searchRoles(SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookVersion updateCookbook(String cookbookName, String version, CookbookVersion cookbook) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public DatabagItem updateDatabagItem(String databagName, DatabagItem item) {
+      return createDatabagItem(databagName, item);
+   }
+
+   @Override
+   public Node updateNode(Node node) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Role updateRole(Role role) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void uploadContent(URI location, Payload content) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public InputStream getResourceContents(Resource resource) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listEnvironments() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void createEnvironment(Environment environment) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Environment deleteEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Environment getEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Environment updateEnvironment(Environment environment) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<CookbookDefinition> listCookbooksInEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<CookbookDefinition> listCookbooksInEnvironment(String environmentName, String numVersions) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookDefinition getCookbookInEnvironment(String environmentName, String cookbookName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookDefinition getCookbookInEnvironment(String environmentName, String cookbookName, String numVersions) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Environment> searchEnvironments() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Environment> searchEnvironments(SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listRecipesInEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listNodesInEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+  
+   @Override
+   public void close() throws IOException {
+      closer.close();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApiMetadata.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApiMetadata.java b/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApiMetadata.java
new file mode 100644
index 0000000..c485080
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApiMetadata.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.test;
+
+import java.net.URI;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefBootstrapModule;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.test.config.TransientChefApiModule;
+import org.jclouds.ohai.config.JMXOhaiModule;
+import org.jclouds.rest.internal.BaseHttpApiMetadata;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+/**
+ * Implementation of {@link ApiMetadata} for the Amazon-specific Chef API
+ */
+public class TransientChefApiMetadata extends BaseHttpApiMetadata<TransientChefApi> {
+
+   @Override
+   public Builder toBuilder() {
+      return new Builder().fromApiMetadata(this);
+   }
+
+   public TransientChefApiMetadata() {
+      this(new Builder());
+   }
+
+   protected TransientChefApiMetadata(Builder builder) {
+      super(builder);
+   }
+
+   public static class Builder extends BaseHttpApiMetadata.Builder<TransientChefApi, Builder> {
+      protected Builder() {
+         id("transientchef")
+               .name("In-memory Chef API")
+               .identityName("unused")
+               .defaultIdentity("api")
+               .documentation(URI.create("http://localhost"))
+               .defaultCredential(
+                     "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAyb2ZJJqGm0KKR+8nfQJNsSd+F9tXNMV7CfOcW6jsqs8EZgiV\nR09hD1IYOj4YqM0qJONlgyg4xRWewdSG7QTPj1lJpVAida9sXy2+kzyagZA1Am0O\nZcbqb5hoeIDgcX+eDa79s0u0DomjcfO9EKhvHLBz+zM+3QqPRkPV8nYTbfs+HjVz\nzOU6D1B0XR3+IPZZl2AnWs2d0qhnStHcDUvnRVQ0P482YwN9VgceOZtpPz0DCKEJ\n5Tx5STub8k0/zt/VAMHQafLSuQMLd2s4ZLuOZptN//uAsTmxireqd37z+8ZTdBbJ\n8LEpJ+iCXuSfm5aUh7iw6oxvToY2AL53+jK2UQIDAQABAoIBAQDA88B3i/xWn0vX\nBVxFamCYoecuNjGwXXkSyZew616A+EOCu47bh4aTurdFbYL0YFaAtaWvzlaN2eHg\nDb+HDuTefE29+WkcGk6SshPmiz5T0XOCAICWw6wSVDkHmGwS4jZvbAFm7W8nwGk9\nYhxgxFiRngswJZFopOLoF5WXs2td8guIYNslMpo7tu50iFnBHwKO2ZsPAk8t9nnS\nxlDavKruymEmqHCr3+dtio5eaenJcp3fjoXBQOKUk3ipII29XRB8NqeCVV/7Kxwq\nckqOBEbRwBclckyIbD+RiAgKvOelORjEiE9R42vuqvxRA6k9kd9o7utlX0AUtpEn\n3gZc6LepAoGBAP9ael5Y75+sK2JJUNOOhO8ae45cdsilp2yI0X+UBaSuQs2+dyPp\nkpEHAxd4pmmSvn/8c9TlEZhr+qYbABXVPlDncxpIuw2Ajbk7s/S4XaSKsRqpXL57\nzj/QOqLkRk8+OVV9q6lMeQNqLtEj1u6JPviX70Ro+FQtRttNOYbfdP/fAoGBAMpA\nXjR5woV5sUb+REg9vE
 uYo8RSyOarxqKFCIXVUNsLOx+22+AK4+CQpbueWN7jotrl\nYD6uT6svWi3AAC7kiY0UI/fjVPRCUi8tVoQUE0TaU5VLITaYOB+W/bBaDE4M9560\n1NuDWO90baA5dfU44iuzva02rGJXK9+nS3o8nk/PAoGBALOL6djnDe4mwAaG6Jco\ncd4xr8jkyPzCRZuyBCSBbwphIUXLc7hDprPky064ncJD1UDmwIdkXd/fpMkg2QmA\n/CUk6LEFjMisqHojOaCL9gQZJPhLN5QUN2x1PJWGjs1vQh8Tkx0iUUCOa8bQPXNR\n+34OTsW6TUna4CSZAycLfhffAoGBAIggVsefBCvuQkF0NeUhmDCRZfhnd8y55RHR\n1HCvqKIlpv+rhcX/zmyBLuteopYyRJRsOiE2FW00i8+rIPRu4Z3Q5nybx7w3PzV9\noHN5R5baE9OyI4KpZWztpYYitZF67NcnAvVULHHOvVJQGnKYfLHJYmrJF7GA1ojM\nAuMdFbjFAoGAPxUhxwFy8gaqBahKUEZn4F81HFP5ihGhkT4QL6AFPO2e+JhIGjuR\n27+85hcFqQ+HHVtFsm81b/a+R7P4UuCRgc8eCjxQMoJ1Xl4n7VbjPbHMnIN0Ryvd\nO4ZpWDWYnCO021JTOUUOJ4J/y0416Bvkw0z59y7sNX7wDBBHHbK/XCc=\n-----END RSA PRIVATE KEY-----\n")
+               .defaultEndpoint("transientchef")
+               .defaultProperties(ChefApiMetadata.defaultProperties())
+               .defaultModules(
+                     ImmutableSet.<Class<? extends Module>> of(TransientChefApiModule.class, ChefParserModule.class,
+                           ChefBootstrapModule.class, JMXOhaiModule.class));
+      }
+
+      @Override
+      public TransientChefApiMetadata build() {
+         return new TransientChefApiMetadata(this);
+      }
+
+      @Override
+      protected Builder self() {
+         return this;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/test/config/TransientChefApiModule.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/test/config/TransientChefApiModule.java b/apis/chef/src/main/java/org/jclouds/chef/test/config/TransientChefApiModule.java
new file mode 100644
index 0000000..d9eab4d
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/test/config/TransientChefApiModule.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.test.config;
+
+import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.List;
+
+import javax.inject.Singleton;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.blobstore.TransientApiMetadata;
+import org.jclouds.blobstore.config.LocalBlobStore;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.config.Validator;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.functions.BootstrapConfigForGroup;
+import org.jclouds.chef.functions.ClientForGroup;
+import org.jclouds.chef.functions.RunListForGroup;
+import org.jclouds.chef.test.TransientChefApi;
+import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.rest.ConfiguresHttpApi;
+import org.jclouds.rest.config.RestModule;
+import org.jclouds.rest.config.SyncToAsyncHttpInvocationModule;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.cache.CacheLoader;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import com.google.inject.name.Names;
+
+@ConfiguresHttpApi
+public class TransientChefApiModule extends AbstractModule {
+
+   @Override
+   protected void configure() {
+      install(new RestModule());
+      install(new SyncToAsyncHttpInvocationModule());
+      bind(ChefApi.class).to(TransientChefApi.class);
+      bind(LocalBlobStore.class).annotatedWith(Names.named("databags"))
+            .toInstance(
+                  ContextBuilder
+                        .newBuilder(new TransientApiMetadata())
+                        .modules(
+                              ImmutableSet.<Module> of(new ExecutorServiceModule(sameThreadExecutor(),
+                                    sameThreadExecutor()))).buildInjector().getInstance(LocalBlobStore.class));
+   }
+
+   @Provides
+   @Singleton
+   public Supplier<PrivateKey> supplyKey() {
+      return new Supplier<PrivateKey>() {
+         @Override
+         public PrivateKey get() {
+            return null;
+         }
+      };
+   }
+
+   @Provides
+   @Singleton
+   CacheLoader<String, List<String>> runListForGroup(RunListForGroup runListForGroup) {
+      return CacheLoader.from(runListForGroup);
+   }
+
+   @Provides
+   @Singleton
+   CacheLoader<String, ? extends JsonBall> bootstrapConfigForGroup(BootstrapConfigForGroup bootstrapConfigForGroup) {
+      return CacheLoader.from(bootstrapConfigForGroup);
+   }
+
+   @Provides
+   @Singleton
+   CacheLoader<String, Client> groupToClient(ClientForGroup clientForGroup) {
+      return CacheLoader.from(clientForGroup);
+   }
+
+   @Provides
+   @Singleton
+   @Validator
+   public Optional<String> provideValidatorName(Injector injector) {
+      return Optional.absent();
+   }
+
+   @Provides
+   @Singleton
+   @Validator
+   public Optional<PrivateKey> provideValidatorCredential(Crypto crypto, Injector injector)
+         throws InvalidKeySpecException, IOException {
+      return Optional.absent();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/util/ChefUtils.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/util/ChefUtils.java b/apis/chef/src/main/java/org/jclouds/chef/util/ChefUtils.java
new file mode 100644
index 0000000..3ba0c59
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/util/ChefUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.ohai.Automatic;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Iterables;
+import com.google.inject.Binder;
+import com.google.inject.TypeLiteral;
+import com.google.inject.multibindings.MapBinder;
+
+public class ChefUtils {
+
+   public static Date fromOhaiTime(JsonBall ohaiDate) {
+      return new Date(Long.parseLong(checkNotNull(ohaiDate, "ohaiDate").toString().replaceAll("\\.[0-9]*$", "")));
+   }
+
+   public static JsonBall toOhaiTime(long millis) {
+      return new JsonBall(millis + "");
+   }
+
+   public static MapBinder<String, Supplier<JsonBall>> ohaiAutomaticAttributeBinder(Binder binder) {
+      MapBinder<String, Supplier<JsonBall>> mapbinder = MapBinder.newMapBinder(binder, new TypeLiteral<String>() {
+      }, new TypeLiteral<Supplier<JsonBall>>() {
+      }, Automatic.class);
+      return mapbinder;
+   }
+
+   /**
+    * 
+    * @return NoSuchElementException if no element in the runList is a role.
+    */
+   public static String findRoleInRunList(List<String> runList) {
+      final Pattern pattern = Pattern.compile("^role\\[(.*)\\]$");
+      String roleToParse = Iterables.find(runList, new Predicate<String>() {
+
+         @Override
+         public boolean apply(String input) {
+            return pattern.matcher(input).matches();
+         }
+
+      });
+      Matcher matcher = pattern.matcher(roleToParse);
+      matcher.find();
+      return matcher.group(1);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/util/CollectionUtils.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/util/CollectionUtils.java b/apis/chef/src/main/java/org/jclouds/chef/util/CollectionUtils.java
new file mode 100644
index 0000000..cabfbde
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/util/CollectionUtils.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.util;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Utility methods to work with collections.
+ */
+public class CollectionUtils {
+
+   /**
+    * Creates an immutable list with the elements of the given list. If the
+    * input list is <code>null</code>, it returns an empty list.
+    * 
+    * @param input
+    *           The list used to build the immutable one.
+    * @return An immutable list with the elements of the given list.
+    */
+   public static <T> ImmutableList<T> copyOfOrEmpty(@Nullable List<T> input) {
+      return input == null ? ImmutableList.<T> of() : ImmutableList.copyOf(input);
+   }
+
+   /**
+    * Creates an immutable set with the elements of the given set. If the input
+    * set is <code>null</code>, it returns an empty set.
+    * 
+    * @param input
+    *           The set used to build the immutable one.
+    * @return An immutable set with the elements of the given set.
+    */
+   public static <T> ImmutableSet<T> copyOfOrEmpty(@Nullable Set<T> input) {
+      return input == null ? ImmutableSet.<T> of() : ImmutableSet.copyOf(input);
+   }
+
+   /**
+    * Creates an immutable map with the elements of the given map. If the input
+    * map is <code>null</code>, it returns an empty map.
+    * 
+    * @param input
+    *           The map used to build the immutable one.
+    * @return An immutable map with the elements of the given map.
+    */
+   public static <K, V> ImmutableMap<K, V> copyOfOrEmpty(@Nullable Map<K, V> input) {
+      return input == null ? ImmutableMap.<K, V> of() : ImmutableMap.copyOf(input);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/util/RunListBuilder.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/util/RunListBuilder.java b/apis/chef/src/main/java/org/jclouds/chef/util/RunListBuilder.java
new file mode 100644
index 0000000..27cc377
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/util/RunListBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * 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.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.addAll;
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Lists.transform;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * builds a run list in the correct syntax for chef.
+ */
+public class RunListBuilder {
+   private List<String> list = newArrayList();
+
+   /**
+    * Add the following recipe to the run list
+    */
+   public RunListBuilder addRecipe(String recipe) {
+      return addRecipes(checkNotNull(recipe, "recipe"));
+   }
+
+   /**
+    * Add the following recipes to the run list
+    */
+   public RunListBuilder addRecipes(String... recipes) {
+      addAll(list, transform(Arrays.asList(checkNotNull(recipes, "recipes")), new Function<String, String>() {
+
+         @Override
+         public String apply(String from) {
+            return "recipe[" + from + "]";
+         }
+
+      }));
+      return this;
+   }
+
+   /**
+    * Add the following role to the run list
+    */
+   public RunListBuilder addRole(String role) {
+      return addRoles(checkNotNull(role, "role"));
+   }
+
+   /**
+    * Add the following roles to the run list
+    */
+   public RunListBuilder addRoles(String... roles) {
+      addAll(list, transform(Arrays.asList(checkNotNull(roles, "roles")), new Function<String, String>() {
+
+         @Override
+         public String apply(String from) {
+            return "role[" + from + "]";
+         }
+
+      }));
+      return this;
+   }
+
+   public List<String> build() {
+      return ImmutableList.copyOf(list);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/Automatic.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/Automatic.java b/apis/chef/src/main/java/org/jclouds/ohai/Automatic.java
new file mode 100644
index 0000000..9b3d134
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/Automatic.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ohai;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+@Retention(RUNTIME)
+@Target({ TYPE, METHOD, PARAMETER })
+@Qualifier
+public @interface Automatic {
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/AutomaticSupplier.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/AutomaticSupplier.java b/apis/chef/src/main/java/org/jclouds/ohai/AutomaticSupplier.java
new file mode 100644
index 0000000..681558e
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/AutomaticSupplier.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.ohai;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.ohai.functions.NestSlashKeys;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.Multimap;
+
+@Singleton
+public class AutomaticSupplier implements Supplier<Map<String, JsonBall>> {
+   private final Multimap<String, Supplier<JsonBall>> autoAttrs;
+   private final NestSlashKeys nester;
+
+   @Inject
+   AutomaticSupplier(@Automatic Multimap<String, Supplier<JsonBall>> autoAttrs, NestSlashKeys nester) {
+      this.autoAttrs = checkNotNull(autoAttrs, "autoAttrs");
+      this.nester = checkNotNull(nester, "nester");
+   }
+
+   @Override
+   public Map<String, JsonBall> get() {
+      return nester.apply(autoAttrs);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/config/ConfiguresOhai.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/config/ConfiguresOhai.java b/apis/chef/src/main/java/org/jclouds/ohai/config/ConfiguresOhai.java
new file mode 100644
index 0000000..1ca0df4
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/config/ConfiguresOhai.java
@@ -0,0 +1,28 @@
+/*
+ * 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.ohai.config;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Retention(RUNTIME)
+@Target(TYPE)
+public @interface ConfiguresOhai {
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/config/JMXOhaiModule.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/config/JMXOhaiModule.java b/apis/chef/src/main/java/org/jclouds/ohai/config/JMXOhaiModule.java
new file mode 100644
index 0000000..a8d01f7
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/config/JMXOhaiModule.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.ohai.config;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+
+import javax.inject.Singleton;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.ohai.suppliers.UptimeSecondsSupplier;
+
+import com.google.common.base.Supplier;
+import com.google.inject.Provides;
+import com.google.inject.multibindings.MapBinder;
+
+/**
+ * Wires the components needed to parse ohai data from a JVM
+ */
+@ConfiguresOhai
+public class JMXOhaiModule extends OhaiModule {
+
+   @Provides
+   @Singleton
+   protected RuntimeMXBean provideRuntimeMXBean() {
+      return ManagementFactory.getRuntimeMXBean();
+   }
+
+   public MapBinder<String, Supplier<JsonBall>> bindOhai() {
+      MapBinder<String, Supplier<JsonBall>> mapBinder = super.bindOhai();
+      mapBinder.addBinding("uptime_seconds").to(UptimeSecondsSupplier.class);
+      return mapBinder;
+   }
+}


[04/10] Move jclouds-chef to the main jclouds repo

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java b/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java
new file mode 100644
index 0000000..9b1354e
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.Objects.equal;
+import static org.testng.Assert.assertEquals;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import org.jclouds.chef.config.ChefParserModule.KeepLastRepeatedKeyMapTypeAdapterFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Unit tests for the {@link ChefParserModule} class.
+ */
+@Test(groups = "unit", testName = "ChefParserModuleTest")
+public class ChefParserModuleTest {
+
+   private static class KeyValue {
+      private final String key;
+      private final String value;
+
+      private KeyValue(String key, String value) {
+         this.key = key;
+         this.value = value;
+      }
+
+      @Override
+      public int hashCode() {
+         return Objects.hashCode(key, value);
+      }
+
+      @Override
+      public boolean equals(Object obj) {
+         if (this == obj)
+            return true;
+         if (obj == null || getClass() != obj.getClass())
+            return false;
+         KeyValue that = KeyValue.class.cast(obj);
+         return equal(this.key, that.key) && equal(this.value, that.value);
+      }
+   }
+
+   private Gson map = new GsonBuilder().registerTypeAdapterFactory(new KeepLastRepeatedKeyMapTypeAdapterFactory())
+         .create();
+   private Type mapType = new TypeToken<Map<String, String>>() {
+      private static final long serialVersionUID = 1L;
+   }.getType();
+   private Type mapkeyValueType = new TypeToken<Map<String, KeyValue>>() {
+      private static final long serialVersionUID = 1L;
+   }.getType();
+
+   public void testKeepLastRepeatedKeyMapTypeAdapter() {
+      Map<String, String> noNulls = map.fromJson("{\"value\":\"a test string!\"}", mapType);
+      assertEquals(noNulls, ImmutableMap.of("value", "a test string!"));
+      Map<String, String> withNull = map.fromJson("{\"value\":null}", mapType);
+      assertEquals(withNull, ImmutableMap.of());
+      Map<String, String> withEmpty = map.fromJson("{\"value\":\"\"}", mapType);
+      assertEquals(withEmpty, ImmutableMap.of("value", ""));
+      Map<String, KeyValue> keyValues = map.fromJson(
+            "{\"i-foo\":{\"key\":\"i-foo\",\"value\":\"foo\"},\"i-bar\":{\"key\":\"i-bar\",\"value\":\"bar\"}}",
+            mapkeyValueType);
+      assertEquals(keyValues,
+            ImmutableMap.of("i-foo", new KeyValue("i-foo", "foo"), "i-bar", new KeyValue("i-bar", "bar")));
+      Map<String, KeyValue> duplicates = map
+            .fromJson(
+                  "{\"i-foo\":{\"key\":\"i-foo\",\"value\":\"foo\", \"value\":\"foo2\"},\"i-bar\":{\"key\":\"i-bar\",\"value\":\"bar\",\"value\":\"bar2\"}}",
+                  mapkeyValueType);
+      assertEquals(duplicates,
+            ImmutableMap.of("i-foo", new KeyValue("i-foo", "foo2"), "i-bar", new KeyValue("i-bar", "bar2")));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java b/apis/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java
new file mode 100644
index 0000000..bfba190
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.filters;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertEqualsNoOrder;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+
+import javax.inject.Provider;
+import javax.ws.rs.HttpMethod;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.http.internal.SignatureWire;
+import org.jclouds.logging.config.NullLoggingModule;
+import org.jclouds.rest.internal.BaseRestApiTest.MockModule;
+import org.jclouds.util.Strings2;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.net.HttpHeaders;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+
+@Test(groups = { "unit" })
+public class SignedHeaderAuthTest {
+
+   public static final String USER_ID = "spec-user";
+   public static final String BODY = "Spec Body";
+   // Base64.encode64(Digest::SHA1.digest("Spec Body")).chomp
+   public static final String HASHED_BODY = "DFteJZPVv6WKdQmMqZUQUumUyRs=";
+   public static final String TIMESTAMP_ISO8601 = "2009-01-01T12:00:00Z";
+
+   public static final String PATH = "/organizations/clownco";
+   // Base64.encode64(Digest::SHA1.digest("/organizations/clownco")).chomp
+
+   public static final String HASHED_CANONICAL_PATH = "YtBWDn1blGGuFIuKksdwXzHU9oE=";
+   public static final String REQUESTING_ACTOR_ID = "c0f8a68c52bffa1020222a56b23cccfa";
+
+   // Content hash is ???TODO
+   public static final String X_OPS_CONTENT_HASH = "DFteJZPVv6WKdQmMqZUQUumUyRs=";
+
+   public static final String[] X_OPS_AUTHORIZATION_LINES = new String[] {
+         "jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
+         "NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
+         "3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
+         "IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
+         "9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0", "utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w==" };
+
+   // We expect Mixlib::Authentication::SignedHeaderAuth//sign to return this
+   // if passed the BODY above.
+   public static final Multimap<String, String> EXPECTED_SIGN_RESULT = ImmutableMultimap.<String, String> builder()
+         .put("X-Ops-Content-Hash", X_OPS_CONTENT_HASH).put("X-Ops-Userid", USER_ID).put("X-Ops-Sign", "version=1.0")
+         .put("X-Ops-Authorization-1", X_OPS_AUTHORIZATION_LINES[0])
+         .put("X-Ops-Authorization-2", X_OPS_AUTHORIZATION_LINES[1])
+         .put("X-Ops-Authorization-3", X_OPS_AUTHORIZATION_LINES[2])
+         .put("X-Ops-Authorization-4", X_OPS_AUTHORIZATION_LINES[3])
+         .put("X-Ops-Authorization-5", X_OPS_AUTHORIZATION_LINES[4])
+         .put("X-Ops-Authorization-6", X_OPS_AUTHORIZATION_LINES[5]).put("X-Ops-Timestamp", TIMESTAMP_ISO8601).build();
+
+   // Content hash for empty string
+   public static final String X_OPS_CONTENT_HASH_EMPTY = "2jmj7l5rSw0yVb/vlWAYkK/YBwk=";
+   public static final Multimap<String, String> EXPECTED_SIGN_RESULT_EMPTY = ImmutableMultimap
+         .<String, String> builder().put("X-Ops-Content-Hash", X_OPS_CONTENT_HASH_EMPTY).put("X-Ops-Userid", USER_ID)
+         .put("X-Ops-Sign", "version=1.0")
+         .put("X-Ops-Authorization-1", "N6U75kopDK64cEFqrB6vw+PnubnXr0w5LQeXnIGNGLRP2LvifwIeisk7QxEx")
+         .put("X-Ops-Authorization-2", "mtpQOWAw8HvnWErjzuk9AvUsqVmWpv14ficvkaD79qsPMvbje+aLcIrCGT1P")
+         .put("X-Ops-Authorization-3", "3d2uvf4w7iqwzrIscPnkxLR6o6pymR90gvJXDPzV7Le0jbfD8kmZ8AAK0sGG")
+         .put("X-Ops-Authorization-4", "09F1ftW80bLatJTA66Cw2wBz261r6x/abZhIKFJFDWLzyQGJ8ZNOkUrDDtgI")
+         .put("X-Ops-Authorization-5", "svLVXpOJKZZfKunsElpWjjsyNt3k8vpI1Y4ANO8Eg2bmeCPeEK+YriGm5fbC")
+         .put("X-Ops-Authorization-6", "DzWNPylHJqMeGKVYwGQKpg62QDfe5yXh3wZLiQcXow==")
+         .put("X-Ops-Timestamp", TIMESTAMP_ISO8601).build();
+
+   public static String PUBLIC_KEY;
+   public static String PRIVATE_KEY;
+
+   static {
+      try {
+         PUBLIC_KEY = Strings2.toStringAndClose(SignedHeaderAuthTest.class.getResourceAsStream("/pubkey.txt"));
+
+         PRIVATE_KEY = Strings2.toStringAndClose(SignedHeaderAuthTest.class.getResourceAsStream("/privkey.txt"));
+      } catch (IOException e) {
+         Throwables.propagate(e);
+      }
+   }
+
+   @Test
+   void canonicalizedPathRemovesMultipleSlashes() {
+      assertEquals(signing_obj.canonicalPath("///"), "/");
+   }
+
+   @Test
+   void canonicalizedPathRemovesTrailingSlash() {
+      assertEquals(signing_obj.canonicalPath("/path/"), "/path");
+   }
+
+   @Test
+   void shouldGenerateTheCorrectStringToSignAndSignature() {
+
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost/" + PATH)
+            .payload(BODY).build();
+
+      String expected_string_to_sign = new StringBuilder().append("Method:POST").append("\n").append("Hashed Path:")
+            .append(HASHED_CANONICAL_PATH).append("\n").append("X-Ops-Content-Hash:").append(HASHED_BODY).append("\n")
+            .append("X-Ops-Timestamp:").append(TIMESTAMP_ISO8601).append("\n").append("X-Ops-UserId:").append(USER_ID)
+            .toString();
+
+      assertEquals(signing_obj.createStringToSign("POST", HASHED_CANONICAL_PATH, HASHED_BODY, TIMESTAMP_ISO8601),
+            expected_string_to_sign);
+      assertEquals(signing_obj.sign(expected_string_to_sign), Joiner.on("").join(X_OPS_AUTHORIZATION_LINES));
+
+      request = signing_obj.filter(request);
+      Multimap<String, String> headersWithoutContentLength = LinkedHashMultimap.create(request.getHeaders());
+      headersWithoutContentLength.removeAll(HttpHeaders.CONTENT_LENGTH);
+      assertEqualsNoOrder(headersWithoutContentLength.values().toArray(), EXPECTED_SIGN_RESULT.values().toArray());
+   }
+
+   @Test
+   void shouldGenerateTheCorrectStringToSignAndSignatureWithNoBody() {
+
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.DELETE).endpoint("http://localhost/" + PATH)
+            .build();
+
+      request = signing_obj.filter(request);
+      Multimap<String, String> headersWithoutContentLength = LinkedHashMultimap.create(request.getHeaders());
+      assertEqualsNoOrder(headersWithoutContentLength.entries().toArray(), EXPECTED_SIGN_RESULT_EMPTY.entries()
+            .toArray());
+   }
+
+   @Test
+   void shouldNotChokeWhenSigningARequestForAResourceWithALongName() {
+      StringBuilder path = new StringBuilder("nodes/");
+      for (int i = 0; i < 100; i++)
+         path.append('A');
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.PUT)
+            .endpoint("http://localhost/" + path.toString()).payload(BODY).build();
+
+      signing_obj.filter(request);
+   }
+
+   @Test
+   void shouldReplacePercentage3FWithQuestionMarkAtUrl() {
+      StringBuilder path = new StringBuilder("nodes/");
+      path.append("test/cookbooks/myCookBook%3Fnum_versions=5");
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.GET)
+            .endpoint("http://localhost/" + path.toString()).payload(BODY).build();
+      request = signing_obj.filter(request);
+      assertTrue(request.getRequestLine().contains("?num_versions=5"));
+   }
+
+   private SignedHeaderAuth signing_obj;
+
+   /**
+    * before class, as we need to ensure that the filter is threadsafe.
+    * 
+    * @throws IOException
+    * 
+    */
+   @BeforeClass
+   protected void createFilter() throws IOException {
+
+      Injector injector = ContextBuilder.newBuilder(new ChefApiMetadata()).credentials(USER_ID, PRIVATE_KEY)
+            .modules(ImmutableSet.<Module> of(new MockModule(), new NullLoggingModule())).buildInjector();
+
+      HttpUtils utils = injector.getInstance(HttpUtils.class);
+      Crypto crypto = injector.getInstance(Crypto.class);
+
+      Supplier<PrivateKey> privateKey = injector.getInstance(Key.get(new TypeLiteral<Supplier<PrivateKey>>() {
+      }));
+
+      signing_obj = new SignedHeaderAuth(new SignatureWire(),
+            Suppliers.ofInstance(new Credentials(USER_ID, PRIVATE_KEY)), privateKey, new Provider<String>() {
+
+               @Override
+               public String get() {
+                  return TIMESTAMP_ISO8601;
+               }
+
+            }, utils, crypto);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java
new file mode 100644
index 0000000..f326482
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.rest.annotations.Api;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "BootstrapConfigForGroupTest")
+public class BootstrapConfigForGroupTest {
+
+   @Test(expectedExceptions = IllegalStateException.class)
+   public void testWhenNoDatabagItem() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Client client = createMock(Client.class);
+
+      BootstrapConfigForGroup fn = new BootstrapConfigForGroup("jclouds", chefApi);
+
+      expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(null);
+
+      replay(client);
+      replay(chefApi);
+
+      fn.apply("foo");
+
+      verify(client);
+      verify(chefApi);
+   }
+
+   @Test
+   public void testReturnsItem() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Api api = createMock(Api.class);
+
+      BootstrapConfigForGroup fn = new BootstrapConfigForGroup("jclouds", chefApi);
+      DatabagItem config = new DatabagItem("foo",
+            "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
+
+      expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(config);
+
+      replay(api);
+      replay(chefApi);
+
+      assertEquals(fn.apply("foo"), config);
+
+      verify(api);
+      verify(chefApi);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ClientForGroupTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ClientForGroupTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ClientForGroupTest.java
new file mode 100644
index 0000000..4be873c
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ClientForGroupTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.functions;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Client;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+@Test(groups = "unit", testName = "ClientForGroupTest")
+public class ClientForGroupTest {
+
+   public void testWhenNoClientsInList() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Client client = createMock(Client.class);
+      PrivateKey privateKey = createMock(PrivateKey.class);
+
+      ClientForGroup fn = new ClientForGroup(chefApi);
+
+      expect(chefApi.listClients()).andReturn(ImmutableSet.<String> of());
+      expect(chefApi.createClient("foo-client-00")).andReturn(client);
+      expect(client.getPrivateKey()).andReturn(privateKey);
+
+      replay(client);
+      replay(chefApi);
+
+      Client compare = fn.apply("foo");
+      assertEquals(compare.getClientname(), "foo-client-00");
+      assertEquals(compare.getName(), "foo-client-00");
+      assertEquals(compare.getPrivateKey(), privateKey);
+
+      verify(client);
+      verify(chefApi);
+   }
+
+   public void testWhenClientsInListAddsToEnd() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Client client = createMock(Client.class);
+      PrivateKey privateKey = createMock(PrivateKey.class);
+
+      ClientForGroup fn = new ClientForGroup(chefApi);
+
+      expect(chefApi.listClients()).andReturn(
+            ImmutableSet.<String> of("foo-client-00", "foo-client-01", "foo-client-02"));
+      expect(chefApi.createClient("foo-client-03")).andReturn(client);
+      expect(client.getPrivateKey()).andReturn(privateKey);
+
+      replay(client);
+      replay(chefApi);
+
+      Client compare = fn.apply("foo");
+      assertEquals(compare.getClientname(), "foo-client-03");
+      assertEquals(compare.getName(), "foo-client-03");
+      assertEquals(compare.getPrivateKey(), privateKey);
+
+      verify(client);
+      verify(chefApi);
+   }
+
+   public void testWhenClientsInListReplacesMissing() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Client client = createMock(Client.class);
+      PrivateKey privateKey = createMock(PrivateKey.class);
+
+      ClientForGroup fn = new ClientForGroup(chefApi);
+
+      expect(chefApi.listClients()).andReturn(ImmutableSet.<String> of("foo-client-00", "foo-client-02"));
+      expect(chefApi.createClient("foo-client-01")).andReturn(client);
+      expect(client.getPrivateKey()).andReturn(privateKey);
+
+      replay(client);
+      replay(chefApi);
+
+      Client compare = fn.apply("foo");
+      assertEquals(compare.getClientname(), "foo-client-01");
+      assertEquals(compare.getName(), "foo-client-01");
+      assertEquals(compare.getPrivateKey(), privateKey);
+
+      verify(client);
+      verify(chefApi);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java
new file mode 100644
index 0000000..8792025
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.functions;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.jclouds.chef.config.ChefProperties.CHEF_UPDATE_GEMS;
+import static org.jclouds.chef.config.ChefProperties.CHEF_UPDATE_GEM_SYSTEM;
+import static org.jclouds.chef.config.ChefProperties.CHEF_USE_OMNIBUS;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.PrivateKey;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefBootstrapModule;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.config.InstallChef;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.crypto.PemsTest;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.jclouds.scriptbuilder.domain.OsFamily;
+import org.jclouds.scriptbuilder.domain.ShellToken;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Functions;
+import com.google.common.base.Optional;
+import com.google.common.base.Suppliers;
+import com.google.common.cache.CacheLoader;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Resources;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.name.Names;
+
+@Test(groups = "unit", testName = "GroupToBootScriptTest")
+public class GroupToBootScriptTest {
+
+   private Json json;
+   private Statement installChefGems;
+   private Statement installChefOmnibus;
+   private Optional<String> validatorName;
+
+   @BeforeClass
+   public void setup() {
+      Injector injectorGems = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+            bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEM_SYSTEM)).toInstance("true");
+            bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEMS)).toInstance("true");
+            bind(String.class).annotatedWith(Names.named(CHEF_USE_OMNIBUS)).toInstance("false");
+         }
+      }, new ChefParserModule(), new GsonModule(), new ChefBootstrapModule());
+
+      Injector injectorOmnibus = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+            bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEM_SYSTEM)).toInstance("true");
+            bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEMS)).toInstance("true");
+            bind(String.class).annotatedWith(Names.named(CHEF_USE_OMNIBUS)).toInstance("true");
+         }
+      }, new ChefParserModule(), new GsonModule(), new ChefBootstrapModule());
+
+      json = injectorGems.getInstance(Json.class);
+      installChefGems = injectorGems.getInstance(Key.get(Statement.class, InstallChef.class));
+      installChefOmnibus = injectorOmnibus.getInstance(Key.get(Statement.class, InstallChef.class));
+      validatorName = Optional.<String> of("chef-validator");
+   }
+
+   @Test(expectedExceptions = IllegalStateException.class)
+   public void testMustHaveValidatorName() {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of())), installChefGems,
+            Optional.<String> absent(), validatorCredential);
+      fn.apply("foo");
+   }
+
+   @Test(expectedExceptions = IllegalStateException.class)
+   public void testMustHaveValidatorCredential() {
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of())), installChefGems,
+            validatorName, Optional.<PrivateKey> absent());
+      fn.apply("foo");
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Key 'foo' not present in map")
+   public void testMustHaveRunScriptsName() {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of())), installChefGems,
+            validatorName, validatorCredential);
+      fn.apply("foo");
+   }
+
+   @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "null value in entry: foo=null")
+   public void testMustHaveRunScriptsValue() {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of("foo", (DatabagItem) null))),
+            installChefGems, validatorName, validatorCredential);
+      fn.apply("foo");
+   }
+
+   public void testOneRecipe() throws IOException {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
+                  "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))),
+            installChefGems, validatorName, validatorCredential);
+
+      PrivateKey validatorKey = validatorCredential.get();
+      expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
+      replay(validatorKey);
+
+      assertEquals(
+            fn.apply("foo").render(OsFamily.UNIX),
+            exitInsteadOfReturn(
+                  OsFamily.UNIX,
+                  Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)),
+                        Charsets.UTF_8)
+                        + Resources.toString(
+                              Resources.getResource("test_install_rubygems." + ShellToken.SH.to(OsFamily.UNIX)),
+                              Charsets.UTF_8)
+                        + "gem install chef --no-rdoc --no-ri\n"
+                        + Resources.toString(Resources.getResource("bootstrap.sh"), Charsets.UTF_8)));
+
+      verify(validatorKey);
+   }
+
+   public void testOneRecipeAndEnvironment() throws IOException {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
+                  "{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\","
+                        + "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), installChefGems,
+            validatorName, validatorCredential);
+
+      PrivateKey validatorKey = validatorCredential.get();
+      expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
+      replay(validatorKey);
+
+      assertEquals(
+            fn.apply("foo").render(OsFamily.UNIX),
+            exitInsteadOfReturn(
+                  OsFamily.UNIX,
+                  Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)),
+                        Charsets.UTF_8)
+                        + Resources.toString(
+                              Resources.getResource("test_install_rubygems." + ShellToken.SH.to(OsFamily.UNIX)),
+                              Charsets.UTF_8)
+                        + "gem install chef --no-rdoc --no-ri\n"
+                        + Resources.toString(Resources.getResource("bootstrap-env.sh"), Charsets.UTF_8)));
+
+      verify(validatorKey);
+   }
+
+   public void testOneRecipeOmnibus() throws IOException {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
+                  "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))),
+            installChefOmnibus, validatorName, validatorCredential);
+
+      PrivateKey validatorKey = validatorCredential.get();
+      expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
+      replay(validatorKey);
+
+      assertEquals(
+            fn.apply("foo").render(OsFamily.UNIX),
+            "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 "
+                  + "-X GET  https://www.opscode.com/chef/install.sh |(bash)\n"
+                  + Resources.toString(Resources.getResource("bootstrap.sh"), Charsets.UTF_8));
+
+      verify(validatorKey);
+   }
+
+   public void testOneRecipeAndEnvironmentOmnibus() throws IOException {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
+                  "{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\","
+                        + "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), installChefOmnibus,
+            validatorName, validatorCredential);
+
+      PrivateKey validatorKey = validatorCredential.get();
+      expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
+      replay(validatorKey);
+
+      assertEquals(
+            fn.apply("foo").render(OsFamily.UNIX),
+            "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 "
+                  + "-X GET  https://www.opscode.com/chef/install.sh |(bash)\n"
+                  + Resources.toString(Resources.getResource("bootstrap-env.sh"), Charsets.UTF_8));
+
+      verify(validatorKey);
+   }
+
+   private static String exitInsteadOfReturn(OsFamily family, String input) {
+      return input.replaceAll(ShellToken.RETURN.to(family), ShellToken.EXIT.to(family));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java
new file mode 100644
index 0000000..e8bebcb
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.crypto.Pems;
+import org.jclouds.encryption.internal.JCECrypto;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.io.Payloads;
+import org.jclouds.io.payloads.RSADecryptingPayload;
+import org.jclouds.io.payloads.RSAEncryptingPayload;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteSource;
+import com.google.common.io.ByteStreams;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code ParseClientFromJson}
+ */
+@Test(groups = { "unit" })
+public class ParseClientFromJsonTest {
+
+   private static final String PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAyb2ZJJqGm0KKR+8nfQJNsSd+F9tXNMV7CfOcW6jsqs8EZgiV\nR09hD1IYOj4YqM0qJONlgyg4xRWewdSG7QTPj1lJpVAida9sXy2+kzyagZA1Am0O\nZcbqb5hoeIDgcX+eDa79s0u0DomjcfO9EKhvHLBz+zM+3QqPRkPV8nYTbfs+HjVz\nzOU6D1B0XR3+IPZZl2AnWs2d0qhnStHcDUvnRVQ0P482YwN9VgceOZtpPz0DCKEJ\n5Tx5STub8k0/zt/VAMHQafLSuQMLd2s4ZLuOZptN//uAsTmxireqd37z+8ZTdBbJ\n8LEpJ+iCXuSfm5aUh7iw6oxvToY2AL53+jK2UQIDAQABAoIBAQDA88B3i/xWn0vX\nBVxFamCYoecuNjGwXXkSyZew616A+EOCu47bh4aTurdFbYL0YFaAtaWvzlaN2eHg\nDb+HDuTefE29+WkcGk6SshPmiz5T0XOCAICWw6wSVDkHmGwS4jZvbAFm7W8nwGk9\nYhxgxFiRngswJZFopOLoF5WXs2td8guIYNslMpo7tu50iFnBHwKO2ZsPAk8t9nnS\nxlDavKruymEmqHCr3+dtio5eaenJcp3fjoXBQOKUk3ipII29XRB8NqeCVV/7Kxwq\nckqOBEbRwBclckyIbD+RiAgKvOelORjEiE9R42vuqvxRA6k9kd9o7utlX0AUtpEn\n3gZc6LepAoGBAP9ael5Y75+sK2JJUNOOhO8ae45cdsilp2yI0X+UBaSuQs2+dyPp\nkpEHAxd4pmmSvn/8c9TlEZhr+qYbABXVPlDncxpIuw2Ajbk7s/S4XaSKsRqpXL57\nzj/QOqLkRk8+OVV9q6lMeQNqLtEj1u6JPviX70Ro+FQtRttNOYbfdP/fAoGB
 AMpA\nXjR5woV5sUb+REg9vEuYo8RSyOarxqKFCIXVUNsLOx+22+AK4+CQpbueWN7jotrl\nYD6uT6svWi3AAC7kiY0UI/fjVPRCUi8tVoQUE0TaU5VLITaYOB+W/bBaDE4M9560\n1NuDWO90baA5dfU44iuzva02rGJXK9+nS3o8nk/PAoGBALOL6djnDe4mwAaG6Jco\ncd4xr8jkyPzCRZuyBCSBbwphIUXLc7hDprPky064ncJD1UDmwIdkXd/fpMkg2QmA\n/CUk6LEFjMisqHojOaCL9gQZJPhLN5QUN2x1PJWGjs1vQh8Tkx0iUUCOa8bQPXNR\n+34OTsW6TUna4CSZAycLfhffAoGBAIggVsefBCvuQkF0NeUhmDCRZfhnd8y55RHR\n1HCvqKIlpv+rhcX/zmyBLuteopYyRJRsOiE2FW00i8+rIPRu4Z3Q5nybx7w3PzV9\noHN5R5baE9OyI4KpZWztpYYitZF67NcnAvVULHHOvVJQGnKYfLHJYmrJF7GA1ojM\nAuMdFbjFAoGAPxUhxwFy8gaqBahKUEZn4F81HFP5ihGhkT4QL6AFPO2e+JhIGjuR\n27+85hcFqQ+HHVtFsm81b/a+R7P4UuCRgc8eCjxQMoJ1Xl4n7VbjPbHMnIN0Ryvd\nO4ZpWDWYnCO021JTOUUOJ4J/y0416Bvkw0z59y7sNX7wDBBHHbK/XCc=\n-----END RSA PRIVATE KEY-----\n";
+   private static final String CERTIFICATE = "-----BEGIN CERTIFICATE-----\nMIIClzCCAgCgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxFjAUBgNVBAoM\nDU9wc2NvZGUsIEluYy4xHDAaBgNVBAsME0NlcnRpZmljYXRlIFNlcnZpY2UxMjAw\nBgNVBAMMKW9wc2NvZGUuY29tL2VtYWlsQWRkcmVzcz1hdXRoQG9wc2NvZGUuY29t\nMB4XDTEwMDczMDIwNDEzMFoXDTIwMDcyNzIwNDEzMFowADCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAMm9mSSahptCikfvJ30CTbEnfhfbVzTFewnznFuo\n7KrPBGYIlUdPYQ9SGDo+GKjNKiTjZYMoOMUVnsHUhu0Ez49ZSaVQInWvbF8tvpM8\nmoGQNQJtDmXG6m+YaHiA4HF/ng2u/bNLtA6Jo3HzvRCobxywc/szPt0Kj0ZD1fJ2\nE237Ph41c8zlOg9QdF0d/iD2WZdgJ1rNndKoZ0rR3A1L50VUND+PNmMDfVYHHjmb\naT89AwihCeU8eUk7m/JNP87f1QDB0Gny0rkDC3drOGS7jmabTf/7gLE5sYq3qnd+\n8/vGU3QWyfCxKSfogl7kn5uWlIe4sOqMb06GNgC+d/oytlECAwEAATANBgkqhkiG\n9w0BAQUFAAOBgQBftzSZxstWw60GqRTDNN/F2GnrdtnKBoXzHww3r6jtGEylYq20\n5KfKpEx+sPX0gyZuYJiXC2CkEjImAluWKcdN9ZF6VD541sheAjbiaU7q7ZsztTxF\nWUH2tCvHeDXYKPKek3QzL7bYpUhLnCN/XxEv6ibeMDwtI7f5qpk2Aspzcw==\n--
 ---END CERTIFICATE-----\n";
+   private ParseJson<Client> handler;
+   private Crypto crypto;
+   private PrivateKey privateKey;
+   private X509Certificate certificate;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException, CertificateException, InvalidKeySpecException {
+      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());
+
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<Client>>() {
+      }));
+      crypto = injector.getInstance(Crypto.class);
+      certificate = Pems.x509Certificate(ByteSource.wrap(CERTIFICATE.getBytes(Charsets.UTF_8)), null);
+      privateKey = crypto.rsaKeyFactory().generatePrivate(Pems.privateKeySpec(ByteSource.wrap(PRIVATE_KEY.getBytes(Charsets.UTF_8))));
+   }
+
+   public void test() throws IOException, CertificateException, NoSuchAlgorithmException {
+
+      Client user = Client.builder().certificate(certificate).orgname("jclouds").clientname("adriancole-jcloudstest")
+            .name("adriancole-jcloudstest").isValidator(false).privateKey(privateKey).build();
+
+      byte[] encrypted = ByteStreams.toByteArray(new RSAEncryptingPayload(new JCECrypto(), Payloads.newPayload("fooya"), user
+            .getCertificate().getPublicKey()));
+
+      assertEquals(
+            ByteStreams.toByteArray(new RSADecryptingPayload(new JCECrypto(), Payloads.newPayload(encrypted), user.getPrivateKey())),
+            "fooya".getBytes());
+
+      assertEquals(
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+                  .payload(ParseClientFromJsonTest.class.getResourceAsStream("/client.json")).build()), user);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersionTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersionTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersionTest.java
new file mode 100644
index 0000000..300c8e0
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersionTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookDefinitionCheckingChefVersion}.
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookDefinitionCheckingChefVersionTest {
+
+   public void testParserFor09() {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance("0.9.8");
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      ParseCookbookDefinitionCheckingChefVersion parser = injector
+            .getInstance(ParseCookbookDefinitionCheckingChefVersion.class);
+      assertTrue(parser.parser instanceof ParseKeySetFromJson);
+   }
+
+   public void testParserFor010() {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance("0.10.8");
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      ParseCookbookDefinitionCheckingChefVersion parser = injector
+            .getInstance(ParseCookbookDefinitionCheckingChefVersion.class);
+      assertTrue(parser.parser instanceof ParseCookbookDefinitionFromJson);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonTest.java
new file mode 100644
index 0000000..736b283
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookDefinitionFromJson}.
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookDefinitionFromJsonTest {
+
+   private ParseCookbookDefinitionFromJson handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(ParseCookbookDefinitionFromJson.class);
+   }
+
+   public void testParse010Response() {
+      assertEquals(handler.apply(HttpResponse
+            .builder()
+            .statusCode(200)
+            .message("ok")
+            .payload(
+                  "{" + "\"apache2\" => {" + "\"url\" => \"http://localhost:4000/cookbooks/apache2\","
+                        + "\"versions\" => [" + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/5.1.0\","
+                        + "\"version\" => \"5.1.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/4.2.0\","
+                        + "\"version\" => \"4.2.0\"}" + "]" + "}," + "\"nginx\" => {"
+                        + "\"url\" => \"http://localhost:4000/cookbooks/nginx\"," + "\"versions\" => ["
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/nginx/1.0.0\"," + "\"version\" => \"1.0.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/nginx/0.3.0\"," + "\"version\" => \"0.3.0\"}"
+                        + "]" + "}" + "}").build()), ImmutableSet.of("apache2", "nginx"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java
new file mode 100644
index 0000000..b5d8a8b
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookDefinitionFromJsonv10Test {
+
+   private ParseCookbookDefinitionFromJsonv10 handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(ParseCookbookDefinitionFromJsonv10.class);
+   }
+
+   public void testCookbokDefinitionParsing() throws URISyntaxException {
+      CookbookDefinition.Version v510 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/apache2/5.1.0")).version("5.1.0").build();
+      CookbookDefinition.Version v420 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/apache2/4.2.0")).version("4.2.0").build();
+      CookbookDefinition definition = CookbookDefinition.builder()
+            .name("apache2").url(new URI("http://localhost:4000/cookbooks/apache2")).version(v510).version(v420).build();
+
+      assertEquals(handler.apply(HttpResponse
+            .builder()
+            .statusCode(200)
+            .message("ok")
+            .payload(
+                  "{" + "\"apache2\" => {" + "\"url\" => \"http://localhost:4000/cookbooks/apache2\","
+                        + "\"versions\" => [" + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/5.1.0\","
+                        + "\"version\" => \"5.1.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/4.2.0\","
+                        + "\"version\" => \"4.2.0\"}" + "]" + "}" + "}").build()), definition);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java
new file mode 100644
index 0000000..04f8e35
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+@Test(groups = {"unit"}, singleThreaded = true)
+public class ParseCookbookDefinitionListFromJsonv10Test {
+
+   private ParseCookbookDefinitionListFromJsonv10 handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(ParseCookbookDefinitionListFromJsonv10.class);
+   }
+
+   public void testCookbokDefinitionListParsing() throws URISyntaxException {
+      CookbookDefinition.Version v510 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/apache2/5.1.0")).version("5.1.0").build();
+      CookbookDefinition.Version v420 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/apache2/4.2.0")).version("4.2.0").build();
+      CookbookDefinition apache2 = CookbookDefinition.builder()
+            .name("apache2").url(new URI("http://localhost:4000/cookbooks/apache2")).version(v510).version(v420).build();
+      
+      CookbookDefinition.Version v100 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/nginx/1.0.0")).version("1.0.0").build();
+      CookbookDefinition.Version v030 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/nginx/0.3.0")).version("0.3.0").build();
+      CookbookDefinition nginx = CookbookDefinition.builder()
+            .name("nginx").url(new URI("http://localhost:4000/cookbooks/nginx")).version(v100).version(v030).build();
+      
+      assertEquals(handler.apply(HttpResponse
+            .builder()
+            .statusCode(200)
+            .message("ok")
+            .payload(
+                  "{" + "\"apache2\" => {" + "\"url\" => \"http://localhost:4000/cookbooks/apache2\","
+                        + "\"versions\" => [" + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/5.1.0\","
+                        + "\"version\" => \"5.1.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/4.2.0\","
+                        + "\"version\" => \"4.2.0\"}" + "]" + "},"
+                        + "\"nginx\" => {"
+                        + "\"url\" => \"http://localhost:4000/cookbooks/nginx\","
+                        + "\"versions\" => ["
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/nginx/1.0.0\","
+                        + "\"version\" => \"1.0.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/nginx/0.3.0\","
+                        + "\"version\" => \"0.3.0\"}"
+                        + "]}" +
+                        "}").build()),
+            ImmutableSet.of(apache2, nginx));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java
new file mode 100644
index 0000000..7bc19bf
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.functions;
+
+import static com.google.common.io.BaseEncoding.base16;
+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.CookbookVersion;
+import org.jclouds.chef.domain.Metadata;
+import org.jclouds.chef.domain.Resource;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code ParseCookbookVersionFromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookVersionFromJsonTest {
+
+   private ParseJson<CookbookVersion> handler;
+   private Injector injector;
+   private Json json;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      json = injector.getInstance(Json.class);
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<CookbookVersion>>() {
+      }));
+   }
+
+   public void testBrew() throws IOException {
+      CookbookVersion cookbook = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/brew-cookbook.json")).build());
+
+      assertEquals(cookbook,
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json.toJson(cookbook)).build()));
+   }
+
+   public void testTomcat() {
+      CookbookVersion cookbook = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/tomcat-cookbook.json")).build());
+
+      assertEquals(cookbook,
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json.toJson(cookbook)).build()));
+   }
+
+   public void testMysql() throws IOException {
+      CookbookVersion cookbook = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/mysql-cookbook.json")).build());
+
+      assertEquals(cookbook,
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json.toJson(cookbook)).build()));
+   }
+
+   public void testApache() {
+      CookbookVersion fromJson = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/apache-chef-demo-cookbook.json"))
+            .build());
+
+      CookbookVersion expected = CookbookVersion
+            .builder("apache-chef-demo", "0.0.0")
+            .metadata(Metadata.builder() //
+                  .license("Apache v2.0") //
+                  .maintainer("Your Name") //
+                  .maintainerEmail("youremail@example.com") //
+                  .description("A fabulous new cookbook") //
+                  .version("0.0.0").name("apache-chef-demo") //
+                  .longDescription("") //
+                  .build())
+            .rootFile(
+                  Resource
+                        .builder()
+                        .name("README")
+                        .path("README")
+                        .checksum(base16().lowerCase().decode("11637f98942eafbf49c71b7f2f048b78"))
+                        .url(URI
+                              .create("https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-11637f98942eafbf49c71b7f2f048b78?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277766181&Signature=zgpNl6wSxjTNovqZu2nJq0JztU8%3D")) //
+                        .build())
+            .rootFile(
+                  Resource
+                        .builder()
+                        .name("Rakefile")
+                        .path("Rakefile")
+                        .checksum(base16().lowerCase().decode("ebcf925a1651b4e04b9cd8aac2bc54eb"))
+                        .url(URI
+                              .create("https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-ebcf925a1651b4e04b9cd8aac2bc54eb?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277766181&Signature=EFzzDSKKytTl7b%2FxrCeNLh05zj4%3D"))
+                        .build()) //
+            .build();
+
+      assertEquals(fromJson, expected);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersionTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersionTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersionTest.java
new file mode 100644
index 0000000..bad0edf
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersionTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookVersionsCheckingChefVersion}.
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookVersionsCheckingChefVersionTest {
+
+   public void testParserFor09() {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance("0.9.8");
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      ParseCookbookVersionsCheckingChefVersion parser = injector
+            .getInstance(ParseCookbookVersionsCheckingChefVersion.class);
+      assertTrue(parser.parser instanceof ParseCookbookVersionsV09FromJson);
+   }
+
+   public void testParserFor010() {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance("0.10.8");
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      ParseCookbookVersionsCheckingChefVersion parser = injector
+            .getInstance(ParseCookbookVersionsCheckingChefVersion.class);
+      assertTrue(parser.parser instanceof ParseCookbookVersionsV10FromJson);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJsonTest.java
new file mode 100644
index 0000000..08cf1d2
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJsonTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookVersionsV09FromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookVersionsV09FromJsonTest {
+
+   private ParseCookbookVersionsV09FromJson handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(ParseCookbookVersionsV09FromJson.class);
+   }
+
+   public void testRegex() {
+      assertEquals(
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+                  .payload("{\"apache2\": [\"0.1.8\", \"0.2\"]}").build()), ImmutableSet.of("0.1.8", "0.2"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJsonTest.java
new file mode 100644
index 0000000..e8b396a
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJsonTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookVersionsV10FromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookVersionsV10FromJsonTest {
+
+   private ParseCookbookVersionsV10FromJson handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(ParseCookbookVersionsV10FromJson.class);
+   }
+
+   public void testRegex() {
+      assertEquals(handler.apply(HttpResponse
+            .builder()
+            .statusCode(200)
+            .message("ok")
+            .payload(
+                  "{" + "\"apache2\" => {" + "\"url\" => \"http://localhost:4000/cookbooks/apache2\","
+                        + "\"versions\" => [" + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/5.1.0\","
+                        + "\"version\" => \"5.1.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/4.2.0\","
+                        + "\"version\" => \"4.2.0\"}" + "]" + "}" + "}").build()), ImmutableSet.of("5.1.0", "4.2.0"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseDataBagItemFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseDataBagItemFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseDataBagItemFromJsonTest.java
new file mode 100644
index 0000000..5dde523
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseDataBagItemFromJsonTest.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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+@Test(groups = { "unit" })
+public class ParseDataBagItemFromJsonTest {
+   private ParseJson<DatabagItem> handler;
+   private Json mapper;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<DatabagItem>>() {
+      }));
+      mapper = injector.getInstance(Json.class);
+   }
+
+   public void test1() {
+      String json = "{\"my_key\":\"my_data\",\"id\":\"item1\"}";
+      DatabagItem item = new DatabagItem("item1", json);
+      assertEquals(handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json).build()), item);
+      assertEquals(mapper.toJson(item), json);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBodyTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBodyTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBodyTest.java
new file mode 100644
index 0000000..940e858
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBodyTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.net.UnknownHostException;
+
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ReturnStringIf2xx;
+import org.jclouds.util.Strings2;
+import org.testng.annotations.Test;
+
+@Test(groups = { "unit" })
+public class ParseErrorFromJsonOrReturnBodyTest {
+
+   @Test
+   public void testApplyInputStreamDetails() throws UnknownHostException {
+      InputStream is = Strings2
+            .toInputStream("{\"error\":[\"invalid tarball: tarball root must contain java-bytearray\"]}");
+
+      ParseErrorFromJsonOrReturnBody parser = new ParseErrorFromJsonOrReturnBody(new ReturnStringIf2xx());
+      String response = parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload(is).build());
+      assertEquals(response, "invalid tarball: tarball root must contain java-bytearray");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java
new file mode 100644
index 0000000..8c646b2
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseKeySetFromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseKeySetFromJsonTest {
+
+   private ParseKeySetFromJson handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(ParseKeySetFromJson.class);
+   }
+
+   public void testRegex() {
+      assertEquals(
+            handler.apply(HttpResponse
+                  .builder()
+                  .statusCode(200)
+                  .message("ok")
+                  .payload(
+                        "{\n\"opscode-validator\": \"https://api.opscode.com/...\", \"pimp-validator\": \"https://api.opscode.com/...\"}")
+                  .build()), ImmutableSet.of("opscode-validator", "pimp-validator"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java
new file mode 100644
index 0000000..72b4956
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code ParseNodeFromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseNodeFromJsonTest {
+
+   private ParseJson<Node> handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<Node>>() {
+      }));
+   }
+
+   public void test() {
+      Node node = Node.builder() //
+            .name("adrian-jcloudstest") //
+            .normalAttribute("tomcat6", new JsonBall("{\"ssl_port\":8433}")) //
+            .runListElement("recipe[java]") //
+            .environment("prod") //
+            .build();
+
+      assertEquals(
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+                  .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/node.json")).build()), node);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java
new file mode 100644
index 0000000..dd31d38
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.Sandbox;
+import org.jclouds.date.DateService;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code ParseSandboxFromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseSandboxFromJsonTest {
+
+   private ParseJson<Sandbox> handler;
+   private DateService dateService;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      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());
+
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<Sandbox>>() {
+      }));
+      dateService = injector.getInstance(DateService.class);
+   }
+
+   public void test() {
+      assertEquals(
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+                  .payload(ParseSandboxFromJsonTest.class.getResourceAsStream("/sandbox.json")).build()),
+            Sandbox.builder().rev("1-8c27b0ea4c2b7aaedbb44cfbdfcc11b2").isCompleted(false)
+                  .createTime(dateService.iso8601SecondsDateParse("2010-07-07T03:36:00+00:00"))
+                  .name("f9d6d9b72bae465890aae87969f98a9c").guid("f9d6d9b72bae465890aae87969f98a9c").build());
+   }
+}


[07/10] Move jclouds-chef to the main jclouds repo

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10.java
new file mode 100644
index 0000000..5da0797
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10.java
@@ -0,0 +1,63 @@
+/*
+ * 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.functions;
+
+import com.google.common.base.Function;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Sets.newLinkedHashSet;
+
+/**
+ * Parses the cookbook versions in a Chef Server >= 0.10.8.
+ */
+@Singleton
+public class ParseCookbookDefinitionListFromJsonv10 implements Function<HttpResponse, Set<CookbookDefinition>> {
+
+   /**
+    * Parser for responses from chef server >= 0.10.8
+    */
+   private final ParseJson<Map<String, CookbookDefinition>> parser;
+
+   @Inject
+   ParseCookbookDefinitionListFromJsonv10(ParseJson<Map<String, CookbookDefinition>> parser) {
+      this.parser = parser;
+   }
+
+   @Override
+   public Set<CookbookDefinition> apply(HttpResponse response) {
+      Set<Map.Entry<String, CookbookDefinition>> result = parser.apply(response).entrySet();
+      return newLinkedHashSet(transform(result, new Function<Map.Entry<String, CookbookDefinition>, CookbookDefinition>() {
+         @Override
+         public CookbookDefinition apply(Map.Entry<String, CookbookDefinition> input) {
+            String cookbookName = input.getKey();
+            CookbookDefinition def = input.getValue();
+            return CookbookDefinition.builder() //
+                   .from(def) //              
+                   .name(cookbookName) //
+                   .build();
+         }
+      }));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersion.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersion.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersion.java
new file mode 100644
index 0000000..f82a900
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersion.java
@@ -0,0 +1,49 @@
+/*
+ * 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.functions;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.config.CookbookVersionsParser;
+import org.jclouds.http.HttpResponse;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+
+/**
+ * Parses a cookbook versions from a Json response, taking care of using the
+ * appropriate parser.
+ */
+@Singleton
+public class ParseCookbookVersionsCheckingChefVersion implements Function<HttpResponse, Set<String>> {
+
+   @VisibleForTesting
+   final Function<HttpResponse, Set<String>> parser;
+
+   @Inject
+   ParseCookbookVersionsCheckingChefVersion(@CookbookVersionsParser Function<HttpResponse, Set<String>> parser) {
+      this.parser = parser;
+   }
+
+   @Override
+   public Set<String> apply(HttpResponse response) {
+      return parser.apply(response);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJson.java
new file mode 100644
index 0000000..4421b3e
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJson.java
@@ -0,0 +1,49 @@
+/*
+ * 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.functions;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+
+import com.google.common.base.Function;
+import static com.google.common.collect.Iterables.getFirst;
+
+/**
+ * Parses the cookbook versions in a Chef Server <= 0.9.8.
+ */
+@Singleton
+public class ParseCookbookVersionsV09FromJson implements Function<HttpResponse, Set<String>> {
+
+   private final ParseJson<Map<String, Set<String>>> json;
+
+   @Inject
+   ParseCookbookVersionsV09FromJson(ParseJson<Map<String, Set<String>>> json) {
+      this.json = json;
+   }
+
+   @Override
+   public Set<String> apply(HttpResponse response) {
+      return getFirst(json.apply(response).values(), null);
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJson.java
new file mode 100644
index 0000000..1a25ac0
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJson.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.chef.functions;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.chef.domain.CookbookDefinition.Version;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+
+import com.google.common.base.Function;
+import static com.google.common.collect.Iterables.getFirst;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Sets.newLinkedHashSet;
+
+/**
+ * Parses the cookbook versions in a Chef Server >= 0.10.8.
+ */
+@Singleton
+public class ParseCookbookVersionsV10FromJson implements Function<HttpResponse, Set<String>> {
+
+   /** Parser for responses from chef server >= 0.10.8 */
+   private final ParseJson<Map<String, CookbookDefinition>> parser;
+
+   @Inject
+   ParseCookbookVersionsV10FromJson(ParseJson<Map<String, CookbookDefinition>> parser) {
+      this.parser = parser;
+   }
+
+   @Override
+   public Set<String> apply(HttpResponse response) {
+      CookbookDefinition def = getFirst(parser.apply(response).values(), null);
+      return newLinkedHashSet(transform(def.getVersions(), new Function<Version, String>() {
+         @Override
+         public String apply(Version input) {
+            return input.getVersion();
+         }
+      }));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBody.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBody.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBody.java
new file mode 100644
index 0000000..6440409
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBody.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.functions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ReturnStringIf2xx;
+
+import com.google.common.base.Function;
+
+@Singleton
+public class ParseErrorFromJsonOrReturnBody implements Function<HttpResponse, String> {
+   Pattern pattern = Pattern.compile(".*\\[\"([^\"]+)\"\\].*");
+   private final ReturnStringIf2xx returnStringIf200;
+
+   @Inject
+   ParseErrorFromJsonOrReturnBody(ReturnStringIf2xx returnStringIf200) {
+      this.returnStringIf200 = returnStringIf200;
+   }
+
+   @Override
+   public String apply(HttpResponse response) {
+      String content = returnStringIf200.apply(response);
+      if (content == null)
+         return null;
+      return parse(content);
+   }
+
+   public String parse(String in) {
+      Matcher matcher = pattern.matcher(in);
+      if (matcher.find()) {
+         return matcher.group(1);
+      }
+      return in;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseKeySetFromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseKeySetFromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseKeySetFromJson.java
new file mode 100644
index 0000000..963c19b
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseKeySetFromJson.java
@@ -0,0 +1,45 @@
+/*
+ * 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.functions;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+
+import com.google.common.base.Function;
+
+@Singleton
+public class ParseKeySetFromJson implements Function<HttpResponse, Set<String>> {
+
+   private final ParseJson<Map<String, String>> json;
+
+   @Inject
+   ParseKeySetFromJson(ParseJson<Map<String, String>> json) {
+      this.json = json;
+   }
+
+   @Override
+   public Set<String> apply(HttpResponse response) {
+      return json.apply(response).keySet();
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchClientsFromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchClientsFromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchClientsFromJson.java
new file mode 100644
index 0000000..18ecbfa
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchClientsFromJson.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.Client;
+import org.jclouds.http.functions.ParseJson;
+
+@Singleton
+public class ParseSearchClientsFromJson extends ParseSearchResultFromJson<Client> {
+
+   // TODO add generic json parser detector
+
+   @Inject
+   ParseSearchClientsFromJson(ParseJson<Response<Client>> json) {
+      super(json);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchDatabagFromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchDatabagFromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchDatabagFromJson.java
new file mode 100644
index 0000000..c2c58ef
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchDatabagFromJson.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.chef.domain.SearchResult;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+
+import com.google.common.base.Function;
+import com.google.gson.annotations.SerializedName;
+import static com.google.common.collect.Iterables.transform;
+
+/**
+ * Parses the search result into a {@link DatabagItem} object.
+ * <p>
+ * When searching databags, the items are contained inside the
+ * <code>raw_data</code> list.
+ */
+@Singleton
+public class ParseSearchDatabagFromJson implements Function<HttpResponse, SearchResult<DatabagItem>> {
+
+   private final ParseJson<Response> responseParser;
+
+   private final Json json;
+
+   static class Row {
+      @SerializedName("raw_data")
+      JsonBall rawData;
+   }
+
+   static class Response {
+      long start;
+      List<Row> rows;
+   }
+
+   @Inject
+   ParseSearchDatabagFromJson(ParseJson<Response> responseParser, Json json) {
+      this.responseParser = responseParser;
+      this.json = json;
+   }
+
+   @Override
+   public SearchResult<DatabagItem> apply(HttpResponse response) {
+      Response returnVal = responseParser.apply(response);
+      Iterable<DatabagItem> items = transform(returnVal.rows, new Function<Row, DatabagItem>() {
+         @Override
+         public DatabagItem apply(Row input) {
+            return json.fromJson(input.rawData.toString(), DatabagItem.class);
+         }
+      });
+
+      return new SearchResult<DatabagItem>(returnVal.start, items);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchEnvironmentsFromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchEnvironmentsFromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchEnvironmentsFromJson.java
new file mode 100644
index 0000000..852e0f3
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchEnvironmentsFromJson.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import org.jclouds.chef.domain.Environment;
+import org.jclouds.http.functions.ParseJson;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
+public class ParseSearchEnvironmentsFromJson extends ParseSearchResultFromJson<Environment> {
+
+   // TODO add generic json parser detector
+
+   @Inject
+   ParseSearchEnvironmentsFromJson(ParseJson<Response<Environment>> json) {
+      super(json);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchNodesFromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchNodesFromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchNodesFromJson.java
new file mode 100644
index 0000000..6d34575
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchNodesFromJson.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.Node;
+import org.jclouds.http.functions.ParseJson;
+
+@Singleton
+public class ParseSearchNodesFromJson extends ParseSearchResultFromJson<Node> {
+
+   // TODO add generic json parser detector
+
+   @Inject
+   ParseSearchNodesFromJson(ParseJson<Response<Node>> json) {
+      super(json);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchResultFromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchResultFromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchResultFromJson.java
new file mode 100644
index 0000000..6c9bd84
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchResultFromJson.java
@@ -0,0 +1,50 @@
+/*
+ * 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.functions;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.SearchResult;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+
+import com.google.common.base.Function;
+
+@Singleton
+public class ParseSearchResultFromJson<T> implements Function<HttpResponse, SearchResult<T>> {
+
+   private final ParseJson<Response<T>> json;
+
+   static class Response<T> {
+      long start;
+      List<T> rows;
+   }
+
+   @Inject
+   ParseSearchResultFromJson(ParseJson<Response<T>> json) {
+      this.json = json;
+   }
+
+   @Override
+   public SearchResult<T> apply(HttpResponse response) {
+      Response<T> returnVal = json.apply(response);
+      return new SearchResult<T>(returnVal.start, returnVal.rows);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchRolesFromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchRolesFromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchRolesFromJson.java
new file mode 100644
index 0000000..42ba797
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseSearchRolesFromJson.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.Role;
+import org.jclouds.http.functions.ParseJson;
+
+@Singleton
+public class ParseSearchRolesFromJson extends ParseSearchResultFromJson<Role> {
+
+   // TODO add generic json parser detector
+
+   @Inject
+   ParseSearchRolesFromJson(ParseJson<Response<Role>> json) {
+      super(json);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/RunListForGroup.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/RunListForGroup.java b/apis/chef/src/main/java/org/jclouds/chef/functions/RunListForGroup.java
new file mode 100644
index 0000000..b14ae71
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/RunListForGroup.java
@@ -0,0 +1,61 @@
+/*
+ * 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.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+
+import com.google.common.base.Function;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Retrieves the run-list for a specific group
+ */
+@Singleton
+public class RunListForGroup implements Function<String, List<String>> {
+   public static final Type RUN_LIST_TYPE = new TypeLiteral<List<String>>() {
+   }.getType();
+   private final BootstrapConfigForGroup bootstrapConfigForGroup;
+
+   private final Json json;
+
+   @Inject
+   public RunListForGroup(BootstrapConfigForGroup bootstrapConfigForGroup, Json json) {
+      this.bootstrapConfigForGroup = checkNotNull(bootstrapConfigForGroup, "bootstrapConfigForGroup");
+      this.json = checkNotNull(json, "json");
+   }
+
+   @Override
+   public List<String> apply(String from) {
+      DatabagItem bootstrapConfig = bootstrapConfigForGroup.apply(from);
+      Map<String, JsonBall> config = json.fromJson(bootstrapConfig.toString(),
+            BootstrapConfigForGroup.BOOTSTRAP_CONFIG_TYPE);
+      JsonBall runlist = config.get("run_list");
+      return json.fromJson(runlist.toString(), RUN_LIST_TYPE);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/UriForResource.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/UriForResource.java b/apis/chef/src/main/java/org/jclouds/chef/functions/UriForResource.java
new file mode 100644
index 0000000..d5d0810
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/UriForResource.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.Resource;
+
+import com.google.common.base.Function;
+
+/**
+ * Extracts the uri field of the given {@link Resource}.
+ */
+@Singleton
+public class UriForResource implements Function<Object, URI> {
+
+   @Override
+   public URI apply(Object input) {
+      checkArgument(checkNotNull(input, "input") instanceof Resource,
+            "This function can only be applied to Resource objects");
+      return ((Resource) input).getUrl();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/handlers/ChefApiErrorRetryHandler.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/handlers/ChefApiErrorRetryHandler.java b/apis/chef/src/main/java/org/jclouds/chef/handlers/ChefApiErrorRetryHandler.java
new file mode 100644
index 0000000..3b4da4a
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/handlers/ChefApiErrorRetryHandler.java
@@ -0,0 +1,67 @@
+/*
+ * 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.handlers;
+
+import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+
+import org.jclouds.Constants;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpRetryHandler;
+import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
+import org.jclouds.logging.Logger;
+
+import com.google.inject.Inject;
+
+/**
+ * Allow for eventual consistency on sandbox requests.
+ */
+public class ChefApiErrorRetryHandler implements HttpRetryHandler {
+
+   @Inject(optional = true)
+   @Named(Constants.PROPERTY_MAX_RETRIES)
+   private int retryCountLimit = 5;
+
+   @Resource
+   protected Logger logger = Logger.NULL;
+
+   private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;
+
+   @Inject
+   ChefApiErrorRetryHandler(BackoffLimitedRetryHandler backoffLimitedRetryHandler) {
+      this.backoffLimitedRetryHandler = backoffLimitedRetryHandler;
+   }
+
+   public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) {
+      if (command.getFailureCount() > retryCountLimit)
+         return false;
+      if (response.getStatusCode() == 400 && command.getCurrentRequest().getMethod().equals("PUT")
+            && command.getCurrentRequest().getEndpoint().getPath().indexOf("sandboxes") != -1) {
+         if (response.getPayload() != null) {
+            String error = new String(closeClientButKeepContentStream(response));
+            if (error != null && error.indexOf("was not uploaded") != -1) {
+               return backoffLimitedRetryHandler.shouldRetryRequest(command, response);
+            }
+         }
+      }
+      return false;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/handlers/ChefErrorHandler.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/handlers/ChefErrorHandler.java b/apis/chef/src/main/java/org/jclouds/chef/handlers/ChefErrorHandler.java
new file mode 100644
index 0000000..3376219
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/handlers/ChefErrorHandler.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.handlers;
+
+import static com.google.common.base.Throwables.propagate;
+
+import java.io.IOException;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.functions.ParseErrorFromJsonOrReturnBody;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
+import org.jclouds.logging.Logger;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.ResourceNotFoundException;
+
+import com.google.common.io.Closeables;
+
+/**
+ * This will parse and set an appropriate exception on the command object.
+ */
+@Singleton
+public class ChefErrorHandler implements HttpErrorHandler {
+   @Resource
+   protected Logger logger = Logger.NULL;
+   private final ParseErrorFromJsonOrReturnBody errorParser;
+
+   @Inject
+   ChefErrorHandler(ParseErrorFromJsonOrReturnBody errorParser) {
+      this.errorParser = errorParser;
+   }
+
+   public void handleError(HttpCommand command, HttpResponse response) {
+      String message = errorParser.apply(response);
+      Exception exception = new HttpResponseException(command, response, message);
+      try {
+         message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(),
+               response.getStatusLine());
+         switch (response.getStatusCode()) {
+            case 401:
+            case 403:
+               exception = new AuthorizationException(message, exception);
+               break;
+            case 404:
+               if (!command.getCurrentRequest().getMethod().equals("DELETE")) {
+                  exception = new ResourceNotFoundException(message, exception);
+               }
+               break;
+         }
+      } finally {
+         if (response.getPayload() != null) {
+            try {
+               Closeables.close(response.getPayload().getInput(), true);
+            } catch (IOException e) {
+               throw propagate(e);
+            }
+         }
+         command.setException(exception);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java b/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java
new file mode 100644
index 0000000..d390555
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java
@@ -0,0 +1,299 @@
+/*
+ * 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.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.chef.config.ChefProperties.CHEF_BOOTSTRAP_DATABAG;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.PrivateKey;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.ChefContext;
+import org.jclouds.chef.ChefService;
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.BootstrapConfig;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.chef.domain.Environment;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.functions.BootstrapConfigForGroup;
+import org.jclouds.chef.functions.GroupToBootScript;
+import org.jclouds.chef.functions.RunListForGroup;
+import org.jclouds.chef.strategy.CleanupStaleNodesAndClients;
+import org.jclouds.chef.strategy.CreateNodeAndPopulateAutomaticAttributes;
+import org.jclouds.chef.strategy.DeleteAllClientsInList;
+import org.jclouds.chef.strategy.DeleteAllNodesInList;
+import org.jclouds.chef.strategy.ListClients;
+import org.jclouds.chef.strategy.ListCookbookVersions;
+import org.jclouds.chef.strategy.ListCookbookVersionsInEnvironment;
+import org.jclouds.chef.strategy.ListEnvironments;
+import org.jclouds.chef.strategy.ListNodes;
+import org.jclouds.chef.strategy.ListNodesInEnvironment;
+import org.jclouds.chef.strategy.UpdateAutomaticAttributesOnNode;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.io.Payloads;
+import org.jclouds.io.payloads.RSADecryptingPayload;
+import org.jclouds.io.payloads.RSAEncryptingPayload;
+import org.jclouds.json.Json;
+import org.jclouds.logging.Logger;
+import org.jclouds.scriptbuilder.domain.Statement;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.InputSupplier;
+
+@Singleton
+public class BaseChefService implements ChefService {
+
+   private final ChefContext chefContext;
+   private final ChefApi api;
+   private final CleanupStaleNodesAndClients cleanupStaleNodesAndClients;
+   private final CreateNodeAndPopulateAutomaticAttributes createNodeAndPopulateAutomaticAttributes;
+   private final DeleteAllNodesInList deleteAllNodesInList;
+   private final ListNodes listNodes;
+   private final DeleteAllClientsInList deleteAllClientsInList;
+   private final ListClients listClients;
+   private final UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode;
+   private final Supplier<PrivateKey> privateKey;
+   private final GroupToBootScript groupToBootScript;
+   private final String databag;
+   private final BootstrapConfigForGroup bootstrapConfigForGroup;
+   private final RunListForGroup runListForGroup;
+   private final ListCookbookVersions listCookbookVersions;
+   private final ListCookbookVersionsInEnvironment listCookbookVersionsInEnvironment;
+   private final ListEnvironments listEnvironments;
+   private final ListNodesInEnvironment listNodesInEnvironment;
+   private final Json json;
+   private final Crypto crypto;
+
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   protected BaseChefService(ChefContext chefContext, ChefApi api,
+         CleanupStaleNodesAndClients cleanupStaleNodesAndClients,
+         CreateNodeAndPopulateAutomaticAttributes createNodeAndPopulateAutomaticAttributes,
+         DeleteAllNodesInList deleteAllNodesInList, ListNodes listNodes, DeleteAllClientsInList deleteAllClientsInList,
+         ListClients listClients, ListCookbookVersions listCookbookVersions,
+         UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode, Supplier<PrivateKey> privateKey,
+         @Named(CHEF_BOOTSTRAP_DATABAG) String databag, GroupToBootScript groupToBootScript,
+         BootstrapConfigForGroup bootstrapConfigForGroup, RunListForGroup runListForGroup,
+         ListEnvironments listEnvironments, ListNodesInEnvironment listNodesInEnvironment,
+         ListCookbookVersionsInEnvironment listCookbookVersionsInEnvironment, Json json, Crypto crypto) {
+      this.chefContext = checkNotNull(chefContext, "chefContext");
+      this.api = checkNotNull(api, "api");
+      this.cleanupStaleNodesAndClients = checkNotNull(cleanupStaleNodesAndClients, "cleanupStaleNodesAndClients");
+      this.createNodeAndPopulateAutomaticAttributes = checkNotNull(createNodeAndPopulateAutomaticAttributes,
+            "createNodeAndPopulateAutomaticAttributes");
+      this.deleteAllNodesInList = checkNotNull(deleteAllNodesInList, "deleteAllNodesInList");
+      this.listNodes = checkNotNull(listNodes, "listNodes");
+      this.deleteAllClientsInList = checkNotNull(deleteAllClientsInList, "deleteAllClientsInList");
+      this.listClients = checkNotNull(listClients, "listClients");
+      this.listCookbookVersions = checkNotNull(listCookbookVersions, "listCookbookVersions");
+      this.updateAutomaticAttributesOnNode = checkNotNull(updateAutomaticAttributesOnNode,
+            "updateAutomaticAttributesOnNode");
+      this.privateKey = checkNotNull(privateKey, "privateKey");
+      this.groupToBootScript = checkNotNull(groupToBootScript, "groupToBootScript");
+      this.databag = checkNotNull(databag, "databag");
+      this.bootstrapConfigForGroup = checkNotNull(bootstrapConfigForGroup, "bootstrapConfigForGroup");
+      this.runListForGroup = checkNotNull(runListForGroup, "runListForGroup");
+      this.listEnvironments = checkNotNull(listEnvironments, "listEnvironments");
+      this.listNodesInEnvironment = checkNotNull(listNodesInEnvironment, "listNodesInEnvironment");
+      this.listCookbookVersionsInEnvironment = checkNotNull(listCookbookVersionsInEnvironment,"listCookbookVersionsInEnvironment");
+      this.json = checkNotNull(json, "json");
+      this.crypto = checkNotNull(crypto, "crypto");
+   }
+
+   @Override
+   public ChefContext getContext() {
+      return chefContext;
+   }
+
+   @Override
+   public byte[] encrypt(InputSupplier<? extends InputStream> supplier) throws IOException {
+      return ByteStreams.toByteArray(new RSAEncryptingPayload(crypto, Payloads.newPayload(supplier.getInput()), privateKey
+                  .get()));
+   }
+
+   @Override
+   public byte[] decrypt(InputSupplier<? extends InputStream> supplier) throws IOException {
+      return ByteStreams.toByteArray(new RSADecryptingPayload(crypto, Payloads.newPayload(supplier.getInput()), privateKey
+                  .get()));
+   }
+
+   @VisibleForTesting
+   String buildBootstrapConfiguration(BootstrapConfig bootstrapConfig) {
+      checkNotNull(bootstrapConfig, "bootstrapConfig must not be null");
+
+      Map<String, Object> configMap = Maps.newHashMap();
+      configMap.put("run_list", bootstrapConfig.getRunList());
+
+      if (bootstrapConfig.getEnvironment().isPresent()) {
+         configMap.put("environment", bootstrapConfig.getEnvironment().get());
+      }
+
+      if (bootstrapConfig.getAttribtues().isPresent()) {
+         Map<String, Object> attributes = json.fromJson(bootstrapConfig.getAttribtues().get().toString(),
+               BootstrapConfigForGroup.BOOTSTRAP_CONFIG_TYPE);
+         configMap.putAll(attributes);
+      }
+
+      return json.toJson(configMap);
+   }
+
+   @Override
+   public Statement createBootstrapScriptForGroup(String group) {
+      return groupToBootScript.apply(group);
+   }
+
+   @Override
+   public void updateBootstrapConfigForGroup(String group, BootstrapConfig bootstrapConfig) {
+      try {
+         api.createDatabag(databag);
+      } catch (IllegalStateException e) {
+
+      }
+
+      String jsonConfig = buildBootstrapConfiguration(bootstrapConfig);
+      DatabagItem runlist = new DatabagItem(group, jsonConfig);
+
+      if (api.getDatabagItem(databag, group) == null) {
+         api.createDatabagItem(databag, runlist);
+      } else {
+         api.updateDatabagItem(databag, runlist);
+      }
+   }
+
+   @Override
+   public List<String> getRunListForGroup(String group) {
+      return runListForGroup.apply(group);
+   }
+
+   @Override
+   public JsonBall getBootstrapConfigForGroup(String group) {
+      return bootstrapConfigForGroup.apply(group);
+   }
+
+   @Override
+   public void cleanupStaleNodesAndClients(String prefix, int secondsStale) {
+      cleanupStaleNodesAndClients.execute(prefix, secondsStale);
+   }
+
+   @Override
+   public Node createNodeAndPopulateAutomaticAttributes(String nodeName, Iterable<String> runList) {
+      return createNodeAndPopulateAutomaticAttributes.execute(nodeName, runList);
+   }
+
+   @Override
+   public void updateAutomaticAttributesOnNode(String nodeName) {
+      updateAutomaticAttributesOnNode.execute(nodeName);
+   }
+
+   @Override
+   public void deleteAllNodesInList(Iterable<String> names) {
+      deleteAllNodesInList.execute(names);
+   }
+
+   @Override
+   public void deleteAllClientsInList(Iterable<String> names) {
+      deleteAllClientsInList.execute(names);
+   }
+
+   @Override
+   public Iterable<? extends Node> listNodes() {
+      return listNodes.execute();
+   }
+
+   @Override
+   public Iterable<? extends Node> listNodes(ExecutorService executorService) {
+      return listNodes.execute(executorService);
+   }
+
+   @Override
+   public Iterable<? extends Client> listClients() {
+      return listClients.execute();
+   }
+
+   @Override
+   public Iterable<? extends Client> listClients(ExecutorService executorService) {
+      return listClients.execute(executorService);
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> listCookbookVersions() {
+      return listCookbookVersions.execute();
+   }
+
+   @Override public Iterable<? extends CookbookVersion> listCookbookVersions(
+         ExecutorService executorService) {
+      return listCookbookVersions.execute(executorService);
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName) {
+      return listCookbookVersionsInEnvironment.execute(environmentName);
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName,
+         ExecutorService executorService) {
+      return listCookbookVersionsInEnvironment.execute(executorService, environmentName);
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName,
+         String numVersions) {
+      return listCookbookVersionsInEnvironment.execute(environmentName, numVersions);
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName,
+         String numVersions, ExecutorService executorService) {
+      return listCookbookVersionsInEnvironment.execute(executorService, environmentName, numVersions);
+   }
+
+   @Override
+   public Iterable<? extends Environment> listEnvironments() {
+      return listEnvironments.execute();
+   }
+
+   @Override
+   public Iterable<? extends Node> listNodesInEnvironment(String environmentName) {
+      return listNodesInEnvironment.execute(environmentName);
+   }
+
+   @Override
+   public Iterable<? extends Node> listNodesInEnvironment(String environmentName, ExecutorService executorService) {
+      return listNodesInEnvironment.execute(executorService, environmentName);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/internal/ChefContextImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/internal/ChefContextImpl.java b/apis/chef/src/main/java/org/jclouds/chef/internal/ChefContextImpl.java
new file mode 100644
index 0000000..8aef880
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/internal/ChefContextImpl.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.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.Context;
+import org.jclouds.chef.ChefContext;
+import org.jclouds.chef.ChefService;
+import org.jclouds.internal.BaseView;
+import org.jclouds.location.Provider;
+
+import com.google.common.reflect.TypeToken;
+
+@Singleton
+public class ChefContextImpl extends BaseView implements ChefContext {
+   private final ChefService chefService;
+
+   @Inject
+   protected ChefContextImpl(@Provider Context backend, @Provider TypeToken<? extends Context> backendType,
+         ChefService chefService) {
+      super(backend, backendType);
+      this.chefService = checkNotNull(chefService, "chefService");
+   }
+
+   @Override
+   public ChefService getChefService() {
+      return chefService;
+   }
+
+   @Override
+   public void close() throws IOException {
+      delegate().close();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..776d7bb
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/options/CreateClientOptions.java
@@ -0,0 +1,64 @@
+/*
+ * 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.options;
+
+/**
+ * Options for the create client method.
+ */
+public class CreateClientOptions implements Cloneable {
+   /** Administrator flag. This flag will be ignored in Opscode Hosted Chef. */
+   private boolean admin;
+
+   public CreateClientOptions() {
+   }
+
+   CreateClientOptions(final boolean admin) {
+      super();
+      this.admin = admin;
+   }
+
+   public boolean isAdmin() {
+      return admin;
+   }
+
+   public CreateClientOptions admin() {
+      this.admin = true;
+      return this;
+   }
+
+   @Override
+   protected Object clone() throws CloneNotSupportedException {
+      return new CreateClientOptions(admin);
+   }
+
+   @Override
+   public String toString() {
+      return "[admin=" + admin + "]";
+   }
+
+   public static class Builder {
+      /**
+       * @see CreateClientOptions#admin()
+       */
+      public static CreateClientOptions admin() {
+         CreateClientOptions options = new CreateClientOptions();
+         return options.admin();
+      }
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/options/SearchOptions.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/options/SearchOptions.java b/apis/chef/src/main/java/org/jclouds/chef/options/SearchOptions.java
new file mode 100644
index 0000000..cbc1d54
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/options/SearchOptions.java
@@ -0,0 +1,95 @@
+/*
+ * 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.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+/**
+ * Options for the search api.
+ */
+public class SearchOptions extends BaseHttpRequestOptions {
+
+   /**
+    * A valid search string.
+    */
+   public SearchOptions query(String query) {
+      this.queryParameters.put("q", checkNotNull(query, "query"));
+      return this;
+   }
+
+   /**
+    * A sort string, such as 'name DESC'.
+    */
+   public SearchOptions sort(String sort) {
+      this.queryParameters.put("sort", checkNotNull(sort, "sort"));
+      return this;
+   }
+
+   /**
+    * The number of rows to return.
+    */
+   public SearchOptions rows(int rows) {
+      this.queryParameters.put("rows", String.valueOf(rows));
+      return this;
+   }
+
+   /**
+    * The result number to start from.
+    */
+   public SearchOptions start(int start) {
+      this.queryParameters.put("start", String.valueOf(start));
+      return this;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see SearchOptions#query(String)
+       */
+      public static SearchOptions query(String query) {
+         SearchOptions options = new SearchOptions();
+         return options.query(query);
+      }
+
+      /**
+       * @see SearchOptions#sort(String)
+       */
+      public static SearchOptions start(String start) {
+         SearchOptions options = new SearchOptions();
+         return options.sort(start);
+      }
+
+      /**
+       * @see SearchOptions#rows(int)
+       */
+      public static SearchOptions rows(int rows) {
+         SearchOptions options = new SearchOptions();
+         return options.rows(rows);
+      }
+
+      /**
+       * @see SearchOptions#start(int)
+       */
+      public static SearchOptions start(int start) {
+         SearchOptions options = new SearchOptions();
+         return options.start(start);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/predicates/CookbookVersionPredicates.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/predicates/CookbookVersionPredicates.java b/apis/chef/src/main/java/org/jclouds/chef/predicates/CookbookVersionPredicates.java
new file mode 100644
index 0000000..7edbcae
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/predicates/CookbookVersionPredicates.java
@@ -0,0 +1,93 @@
+/*
+ * 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.predicates;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.any;
+import static com.google.common.collect.Iterables.get;
+
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.domain.Resource;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Container for cookbook filters (predicates).
+ * 
+ * This class has static methods that create customized predicates to use with
+ * {@link org.jclouds.chef.ChefService}.
+ */
+public class CookbookVersionPredicates {
+   /**
+    * @see #containsRecipes
+    */
+   public static Predicate<CookbookVersion> containsRecipe(String recipe) {
+      return containsRecipes(checkNotNull(recipe, "recipe must be defined"));
+   }
+
+   /**
+    * Note that the default recipe of a cookbook is its name. Otherwise, you
+    * prefix the recipe with the name of the cookbook. ex. {@code apache2} will
+    * be the default recipe where {@code apache2::mod_proxy} is a specific one
+    * in the cookbook.
+    * 
+    * @param recipes
+    *           names of the recipes.
+    * @return true if the cookbook version contains a recipe in the list.
+    */
+   public static Predicate<CookbookVersion> containsRecipes(String... recipes) {
+      checkNotNull(recipes, "recipes must be defined");
+      final Multimap<String, String> search = LinkedListMultimap.create();
+      for (String recipe : recipes) {
+         if (recipe.indexOf("::") != -1) {
+            Iterable<String> nameRecipe = Splitter.on("::").split(recipe);
+            search.put(get(nameRecipe, 0), get(nameRecipe, 1) + ".rb");
+         } else {
+            search.put(recipe, "default.rb");
+         }
+      }
+      return new Predicate<CookbookVersion>() {
+         @Override
+         public boolean apply(final CookbookVersion cookbookVersion) {
+            return search.containsKey(cookbookVersion.getCookbookName())
+                  && any(search.get(cookbookVersion.getCookbookName()), new Predicate<String>() {
+
+                     @Override
+                     public boolean apply(final String recipeName) {
+                        return any(cookbookVersion.getRecipes(), new Predicate<Resource>() {
+
+                           @Override
+                           public boolean apply(Resource resource) {
+                              return resource.getName().equals(recipeName);
+                           }
+
+                        });
+                     }
+
+                  });
+         }
+
+         @Override
+         public String toString() {
+            return "containsRecipes(" + search + ")";
+         }
+      };
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/CleanupStaleNodesAndClients.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/CleanupStaleNodesAndClients.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/CleanupStaleNodesAndClients.java
new file mode 100644
index 0000000..8519450
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/CleanupStaleNodesAndClients.java
@@ -0,0 +1,32 @@
+/*
+ * 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.strategy;
+
+import org.jclouds.chef.strategy.internal.CleanupStaleNodesAndClientsImpl;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * 
+ * Cleans up nodes and clients who have been hanging around too long.
+ */
+@ImplementedBy(CleanupStaleNodesAndClientsImpl.class)
+public interface CleanupStaleNodesAndClients {
+
+   void execute(String prefix, int secondsStale);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/CreateNodeAndPopulateAutomaticAttributes.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/CreateNodeAndPopulateAutomaticAttributes.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/CreateNodeAndPopulateAutomaticAttributes.java
new file mode 100644
index 0000000..249be48
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/CreateNodeAndPopulateAutomaticAttributes.java
@@ -0,0 +1,33 @@
+/*
+ * 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.strategy;
+
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.internal.CreateNodeAndPopulateAutomaticAttributesImpl;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * 
+ * Creates a new node with automatic attributes.
+ */
+@ImplementedBy(CreateNodeAndPopulateAutomaticAttributesImpl.class)
+public interface CreateNodeAndPopulateAutomaticAttributes {
+   Node execute(Node node);
+
+   Node execute(String nodeName, Iterable<String> runList);
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/DeleteAllClientsInList.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/DeleteAllClientsInList.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/DeleteAllClientsInList.java
new file mode 100644
index 0000000..7dd7b89
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/DeleteAllClientsInList.java
@@ -0,0 +1,34 @@
+/*
+ * 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.strategy;
+
+import org.jclouds.chef.strategy.internal.DeleteAllClientsInListImpl;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.ImplementedBy;
+
+/**
+ * Deletes all clients in a given list.
+ */
+@ImplementedBy(DeleteAllClientsInListImpl.class)
+public interface DeleteAllClientsInList {
+
+   void execute(Iterable<String> names);
+
+   void execute(ListeningExecutorService executor, Iterable<String> names);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/DeleteAllNodesInList.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/DeleteAllNodesInList.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/DeleteAllNodesInList.java
new file mode 100644
index 0000000..8867e49
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/DeleteAllNodesInList.java
@@ -0,0 +1,31 @@
+/*
+ * 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.strategy;
+
+import org.jclouds.chef.strategy.internal.DeleteAllNodesInListImpl;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.ImplementedBy;
+
+@ImplementedBy(DeleteAllNodesInListImpl.class)
+public interface DeleteAllNodesInList {
+
+   void execute(Iterable<String> names);
+
+   void execute(ListeningExecutorService executor, Iterable<String> names);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/ListClients.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/ListClients.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListClients.java
new file mode 100644
index 0000000..aa40c2a
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListClients.java
@@ -0,0 +1,32 @@
+/*
+ * 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.strategy;
+
+import com.google.inject.ImplementedBy;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.strategy.internal.ListClientsImpl;
+
+import java.util.concurrent.ExecutorService;
+
+@ImplementedBy(ListClientsImpl.class)
+public interface ListClients {
+
+   Iterable<? extends Client> execute();
+
+   Iterable<? extends Client> execute(ExecutorService executor);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/ListCookbookVersions.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/ListCookbookVersions.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListCookbookVersions.java
new file mode 100644
index 0000000..45663a3
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListCookbookVersions.java
@@ -0,0 +1,32 @@
+/*
+ * 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.strategy;
+
+import com.google.inject.ImplementedBy;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.strategy.internal.ListCookbookVersionsImpl;
+
+import java.util.concurrent.ExecutorService;
+
+@ImplementedBy(ListCookbookVersionsImpl.class)
+public interface ListCookbookVersions {
+
+   Iterable<? extends CookbookVersion> execute();
+
+   Iterable<? extends CookbookVersion> execute(ExecutorService executor);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/ListCookbookVersionsInEnvironment.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/ListCookbookVersionsInEnvironment.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListCookbookVersionsInEnvironment.java
new file mode 100644
index 0000000..188d29f
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListCookbookVersionsInEnvironment.java
@@ -0,0 +1,37 @@
+/*
+ * 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.strategy;
+
+import com.google.inject.ImplementedBy;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.strategy.internal.ListCookbookVersionsInEnvironmentImpl;
+
+import java.util.concurrent.ExecutorService;
+
+@ImplementedBy(ListCookbookVersionsInEnvironmentImpl.class)
+public interface ListCookbookVersionsInEnvironment {
+
+   Iterable<? extends CookbookVersion> execute(String environmentName);
+
+   Iterable<? extends CookbookVersion> execute(String environmentName, String numVersions);
+
+   Iterable<? extends CookbookVersion> execute(ExecutorService executor, String environmentName);
+
+   Iterable<? extends CookbookVersion> execute(ExecutorService executor, String environmentName, String numVersions);
+
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/ListEnvironments.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/ListEnvironments.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListEnvironments.java
new file mode 100644
index 0000000..553e8d4
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListEnvironments.java
@@ -0,0 +1,31 @@
+/*
+ * 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.strategy;
+
+import com.google.inject.ImplementedBy;
+import org.jclouds.chef.domain.Environment;
+import org.jclouds.chef.strategy.internal.ListEnvironmentsImpl;
+
+import java.util.concurrent.ExecutorService;
+
+@ImplementedBy(ListEnvironmentsImpl.class)
+public interface ListEnvironments {
+
+   Iterable<? extends Environment> execute();
+
+   Iterable<? extends Environment> execute(ExecutorService executor);
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/ListNodes.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/ListNodes.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListNodes.java
new file mode 100644
index 0000000..660eed0
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListNodes.java
@@ -0,0 +1,32 @@
+/*
+ * 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.strategy;
+
+import com.google.inject.ImplementedBy;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.internal.ListNodesImpl;
+
+import java.util.concurrent.ExecutorService;
+
+@ImplementedBy(ListNodesImpl.class)
+public interface ListNodes {
+
+   Iterable<? extends Node> execute();
+
+   Iterable<? extends Node> execute(ExecutorService executor);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/ListNodesInEnvironment.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/ListNodesInEnvironment.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListNodesInEnvironment.java
new file mode 100644
index 0000000..efeffe6
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/ListNodesInEnvironment.java
@@ -0,0 +1,32 @@
+/*
+ * 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.strategy;
+
+import com.google.inject.ImplementedBy;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.internal.ListNodesInEnvironmentImpl;
+
+import java.util.concurrent.ExecutorService;
+
+@ImplementedBy(ListNodesInEnvironmentImpl.class)
+public interface ListNodesInEnvironment {
+
+   Iterable<? extends Node> execute(String environmentName);
+
+   Iterable<? extends Node> execute(ExecutorService executor, String environmentName);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/UpdateAutomaticAttributesOnNode.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/UpdateAutomaticAttributesOnNode.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/UpdateAutomaticAttributesOnNode.java
new file mode 100644
index 0000000..0dcb81e
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/UpdateAutomaticAttributesOnNode.java
@@ -0,0 +1,31 @@
+/*
+ * 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.strategy;
+
+import org.jclouds.chef.strategy.internal.UpdateAutomaticAttributesOnNodeImpl;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * 
+ * Updates node with new automatic attributes.
+ */
+@ImplementedBy(UpdateAutomaticAttributesOnNodeImpl.class)
+public interface UpdateAutomaticAttributesOnNode {
+
+   void execute(String nodeName);
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/BaseListCookbookVersionsImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/BaseListCookbookVersionsImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/BaseListCookbookVersionsImpl.java
new file mode 100644
index 0000000..94cf79a
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/BaseListCookbookVersionsImpl.java
@@ -0,0 +1,97 @@
+/*
+ * 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.strategy.internal;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.logging.Logger;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+
+public abstract class BaseListCookbookVersionsImpl {
+
+   protected final ChefApi api;
+
+   protected Logger logger = Logger.NULL;
+
+   BaseListCookbookVersionsImpl(ChefApi api) {
+      this.api = checkNotNull(api, "api");
+   }
+
+   protected Iterable<? extends CookbookVersion> execute(Iterable<String> toGet) {
+      return concat(transform(toGet, new Function<String, Iterable<? extends CookbookVersion>>() {
+
+         @Override
+         public Iterable<? extends CookbookVersion> apply(final String cookbook) {
+            // TODO getting each version could also go parallel
+            Set<String> cookbookVersions = api.listVersionsOfCookbook(cookbook);
+            Iterable<? extends CookbookVersion> cookbooksVersions = transform(cookbookVersions,
+                  new Function<String, CookbookVersion>() {
+                     @Override
+                     public CookbookVersion apply(final String version) {
+                        return api.getCookbook(cookbook, version);
+                     }
+                  }
+            );
+
+            logger.trace(String.format("getting versions of cookbook: %s", cookbook));
+            return cookbooksVersions;
+         }
+      }));
+
+   }
+
+   protected Iterable<? extends CookbookVersion> executeConcurrently(final ListeningExecutorService executor,
+         Iterable<String> cookbookNames) {
+      return concat(transform(cookbookNames, new Function<String, Iterable<? extends CookbookVersion>>() {
+
+         @Override
+         public Iterable<? extends CookbookVersion> apply(final String cookbook) {
+            // TODO getting each version could also go parallel
+            Set<String> cookbookVersions = api.listVersionsOfCookbook(cookbook);
+            ListenableFuture<List<CookbookVersion>> futures = allAsList(transform(cookbookVersions,
+                  new Function<String, ListenableFuture<CookbookVersion>>() {
+                     @Override
+                     public ListenableFuture<CookbookVersion> apply(final String version) {
+                        return executor.submit(new Callable<CookbookVersion>() {
+                           @Override
+                           public CookbookVersion call() throws Exception {
+                              return api.getCookbook(cookbook, version);
+                           }
+                        });
+                     }
+                  }
+            ));
+
+            logger.trace(String.format("getting versions of cookbook: %s", cookbook));
+            return getUnchecked(futures);
+         }
+      }));
+   }
+
+}


[08/10] Move jclouds-chef to the main jclouds repo

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/Environment.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Environment.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Environment.java
new file mode 100644
index 0000000..48e6efa
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Environment.java
@@ -0,0 +1,178 @@
+/*
+ * 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 static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
+
+import java.beans.ConstructorProperties;
+import java.util.Map;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * An environment.
+ */
+public class Environment {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private String name;
+      private ImmutableMap.Builder<String, JsonBall> attributes = ImmutableMap.builder();
+      private ImmutableMap.Builder<String, JsonBall> overrideAttributes = ImmutableMap.builder();
+      private String description = "";
+      private ImmutableMap.Builder<String, String> cookbookVersions = ImmutableMap.builder();
+
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      public Builder attribute(String key, JsonBall value) {
+         this.attributes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder attributes(Map<String, JsonBall> attributes) {
+         this.attributes.putAll(checkNotNull(attributes, "attributes"));
+         return this;
+      }
+
+      public Builder overrideAttribute(String key, JsonBall value) {
+         this.overrideAttributes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder overrideAttributes(Map<String, JsonBall> overrideAttributes) {
+         this.overrideAttributes.putAll(checkNotNull(overrideAttributes, "overrideAttributes"));
+         return this;
+      }
+
+      public Builder cookbookVersion(String key, String version) {
+         this.cookbookVersions.put(checkNotNull(key, "key"), checkNotNull(version, "version"));
+         return this;
+      }
+
+      public Builder cookbookVersions(Map<String, String> cookbookVersions) {
+         this.cookbookVersions.putAll(checkNotNull(cookbookVersions, "cookbookVersions"));
+         return this;
+      }
+
+      public Builder description(String description) {
+         this.description = checkNotNull(description, "description");
+         return this;
+      }
+
+      public Environment build() {
+         return new Environment(name, attributes.build(), overrideAttributes.build(), description,
+               cookbookVersions.build());
+      }
+   }
+
+   private final String name;
+   @SerializedName("default_attributes")
+   private final Map<String, JsonBall> attributes;
+   @SerializedName("override_attributes")
+   private final Map<String, JsonBall> overrideAttributes;
+   private final String description;
+   @SerializedName("cookbook_versions")
+   private final Map<String, String> cookbookVersions;
+
+   // internal
+   @SerializedName("json_class")
+   private final String _jsonClass = "Chef::Environment";
+   @SerializedName("chef_type")
+   private final String _chefType = "environment";
+
+   @ConstructorProperties({ "name", "default_attributes", "override_attributes", "description", "cookbook_versions" })
+   protected Environment(String name, @Nullable Map<String, JsonBall> attributes,
+         @Nullable Map<String, JsonBall> overrideAttributes, String description,
+         @Nullable Map<String, String> cookbookVersions) {
+      this.name = name;
+      this.attributes = copyOfOrEmpty(attributes);
+      this.overrideAttributes = copyOfOrEmpty(overrideAttributes);
+      this.description = description;
+      this.cookbookVersions = copyOfOrEmpty(cookbookVersions);
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public Map<String, JsonBall> getAttributes() {
+      return attributes;
+   }
+
+   public Map<String, JsonBall> getOverrideAttributes() {
+      return overrideAttributes;
+   }
+
+   public String getDescription() {
+      return description;
+   }
+
+   public Map<String, String> getCookbookVersions() {
+      return cookbookVersions;
+   }
+
+   @Override
+   public boolean equals(Object o) {
+      if (this == o)
+         return true;
+      if (o == null || getClass() != o.getClass())
+         return false;
+
+      Environment that = (Environment) o;
+
+      if (attributes != null ? !attributes.equals(that.attributes) : that.attributes != null)
+         return false;
+      if (cookbookVersions != null ? !cookbookVersions.equals(that.cookbookVersions) : that.cookbookVersions != null)
+         return false;
+      if (description != null ? !description.equals(that.description) : that.description != null)
+         return false;
+      if (!name.equals(that.name))
+         return false;
+      if (overrideAttributes != null ? !overrideAttributes.equals(that.overrideAttributes)
+            : that.overrideAttributes != null)
+         return false;
+
+      return true;
+   }
+
+   @Override
+   public int hashCode() {
+      int result = name.hashCode();
+      result = 31 * result + (attributes != null ? attributes.hashCode() : 0);
+      result = 31 * result + (overrideAttributes != null ? overrideAttributes.hashCode() : 0);
+      result = 31 * result + (description != null ? description.hashCode() : 0);
+      result = 31 * result + (cookbookVersions != null ? cookbookVersions.hashCode() : 0);
+      return result;
+   }
+
+   @Override
+   public String toString() {
+      return "Environment [" + "name='" + name + '\'' + ", attributes=" + attributes + ", overrideAttributes="
+            + overrideAttributes + ", description='" + description + '\'' + ", cookbookVersions=" + cookbookVersions
+            + ']';
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/Metadata.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Metadata.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Metadata.java
new file mode 100644
index 0000000..33f8dfb
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Metadata.java
@@ -0,0 +1,447 @@
+/*
+ * 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 static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
+
+import java.beans.ConstructorProperties;
+import java.util.Map;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * A metadata object.
+ */
+public class Metadata {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private String license;
+      private String maintainer;
+      private ImmutableMap.Builder<String, String> suggestions = ImmutableMap.builder();
+      private ImmutableMap.Builder<String, String> dependencies = ImmutableMap.builder();
+      private String maintainerEmail;
+      private ImmutableMap.Builder<String, String> conflicting = ImmutableMap.builder();
+      private String description;
+      private ImmutableMap.Builder<String, String> providing = ImmutableMap.builder();
+      private ImmutableMap.Builder<String, String> platforms = ImmutableMap.builder();
+      private String version;
+      private ImmutableMap.Builder<String, String> recipes = ImmutableMap.builder();
+      private ImmutableMap.Builder<String, String> replacing = ImmutableMap.builder();
+      private String name;
+      private ImmutableMap.Builder<String, String> groupings = ImmutableMap.builder();
+      private String longDescription;
+      private ImmutableMap.Builder<String, Attribute> attributes = ImmutableMap.builder();
+      private ImmutableMap.Builder<String, String> recommendations = ImmutableMap.builder();
+
+      public Builder license(String license) {
+         this.license = checkNotNull(license, "license");
+         return this;
+      }
+
+      public Builder maintainer(String maintainer) {
+         this.maintainer = checkNotNull(maintainer, "maintainer");
+         return this;
+      }
+
+      public Builder suggestion(String key, String value) {
+         this.suggestions.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder suggestions(Map<String, String> suggestions) {
+         this.suggestions.putAll(checkNotNull(suggestions, "suggestions"));
+         return this;
+      }
+
+      public Builder dependency(String key, String value) {
+         this.dependencies.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder dependencies(Map<String, String> dependencies) {
+         this.dependencies.putAll(checkNotNull(dependencies, "dependencies"));
+         return this;
+      }
+
+      public Builder maintainerEmail(String maintainerEmail) {
+         this.maintainerEmail = checkNotNull(maintainerEmail, "maintainerEmail");
+         return this;
+      }
+
+      public Builder conflicting(String key, String value) {
+         this.conflicting.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder conflicting(Map<String, String> conflicting) {
+         this.conflicting.putAll(checkNotNull(conflicting, "conflicting"));
+         return this;
+      }
+
+      public Builder description(String description) {
+         this.description = checkNotNull(description, "description");
+         return this;
+      }
+
+      public Builder providing(String key, String value) {
+         this.providing.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder providing(Map<String, String> providing) {
+         this.providing.putAll(checkNotNull(providing, "providing"));
+         return this;
+      }
+
+      public Builder platform(String key, String value) {
+         this.platforms.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder platforms(Map<String, String> platforms) {
+         this.platforms.putAll(checkNotNull(platforms, "platforms"));
+         return this;
+      }
+
+      public Builder version(String version) {
+         this.version = checkNotNull(version, "version");
+         return this;
+      }
+
+      public Builder recipe(String key, String value) {
+         this.recipes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder recipes(Map<String, String> recipes) {
+         this.recipes.putAll(checkNotNull(recipes, "recipes"));
+         return this;
+      }
+
+      public Builder replacing(String key, String value) {
+         this.replacing.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder replacing(Map<String, String> replacing) {
+         this.replacing.putAll(checkNotNull(replacing, "replacing"));
+         return this;
+      }
+
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      public Builder grouping(String key, String value) {
+         this.groupings.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder grouping(Map<String, String> groupings) {
+         this.groupings.putAll(checkNotNull(groupings, "groupings"));
+         return this;
+      }
+
+      public Builder longDescription(String longDescription) {
+         this.longDescription = checkNotNull(longDescription, "longDescription");
+         return this;
+      }
+
+      public Builder attribute(String key, Attribute value) {
+         this.attributes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder attributes(Map<String, Attribute> attributes) {
+         this.attributes.putAll(checkNotNull(attributes, "attributes"));
+         return this;
+      }
+
+      public Builder recommendation(String key, String value) {
+         this.recommendations.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder recommendations(Map<String, String> recommendations) {
+         this.recommendations.putAll(checkNotNull(recommendations, "recommendations"));
+         return this;
+      }
+
+      public Metadata build() {
+         return new Metadata(license, maintainer, suggestions.build(), dependencies.build(), maintainerEmail,
+               conflicting.build(), description, providing.build(), platforms.build(), version, recipes.build(),
+               replacing.build(), name, groupings.build(), longDescription, attributes.build(), recommendations.build());
+      }
+
+   }
+
+   private final String license;
+   private final String maintainer;
+   private final Map<String, String> suggestions;
+   private final Map<String, String> dependencies;
+   @SerializedName("maintainer_email")
+   private final String maintainerEmail;
+   private final Map<String, String> conflicting;
+   private final String description;
+   private final Map<String, String> providing;
+   private final Map<String, String> platforms;
+   private final String version;
+   private final Map<String, String> recipes;
+   private final Map<String, String> replacing;
+   private final String name;
+   private final Map<String, String> groupings;
+   @SerializedName("long_description")
+   private final String longDescription;
+   private final Map<String, Attribute> attributes;
+   private final Map<String, String> recommendations;
+
+   @ConstructorProperties({ "license", "maintainer", "suggestions", "dependencies", "maintainer_email", "conflicting",
+         "description", "providing", "platforms", "version", "recipes", "replacing", "name", "groupings",
+         "long_description", "attributes", "recommendations" })
+   protected Metadata(String license, String maintainer, @Nullable Map<String, String> suggestions,
+         @Nullable Map<String, String> dependencies, String maintainerEmail, @Nullable Map<String, String> conflicting,
+         String description, @Nullable Map<String, String> providing, @Nullable Map<String, String> platforms,
+         String version, @Nullable Map<String, String> recipes, @Nullable Map<String, String> replacing, String name,
+         @Nullable Map<String, String> groupings, String longDescription, @Nullable Map<String, Attribute> attributes,
+         @Nullable Map<String, String> recommendations) {
+      this.license = license;
+      this.maintainer = maintainer;
+      this.suggestions = copyOfOrEmpty(suggestions);
+      this.dependencies = copyOfOrEmpty(dependencies);
+      this.maintainerEmail = maintainerEmail;
+      this.conflicting = copyOfOrEmpty(conflicting);
+      this.description = description;
+      this.providing = copyOfOrEmpty(providing);
+      this.platforms = copyOfOrEmpty(platforms);
+      this.version = version;
+      this.recipes = copyOfOrEmpty(recipes);
+      this.replacing = copyOfOrEmpty(replacing);
+      this.name = name;
+      this.groupings = copyOfOrEmpty(groupings);
+      this.longDescription = longDescription;
+      this.attributes = copyOfOrEmpty(attributes);
+      this.recommendations = copyOfOrEmpty(recommendations);
+   }
+
+   public String getLicense() {
+      return license;
+   }
+
+   public String getMaintainer() {
+      return maintainer;
+   }
+
+   public Map<String, String> getSuggestions() {
+      return suggestions;
+   }
+
+   public Map<String, String> getDependencies() {
+      return dependencies;
+   }
+
+   public String getMaintainerEmail() {
+      return maintainerEmail;
+   }
+
+   public Map<String, String> getConflicting() {
+      return conflicting;
+   }
+
+   public String getDescription() {
+      return description;
+   }
+
+   public Map<String, String> getProviding() {
+      return providing;
+   }
+
+   public Map<String, String> getPlatforms() {
+      return platforms;
+   }
+
+   public String getVersion() {
+      return version;
+   }
+
+   public Map<String, String> getRecipes() {
+      return recipes;
+   }
+
+   public Map<String, String> getReplacing() {
+      return replacing;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public Map<String, String> getGroupings() {
+      return groupings;
+   }
+
+   public String getLongDescription() {
+      return longDescription;
+   }
+
+   public Map<String, Attribute> getAttributes() {
+      return attributes;
+   }
+
+   public Map<String, String> getRecommendations() {
+      return recommendations;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((attributes == null) ? 0 : attributes.hashCode());
+      result = prime * result + ((conflicting == null) ? 0 : conflicting.hashCode());
+      result = prime * result + ((dependencies == null) ? 0 : dependencies.hashCode());
+      result = prime * result + ((description == null) ? 0 : description.hashCode());
+      result = prime * result + ((groupings == null) ? 0 : groupings.hashCode());
+      result = prime * result + ((license == null) ? 0 : license.hashCode());
+      result = prime * result + ((longDescription == null) ? 0 : longDescription.hashCode());
+      result = prime * result + ((maintainer == null) ? 0 : maintainer.hashCode());
+      result = prime * result + ((maintainerEmail == null) ? 0 : maintainerEmail.hashCode());
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((platforms == null) ? 0 : platforms.hashCode());
+      result = prime * result + ((providing == null) ? 0 : providing.hashCode());
+      result = prime * result + ((recipes == null) ? 0 : recipes.hashCode());
+      result = prime * result + ((recommendations == null) ? 0 : recommendations.hashCode());
+      result = prime * result + ((replacing == null) ? 0 : replacing.hashCode());
+      result = prime * result + ((suggestions == null) ? 0 : suggestions.hashCode());
+      result = prime * result + ((version == null) ? 0 : version.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;
+      Metadata other = (Metadata) obj;
+      if (attributes == null) {
+         if (other.attributes != null)
+            return false;
+      } else if (!attributes.equals(other.attributes))
+         return false;
+      if (conflicting == null) {
+         if (other.conflicting != null)
+            return false;
+      } else if (!conflicting.equals(other.conflicting))
+         return false;
+      if (dependencies == null) {
+         if (other.dependencies != null)
+            return false;
+      } else if (!dependencies.equals(other.dependencies))
+         return false;
+      if (description == null) {
+         if (other.description != null)
+            return false;
+      } else if (!description.equals(other.description))
+         return false;
+      if (groupings == null) {
+         if (other.groupings != null)
+            return false;
+      } else if (!groupings.equals(other.groupings))
+         return false;
+      if (license == null) {
+         if (other.license != null)
+            return false;
+      } else if (!license.equals(other.license))
+         return false;
+      if (longDescription == null) {
+         if (other.longDescription != null)
+            return false;
+      } else if (!longDescription.equals(other.longDescription))
+         return false;
+      if (maintainer == null) {
+         if (other.maintainer != null)
+            return false;
+      } else if (!maintainer.equals(other.maintainer))
+         return false;
+      if (maintainerEmail == null) {
+         if (other.maintainerEmail != null)
+            return false;
+      } else if (!maintainerEmail.equals(other.maintainerEmail))
+         return false;
+      if (name == null) {
+         if (other.name != null)
+            return false;
+      } else if (!name.equals(other.name))
+         return false;
+      if (platforms == null) {
+         if (other.platforms != null)
+            return false;
+      } else if (!platforms.equals(other.platforms))
+         return false;
+      if (providing == null) {
+         if (other.providing != null)
+            return false;
+      } else if (!providing.equals(other.providing))
+         return false;
+      if (recipes == null) {
+         if (other.recipes != null)
+            return false;
+      } else if (!recipes.equals(other.recipes))
+         return false;
+      if (recommendations == null) {
+         if (other.recommendations != null)
+            return false;
+      } else if (!recommendations.equals(other.recommendations))
+         return false;
+      if (replacing == null) {
+         if (other.replacing != null)
+            return false;
+      } else if (!replacing.equals(other.replacing))
+         return false;
+      if (suggestions == null) {
+         if (other.suggestions != null)
+            return false;
+      } else if (!suggestions.equals(other.suggestions))
+         return false;
+      if (version == null) {
+         if (other.version != null)
+            return false;
+      } else if (!version.equals(other.version))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "Metadata [attributes=" + attributes + ", conflicting=" + conflicting + ", dependencies=" + dependencies
+            + ", description=" + description + ", groupings=" + groupings + ", license=" + license
+            + ", longDescription=" + longDescription + ", maintainer=" + maintainer + ", maintainerEmail="
+            + maintainerEmail + ", name=" + name + ", platforms=" + platforms + ", providing=" + providing
+            + ", recipes=" + recipes + ", recommendations=" + recommendations + ", replacing=" + replacing
+            + ", suggestions=" + suggestions + ", version=" + version + "]";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/Node.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Node.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Node.java
new file mode 100644
index 0000000..ec83f4c
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Node.java
@@ -0,0 +1,263 @@
+/*
+ * 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 static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+import java.util.Map;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Node object.
+ */
+public class Node {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private String name;
+      private ImmutableMap.Builder<String, JsonBall> normalAttributes = ImmutableMap.builder();
+      private ImmutableMap.Builder<String, JsonBall> overrideAttributes = ImmutableMap.builder();
+      private ImmutableMap.Builder<String, JsonBall> defaultAttributes = ImmutableMap.builder();
+      private ImmutableMap.Builder<String, JsonBall> automaticAttributes = ImmutableMap.builder();
+      private ImmutableList.Builder<String> runList = ImmutableList.builder();
+      private String environment;
+
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      public Builder normalAttribute(String key, JsonBall value) {
+         this.normalAttributes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder normalAttributes(Map<String, JsonBall> normalAttributes) {
+         this.normalAttributes.putAll(checkNotNull(normalAttributes, "normalAttributes"));
+         return this;
+      }
+
+      public Builder overrideAttribute(String key, JsonBall value) {
+         this.overrideAttributes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder overrideAttributes(Map<String, JsonBall> overrideAttributes) {
+         this.overrideAttributes.putAll(checkNotNull(overrideAttributes, "overrideAttributes"));
+         return this;
+      }
+
+      public Builder defaultAttribute(String key, JsonBall value) {
+         this.defaultAttributes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder defaultAttributes(Map<String, JsonBall> defaultAttributes) {
+         this.defaultAttributes.putAll(checkNotNull(defaultAttributes, "defaultAttributes"));
+         return this;
+      }
+
+      public Builder automaticAttribute(String key, JsonBall value) {
+         this.automaticAttributes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder automaticAttributes(Map<String, JsonBall> automaticAttribute) {
+         this.automaticAttributes.putAll(checkNotNull(automaticAttribute, "automaticAttribute"));
+         return this;
+      }
+
+      public Builder runListElement(String element) {
+         this.runList.add(checkNotNull(element, "element"));
+         return this;
+      }
+
+      public Builder runList(Iterable<String> runList) {
+         this.runList.addAll(checkNotNull(runList, "runList"));
+         return this;
+      }
+
+      /**
+       * @since Chef 0.10
+       */
+      public Builder environment(String environment) {
+         this.environment = checkNotNull(environment, "environment");
+         return this;
+      }
+
+      public Node build() {
+         return new Node(name, normalAttributes.build(), overrideAttributes.build(), defaultAttributes.build(),
+               automaticAttributes.build(), runList.build(), environment);
+      }
+   }
+
+   private final String name;
+   @SerializedName("normal")
+   private final Map<String, JsonBall> normalAttributes;
+   @SerializedName("override")
+   private final Map<String, JsonBall> overrideAttributes;
+   @SerializedName("default")
+   private final Map<String, JsonBall> defaultAttributes;
+   @SerializedName("automatic")
+   private final Map<String, JsonBall> automaticAttributes;
+   @SerializedName("run_list")
+   private final List<String> runList;
+   @SerializedName("chef_environment")
+   private final String environment;
+
+   // internal
+   @SerializedName("json_class")
+   private final String _jsonClass = "Chef::Node";
+   @SerializedName("chef_type")
+   private final String _chefType = "node";
+
+   @ConstructorProperties({ "name", "normal", "override", "default", "automatic", "run_list", "chef_environment" })
+   protected Node(String name, @Nullable Map<String, JsonBall> normalAttributes,
+         @Nullable Map<String, JsonBall> overrideAttributes, @Nullable Map<String, JsonBall> defaultAttributes,
+         @Nullable Map<String, JsonBall> automaticAttributes, List<String> runList, @Nullable String environment) {
+      this.name = name;
+      this.environment = environment;
+      this.normalAttributes = copyOfOrEmpty(normalAttributes);
+      this.overrideAttributes = copyOfOrEmpty(overrideAttributes);
+      this.defaultAttributes = copyOfOrEmpty(defaultAttributes);
+      this.automaticAttributes = copyOfOrEmpty(automaticAttributes);
+      this.runList = copyOfOrEmpty(runList);
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public Map<String, JsonBall> getNormalAttributes() {
+      return normalAttributes;
+   }
+
+   public Map<String, JsonBall> getOverrideAttributes() {
+      return overrideAttributes;
+   }
+
+   public Map<String, JsonBall> getDefaultAttributes() {
+      return defaultAttributes;
+   }
+
+   public Map<String, JsonBall> getAutomaticAttributes() {
+      return automaticAttributes;
+   }
+
+   public List<String> getRunList() {
+      return runList;
+   }
+
+   /**
+    * @since Chef 0.10
+    */
+   public String getEnvironment() {
+      return environment;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((_chefType == null) ? 0 : _chefType.hashCode());
+      result = prime * result + ((_jsonClass == null) ? 0 : _jsonClass.hashCode());
+      result = prime * result + ((automaticAttributes == null) ? 0 : automaticAttributes.hashCode());
+      result = prime * result + ((defaultAttributes == null) ? 0 : defaultAttributes.hashCode());
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((normalAttributes == null) ? 0 : normalAttributes.hashCode());
+      result = prime * result + ((overrideAttributes == null) ? 0 : overrideAttributes.hashCode());
+      result = prime * result + ((runList == null) ? 0 : runList.hashCode());
+      result = prime * result + ((environment == null) ? 0 : environment.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;
+      Node other = (Node) obj;
+      if (_chefType == null) {
+         if (other._chefType != null)
+            return false;
+      } else if (!_chefType.equals(other._chefType))
+         return false;
+      if (_jsonClass == null) {
+         if (other._jsonClass != null)
+            return false;
+      } else if (!_jsonClass.equals(other._jsonClass))
+         return false;
+      if (automaticAttributes == null) {
+         if (other.automaticAttributes != null)
+            return false;
+      } else if (!automaticAttributes.equals(other.automaticAttributes))
+         return false;
+      if (defaultAttributes == null) {
+         if (other.defaultAttributes != null)
+            return false;
+      } else if (!defaultAttributes.equals(other.defaultAttributes))
+         return false;
+      if (name == null) {
+         if (other.name != null)
+            return false;
+      } else if (!name.equals(other.name))
+         return false;
+      if (normalAttributes == null) {
+         if (other.normalAttributes != null)
+            return false;
+      } else if (!normalAttributes.equals(other.normalAttributes))
+         return false;
+      if (overrideAttributes == null) {
+         if (other.overrideAttributes != null)
+            return false;
+      } else if (!overrideAttributes.equals(other.overrideAttributes))
+         return false;
+      if (runList == null) {
+         if (other.runList != null)
+            return false;
+      } else if (!runList.equals(other.runList))
+         return false;
+      if (environment == null) {
+         if (other.environment != null)
+            return false;
+      } else if (!environment.equals(other.environment))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "Node [name=" + name + ", runList=" + runList + ", normalAttributes=" + normalAttributes
+            + ", defaultAttributes=" + defaultAttributes + ", overrideAttributes=" + overrideAttributes
+            + ", chefEnvironment=" + environment + ", automaticAttributes=" + automaticAttributes + "]";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/Resource.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Resource.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Resource.java
new file mode 100644
index 0000000..94b5471
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Resource.java
@@ -0,0 +1,169 @@
+/*
+ * 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.net.URI;
+import java.util.Arrays;
+
+import org.jclouds.io.payloads.FilePayload;
+
+import com.google.common.primitives.Bytes;
+
+/**
+ * Resource object.
+ */
+public class Resource {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private String name;
+      private URI url;
+      private byte[] checksum;
+      private String path;
+      private String specificity = "default";
+
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      public Builder url(URI url) {
+         this.url = checkNotNull(url, "url");
+         return this;
+      }
+
+      public Builder checksum(byte[] checksum) {
+         this.checksum = checkNotNull(checksum, "checksum");
+         return this;
+      }
+
+      public Builder path(String path) {
+         this.path = checkNotNull(path, "path");
+         return this;
+      }
+
+      public Builder specificity(String specificity) {
+         this.specificity = checkNotNull(specificity, "specificity");
+         return this;
+      }
+
+      public Builder fromPayload(FilePayload payload) {
+         checkNotNull(payload, "payload");
+         this.name(payload.getRawContent().getName());
+         this.checksum(payload.getContentMetadata().getContentMD5());
+         this.path(payload.getRawContent().getPath());
+         return this;
+      }
+
+      public Resource build() {
+         return new Resource(name, url, checksum, path, specificity);
+      }
+   }
+
+   private final String name;
+   private final URI url;
+   private final byte[] checksum;
+   private final String path;
+   private final String specificity;
+
+   @ConstructorProperties({ "name", "url", "checksum", "path", "specificity" })
+   protected Resource(String name, URI url, byte[] checksum, String path, String specificity) {
+      this.name = name;
+      this.url = url;
+      this.checksum = checksum;
+      this.path = path;
+      this.specificity = specificity;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public URI getUrl() {
+      return url;
+   }
+
+   public byte[] getChecksum() {
+      return checksum;
+   }
+
+   public String getPath() {
+      return path;
+   }
+
+   public String getSpecificity() {
+      return specificity;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + Arrays.hashCode(checksum);
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((path == null) ? 0 : path.hashCode());
+      result = prime * result + ((specificity == null) ? 0 : specificity.hashCode());
+      result = prime * result + ((url == null) ? 0 : url.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;
+      Resource other = (Resource) obj;
+      if (!Arrays.equals(checksum, other.checksum))
+         return false;
+      if (name == null) {
+         if (other.name != null)
+            return false;
+      } else if (!name.equals(other.name))
+         return false;
+      if (path == null) {
+         if (other.path != null)
+            return false;
+      } else if (!path.equals(other.path))
+         return false;
+      if (specificity == null) {
+         if (other.specificity != null)
+            return false;
+      } else if (!specificity.equals(other.specificity))
+         return false;
+      if (url == null) {
+         if (other.url != null)
+            return false;
+      } else if (!url.equals(other.url))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "Resource [checksum=" + Bytes.asList(checksum) + ", name=" + name + ", path=" + path + ", specificity="
+            + specificity + ", url=" + url + "]";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/Role.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Role.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Role.java
new file mode 100644
index 0000000..d51dc1e
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Role.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.chef.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+import java.util.Map;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Role object.
+ */
+public class Role {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private String name;
+      private String description;
+      private ImmutableMap.Builder<String, JsonBall> overrideAttributes = ImmutableMap.builder();
+      private ImmutableMap.Builder<String, JsonBall> defaultAttributes = ImmutableMap.builder();
+      private ImmutableList.Builder<String> runList = ImmutableList.builder();
+
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      public Builder description(String description) {
+         this.description = checkNotNull(description, "description");
+         return this;
+      }
+
+      public Builder overrideAttribute(String key, JsonBall value) {
+         this.overrideAttributes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder overrideAttributes(Map<String, JsonBall> overrideAttributes) {
+         this.overrideAttributes.putAll(checkNotNull(overrideAttributes, "overrideAttributes"));
+         return this;
+      }
+
+      public Builder defaultAttribute(String key, JsonBall value) {
+         this.defaultAttributes.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder defaultAttributes(Map<String, JsonBall> defaultAttributes) {
+         this.defaultAttributes.putAll(checkNotNull(defaultAttributes, "defaultAttributes"));
+         return this;
+      }
+
+      public Builder runListElement(String element) {
+         this.runList.add(checkNotNull(element, "element"));
+         return this;
+      }
+
+      public Builder runList(Iterable<String> runList) {
+         this.runList.addAll(checkNotNull(runList, "runList"));
+         return this;
+      }
+
+      public Role build() {
+         return new Role(name, description, defaultAttributes.build(), runList.build(), overrideAttributes.build());
+      }
+   }
+
+   private final String name;
+   private final String description;
+   @SerializedName("override_attributes")
+   private final Map<String, JsonBall> overrideAttributes;
+   @SerializedName("default_attributes")
+   private final Map<String, JsonBall> defaultAttributes;
+   @SerializedName("run_list")
+   private final List<String> runList;
+
+   // internal
+   @SerializedName("json_class")
+   private final String _jsonClass = "Chef::Role";
+   @SerializedName("chef_type")
+   private final String _chefType = "role";
+
+   @ConstructorProperties({ "name", "description", "default_attributes", "run_list", "override_attributes" })
+   protected Role(String name, String description, @Nullable Map<String, JsonBall> defaultAttributes,
+         @Nullable List<String> runList, @Nullable Map<String, JsonBall> overrideAttributes) {
+      this.name = name;
+      this.description = description;
+      this.defaultAttributes = copyOfOrEmpty(defaultAttributes);
+      this.runList = copyOfOrEmpty(runList);
+      this.overrideAttributes = copyOfOrEmpty(overrideAttributes);
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public String getDescription() {
+      return description;
+   }
+
+   public Map<String, JsonBall> getOverrideAttributes() {
+      return overrideAttributes;
+   }
+
+   public Map<String, JsonBall> getDefaultAttributes() {
+      return defaultAttributes;
+   }
+
+   public List<String> getRunList() {
+      return runList;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((_chefType == null) ? 0 : _chefType.hashCode());
+      result = prime * result + ((_jsonClass == null) ? 0 : _jsonClass.hashCode());
+      result = prime * result + ((defaultAttributes == null) ? 0 : defaultAttributes.hashCode());
+      result = prime * result + ((description == null) ? 0 : description.hashCode());
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((overrideAttributes == null) ? 0 : overrideAttributes.hashCode());
+      result = prime * result + ((runList == null) ? 0 : runList.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;
+      Role other = (Role) obj;
+      if (_chefType == null) {
+         if (other._chefType != null)
+            return false;
+      } else if (!_chefType.equals(other._chefType))
+         return false;
+      if (_jsonClass == null) {
+         if (other._jsonClass != null)
+            return false;
+      } else if (!_jsonClass.equals(other._jsonClass))
+         return false;
+      if (defaultAttributes == null) {
+         if (other.defaultAttributes != null)
+            return false;
+      } else if (!defaultAttributes.equals(other.defaultAttributes))
+         return false;
+      if (description == null) {
+         if (other.description != null)
+            return false;
+      } else if (!description.equals(other.description))
+         return false;
+      if (name == null) {
+         if (other.name != null)
+            return false;
+      } else if (!name.equals(other.name))
+         return false;
+      if (overrideAttributes == null) {
+         if (other.overrideAttributes != null)
+            return false;
+      } else if (!overrideAttributes.equals(other.overrideAttributes))
+         return false;
+      if (runList == null) {
+         if (other.runList != null)
+            return false;
+      } else if (!runList.equals(other.runList))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "Role [name=" + name + ", description=" + description + ", defaultAttributes=" + defaultAttributes
+            + ", overrideAttributes=" + overrideAttributes + ", runList=" + runList + "]";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/Sandbox.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Sandbox.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Sandbox.java
new file mode 100644
index 0000000..c58d02d
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Sandbox.java
@@ -0,0 +1,195 @@
+/*
+ * 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 static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
+
+import java.beans.ConstructorProperties;
+import java.util.Date;
+import java.util.Set;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Sandbox object.
+ */
+public class Sandbox {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private String rev;
+      private boolean isCompleted;
+      private Date createTime;
+      private ImmutableSet.Builder<String> checksums = ImmutableSet.builder();
+      private String name;
+      private String guid;
+
+      public Builder rev(String rev) {
+         this.rev = checkNotNull(rev, "rev");
+         return this;
+      }
+
+      public Builder isCompleted(boolean isCompleted) {
+         this.isCompleted = isCompleted;
+         return this;
+      }
+
+      public Builder createTime(Date createTime) {
+         this.createTime = createTime;
+         return this;
+      }
+
+      public Builder checksum(String checksum) {
+         this.checksums.add(checkNotNull(checksum, "checksum"));
+         return this;
+      }
+
+      public Builder checksums(Iterable<String> checksums) {
+         this.checksums.addAll(checkNotNull(checksums, "checksums"));
+         return this;
+      }
+
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      public Builder guid(String guid) {
+         this.guid = checkNotNull(guid, "guid");
+         return this;
+      }
+
+      public Sandbox build() {
+         return new Sandbox(rev, isCompleted, createTime, checksums.build(), name, guid);
+      }
+   }
+
+   @SerializedName("_rev")
+   private final String rev;
+   @SerializedName("is_completed")
+   private final boolean isCompleted;
+   @SerializedName("create_time")
+   private final Date createTime;
+   private final Set<String> checksums;
+   private final String name;
+   private final String guid;
+
+   // internal
+   @SerializedName("json_class")
+   private final String _jsonClass = "Chef::Sandbox";
+   @SerializedName("chef_type")
+   private final String _chefType = "sandbox";
+
+   @ConstructorProperties({ "_rev", "is_completed", "create_time", "checksums", "name", "guid" })
+   protected Sandbox(String rev, boolean isCompleted, Date createTime, @Nullable Set<String> checksums, String name,
+         String guid) {
+      this.rev = rev;
+      this.isCompleted = isCompleted;
+      this.createTime = createTime;
+      this.checksums = copyOfOrEmpty(checksums);
+      this.name = name;
+      this.guid = guid;
+   }
+
+   public String getRev() {
+      return rev;
+   }
+
+   public boolean isCompleted() {
+      return isCompleted;
+   }
+
+   public Date getCreateTime() {
+      return createTime;
+   }
+
+   public Set<String> getChecksums() {
+      return checksums;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public String getGuid() {
+      return guid;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((checksums == null) ? 0 : checksums.hashCode());
+      result = prime * result + ((createTime == null) ? 0 : createTime.hashCode());
+      result = prime * result + ((guid == null) ? 0 : guid.hashCode());
+      result = prime * result + (isCompleted ? 1231 : 1237);
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((rev == null) ? 0 : rev.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;
+      Sandbox other = (Sandbox) obj;
+      if (checksums == null) {
+         if (other.checksums != null)
+            return false;
+      } else if (!checksums.equals(other.checksums))
+         return false;
+      if (createTime == null) {
+         if (other.createTime != null)
+            return false;
+      } else if (!createTime.equals(other.createTime))
+         return false;
+      if (guid == null) {
+         if (other.guid != null)
+            return false;
+      } else if (!guid.equals(other.guid))
+         return false;
+      if (isCompleted != other.isCompleted)
+         return false;
+      if (name == null) {
+         if (other.name != null)
+            return false;
+      } else if (!name.equals(other.name))
+         return false;
+      if (rev == null) {
+         if (other.rev != null)
+            return false;
+      } else if (!rev.equals(other.rev))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "Sandbox [checksums=" + checksums + ", createTime=" + createTime + ", guid=" + guid + ", isCompleted="
+            + isCompleted + ", name=" + name + ", rev=" + rev + "]";
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/SearchResult.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/SearchResult.java b/apis/chef/src/main/java/org/jclouds/chef/domain/SearchResult.java
new file mode 100644
index 0000000..7e406c1
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/SearchResult.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.chef.domain;
+
+import java.util.LinkedHashSet;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * A result of a search.
+ */
+public class SearchResult<T> extends LinkedHashSet<T> {
+   private static final long serialVersionUID = 4000610660948065287L;
+   private long start;
+
+   SearchResult() {
+   }
+
+   public SearchResult(long start, Iterable<T> results) {
+      this.start = start;
+      Iterables.addAll(this, results);
+   }
+
+   /**
+    * 
+    * @return the result position this started from from
+    */
+   long getStart() {
+      return start;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/UploadSandbox.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/UploadSandbox.java b/apis/chef/src/main/java/org/jclouds/chef/domain/UploadSandbox.java
new file mode 100644
index 0000000..9ca4c0f
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/UploadSandbox.java
@@ -0,0 +1,136 @@
+/*
+ * 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 static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
+
+import java.beans.ConstructorProperties;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * An upload sandbox.
+ */
+public class UploadSandbox {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private URI uri;
+      private ImmutableMap.Builder<List<Byte>, ChecksumStatus> checksums = ImmutableMap.builder();
+      private String sandboxId;
+
+      public Builder uri(URI uri) {
+         this.uri = checkNotNull(uri, "uri");
+         return this;
+      }
+
+      public Builder checksum(List<Byte> key, ChecksumStatus value) {
+         this.checksums.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
+         return this;
+      }
+
+      public Builder checksums(Map<List<Byte>, ChecksumStatus> checksums) {
+         this.checksums.putAll(checkNotNull(checksums, "checksums"));
+         return this;
+      }
+
+      public Builder sandboxId(String sandboxId) {
+         this.sandboxId = checkNotNull(sandboxId, "sandboxId");
+         return this;
+      }
+
+      public UploadSandbox build() {
+         return new UploadSandbox(uri, checksums.build(), sandboxId);
+      }
+   }
+
+   private final URI uri;
+   private final Map<List<Byte>, ChecksumStatus> checksums;
+   @SerializedName("sandbox_id")
+   private final String sandboxId;
+
+   @ConstructorProperties({ "uri", "checksums", "sandbox_id" })
+   protected UploadSandbox(URI uri, @Nullable Map<List<Byte>, ChecksumStatus> checksums, String sandboxId) {
+      this.uri = uri;
+      this.checksums = copyOfOrEmpty(checksums);
+      this.sandboxId = sandboxId;
+   }
+
+   public URI getUri() {
+      return uri;
+   }
+
+   public Map<List<Byte>, ChecksumStatus> getChecksums() {
+      return checksums;
+   }
+
+   public String getSandboxId() {
+      return sandboxId;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((checksums == null) ? 0 : checksums.hashCode());
+      result = prime * result + ((sandboxId == null) ? 0 : sandboxId.hashCode());
+      result = prime * result + ((uri == null) ? 0 : uri.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;
+      UploadSandbox other = (UploadSandbox) obj;
+      if (checksums == null) {
+         if (other.checksums != null)
+            return false;
+      } else if (!checksums.equals(other.checksums))
+         return false;
+      if (sandboxId == null) {
+         if (other.sandboxId != null)
+            return false;
+      } else if (!sandboxId.equals(other.sandboxId))
+         return false;
+      if (uri == null) {
+         if (other.uri != null)
+            return false;
+      } else if (!uri.equals(other.uri))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "UploadSandbox [checksums=" + checksums + ", id=" + sandboxId + ", uri=" + uri + "]";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..cabe579
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java
@@ -0,0 +1,200 @@
+/*
+ * 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.filters;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.hash.Hashing.sha1;
+import static com.google.common.io.BaseEncoding.base64;
+import static com.google.common.io.ByteStreams.toByteArray;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.util.NoSuchElementException;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpException;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpRequestFilter;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.http.internal.SignatureWire;
+import org.jclouds.io.ByteStreams2;
+import org.jclouds.io.Payload;
+import org.jclouds.io.Payloads;
+import org.jclouds.io.payloads.MultipartForm;
+import org.jclouds.io.payloads.Part;
+import org.jclouds.io.payloads.RSAEncryptingPayload;
+import org.jclouds.logging.Logger;
+import org.jclouds.util.Strings2;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+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" />
+ */
+@Singleton
+public class SignedHeaderAuth implements HttpRequestFilter {
+   public static final String SIGNING_DESCRIPTION = "version=1.0";
+
+   private final SignatureWire signatureWire;
+   private final Supplier<Credentials> creds;
+   private final Supplier<PrivateKey> supplyKey;
+   private final Provider<String> timeStampProvider;
+   private final String emptyStringHash;
+   private final HttpUtils utils;
+   private final Crypto crypto;
+
+   @Resource
+   @Named(Constants.LOGGER_SIGNATURE)
+   Logger signatureLog = Logger.NULL;
+
+   @Inject
+   public SignedHeaderAuth(SignatureWire signatureWire, @org.jclouds.location.Provider Supplier<Credentials> creds,
+         Supplier<PrivateKey> supplyKey, @TimeStamp Provider<String> timeStampProvider, HttpUtils utils, Crypto crypto) {
+      this.signatureWire = checkNotNull(signatureWire, "signatureWire");
+      this.creds = checkNotNull(creds, "creds");
+      this.supplyKey = checkNotNull(supplyKey, "supplyKey");
+      this.timeStampProvider = checkNotNull(timeStampProvider, "timeStampProvider");
+      this.emptyStringHash = hashBody(Payloads.newStringPayload(""));
+      this.utils = checkNotNull(utils, "utils");
+      this.crypto = checkNotNull(crypto, "crypto");
+   }
+
+   public HttpRequest filter(HttpRequest input) throws HttpException {
+      HttpRequest request = input.toBuilder().endpoint(input.getEndpoint().toString().replace("%3F", "?")).build();
+      String contentHash = hashBody(request.getPayload());
+      Multimap<String, String> headers = ArrayListMultimap.create();
+      headers.put("X-Ops-Content-Hash", contentHash);
+      String timestamp = timeStampProvider.get();
+      String toSign = createStringToSign(request.getMethod(), hashPath(request.getEndpoint().getPath()), contentHash,
+            timestamp);
+      headers.put("X-Ops-Userid", creds.get().identity);
+      headers.put("X-Ops-Sign", SIGNING_DESCRIPTION);
+      request = calculateAndReplaceAuthorizationHeaders(request, toSign);
+      headers.put("X-Ops-Timestamp", timestamp);
+      utils.logRequest(signatureLog, request, "<<");
+
+      return request.toBuilder().replaceHeaders(headers).build();
+   }
+
+   @VisibleForTesting
+   HttpRequest calculateAndReplaceAuthorizationHeaders(HttpRequest request, String toSign) throws HttpException {
+      String signature = sign(toSign);
+      if (signatureWire.enabled())
+         signatureWire.input(Strings2.toInputStream(signature));
+      String[] signatureLines = Iterables.toArray(Splitter.fixedLength(60).split(signature), String.class);
+
+      Multimap<String, String> headers = ArrayListMultimap.create();
+      for (int i = 0; i < signatureLines.length; i++) {
+         headers.put("X-Ops-Authorization-" + (i + 1), signatureLines[i]);
+      }
+      return request.toBuilder().replaceHeaders(headers).build();
+   }
+
+   public String createStringToSign(String request, String hashedPath, String contentHash, String timestamp) {
+
+      return new StringBuilder().append("Method:").append(request).append("\n").append("Hashed Path:")
+            .append(hashedPath).append("\n").append("X-Ops-Content-Hash:").append(contentHash).append("\n")
+            .append("X-Ops-Timestamp:").append(timestamp).append("\n").append("X-Ops-UserId:")
+            .append(creds.get().identity).toString();
+
+   }
+
+   @VisibleForTesting
+   String hashPath(String path) {
+      try {
+         return base64().encode(ByteSource.wrap(canonicalPath(path).getBytes(UTF_8)).hash(sha1()).asBytes());
+      } catch (Exception e) {
+         Throwables.propagateIfPossible(e);
+         throw new HttpException("error creating sigature for path: " + path, e);
+      }
+   }
+
+   /**
+    * Build the canonicalized path, which collapses multiple slashes (/) and
+    * removes a trailing slash unless the path is only "/"
+    */
+   @VisibleForTesting
+   String canonicalPath(String path) {
+      path = path.replaceAll("\\/+", "/");
+      return path.endsWith("/") && path.length() > 1 ? path.substring(0, path.length() - 1) : path;
+   }
+
+   @VisibleForTesting
+   String hashBody(Payload payload) {
+      if (payload == null)
+         return emptyStringHash;
+      payload = useTheFilePartIfForm(payload);
+      checkArgument(payload != null, "payload was null");
+      checkArgument(payload.isRepeatable(), "payload must be repeatable: " + payload);
+      try {
+         return base64().encode(ByteStreams2.hashAndClose(payload.getInput(), sha1()).asBytes());
+      } catch (Exception e) {
+         Throwables.propagateIfPossible(e);
+         throw new HttpException("error creating sigature for payload: " + payload, e);
+      }
+   }
+
+   private Payload useTheFilePartIfForm(Payload payload) {
+      if (payload instanceof MultipartForm) {
+         Iterable<? extends Part> parts = MultipartForm.class.cast(payload).getRawContent();
+         try {
+            payload = Iterables.find(parts, new Predicate<Part>() {
+
+               @Override
+               public boolean apply(Part input) {
+                  return "file".equals(input.getName());
+               }
+
+            });
+         } catch (NoSuchElementException e) {
+
+         }
+      }
+      return payload;
+   }
+
+   public String sign(String toSign) {
+      try {
+         byte[] encrypted = toByteArray(new RSAEncryptingPayload(crypto, Payloads.newStringPayload(toSign), supplyKey.get()));
+         return base64().encode(encrypted);
+      } catch (IOException e) {
+         throw new HttpException("error signing request", e);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/BootstrapConfigForGroup.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/BootstrapConfigForGroup.java b/apis/chef/src/main/java/org/jclouds/chef/functions/BootstrapConfigForGroup.java
new file mode 100644
index 0000000..ec75e47
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/BootstrapConfigForGroup.java
@@ -0,0 +1,61 @@
+/*
+ * 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.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.jclouds.chef.config.ChefProperties.CHEF_BOOTSTRAP_DATABAG;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.domain.JsonBall;
+
+import com.google.common.base.Function;
+import com.google.inject.TypeLiteral;
+
+/**
+ * 
+ * Retrieves the bootstrap configuration for a specific group
+ */
+@Singleton
+public class BootstrapConfigForGroup implements Function<String, DatabagItem> {
+   public static final Type BOOTSTRAP_CONFIG_TYPE = new TypeLiteral<Map<String, JsonBall>>() {
+   }.getType();
+   private final ChefApi api;
+   private final String databag;
+
+   @Inject
+   public BootstrapConfigForGroup(@Named(CHEF_BOOTSTRAP_DATABAG) String databag, ChefApi api) {
+      this.databag = checkNotNull(databag, "databag");
+      this.api = checkNotNull(api, "api");
+   }
+
+   @Override
+   public DatabagItem apply(String from) {
+      DatabagItem bootstrapConfig = api.getDatabagItem(databag, from);
+      checkState(bootstrapConfig != null, "databag item %s/%s not found", databag, from);
+      return bootstrapConfig;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ClientForGroup.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ClientForGroup.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ClientForGroup.java
new file mode 100644
index 0000000..d1a9250
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ClientForGroup.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Sets.newHashSet;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Client;
+
+import com.google.common.base.Function;
+
+/**
+ * 
+ * Generates a client relevant for a particular group
+ */
+@Singleton
+public class ClientForGroup implements Function<String, Client> {
+   private final ChefApi chefApi;
+
+   @Inject
+   public ClientForGroup(ChefApi chefApi) {
+      this.chefApi = checkNotNull(chefApi, "chefApi");
+   }
+
+   @Override
+   public Client apply(String from) {
+      String clientName = findNextClientName(chefApi.listClients(), from + "-client-%02d");
+      Client client = chefApi.createClient(clientName);
+      // response from create only includes the key
+      return Client.builder() //
+            .clientname(clientName) //
+            .name(clientName) //
+            .isValidator(false) //
+            .privateKey(client.getPrivateKey()) //
+            .build();
+   }
+
+   private static String findNextClientName(Set<String> clients, String pattern) {
+      String clientName;
+      Set<String> names = newHashSet(clients);
+      int index = 0;
+      while (true) {
+         clientName = String.format(pattern, index++);
+         if (!names.contains(clientName))
+            break;
+      }
+      return clientName;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java b/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java
new file mode 100644
index 0000000..516d9f9
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.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.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Throwables.propagate;
+import static org.jclouds.scriptbuilder.domain.Statements.appendFile;
+import static org.jclouds.scriptbuilder.domain.Statements.exec;
+import static org.jclouds.scriptbuilder.domain.Statements.newStatementList;
+
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.security.PrivateKey;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.config.InstallChef;
+import org.jclouds.chef.config.Validator;
+import org.jclouds.crypto.Pems;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+import org.jclouds.location.Provider;
+import org.jclouds.scriptbuilder.ExitInsteadOfReturn;
+import org.jclouds.scriptbuilder.domain.Statement;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.cache.CacheLoader;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.TypeLiteral;
+
+/**
+ * 
+ * Generates a bootstrap script relevant for a particular group
+ */
+@Singleton
+public class GroupToBootScript implements Function<String, Statement> {
+   private static final Pattern newLinePattern = Pattern.compile("(\\r\\n)|(\\n)");
+   
+   @VisibleForTesting
+   static final Type RUN_LIST_TYPE = new TypeLiteral<Map<String, List<String>>>() {
+   }.getType();
+   private final Supplier<URI> endpoint;
+   private final Json json;
+   private final CacheLoader<String, ? extends JsonBall> bootstrapConfigForGroup;
+   private final Statement installChef;
+   private final Optional<String> validatorName;
+   private final Optional<PrivateKey> validatorCredential;
+
+   @Inject
+   public GroupToBootScript(@Provider Supplier<URI> endpoint, Json json,
+         CacheLoader<String, ? extends JsonBall> bootstrapConfigForGroup,
+         @InstallChef Statement installChef, @Validator Optional<String> validatorName,
+         @Validator Optional<PrivateKey> validatorCredential) {
+      this.endpoint = checkNotNull(endpoint, "endpoint");
+      this.json = checkNotNull(json, "json");
+      this.bootstrapConfigForGroup = checkNotNull(bootstrapConfigForGroup, "bootstrapConfigForGroup");
+      this.installChef = checkNotNull(installChef, "installChef");
+      this.validatorName = checkNotNull(validatorName, "validatorName");
+      this.validatorCredential = checkNotNull(validatorCredential, validatorCredential);
+   }
+
+   @Override
+   public Statement apply(String group) {
+      checkNotNull(group, "group");
+      String validatorClientName = validatorName.get();
+      PrivateKey validatorKey = validatorCredential.get();
+
+      JsonBall bootstrapConfig = null;
+      try {
+         bootstrapConfig = bootstrapConfigForGroup.load(group);
+      } catch (Exception e) {
+         throw propagate(e);
+      }
+
+      Map<String, JsonBall> config = json.fromJson(bootstrapConfig.toString(),
+            BootstrapConfigForGroup.BOOTSTRAP_CONFIG_TYPE);
+      Optional<JsonBall> environment = Optional.fromNullable(config.get("environment"));
+
+      String chefConfigDir = "{root}etc{fs}chef";
+      Statement createChefConfigDir = exec("{md} " + chefConfigDir);
+      Statement createClientRb = appendFile(chefConfigDir + "{fs}client.rb", ImmutableList.of("require 'rubygems'",
+            "require 'ohai'", "o = Ohai::System.new", "o.all_plugins",
+            String.format("node_name \"%s-\" + o[:ipaddress]", group), "log_level :info", "log_location STDOUT",
+            String.format("validation_client_name \"%s\"", validatorClientName),
+            String.format("chef_server_url \"%s\"", endpoint.get())));
+
+      Statement createValidationPem = appendFile(chefConfigDir + "{fs}validation.pem",
+            Splitter.on(newLinePattern).split(Pems.pem(validatorKey)));
+
+      String chefBootFile = chefConfigDir + "{fs}first-boot.json";
+      Statement createFirstBoot = appendFile(chefBootFile, Collections.singleton(json.toJson(bootstrapConfig)));
+
+      ImmutableMap.Builder<String, String> options = ImmutableMap.builder();
+      options.put("-j", chefBootFile);
+      if (environment.isPresent()) {
+         options.put("-E", environment.get().toString());
+      }
+      String strOptions = Joiner.on(' ').withKeyValueSeparator(" ").join(options.build());
+      Statement runChef = exec("chef-client " + strOptions);
+
+      return newStatementList(new ExitInsteadOfReturn(installChef), createChefConfigDir, createClientRb, createValidationPem,
+            createFirstBoot, runChef);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersion.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersion.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersion.java
new file mode 100644
index 0000000..ffb4201
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersion.java
@@ -0,0 +1,49 @@
+/*
+ * 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.functions;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.config.CookbookParser;
+import org.jclouds.http.HttpResponse;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+
+/**
+ * Parses a cookbook definition from a Json response, taking care of using the
+ * appropriate parser.
+ */
+@Singleton
+public class ParseCookbookDefinitionCheckingChefVersion implements Function<HttpResponse, Set<String>> {
+
+   @VisibleForTesting
+   final Function<HttpResponse, Set<String>> parser;
+
+   @Inject
+   ParseCookbookDefinitionCheckingChefVersion(@CookbookParser Function<HttpResponse, Set<String>> parser) {
+      this.parser = parser;
+   }
+
+   @Override
+   public Set<String> apply(HttpResponse response) {
+      return parser.apply(response);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJson.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJson.java
new file mode 100644
index 0000000..3e172e4
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJson.java
@@ -0,0 +1,50 @@
+/*
+ * 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.functions;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+
+import com.google.common.base.Function;
+
+/**
+ * Parses a cookbook definition from a Json response, assuming a Chef Server >=
+ * 0.10.8.
+ */
+@Singleton
+public class ParseCookbookDefinitionFromJson implements Function<HttpResponse, Set<String>> {
+
+   /** Parser for responses from chef server >= 0.10.8 */
+   private final ParseJson<Map<String, CookbookDefinition>> parser;
+
+   @Inject
+   ParseCookbookDefinitionFromJson(ParseJson<Map<String, CookbookDefinition>> parser) {
+      this.parser = parser;
+   }
+
+   @Override
+   public Set<String> apply(HttpResponse response) {
+      return parser.apply(response).keySet();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10.java
new file mode 100644
index 0000000..692d969
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10.java
@@ -0,0 +1,52 @@
+/*
+ * 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.functions;
+
+import com.google.common.base.Function;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Map;
+
+/**
+ * Parses the cookbook versions in a Chef Server >= 0.10.8.
+ */
+@Singleton
+public class ParseCookbookDefinitionFromJsonv10 implements Function<HttpResponse, CookbookDefinition> {
+
+   /** Parser for responses from chef server >= 0.10.8 */
+   private final ParseJson<Map<String, CookbookDefinition>> parser;
+
+   @Inject
+   ParseCookbookDefinitionFromJsonv10(ParseJson<Map<String, CookbookDefinition>> parser) {
+      this.parser = parser;
+   }
+
+   @Override
+   public CookbookDefinition apply(HttpResponse response) {
+      Map<String, CookbookDefinition> result = parser.apply(response);
+      String cookbookName = result.keySet().iterator().next();
+      CookbookDefinition def = result.values().iterator().next();
+      return CookbookDefinition.builder() //
+             .from(def) //
+             .name(cookbookName) //
+             .build();
+   }
+}


[09/10] Move jclouds-chef to the main jclouds repo

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..2fc4201
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/BaseChefHttpApiModule.java
@@ -0,0 +1,208 @@
+/*
+ * 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.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.functions.BootstrapConfigForGroup;
+import org.jclouds.chef.functions.ClientForGroup;
+import org.jclouds.chef.functions.RunListForGroup;
+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.domain.JsonBall;
+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 String provideTimeStamp(@TimeStamp Supplier<String> cache) {
+      return cache.get();
+   }
+
+   /**
+    * borrowing concurrency code to ensure that caching takes place properly
+    */
+   @Provides
+   @TimeStamp
+   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 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
+   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 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 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
+   CacheLoader<String, List<String>> runListForGroup(RunListForGroup runListForGroup) {
+      return CacheLoader.from(runListForGroup);
+   }
+
+   @Provides
+   @Singleton
+   CacheLoader<String, ? extends JsonBall> bootstrapConfigForGroup(BootstrapConfigForGroup bootstrapConfigForGroup) {
+      return CacheLoader.from(bootstrapConfigForGroup);
+   }
+
+   @Provides
+   @Singleton
+   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/867c7a40/apis/chef/src/main/java/org/jclouds/chef/config/ChefBootstrapModule.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/ChefBootstrapModule.java b/apis/chef/src/main/java/org/jclouds/chef/config/ChefBootstrapModule.java
new file mode 100644
index 0000000..a5fd231
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/ChefBootstrapModule.java
@@ -0,0 +1,121 @@
+/*
+ * 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 org.jclouds.chef.config.ChefProperties.CHEF_GEM_SYSTEM_VERSION;
+import static org.jclouds.chef.config.ChefProperties.CHEF_UPDATE_GEMS;
+import static org.jclouds.chef.config.ChefProperties.CHEF_UPDATE_GEM_SYSTEM;
+import static org.jclouds.chef.config.ChefProperties.CHEF_USE_OMNIBUS;
+import static org.jclouds.chef.config.ChefProperties.CHEF_VERSION;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.jclouds.scriptbuilder.domain.StatementList;
+import org.jclouds.scriptbuilder.statements.chef.InstallChefGems;
+import org.jclouds.scriptbuilder.statements.chef.InstallChefUsingOmnibus;
+import org.jclouds.scriptbuilder.statements.ruby.InstallRuby;
+import org.jclouds.scriptbuilder.statements.ruby.InstallRubyGems;
+
+import com.google.common.base.Optional;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+
+/**
+ * Provides bootstrap configuration for nodes.
+ */
+public class ChefBootstrapModule extends AbstractModule {
+
+   @Provides
+   @Named("installChefGems")
+   @Singleton
+   Statement installChefGems(BootstrapProperties bootstrapProperties) {
+      InstallRubyGems installRubyGems = InstallRubyGems.builder()
+            .version(bootstrapProperties.gemSystemVersion().orNull())
+            .updateSystem(bootstrapProperties.updateGemSystem(), bootstrapProperties.gemSystemVersion().orNull())
+            .updateExistingGems(bootstrapProperties.updateGems()) //
+            .build();
+
+      Statement installChef = InstallChefGems.builder().version(bootstrapProperties.chefVersion().orNull()).build();
+
+      return new StatementList(InstallRuby.builder().build(), installRubyGems, installChef);
+   }
+
+   @Provides
+   @Named("installChefOmnibus")
+   @Singleton
+   Statement installChefUsingOmnibus() {
+      return new InstallChefUsingOmnibus();
+   }
+
+   @Provides
+   @InstallChef
+   @Singleton
+   Statement installChef(BootstrapProperties bootstrapProperties, @Named("installChefGems") Statement installChefGems,
+         @Named("installChefOmnibus") Statement installChefOmnibus) {
+      return bootstrapProperties.useOmnibus() ? installChefOmnibus : installChefGems;
+   }
+
+   @Singleton
+   private static class BootstrapProperties {
+      @Named(CHEF_VERSION)
+      @Inject(optional = true)
+      private String chefVersionProperty;
+
+      @Named(CHEF_GEM_SYSTEM_VERSION)
+      @Inject(optional = true)
+      private String gemSystemVersionProperty;
+
+      @Named(CHEF_UPDATE_GEM_SYSTEM)
+      @Inject
+      private String updateGemSystemProeprty;
+
+      @Named(CHEF_UPDATE_GEMS)
+      @Inject
+      private String updateGemsProperty;
+
+      @Named(CHEF_USE_OMNIBUS)
+      @Inject
+      private String useOmnibus;
+
+      public Optional<String> chefVersion() {
+         return Optional.fromNullable(chefVersionProperty);
+      }
+
+      public Optional<String> gemSystemVersion() {
+         return Optional.fromNullable(gemSystemVersionProperty);
+      }
+
+      public boolean updateGemSystem() {
+         return Boolean.parseBoolean(updateGemSystemProeprty);
+      }
+
+      public boolean updateGems() {
+         return Boolean.parseBoolean(updateGemsProperty);
+      }
+
+      public boolean useOmnibus() {
+         return Boolean.parseBoolean(useOmnibus);
+      }
+   }
+
+   @Override
+   protected void configure() {
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..ffd3b0b
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/ChefHttpApiModule.java
@@ -0,0 +1,28 @@
+/*
+ * 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 org.jclouds.chef.ChefApi;
+import org.jclouds.rest.ConfiguresHttpApi;
+
+/**
+ * Configures the Chef connection.
+ */
+@ConfiguresHttpApi
+public class ChefHttpApiModule extends BaseChefHttpApiModule<ChefApi> {
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java b/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java
new file mode 100644
index 0000000..c214236
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java
@@ -0,0 +1,321 @@
+/*
+ * 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.Objects.equal;
+import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Type;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.chef.functions.ParseCookbookDefinitionFromJson;
+import org.jclouds.chef.functions.ParseCookbookVersionsV09FromJson;
+import org.jclouds.chef.functions.ParseCookbookVersionsV10FromJson;
+import org.jclouds.chef.functions.ParseKeySetFromJson;
+import org.jclouds.chef.suppliers.ChefVersionSupplier;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.crypto.Pems;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule.DateAdapter;
+import org.jclouds.json.config.GsonModule.Iso8601DateAdapter;
+import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.MapTypeAdapterFactory;
+import org.jclouds.json.internal.NullHackJsonLiteralAdapter;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteSource;
+import com.google.gson.Gson;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.internal.JsonReaderInternalAccess;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import com.google.inject.AbstractModule;
+import com.google.inject.ImplementedBy;
+import com.google.inject.Provides;
+
+public class ChefParserModule extends AbstractModule {
+   @ImplementedBy(PrivateKeyAdapterImpl.class)
+   public interface PrivateKeyAdapter extends JsonDeserializer<PrivateKey> {
+
+   }
+
+   @Singleton
+   public static class PrivateKeyAdapterImpl implements PrivateKeyAdapter {
+      private final Crypto crypto;
+
+      @Inject
+      PrivateKeyAdapterImpl(Crypto crypto) {
+         this.crypto = crypto;
+      }
+
+      @Override
+      public PrivateKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+            throws JsonParseException {
+         String keyText = json.getAsString().replaceAll("\\n", "\n");
+         try {
+            return crypto.rsaKeyFactory().generatePrivate(
+                  Pems.privateKeySpec(ByteSource.wrap(keyText.getBytes(Charsets.UTF_8))));
+         } catch (UnsupportedEncodingException e) {
+            Throwables.propagate(e);
+            return null;
+         } catch (InvalidKeySpecException e) {
+            Throwables.propagate(e);
+            return null;
+         } catch (IOException e) {
+            Throwables.propagate(e);
+            return null;
+         }
+      }
+   }
+
+   @ImplementedBy(PublicKeyAdapterImpl.class)
+   public interface PublicKeyAdapter extends JsonDeserializer<PublicKey> {
+
+   }
+
+   @Singleton
+   public static class PublicKeyAdapterImpl implements PublicKeyAdapter {
+      private final Crypto crypto;
+
+      @Inject
+      PublicKeyAdapterImpl(Crypto crypto) {
+         this.crypto = crypto;
+      }
+
+      @Override
+      public PublicKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+            throws JsonParseException {
+         String keyText = json.getAsString().replaceAll("\\n", "\n");
+         try {
+            return crypto.rsaKeyFactory().generatePublic(
+                  Pems.publicKeySpec(ByteSource.wrap(keyText.getBytes(Charsets.UTF_8))));
+         } catch (UnsupportedEncodingException e) {
+            Throwables.propagate(e);
+            return null;
+         } catch (InvalidKeySpecException e) {
+            Throwables.propagate(e);
+            return null;
+         } catch (IOException e) {
+            Throwables.propagate(e);
+            return null;
+         }
+      }
+   }
+
+   @ImplementedBy(X509CertificateAdapterImpl.class)
+   public interface X509CertificateAdapter extends JsonDeserializer<X509Certificate> {
+
+   }
+
+   @Singleton
+   public static class X509CertificateAdapterImpl implements X509CertificateAdapter {
+      private final Crypto crypto;
+
+      @Inject
+      X509CertificateAdapterImpl(Crypto crypto) {
+         this.crypto = crypto;
+      }
+
+      @Override
+      public X509Certificate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+            throws JsonParseException {
+         String keyText = json.getAsString().replaceAll("\\n", "\n");
+         try {
+            return Pems.x509Certificate(ByteSource.wrap(keyText.getBytes(Charsets.UTF_8)),
+                  crypto.certFactory());
+         } catch (UnsupportedEncodingException e) {
+            Throwables.propagate(e);
+            return null;
+         } catch (IOException e) {
+            Throwables.propagate(e);
+            return null;
+         } catch (CertificateException e) {
+            Throwables.propagate(e);
+            return null;
+         }
+      }
+   }
+
+   /**
+    * writes or reads the literal directly
+    */
+   @Singleton
+   public static class DataBagItemAdapter extends NullHackJsonLiteralAdapter<DatabagItem> {
+      final Gson gson = new Gson();
+
+      @Override
+      protected DatabagItem createJsonLiteralFromRawJson(String text) {
+         IdHolder idHolder = gson.fromJson(text, IdHolder.class);
+         checkState(idHolder.id != null,
+               "databag item must be a json hash ex. {\"id\":\"item1\",\"my_key\":\"my_data\"}; was %s", text);
+         text = text.replaceFirst(String.format("\\{\"id\"[ ]?:\"%s\",", idHolder.id), "{");
+         return new DatabagItem(idHolder.id, text);
+      }
+
+      @Override
+      protected String toString(DatabagItem value) {
+         String text = value.toString();
+
+         try {
+            IdHolder idHolder = gson.fromJson(text, IdHolder.class);
+            if (idHolder.id == null) {
+               text = text.replaceFirst("\\{", String.format("{\"id\":\"%s\",", value.getId()));
+            } else {
+               checkArgument(value.getId().equals(idHolder.id),
+                     "incorrect id in databagItem text, should be %s: was %s", value.getId(), idHolder.id);
+            }
+         } catch (JsonSyntaxException e) {
+            throw new IllegalArgumentException(e);
+         }
+
+         return text;
+      }
+   }
+
+   private static class IdHolder {
+      private String id;
+   }
+
+   // The NullFilteringTypeAdapterFactories.MapTypeAdapter class is final. Do
+   // the same logic here
+   private static final class KeepLastRepeatedKeyMapTypeAdapter<K, V> extends TypeAdapter<Map<K, V>> {
+
+      protected final TypeAdapter<K> keyAdapter;
+      protected final TypeAdapter<V> valueAdapter;
+
+      protected KeepLastRepeatedKeyMapTypeAdapter(TypeAdapter<K> keyAdapter, TypeAdapter<V> valueAdapter) {
+         this.keyAdapter = keyAdapter;
+         this.valueAdapter = valueAdapter;
+         nullSafe();
+      }
+
+      public void write(JsonWriter out, Map<K, V> value) throws IOException {
+         if (value == null) {
+            out.nullValue();
+            return;
+         }
+         out.beginObject();
+         for (Map.Entry<K, V> element : value.entrySet()) {
+            out.name(String.valueOf(element.getKey()));
+            valueAdapter.write(out, element.getValue());
+         }
+         out.endObject();
+      }
+
+      public Map<K, V> read(JsonReader in) throws IOException {
+         Map<K, V> result = Maps.newHashMap();
+         in.beginObject();
+         while (in.hasNext()) {
+            JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
+            K name = keyAdapter.read(in);
+            V value = valueAdapter.read(in);
+            if (value != null) {
+               // If there are repeated keys, overwrite them to only keep the last one
+               result.put(name, value);
+            }
+         }
+         in.endObject();
+         return ImmutableMap.copyOf(result);
+      }
+
+      @Override
+      public int hashCode() {
+         return Objects.hashCode(keyAdapter, valueAdapter);
+      }
+
+      @Override
+      public boolean equals(Object obj) {
+         if (this == obj)
+            return true;
+         if (obj == null || getClass() != obj.getClass())
+            return false;
+         KeepLastRepeatedKeyMapTypeAdapter<?, ?> that = KeepLastRepeatedKeyMapTypeAdapter.class.cast(obj);
+         return equal(this.keyAdapter, that.keyAdapter) && equal(this.valueAdapter, that.valueAdapter);
+      }
+
+      @Override
+      public String toString() {
+         return toStringHelper(this).add("keyAdapter", keyAdapter).add("valueAdapter", valueAdapter).toString();
+      }
+   }
+
+   public static class KeepLastRepeatedKeyMapTypeAdapterFactory extends MapTypeAdapterFactory {
+
+      public KeepLastRepeatedKeyMapTypeAdapterFactory() {
+         super(Map.class);
+      }
+
+      @SuppressWarnings("unchecked")
+      @Override
+      protected <K, V, T> TypeAdapter<T> newAdapter(TypeAdapter<K> keyAdapter, TypeAdapter<V> valueAdapter) {
+         return (TypeAdapter<T>) new KeepLastRepeatedKeyMapTypeAdapter<K, V>(keyAdapter, valueAdapter);
+      }
+   }
+
+   @Provides
+   @Singleton
+   public Map<Type, Object> provideCustomAdapterBindings(DataBagItemAdapter adapter, PrivateKeyAdapter privateAdapter,
+         PublicKeyAdapter publicAdapter, X509CertificateAdapter certAdapter) {
+      return ImmutableMap.<Type, Object> of(DatabagItem.class, adapter, PrivateKey.class, privateAdapter,
+            PublicKey.class, publicAdapter, X509Certificate.class, certAdapter);
+   }
+
+   @Provides
+   @Singleton
+   @CookbookParser
+   public Function<HttpResponse, Set<String>> provideCookbookDefinitionAdapter(ChefVersionSupplier chefVersionSupplier,
+         ParseCookbookDefinitionFromJson v10parser, ParseKeySetFromJson v09parser) {
+      return chefVersionSupplier.get() >= 10 ? v10parser : v09parser;
+   }
+
+   @Provides
+   @Singleton
+   @CookbookVersionsParser
+   public Function<HttpResponse, Set<String>> provideCookbookDefinitionAdapter(ChefVersionSupplier chefVersionSupplier,
+         ParseCookbookVersionsV10FromJson v10parser, ParseCookbookVersionsV09FromJson v09parser) {
+      return chefVersionSupplier.get() >= 10 ? v10parser : v09parser;
+   }
+
+   @Override
+   protected void configure() {
+      bind(DateAdapter.class).to(Iso8601DateAdapter.class);
+      bind(MapTypeAdapterFactory.class).to(KeepLastRepeatedKeyMapTypeAdapterFactory.class);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/config/ChefProperties.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/ChefProperties.java b/apis/chef/src/main/java/org/jclouds/chef/config/ChefProperties.java
new file mode 100644
index 0000000..211b5d6
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/ChefProperties.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.config;
+
+
+/**
+ * Configuration properties and constants used in Chef connections.
+ */
+public interface ChefProperties {
+
+   /**
+    * The name of the Chef logger.
+    */
+   public static final String CHEF_LOGGER = "jclouds.chef";
+
+   /**
+    * Databag that holds chef bootstrap hints, should be a json ball in the
+    * following format:
+    * <p>
+    * {"tag":{"run_list":["recipe[apache2]"]}}
+    */
+   public static final String CHEF_BOOTSTRAP_DATABAG = "chef.bootstrap-databag";
+
+   /**
+    * The name of the validator client used to allow nodes to autoregister in
+    * the Chef server.
+    * <p>
+    * This property must be set prior to running the
+    * {@link ChefService#createBootstrapScriptForGroup(String)} method.
+    */
+   public static final String CHEF_VALIDATOR_NAME = "chef.validator-name";
+
+   /**
+    * The credential of the validator client used to allow nodes to autoregister
+    * in the Chef server.
+    * <p>
+    * This property must be set prior to running the
+    * {@link ChefService#createBootstrapScriptForGroup(String)} method.
+    */
+   public static final String CHEF_VALIDATOR_CREDENTIAL = "chef.validator-credential";
+
+   /**
+    * The version of the Chef gem to install when bootstrapping nodes.
+    * <p>
+    * If this property is not set, by default the latest available Chef gem will
+    * be installed. The values can be fixed versions such as '0.10.8' or
+    * constrained values such as '>= 0.10.8'.
+    * <p>
+    * This property must be set prior to running the
+    * {@link ChefService#createBootstrapScriptForGroup(String)} method.
+    */
+   public static final String CHEF_VERSION = "chef.version";
+
+   /**
+    * Boolean property. Default (false).
+    * <p>
+    * When bootstrapping a node, forces a gem system update before installing
+    * the Chef gems.
+    * <p>
+    * This property must be set prior to running the
+    * {@link ChefService#createBootstrapScriptForGroup(String)} method.
+    */
+   public static final String CHEF_UPDATE_GEM_SYSTEM = "chef.update-gem-system";
+
+   /**
+    * To be used in conjunction with {@link #CHEF_UPDATE_GEM_SYSTEM}. This
+    * property will force the version of RubyGems to update the system to.
+    * <p>
+    * This property must be set prior to running the
+    * {@link ChefService#createBootstrapScriptForGroup(String)} method.
+    */
+   public static final String CHEF_GEM_SYSTEM_VERSION = "chef.gem-system-version";
+
+   /**
+    * Boolean property. Default (false).
+    * <p>
+    * When bootstrapping a node, updates the existing gems before installing
+    * Chef.
+    * <p>
+    * This property must be set prior to running the
+    * {@link ChefService#createBootstrapScriptForGroup(String)} method.
+    */
+   public static final String CHEF_UPDATE_GEMS = "chef.update-gems";
+
+   /**
+    * Boolean property. Default (true).
+    * <p>
+    * When bootstrapping a node, install the Chef client using the Omnibus
+    * installer.
+    * <p>
+    * This property must be set prior to running the
+    * {@link ChefService#createBootstrapScriptForGroup(String)} method.
+    */
+   public static final String CHEF_USE_OMNIBUS = "chef.use-omnibus";
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/config/CookbookParser.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/CookbookParser.java b/apis/chef/src/main/java/org/jclouds/chef/config/CookbookParser.java
new file mode 100644
index 0000000..34eedde
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/CookbookParser.java
@@ -0,0 +1,41 @@
+/*
+ * 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 java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/**
+ * Used to configure the cookbook Json parser.
+ * <p>
+ * Chef Server version 0.9 and 0.10 return a different Json when rquesting the
+ * cookbook definitions. This annotation can be used to setup the cookbook
+ * parser.
+ */
+@Target({ METHOD, PARAMETER, FIELD })
+@Retention(RUNTIME)
+@Qualifier
+public @interface CookbookParser {
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/config/CookbookVersionsParser.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/CookbookVersionsParser.java b/apis/chef/src/main/java/org/jclouds/chef/config/CookbookVersionsParser.java
new file mode 100644
index 0000000..c946917
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/CookbookVersionsParser.java
@@ -0,0 +1,41 @@
+/*
+ * 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 java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/**
+ * Used to configure the cookbook versions Json parser.
+ * <p>
+ * Chef Server version 0.9 and 0.10 return a different Json when rquesting the
+ * cookbook versions. This annotation can be used to setup the cookbook versions
+ * parser.
+ */
+@Target({ METHOD, PARAMETER, FIELD })
+@Retention(RUNTIME)
+@Qualifier
+public @interface CookbookVersionsParser {
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/config/InstallChef.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/InstallChef.java b/apis/chef/src/main/java/org/jclouds/chef/config/InstallChef.java
new file mode 100644
index 0000000..d40ae91
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/InstallChef.java
@@ -0,0 +1,37 @@
+/*
+ * 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 java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/**
+ * Used to configure the Chef install script.
+ */
+@Target({ METHOD, PARAMETER, FIELD })
+@Retention(RUNTIME)
+@Qualifier
+public @interface InstallChef {
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/config/Validator.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/Validator.java b/apis/chef/src/main/java/org/jclouds/chef/config/Validator.java
new file mode 100644
index 0000000..99825d8
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/config/Validator.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.config;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/**
+ * Used to configure the validator client information.
+ * <p>
+ * In a Chef server it must be only one validator client. This client is used by
+ * new nodes to autoregister themselves in the Chef server.
+ */
+@Target({ METHOD, PARAMETER, FIELD })
+@Retention(RUNTIME)
+@Qualifier
+public @interface Validator {
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/Attribute.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Attribute.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Attribute.java
new file mode 100644
index 0000000..34be6b7
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Attribute.java
@@ -0,0 +1,235 @@
+/*
+ * 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.List;
+import java.util.Set;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * An attribute in a cookbook metadata.
+ */
+public class Attribute {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private String required;
+      private boolean calculated;
+      private ImmutableSet.Builder<String> choice = ImmutableSet.builder();
+      private JsonBall defaultValue;
+      private String type;
+      private ImmutableList.Builder<String> recipes = ImmutableList.builder();
+      private String displayName;
+      private String description;
+
+      public Builder required(String required) {
+         this.required = checkNotNull(required, "required");
+         return this;
+      }
+
+      public Builder calculated(boolean calculated) {
+         this.calculated = calculated;
+         return this;
+      }
+
+      public Builder choice(String choice) {
+         this.choice.add(checkNotNull(choice, "choice"));
+         return this;
+      }
+
+      public Builder choices(Iterable<String> choices) {
+         this.choice.addAll(checkNotNull(choices, "choices"));
+         return this;
+      }
+
+      public Builder defaultValue(JsonBall defaultValue) {
+         this.defaultValue = checkNotNull(defaultValue, "defaultValue");
+         return this;
+      }
+
+      public Builder type(String type) {
+         this.type = checkNotNull(type, "type");
+         return this;
+      }
+
+      public Builder recipe(String recipe) {
+         this.recipes.add(checkNotNull(recipe, "recipe"));
+         return this;
+      }
+
+      public Builder recipes(Iterable<String> recipes) {
+         this.recipes.addAll(checkNotNull(recipes, "recipes"));
+         return this;
+      }
+
+      public Builder displayName(String displayName) {
+         this.displayName = checkNotNull(displayName, "displayName");
+         return this;
+      }
+
+      public Builder description(String description) {
+         this.description = checkNotNull(description, "description");
+         return this;
+      }
+
+      public Attribute build() {
+         return new Attribute(required, calculated, choice.build(), defaultValue, type, recipes.build(), displayName,
+               description);
+      }
+   }
+
+   private final String required;
+   private final boolean calculated;
+   private final Set<String> choice;
+   @SerializedName("default")
+   private final JsonBall defaultValue;
+   private final String type;
+   private final List<String> recipes;
+   @SerializedName("display_name")
+   private final String displayName;
+   private final String description;
+
+   @ConstructorProperties({ "required", "calculated", "choice", "default", "type", "recipes", "display_name",
+         "description" })
+   protected Attribute(String required, boolean calculated, @Nullable Set<String> choice, JsonBall defaultValue,
+         String type, @Nullable List<String> recipes, String displayName, String description) {
+      this.required = required;
+      this.calculated = calculated;
+      this.choice = copyOfOrEmpty(choice);
+      this.defaultValue = defaultValue;
+      this.type = type;
+      this.recipes = copyOfOrEmpty(recipes);
+      this.displayName = displayName;
+      this.description = description;
+   }
+
+   public String getRequired() {
+      return required;
+   }
+
+   public boolean isCalculated() {
+      return calculated;
+   }
+
+   public Set<String> getChoice() {
+      return choice;
+   }
+
+   public JsonBall getDefaultValue() {
+      return defaultValue;
+   }
+
+   public String getType() {
+      return type;
+   }
+
+   public List<String> getRecipes() {
+      return recipes;
+   }
+
+   public String getDisplayName() {
+      return displayName;
+   }
+
+   public String getDescription() {
+      return description;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + (calculated ? 1231 : 1237);
+      result = prime * result + ((choice == null) ? 0 : choice.hashCode());
+      result = prime * result + ((defaultValue == null) ? 0 : defaultValue.hashCode());
+      result = prime * result + ((description == null) ? 0 : description.hashCode());
+      result = prime * result + ((displayName == null) ? 0 : displayName.hashCode());
+      result = prime * result + ((recipes == null) ? 0 : recipes.hashCode());
+      result = prime * result + ((required == null) ? 0 : required.hashCode());
+      result = prime * result + ((type == null) ? 0 : type.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;
+      Attribute other = (Attribute) obj;
+      if (calculated != other.calculated)
+         return false;
+      if (choice == null) {
+         if (other.choice != null)
+            return false;
+      } else if (!choice.equals(other.choice))
+         return false;
+      if (defaultValue == null) {
+         if (other.defaultValue != null)
+            return false;
+      } else if (!defaultValue.equals(other.defaultValue))
+         return false;
+      if (description == null) {
+         if (other.description != null)
+            return false;
+      } else if (!description.equals(other.description))
+         return false;
+      if (displayName == null) {
+         if (other.displayName != null)
+            return false;
+      } else if (!displayName.equals(other.displayName))
+         return false;
+      if (recipes == null) {
+         if (other.recipes != null)
+            return false;
+      } else if (!recipes.equals(other.recipes))
+         return false;
+      if (required == null) {
+         if (other.required != null)
+            return false;
+      } else if (!required.equals(other.required))
+         return false;
+      if (type == null) {
+         if (other.type != null)
+            return false;
+      } else if (!type.equals(other.type))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "Attribute [calculated=" + calculated + ", choice=" + choice + ", defaultValue=" + defaultValue
+            + ", description=" + description + ", displayName=" + displayName + ", recipes=" + recipes + ", required="
+            + required + ", type=" + type + "]";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java b/apis/chef/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java
new file mode 100644
index 0000000..c10c150
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java
@@ -0,0 +1,95 @@
+/*
+ * 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.util.List;
+
+import org.jclouds.domain.JsonBall;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Configures how the nodes in a group will bootstrap.
+ * 
+ * @since 1.7
+ */
+public class BootstrapConfig {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private ImmutableList.Builder<String> runList = ImmutableList.builder();
+      private String environment;
+      private JsonBall attribtues;
+
+      /**
+       * Sets the run list that will be executed in the nodes of the group.
+       */
+      public Builder runList(Iterable<String> runList) {
+         this.runList.addAll(checkNotNull(runList, "runList"));
+         return this;
+      }
+
+      /**
+       * Sets the environment where the nodes in the group will be deployed.
+       */
+      public Builder environment(String environment) {
+         this.environment = checkNotNull(environment, "environment");
+         return this;
+      }
+
+      /**
+       * Sets the attributes that will be populated to the deployed nodes.
+       */
+      public Builder attributes(JsonBall attributes) {
+         this.attribtues = checkNotNull(attributes, "attributes");
+         return this;
+      }
+
+      public BootstrapConfig build() {
+         return new BootstrapConfig(runList.build(), Optional.fromNullable(environment),
+               Optional.fromNullable(attribtues));
+      }
+   }
+
+   private final List<String> runList;
+   private final Optional<String> environment;
+   private final Optional<JsonBall> attribtues;
+
+   protected BootstrapConfig(List<String> runList, Optional<String> environment, Optional<JsonBall> attribtues) {
+      this.runList = checkNotNull(runList, "runList");
+      this.environment = checkNotNull(environment, "environment");
+      this.attribtues = checkNotNull(attribtues, "attributes");
+   }
+
+   public List<String> getRunList() {
+      return runList;
+   }
+
+   public Optional<String> getEnvironment() {
+      return environment;
+   }
+
+   public Optional<JsonBall> getAttribtues() {
+      return attribtues;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/ChecksumStatus.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/ChecksumStatus.java b/apis/chef/src/main/java/org/jclouds/chef/domain/ChecksumStatus.java
new file mode 100644
index 0000000..e5a48ca
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/ChecksumStatus.java
@@ -0,0 +1,102 @@
+/*
+ * 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.net.URI;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The checksum of an uploaded resource.
+ */
+public class ChecksumStatus {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private URI url;
+      private boolean needsUpload;
+
+      public Builder url(URI url) {
+         this.url = checkNotNull(url, "url");
+         return this;
+      }
+
+      public Builder needsUpload(boolean needsUpload) {
+         this.needsUpload = needsUpload;
+         return this;
+      }
+
+      public ChecksumStatus build() {
+         return new ChecksumStatus(url, needsUpload);
+      }
+   }
+
+   private final URI url;
+   @SerializedName("needs_upload")
+   private final boolean needsUpload;
+
+   @ConstructorProperties({ "url", "needs_upload" })
+   protected ChecksumStatus(URI url, boolean needsUpload) {
+      this.url = url;
+      this.needsUpload = needsUpload;
+   }
+
+   public URI getUrl() {
+      return url;
+   }
+
+   public boolean needsUpload() {
+      return needsUpload;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + (needsUpload ? 1231 : 1237);
+      result = prime * result + ((url == null) ? 0 : url.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;
+      ChecksumStatus other = (ChecksumStatus) obj;
+      if (needsUpload != other.needsUpload)
+         return false;
+      if (url == null) {
+         if (other.url != null)
+            return false;
+      } else if (!url.equals(other.url))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "ChecksumStatus [needsUpload=" + needsUpload + ", url=" + url + "]";
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/Client.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Client.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Client.java
new file mode 100644
index 0000000..7335609
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Client.java
@@ -0,0 +1,182 @@
+/*
+ * 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.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Client object.
+ */
+public class Client {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private X509Certificate certificate;
+      private PrivateKey privateKey;
+      private String orgname;
+      private String clientname;
+      private String name;
+      private boolean validator;
+
+      public Builder certificate(X509Certificate certificate) {
+         this.certificate = checkNotNull(certificate, "certificate");
+         return this;
+      }
+
+      public Builder privateKey(PrivateKey privateKey) {
+         this.privateKey = checkNotNull(privateKey, "privateKey");
+         return this;
+      }
+
+      public Builder orgname(String orgname) {
+         this.orgname = checkNotNull(orgname, "orgname");
+         return this;
+      }
+
+      public Builder clientname(String clientname) {
+         this.clientname = checkNotNull(clientname, "clientname");
+         return this;
+      }
+
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      public Builder isValidator(boolean validator) {
+         this.validator = validator;
+         return this;
+      }
+
+      public Client build() {
+         return new Client(certificate, orgname, clientname, name, validator, privateKey);
+      }
+   }
+
+   private final X509Certificate certificate;
+   @SerializedName("private_key")
+   private final PrivateKey privateKey;
+   private final String orgname;
+   private final String clientname;
+   private final String name;
+   private final boolean validator;
+
+   @ConstructorProperties({ "certificate", "orgname", "clientname", "name", "validator", "private_key" })
+   protected Client(X509Certificate certificate, String orgname, String clientname, String name, boolean validator,
+         @Nullable PrivateKey privateKey) {
+      this.certificate = certificate;
+      this.orgname = orgname;
+      this.clientname = clientname;
+      this.name = name;
+      this.validator = validator;
+      this.privateKey = privateKey;
+   }
+
+   public PrivateKey getPrivateKey() {
+      return privateKey;
+   }
+
+   public X509Certificate getCertificate() {
+      return certificate;
+   }
+
+   public String getOrgname() {
+      return orgname;
+   }
+
+   public String getClientname() {
+      return clientname;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public boolean isValidator() {
+      return validator;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((certificate == null) ? 0 : certificate.hashCode());
+      result = prime * result + ((clientname == null) ? 0 : clientname.hashCode());
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((orgname == null) ? 0 : orgname.hashCode());
+      result = prime * result + ((privateKey == null) ? 0 : privateKey.hashCode());
+      result = prime * result + (validator ? 1231 : 1237);
+      return result;
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null)
+         return false;
+      if (getClass() != obj.getClass())
+         return false;
+      Client other = (Client) obj;
+      if (certificate == null) {
+         if (other.certificate != null)
+            return false;
+      } else if (!certificate.equals(other.certificate))
+         return false;
+      if (clientname == null) {
+         if (other.clientname != null)
+            return false;
+      } else if (!clientname.equals(other.clientname))
+         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;
+      if (privateKey == null) {
+         if (other.privateKey != null)
+            return false;
+      } else if (!privateKey.equals(other.privateKey))
+         return false;
+      if (validator != other.validator)
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "Client [name=" + name + ", clientname=" + clientname + ", orgname=" + orgname + ", isValidator="
+            + validator + ", certificate=" + certificate + ", privateKey=" + (privateKey == null ? "not " : "")
+            + "present]";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/CookbookDefinition.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/CookbookDefinition.java b/apis/chef/src/main/java/org/jclouds/chef/domain/CookbookDefinition.java
new file mode 100644
index 0000000..17db702
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/CookbookDefinition.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
+
+import java.beans.ConstructorProperties;
+import java.net.URI;
+import java.util.Set;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Cookbook definition as returned by the Chef server >= 0.10.8.
+ */
+public class CookbookDefinition {
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      private String name;
+      private URI url;
+      private ImmutableSet.Builder<Version> versions = ImmutableSet.builder();
+
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      public Builder url(URI url) {
+         this.url = checkNotNull(url, "url");
+         return this;
+      }
+
+      public Builder version(Version version) {
+         this.versions.add(checkNotNull(version, "version"));
+         return this;
+      }
+
+      public Builder versions(Iterable<Version> versions) {
+         this.versions.addAll(checkNotNull(versions, "versions"));
+         return this;
+      }
+
+      public Builder from(CookbookDefinition def) {
+         this.url = checkNotNull(def.getUrl(), "url");
+         this.versions.addAll(checkNotNull(def.getVersions(), "versions"));
+         this.name = def.getName();
+         return this;
+      }
+
+      public CookbookDefinition build() {
+         return new CookbookDefinition(name, url, versions.build());
+      }
+   }
+
+   private final String name;
+   private final URI url;
+   private final Set<Version> versions;
+
+   @ConstructorProperties({"name", "url", "versions" })
+   protected CookbookDefinition(String name, URI url, @Nullable Set<Version> versions) {
+      this.name = name;
+      this.url = url;
+      this.versions = copyOfOrEmpty(versions);
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public URI getUrl() {
+      return url;
+   }
+
+   public Set<Version> getVersions() {
+      return versions;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((url == null) ? 0 : url.hashCode());
+      result = prime * result + ((versions == null) ? 0 : versions.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;
+      CookbookDefinition other = (CookbookDefinition) obj;
+      if (name == null) {
+         if (other.name != null)
+            return false;
+      } else if (!name.equals(other.name))
+         return false;
+      if (url == null) {
+         if (other.url != null)
+            return false;
+      } else if (!url.equals(other.url))
+         return false;
+      if (versions == null) {
+         if (other.versions != null)
+            return false;
+      } else if (!versions.equals(other.versions))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "CookbookDefinition [name=" + name + ", url=" + url + ", versions=" + versions + "]";
+   }
+
+   public static class Version {
+      public static Builder builder() {
+         return new Builder();
+      }
+
+      public static class Builder {
+         private URI url;
+         private String version;
+
+         public Builder url(URI url) {
+            this.url = checkNotNull(url, "url");
+            return this;
+         }
+
+         public Builder version(String version) {
+            this.version = checkNotNull(version, "version");
+            return this;
+         }
+
+         public Version build() {
+            return new Version(url, version);
+         }
+      }
+
+      private final URI url;
+      private final String version;
+
+      @ConstructorProperties({ "url", "version" })
+      protected Version(URI url, String version) {
+         this.url = url;
+         this.version = version;
+      }
+
+      public URI getUrl() {
+         return url;
+      }
+
+      public String getVersion() {
+         return version;
+      }
+
+      @Override
+      public int hashCode() {
+         final int prime = 31;
+         int result = 1;
+         result = prime * result + ((url == null) ? 0 : url.hashCode());
+         result = prime * result + ((version == null) ? 0 : version.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;
+         Version other = (Version) obj;
+         if (url == null) {
+            if (other.url != null)
+               return false;
+         } else if (!url.equals(other.url))
+            return false;
+         if (version == null) {
+            if (other.version != null)
+               return false;
+         } else if (!version.equals(other.version))
+            return false;
+         return true;
+      }
+
+      @Override
+      public String toString() {
+         return "Version [url=" + url + ", version=" + version + "]";
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/CookbookVersion.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/CookbookVersion.java b/apis/chef/src/main/java/org/jclouds/chef/domain/CookbookVersion.java
new file mode 100644
index 0000000..df9bd82
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/CookbookVersion.java
@@ -0,0 +1,369 @@
+/*
+ * 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 static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
+
+import java.beans.ConstructorProperties;
+import java.util.Set;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Cookbook object.
+ */
+public class CookbookVersion {
+   public static Builder builder(String name, String version) {
+      return new Builder(name, version);
+   }
+
+   public static class Builder {
+      private String cookbookName;
+      private ImmutableSet.Builder<Resource> definitions = ImmutableSet.builder();
+      private ImmutableSet.Builder<Attribute> attributes = ImmutableSet.builder();
+      private ImmutableSet.Builder<Resource> files = ImmutableSet.builder();
+      private Metadata metadata = Metadata.builder().build();
+      private ImmutableSet.Builder<Resource> providers = ImmutableSet.builder();
+      private ImmutableSet.Builder<Resource> resources = ImmutableSet.builder();
+      private ImmutableSet.Builder<Resource> templates = ImmutableSet.builder();
+      private ImmutableSet.Builder<Resource> libraries = ImmutableSet.builder();
+      private String version;
+      private ImmutableSet.Builder<Resource> recipes = ImmutableSet.builder();
+      private ImmutableSet.Builder<Resource> rootFiles = ImmutableSet.builder();
+
+      public Builder(String name, String version) {
+         this.cookbookName = checkNotNull(name, "name");
+         this.version = checkNotNull(version, "version");
+      }
+
+      public Builder cookbookName(String cookbookName) {
+         this.cookbookName = checkNotNull(cookbookName, "cookbookName");
+         return this;
+      }
+
+      public Builder definition(Resource definition) {
+         this.definitions.add(checkNotNull(definition, "definition"));
+         return this;
+      }
+
+      public Builder definitions(Iterable<Resource> definitions) {
+         this.definitions.addAll(checkNotNull(definitions, "definitions"));
+         return this;
+      }
+
+      public Builder attribute(Attribute attribute) {
+         this.attributes.add(checkNotNull(attribute, "attribute"));
+         return this;
+      }
+
+      public Builder attributes(Iterable<Attribute> attributes) {
+         this.attributes.addAll(checkNotNull(attributes, "attributes"));
+         return this;
+      }
+
+      public Builder file(Resource file) {
+         this.files.add(checkNotNull(file, "file"));
+         return this;
+      }
+
+      public Builder files(Iterable<Resource> files) {
+         this.files.addAll(checkNotNull(files, "files"));
+         return this;
+      }
+
+      public Builder metadata(Metadata metadata) {
+         this.metadata = checkNotNull(metadata, "metadata");
+         return this;
+      }
+
+      public Builder provider(Resource provider) {
+         this.providers.add(checkNotNull(provider, "provider"));
+         return this;
+      }
+
+      public Builder providers(Iterable<Resource> providers) {
+         this.providers.addAll(checkNotNull(providers, "providers"));
+         return this;
+      }
+
+      public Builder resource(Resource resource) {
+         this.resources.add(checkNotNull(resource, "resource"));
+         return this;
+      }
+
+      public Builder resources(Iterable<Resource> resources) {
+         this.resources.addAll(checkNotNull(resources, "resources"));
+         return this;
+      }
+
+      public Builder template(Resource template) {
+         this.templates.add(checkNotNull(template, "template"));
+         return this;
+      }
+
+      public Builder templates(Iterable<Resource> templates) {
+         this.templates.addAll(checkNotNull(templates, "templates"));
+         return this;
+      }
+
+      public Builder library(Resource library) {
+         this.libraries.add(checkNotNull(library, "library"));
+         return this;
+      }
+
+      public Builder libraries(Iterable<Resource> libraries) {
+         this.libraries.addAll(checkNotNull(libraries, "libraries"));
+         return this;
+      }
+
+      public Builder version(String version) {
+         this.version = checkNotNull(version, "version");
+         return this;
+      }
+
+      public Builder recipe(Resource recipe) {
+         this.recipes.add(checkNotNull(recipe, "recipe"));
+         return this;
+      }
+
+      public Builder recipes(Iterable<Resource> recipes) {
+         this.recipes.addAll(checkNotNull(recipes, "recipes"));
+         return this;
+      }
+
+      public Builder rootFile(Resource rootFile) {
+         this.rootFiles.add(checkNotNull(rootFile, "rootFile"));
+         return this;
+      }
+
+      public Builder rootFiles(Iterable<Resource> rootFiles) {
+         this.rootFiles.addAll(checkNotNull(rootFiles, "rootFiles"));
+         return this;
+      }
+
+      public CookbookVersion build() {
+         return new CookbookVersion(checkNotNull(cookbookName, "name") + "-" + checkNotNull(version, "version"),
+               definitions.build(), attributes.build(), files.build(), metadata, providers.build(), cookbookName,
+               resources.build(), templates.build(), libraries.build(), version, recipes.build(), rootFiles.build());
+      }
+   }
+
+   private final String name;
+   private final Set<Resource> definitions;
+   private final Set<Attribute> attributes;
+   private final Set<Resource> files;
+   private final Metadata metadata;
+   private final Set<Resource> providers;
+   @SerializedName("cookbook_name")
+   private final String cookbookName;
+   private final Set<Resource> resources;
+   private final Set<Resource> templates;
+   private final Set<Resource> libraries;
+   private final String version;
+   private final Set<Resource> recipes;
+   @SerializedName("root_files")
+   private final Set<Resource> rootFiles;
+
+   // internal
+   @SerializedName("json_class")
+   private String _jsonClass = "Chef::CookbookVersion";
+   @SerializedName("chef_type")
+   private String _chefType = "cookbook_version";
+
+   @ConstructorProperties({ "name", "definitions", "attributes", "files", "metadata", "providers", "cookbook_name",
+         "resources", "templates", "libraries", "version", "recipes", "root_files" })
+   protected CookbookVersion(String name, @Nullable Set<Resource> definitions, @Nullable Set<Attribute> attributes,
+         @Nullable Set<Resource> files, Metadata metadata, @Nullable Set<Resource> providers, String cookbookName,
+         @Nullable Set<Resource> resources, @Nullable Set<Resource> templates, @Nullable Set<Resource> libraries,
+         String version, @Nullable Set<Resource> recipes, @Nullable Set<Resource> rootFiles) {
+      this.name = name;
+      this.definitions = copyOfOrEmpty(definitions);
+      this.attributes = copyOfOrEmpty(attributes);
+      this.files = copyOfOrEmpty(files);
+      this.metadata = metadata;
+      this.providers = copyOfOrEmpty(providers);
+      this.cookbookName = cookbookName;
+      this.resources = copyOfOrEmpty(resources);
+      this.templates = copyOfOrEmpty(templates);
+      this.libraries = copyOfOrEmpty(libraries);
+      this.version = version;
+      this.recipes = copyOfOrEmpty(recipes);
+      this.rootFiles = copyOfOrEmpty(rootFiles);
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public Set<Resource> getDefinitions() {
+      return definitions;
+   }
+
+   public Set<Attribute> getAttributes() {
+      return attributes;
+   }
+
+   public Set<Resource> getFiles() {
+      return files;
+   }
+
+   public Metadata getMetadata() {
+      return metadata;
+   }
+
+   public Set<Resource> getSuppliers() {
+      return providers;
+   }
+
+   public String getCookbookName() {
+      return cookbookName;
+   }
+
+   public Set<Resource> getResources() {
+      return resources;
+   }
+
+   public Set<Resource> getTemplates() {
+      return templates;
+   }
+
+   public Set<Resource> getLibraries() {
+      return libraries;
+   }
+
+   public String getVersion() {
+      return version;
+   }
+
+   public Set<Resource> getRecipes() {
+      return recipes;
+   }
+
+   public Set<Resource> getRootFiles() {
+      return rootFiles;
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((attributes == null) ? 0 : attributes.hashCode());
+      result = prime * result + ((cookbookName == null) ? 0 : cookbookName.hashCode());
+      result = prime * result + ((definitions == null) ? 0 : definitions.hashCode());
+      result = prime * result + ((files == null) ? 0 : files.hashCode());
+      result = prime * result + ((libraries == null) ? 0 : libraries.hashCode());
+      result = prime * result + ((metadata == null) ? 0 : metadata.hashCode());
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((providers == null) ? 0 : providers.hashCode());
+      result = prime * result + ((recipes == null) ? 0 : recipes.hashCode());
+      result = prime * result + ((resources == null) ? 0 : resources.hashCode());
+      result = prime * result + ((rootFiles == null) ? 0 : rootFiles.hashCode());
+      result = prime * result + ((templates == null) ? 0 : templates.hashCode());
+      result = prime * result + ((version == null) ? 0 : version.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;
+      CookbookVersion other = (CookbookVersion) obj;
+      if (attributes == null) {
+         if (other.attributes != null)
+            return false;
+      } else if (!attributes.equals(other.attributes))
+         return false;
+      if (cookbookName == null) {
+         if (other.cookbookName != null)
+            return false;
+      } else if (!cookbookName.equals(other.cookbookName))
+         return false;
+      if (definitions == null) {
+         if (other.definitions != null)
+            return false;
+      } else if (!definitions.equals(other.definitions))
+         return false;
+      if (files == null) {
+         if (other.files != null)
+            return false;
+      } else if (!files.equals(other.files))
+         return false;
+      if (libraries == null) {
+         if (other.libraries != null)
+            return false;
+      } else if (!libraries.equals(other.libraries))
+         return false;
+      if (metadata == null) {
+         if (other.metadata != null)
+            return false;
+      } else if (!metadata.equals(other.metadata))
+         return false;
+      if (name == null) {
+         if (other.name != null)
+            return false;
+      } else if (!name.equals(other.name))
+         return false;
+      if (providers == null) {
+         if (other.providers != null)
+            return false;
+      } else if (!providers.equals(other.providers))
+         return false;
+      if (recipes == null) {
+         if (other.recipes != null)
+            return false;
+      } else if (!recipes.equals(other.recipes))
+         return false;
+      if (resources == null) {
+         if (other.resources != null)
+            return false;
+      } else if (!resources.equals(other.resources))
+         return false;
+      if (rootFiles == null) {
+         if (other.rootFiles != null)
+            return false;
+      } else if (!rootFiles.equals(other.rootFiles))
+         return false;
+      if (templates == null) {
+         if (other.templates != null)
+            return false;
+      } else if (!templates.equals(other.templates))
+         return false;
+      if (version == null) {
+         if (other.version != null)
+            return false;
+      } else if (!version.equals(other.version))
+         return false;
+      return true;
+   }
+
+   @Override
+   public String toString() {
+      return "Cookbook [attributes=" + attributes + ", cookbookName=" + cookbookName + ", definitions=" + definitions
+            + ", files=" + files + ", libraries=" + libraries + ", metadata=" + metadata + ", name=" + name
+            + ", providers=" + providers + ", recipes=" + recipes + ", resources=" + resources + ", rootFiles="
+            + rootFiles + ", templates=" + templates + ", version=" + version + "]";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/domain/DatabagItem.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/DatabagItem.java b/apis/chef/src/main/java/org/jclouds/chef/domain/DatabagItem.java
new file mode 100644
index 0000000..ec68d99
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/domain/DatabagItem.java
@@ -0,0 +1,63 @@
+/*
+ * 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 org.jclouds.domain.JsonBall;
+
+/**
+ * An item in a data bag.
+ */
+public class DatabagItem extends JsonBall {
+
+   private final String id;
+
+   public DatabagItem(String id, String value) {
+      super(value);
+      this.id = checkNotNull(id, "id");
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = super.hashCode();
+      result = prime * result + ((id == null) ? 0 : id.hashCode());
+      return result;
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (!super.equals(obj))
+         return false;
+      if (getClass() != obj.getClass())
+         return false;
+      DatabagItem other = (DatabagItem) obj;
+      if (id == null) {
+         if (other.id != null)
+            return false;
+      } else if (!id.equals(other.id))
+         return false;
+      return true;
+   }
+
+   public String getId() {
+      return id;
+   }
+}


[05/10] Move jclouds-chef to the main jclouds repo

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/config/OhaiModule.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/config/OhaiModule.java b/apis/chef/src/main/java/org/jclouds/ohai/config/OhaiModule.java
new file mode 100644
index 0000000..650fe9e
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/config/OhaiModule.java
@@ -0,0 +1,183 @@
+/*
+ * 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.ohai.config;
+
+import static org.jclouds.chef.util.ChefUtils.ohaiAutomaticAttributeBinder;
+import static org.jclouds.chef.util.ChefUtils.toOhaiTime;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+import org.jclouds.ohai.Automatic;
+import org.jclouds.ohai.AutomaticSupplier;
+import org.jclouds.ohai.functions.ByteArrayToMacAddress;
+import org.jclouds.ohai.functions.MapSetToMultimap;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Multimap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.multibindings.MapBinder;
+
+/**
+ * Wires the components needed to parse ohai data
+ */
+@ConfiguresOhai
+public class OhaiModule extends AbstractModule {
+
+   @Override
+   protected void configure() {
+      bind(new TypeLiteral<Function<byte[], String>>() {
+      }).to(new TypeLiteral<ByteArrayToMacAddress>() {
+      });
+      bindOhai();
+   }
+
+   @Provides
+   @Automatic
+   protected Supplier<Map<String, JsonBall>> provideAutomatic(AutomaticSupplier in) {
+      return in;
+   }
+
+   @Provides
+   @Automatic
+   Multimap<String, Supplier<JsonBall>> provideAutomatic(MapSetToMultimap<String, Supplier<JsonBall>> converter,
+         @Automatic Map<String, Set<Supplier<JsonBall>>> input) {
+      return converter.apply(input);
+
+   }
+
+   @Named("systemProperties")
+   @Provides
+   protected Properties systemProperties() {
+      return System.getProperties();
+   }
+
+   public MapBinder<String, Supplier<JsonBall>> bindOhai() {
+      MapBinder<String, Supplier<JsonBall>> mapbinder = ohaiAutomaticAttributeBinder(binder()).permitDuplicates();
+      mapbinder.addBinding("ohai_time").to(OhaiTimeProvider.class);
+      mapbinder.addBinding("jvm/system").to(SystemPropertiesProvider.class);
+      mapbinder.addBinding("platform").to(PlatformProvider.class);
+      mapbinder.addBinding("platform_version").to(PlatformVersionProvider.class);
+      mapbinder.addBinding("current_user").to(CurrentUserProvider.class);
+      return mapbinder;
+   }
+
+   @Singleton
+   public static class OhaiTimeProvider implements Supplier<JsonBall> {
+      private final Provider<Long> timeProvider;
+
+      @Inject
+      OhaiTimeProvider(Provider<Long> timeProvider) {
+         this.timeProvider = timeProvider;
+      }
+
+      @Override
+      public JsonBall get() {
+         return toOhaiTime(timeProvider.get());
+      }
+
+   }
+
+   @Provides
+   protected Long millis() {
+      return System.currentTimeMillis();
+   }
+
+   @Singleton
+   public static class SystemPropertiesProvider implements Supplier<JsonBall> {
+
+      private final Json json;
+      private final Properties systemProperties;
+
+      @Inject
+      SystemPropertiesProvider(Json json, @Named("systemProperties") Properties systemProperties) {
+         this.json = json;
+         this.systemProperties = systemProperties;
+      }
+
+      @Override
+      public JsonBall get() {
+         return new JsonBall(json.toJson(systemProperties));
+      }
+
+   }
+
+   @Singleton
+   public static class PlatformProvider extends SystemPropertyProvider {
+
+      @Inject
+      PlatformProvider(@Named("systemProperties") Properties systemProperties) {
+         super("os.name", systemProperties);
+      }
+
+      @Override
+      public JsonBall get() {
+         JsonBall returnValue = super.get();
+         return returnValue != null ? new JsonBall(returnValue.toString().replaceAll("[ -]", "").toLowerCase()) : null;
+      }
+
+   }
+
+   @Singleton
+   public static class PlatformVersionProvider extends SystemPropertyProvider {
+
+      @Inject
+      PlatformVersionProvider(@Named("systemProperties") Properties systemProperties) {
+         super("os.version", systemProperties);
+      }
+
+   }
+
+   @Singleton
+   public static class CurrentUserProvider extends SystemPropertyProvider {
+
+      @Inject
+      CurrentUserProvider(@Named("systemProperties") Properties systemProperties) {
+         super("user.name", systemProperties);
+      }
+
+   }
+
+   public static class SystemPropertyProvider implements Supplier<JsonBall> {
+      private final Properties systemProperties;
+      private final String property;
+
+      @Inject
+      SystemPropertyProvider(String property, @Named("systemProperties") Properties systemProperties) {
+         this.property = property;
+         this.systemProperties = systemProperties;
+      }
+
+      @Override
+      public JsonBall get() {
+         return systemProperties.containsKey(property) ? new JsonBall(systemProperties.getProperty(property)) : null;
+      }
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/functions/ByteArrayToMacAddress.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/functions/ByteArrayToMacAddress.java b/apis/chef/src/main/java/org/jclouds/ohai/functions/ByteArrayToMacAddress.java
new file mode 100644
index 0000000..be7e46a
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/functions/ByteArrayToMacAddress.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ohai.functions;
+
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Lists.partition;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.asList;
+import static com.google.common.primitives.Bytes.toArray;
+
+import java.util.List;
+
+import javax.inject.Singleton;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+
+/**
+ * 
+ * Creates a string in the form: {@code 00:26:bb:09:e6:c4 }
+ */
+@Singleton
+public class ByteArrayToMacAddress implements Function<byte[], String> {
+
+   @Override
+   public String apply(byte[] from) {
+      return Joiner.on(':').join(transform(partition(asList(from), 1), new Function<List<Byte>, String>() {
+
+         @Override
+         public String apply(List<Byte> from) {
+            return base16().lowerCase().encode(toArray(from));
+         }
+
+      }));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java b/apis/chef/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java
new file mode 100644
index 0000000..4ef3c87
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.ohai.functions;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.inject.Singleton;
+
+import com.google.common.base.Function;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+
+@Singleton
+public class MapSetToMultimap<K, V> implements Function<Map<K, Set<V>>, Multimap<K, V>> {
+
+   @Override
+   public Multimap<K, V> apply(Map<K, Set<V>> from) {
+      Multimap<K, V> returnV = LinkedHashMultimap.create();
+      for (Entry<K, Set<V>> entry : from.entrySet()) {
+         for (V value : entry.getValue())
+            returnV.put(entry.getKey(), value);
+      }
+      return returnV;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java b/apis/chef/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java
new file mode 100644
index 0000000..c709463
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java
@@ -0,0 +1,156 @@
+/*
+ * 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.ohai.functions;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.inject.TypeLiteral;
+
+@Singleton
+public class NestSlashKeys implements Function<Multimap<String, Supplier<JsonBall>>, Map<String, JsonBall>> {
+
+   private final Json json;
+
+   @Inject
+   NestSlashKeys(Json json) {
+      this.json = checkNotNull(json, "json");
+   }
+
+   @Override
+   public Map<String, JsonBall> apply(Multimap<String, Supplier<JsonBall>> from) {
+
+      Map<String, JsonBall> autoAttrs = mergeSameKeys(from);
+
+      Map<String, JsonBall> modifiableFlatMap = Maps.newLinkedHashMap(Maps.filterKeys(autoAttrs,
+            new Predicate<String>() {
+
+               @Override
+               public boolean apply(String input) {
+                  return input.indexOf('/') == -1;
+               }
+
+            }));
+      Map<String, JsonBall> withSlashesMap = Maps.difference(autoAttrs, modifiableFlatMap).entriesOnlyOnLeft();
+      for (Entry<String, JsonBall> entry : withSlashesMap.entrySet()) {
+         List<String> keyParts = Lists.newArrayList(Splitter.on('/').split(entry.getKey()));
+         JsonBall toInsert = entry.getValue();
+         try {
+            putUnderContext(keyParts, toInsert, modifiableFlatMap);
+         } catch (IllegalArgumentException e) {
+            throw new IllegalArgumentException("error inserting value in entry: " + entry.getKey(), e);
+         }
+      }
+      return modifiableFlatMap;
+   }
+
+   private Map<String, JsonBall> mergeSameKeys(Multimap<String, Supplier<JsonBall>> from) {
+      Map<String, JsonBall> merged = Maps.newLinkedHashMap();
+      for (Entry<String, Supplier<JsonBall>> entry : from.entries()) {
+         if (merged.containsKey(entry.getKey())) {
+            mergeAsPeer(entry.getKey(), entry.getValue().get(), merged);
+         } else {
+            merged.put(entry.getKey(), entry.getValue().get());
+         }
+      }
+      return merged;
+   }
+
+   @VisibleForTesting
+   void mergeAsPeer(String key, JsonBall value, Map<String, JsonBall> insertionContext) {
+      Map<String, JsonBall> immutableValueContext = json.fromJson(insertionContext.get(key).toString(), mapLiteral);
+      Map<String, JsonBall> valueContext = Maps.newHashMap(immutableValueContext);
+      Map<String, JsonBall> toPut = json.<Map<String, JsonBall>> fromJson(value.toString(), mapLiteral);
+      Set<String> uniques = Sets.difference(toPut.keySet(), valueContext.keySet());
+      for (String k : uniques) {
+         valueContext.put(k, toPut.get(k));
+      }
+      Set<String> conflicts = Sets.difference(toPut.keySet(), uniques);
+      for (String k : conflicts) {
+         JsonBall v = toPut.get(k);
+         if (v.toString().matches("^\\{.*\\}$")) {
+            mergeAsPeer(k, v, valueContext);
+         } else {
+            // replace
+            valueContext.put(k, v);
+         }
+      }
+      insertionContext.put(key, new JsonBall(json.toJson(valueContext, mapLiteral)));
+   }
+
+   /**
+    * @param keyParts
+    * @param toInsert
+    * @param destination
+    * @throws IllegalArgumentException
+    *            <p/>
+    *            if destination.get(keyParts(0)) is not a map *
+    *            <p/>
+    *            keyParts is zero length
+    */
+   void putUnderContext(List<String> keyParts, JsonBall toInsert, Map<String, JsonBall> destination) {
+      checkNotNull(keyParts, "keyParts");
+      checkArgument(keyParts.size() >= 1, "keyParts must contain at least one element");
+
+      checkNotNull(toInsert, "toInsert");
+      checkNotNull(destination, "destination");
+
+      String rootKey = keyParts.remove(0);
+      String rootValue = destination.containsKey(rootKey) ? destination.get(rootKey).toString() : "{}";
+
+      checkArgument(rootValue.matches("^\\{.*\\}$"), "value must be a hash: %s", rootValue);
+      Map<String, JsonBall> immutableInsertionContext = json.fromJson(rootValue, mapLiteral);
+      Map<String, JsonBall> insertionContext = Maps.newHashMap(immutableInsertionContext);
+      if (keyParts.size() == 1) {
+         if (!insertionContext.containsKey(keyParts.get(0))) {
+            insertionContext.put(keyParts.get(0), toInsert);
+         } else {
+            String key = keyParts.get(0);
+            mergeAsPeer(key, toInsert, insertionContext);
+         }
+      } else {
+         putUnderContext(keyParts, toInsert, insertionContext);
+      }
+      destination.put(rootKey, new JsonBall(json.toJson(insertionContext, mapLiteral)));
+   }
+
+   final Type mapLiteral = new TypeLiteral<Map<String, JsonBall>>() {
+   }.getType();
+   final Type listLiteral = new TypeLiteral<List<JsonBall>>() {
+   }.getType();
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java b/apis/chef/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java
new file mode 100644
index 0000000..9713902
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java
@@ -0,0 +1,44 @@
+/*
+ * 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.ohai.suppliers;
+
+import java.lang.management.RuntimeMXBean;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.domain.JsonBall;
+
+import com.google.common.base.Supplier;
+
+@Singleton
+public class UptimeSecondsSupplier implements Supplier<JsonBall> {
+
+   @Inject
+   UptimeSecondsSupplier(RuntimeMXBean runtime) {
+      this.runtime = runtime;
+   }
+
+   private final RuntimeMXBean runtime;
+
+   @Override
+   public JsonBall get() {
+      long uptimeInSeconds = runtime.getUptime() / 1000;
+      return new JsonBall(uptimeInSeconds);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/apis/chef/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
new file mode 100644
index 0000000..07e3240
--- /dev/null
+++ b/apis/chef/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
@@ -0,0 +1,2 @@
+org.jclouds.chef.test.TransientChefApiMetadata
+org.jclouds.chef.ChefApiMetadata
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/clojure/org/jclouds/chef_test.clj
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/clojure/org/jclouds/chef_test.clj b/apis/chef/src/test/clojure/org/jclouds/chef_test.clj
new file mode 100644
index 0000000..bc542f1
--- /dev/null
+++ b/apis/chef/src/test/clojure/org/jclouds/chef_test.clj
@@ -0,0 +1,70 @@
+;
+; Licensed to the Apache Software Foundation (ASF) under one or more
+; contributor license agreements.  See the NOTICE file distributed with
+; this work for additional information regarding copyright ownership.
+; The ASF licenses this file to You under the Apache License, Version 2.0
+; (the "License"); you may not use this file except in compliance with
+; the License.  You may obtain a copy of the License at
+;
+;     http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+
+(ns org.jclouds.chef-test
+  (:use [org.jclouds.chef] :reload-all)
+  (:use [clojure.test]))
+
+(defn clean-stub-fixture
+  "This should allow basic tests to easily be run with another service."
+  [service account key & options]
+  (fn [f]
+ (with-chef-service [(apply chef-service service account key options)]
+(doseq [databag (databags)]
+  (delete-databag databag))
+(f))))
+
+(use-fixtures :each (clean-stub-fixture "transientchef" "" ""))
+
+(deftest chef-service?-test
+  (is (chef-service? *chef*)))
+
+(deftest as-chef-service-test
+  (is (chef-service? (chef-service "transientchef" "" "")))
+  (is (chef-service? (as-chef-service *chef*)))
+  (is (chef-service? (as-chef-service (chef-context *chef*)))))
+
+(deftest create-existing-databag-test
+  (is (not (databag-exists? "")))
+  (create-databag "fred")
+  (is (databag-exists? "fred")))
+
+(deftest create-databag-test
+  (create-databag "fred")
+  (is (databag-exists? "fred")))
+
+(deftest databags-test
+  (is (empty? (databags)))
+  (create-databag "fred")
+  (is (= 1 (count (databags)))))
+
+(deftest databag-items-test
+  (create-databag "databag")
+  (is (empty? (databag-items "databag")))
+  (is (create-databag-item "databag" {:id "databag-item1" :value "databag-value1"}))
+  (is (create-databag-item "databag" {:id "databag-item2" :value "databag-value2"}))
+  (is (= 2 (count (databag-items "databag")))))
+
+(deftest databag-item-test
+  (create-databag "databag")
+  (is (create-databag-item "databag" {:id "databag-item1" :value "databag-value1"}))
+  (is (create-databag-item "databag" {:id "databag-item2" :value "databag-value2"}))
+  (is (= {:id "databag-item2" :value "databag-value2"} (databag-item "databag" "databag-item2"))))
+
+(deftest run-list-test
+  (update-run-list #{"recipe[foo]"} "tag")
+  (is (= ["recipe[foo]"] (run-list "tag"))))

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..23013a9
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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 org.jclouds.chef.filters.SignedHeaderAuth;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.rest.internal.BaseRestApiExpectTest;
+
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Base class for Chef Api expect tests.
+ */
+public abstract class BaseChefApiExpectTest<S> extends BaseRestApiExpectTest<S> {
+   public static final String PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAyb2ZJJqGm0KKR+8nfQJNsSd+F9tXNMV7CfOcW6jsqs8EZgiV\nR09hD1IYOj4YqM0qJONlgyg4xRWewdSG7QTPj1lJpVAida9sXy2+kzyagZA1Am0O\nZcbqb5hoeIDgcX+eDa79s0u0DomjcfO9EKhvHLBz+zM+3QqPRkPV8nYTbfs+HjVz\nzOU6D1B0XR3+IPZZl2AnWs2d0qhnStHcDUvnRVQ0P482YwN9VgceOZtpPz0DCKEJ\n5Tx5STub8k0/zt/VAMHQafLSuQMLd2s4ZLuOZptN//uAsTmxireqd37z+8ZTdBbJ\n8LEpJ+iCXuSfm5aUh7iw6oxvToY2AL53+jK2UQIDAQABAoIBAQDA88B3i/xWn0vX\nBVxFamCYoecuNjGwXXkSyZew616A+EOCu47bh4aTurdFbYL0YFaAtaWvzlaN2eHg\nDb+HDuTefE29+WkcGk6SshPmiz5T0XOCAICWw6wSVDkHmGwS4jZvbAFm7W8nwGk9\nYhxgxFiRngswJZFopOLoF5WXs2td8guIYNslMpo7tu50iFnBHwKO2ZsPAk8t9nnS\nxlDavKruymEmqHCr3+dtio5eaenJcp3fjoXBQOKUk3ipII29XRB8NqeCVV/7Kxwq\nckqOBEbRwBclckyIbD+RiAgKvOelORjEiE9R42vuqvxRA6k9kd9o7utlX0AUtpEn\n3gZc6LepAoGBAP9ael5Y75+sK2JJUNOOhO8ae45cdsilp2yI0X+UBaSuQs2+dyPp\nkpEHAxd4pmmSvn/8c9TlEZhr+qYbABXVPlDncxpIuw2Ajbk7s/S4XaSKsRqpXL57\nzj/QOqLkRk8+OVV9q6lMeQNqLtEj1u6JPviX70Ro+FQtRttNOYbfdP/fAoGBA
 MpA\nXjR5woV5sUb+REg9vEuYo8RSyOarxqKFCIXVUNsLOx+22+AK4+CQpbueWN7jotrl\nYD6uT6svWi3AAC7kiY0UI/fjVPRCUi8tVoQUE0TaU5VLITaYOB+W/bBaDE4M9560\n1NuDWO90baA5dfU44iuzva02rGJXK9+nS3o8nk/PAoGBALOL6djnDe4mwAaG6Jco\ncd4xr8jkyPzCRZuyBCSBbwphIUXLc7hDprPky064ncJD1UDmwIdkXd/fpMkg2QmA\n/CUk6LEFjMisqHojOaCL9gQZJPhLN5QUN2x1PJWGjs1vQh8Tkx0iUUCOa8bQPXNR\n+34OTsW6TUna4CSZAycLfhffAoGBAIggVsefBCvuQkF0NeUhmDCRZfhnd8y55RHR\n1HCvqKIlpv+rhcX/zmyBLuteopYyRJRsOiE2FW00i8+rIPRu4Z3Q5nybx7w3PzV9\noHN5R5baE9OyI4KpZWztpYYitZF67NcnAvVULHHOvVJQGnKYfLHJYmrJF7GA1ojM\nAuMdFbjFAoGAPxUhxwFy8gaqBahKUEZn4F81HFP5ihGhkT4QL6AFPO2e+JhIGjuR\n27+85hcFqQ+HHVtFsm81b/a+R7P4UuCRgc8eCjxQMoJ1Xl4n7VbjPbHMnIN0Ryvd\nO4ZpWDWYnCO021JTOUUOJ4J/y0416Bvkw0z59y7sNX7wDBBHHbK/XCc=\n-----END RSA PRIVATE KEY-----\n";
+
+   protected SignedHeaderAuth signedHeaderAuth;
+
+   public BaseChefApiExpectTest() {
+      credential = PRIVATE_KEY;
+      signedHeaderAuth = createInjector(Functions.forMap(ImmutableMap.<HttpRequest, HttpResponse> of()),
+            createModule(), setupProperties()).getInstance(SignedHeaderAuth.class);
+   }
+
+   protected HttpRequest signed(HttpRequest input) {
+      return signedHeaderAuth.filter(input);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..ea4070d
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java
@@ -0,0 +1,279 @@
+/*
+ * 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.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.chef.config.ChefHttpApiModule;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.chef.domain.Role;
+import org.jclouds.chef.domain.SearchResult;
+import org.jclouds.chef.options.SearchOptions;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.rest.ConfiguresRestClient;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.inject.Module;
+
+/**
+ * Expect tests for the {@link ChefApi} class.
+ */
+@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() //
+                  .method(method) //
+                  .endpoint("http://localhost:4000" + endPoint) //
+                  .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+                  .addHeader("Accept", MediaType.APPLICATION_JSON);
+   }
+
+   public void testListClientsReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/clients").build()),
+            HttpResponse.builder().statusCode(200) //
+                        .payload(payloadFromResourceWithContentType("/clients_list.json", MediaType.APPLICATION_JSON)) //
+                        .build());
+      Set<String> nodes = api.listClients();
+      assertEquals(nodes.size(), 3);
+      assertTrue(nodes.contains("adam"), String.format("Expected nodes to contain 'adam' but was: %s", nodes));
+   }
+
+   public void testListClientsReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/clients").build()),
+            HttpResponse.builder().statusCode(404)
+                  .build());
+      Set<String> clients = api.listClients();
+      assertTrue(clients.isEmpty(), String.format("Expected clients to be empty but was: %s", clients));
+   }
+
+   public void testListNodesReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/nodes").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/nodes_list.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> nodes = api.listNodes();
+      assertEquals(nodes.size(), 3);
+      assertTrue(nodes.contains("blah"), String.format("Expected nodes to contain 'blah' but was: %s", nodes));
+   }
+
+   public void testListNodesReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/nodes").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> nodes = api.listNodes();
+      assertTrue(nodes.isEmpty(), String.format("Expected nodes to be empty but was: %s", nodes));
+   }
+
+   public void testListRecipesInEnvironmentReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/recipes").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/environment_recipes.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> recipes = api.listRecipesInEnvironment("dev");
+      assertEquals(recipes.size(), 3);
+      assertTrue(recipes.contains("apache2"), String.format("Expected recipes to contain 'apache2' but was: %s", recipes));
+   }
+
+   public void testListRecipesInEnvironmentReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/recipes").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> recipes = api.listRecipesInEnvironment("dev");
+      assertTrue(recipes.isEmpty(), String.format("Expected recipes to be empty but was: %s", recipes));
+   }
+
+   public void testListNodesInEnvironmentReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/nodes").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/nodes_list.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> nodes = api.listNodesInEnvironment("dev");
+      assertEquals(nodes.size(), 3);
+      assertTrue(nodes.contains("blah"), String.format("Expected nodes to contain 'blah' but was: %s", nodes));
+   }
+
+   public void testListNodesInEnvironmentReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/nodes").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> nodes = api.listNodesInEnvironment("dev");
+      assertTrue(nodes.isEmpty(), String.format("Expected nodes to be empty but was: %s", nodes));
+   }
+
+   public void testListCookbooksReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/cookbooks").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/env_cookbooks.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> cookbooks = api.listCookbooks();
+      assertEquals(cookbooks.size(), 2);
+      assertTrue(cookbooks.contains("apache2"), String.format("Expected cookbooks to contain 'apache2' but was: %s", cookbooks));
+   }
+
+   public void testListCookbooksReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/cookbooks").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> cookbooks = api.listCookbooks();
+      assertTrue(cookbooks.isEmpty(), String.format("Expected cookbooks to be empty but was: %s", cookbooks));
+   }
+
+   public void testListCookbooksInEnvironmentReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/cookbooks").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/env_cookbooks.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<CookbookDefinition> cookbooks = api.listCookbooksInEnvironment("dev");
+      assertEquals(cookbooks.size(), 2);
+   }
+
+   public void testListCookbooksInEnvironmentReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/cookbooks").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<CookbookDefinition> cookbooks = api.listCookbooksInEnvironment("dev");
+      assertTrue(cookbooks.isEmpty(), String.format("Expected cookbooks to be empty but was: %s", cookbooks));
+   }
+
+   public void testListCookbooksInEnvironmentWithNumVersionReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/cookbooks").addQueryParam("num_versions", "2").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<CookbookDefinition> cookbooks = api.listCookbooksInEnvironment("dev", "2");
+      assertTrue(cookbooks.isEmpty(), String.format("Expected cookbooks to be empty but was: %s", cookbooks));
+   }
+
+   public void testSearchRolesReturnsValidResult() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/search/role").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/search_role.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      SearchResult<? extends Role> result = api.searchRoles();
+      assertEquals(result.size(), 1);
+      assertEquals(result.iterator().next().getName(), "webserver");
+   }
+
+   public void testSearchRolesReturnsEmptyResult() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/search/role").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/search_role_empty.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      SearchResult<? extends Role> result = api.searchRoles();
+      assertTrue(result.isEmpty(), String.format("Expected search result to be empty but was: %s", result));
+   }
+
+   public void testSearchRolesWithOptionsReturnsValidResult() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/search/role").addQueryParam("q", "name:webserver").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/search_role.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      SearchOptions options = SearchOptions.Builder.query("name:webserver");
+      SearchResult<? extends Role> result = api.searchRoles(options);
+      assertEquals(result.size(), 1);
+      assertEquals(result.iterator().next().getName(), "webserver");
+   }
+
+   public void testSearchRolesWithOptionsReturnsEmptyResult() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/search/role").addQueryParam("q", "name:dummy").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/search_role_empty.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      SearchOptions options = SearchOptions.Builder.query("name:dummy");
+      SearchResult<? extends Role> result = api.searchRoles(options);
+      assertTrue(result.isEmpty(), String.format("Expected search result to be empty but was: %s", result));
+   }
+
+   public void testListRolesReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/roles").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/roles_list.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> roles = api.listRoles();
+      assertEquals(roles.size(), 2);
+      assertTrue(roles.contains("webserver"), String.format("Expected roles to contain 'websever' but was: %s", roles));
+   }
+
+   public void testListRolesReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/roles").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> roles = api.listRoles();
+      assertTrue(roles.isEmpty(), String.format("Expected roles to be empty but was: %s", roles));
+   }
+
+   public void testListDatabagsReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/data").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/data_list.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> databags = api.listDatabags();
+      assertEquals(databags.size(), 2);
+      assertTrue(databags.contains("applications"), String.format("Expected databags to contain 'applications' but was: %s", databags));
+   }
+
+   public void testListDatabagsReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/data").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> databags = api.listDatabags();
+      assertTrue(databags.isEmpty(), String.format("Expected databags to be empty but was: %s", databags));
+   }
+
+   @Override
+   protected Module createModule() {
+      return new TestChefRestClientModule();
+   }
+
+   @ConfiguresRestClient
+   static class TestChefRestClientModule extends ChefHttpApiModule {
+      @Override
+      protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
+         return "timestamp";
+      }
+   }
+
+   @Override
+   protected ChefApiMetadata createApiMetadata() {
+      return new ChefApiMetadata();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/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
new file mode 100644
index 0000000..e4ad583
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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 org.jclouds.chef.internal.BaseChefApiLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ * Tests behavior of {@code ChefApi} against a Chef Server <= 0.9.8.
+ */
+@Test(groups = { "live" })
+public class ChefApiLiveTest extends BaseChefApiLiveTest<ChefApi> {
+
+   protected ChefApiLiveTest() {
+      provider = "chef";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/ChefApiMetadataTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiMetadataTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiMetadataTest.java
new file mode 100644
index 0000000..a78c20a
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiMetadataTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.jclouds.View;
+import org.jclouds.rest.internal.BaseHttpApiMetadataTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "ChefApiMetadataTest")
+public class ChefApiMetadataTest extends BaseHttpApiMetadataTest {
+
+   // no config management abstraction, yet
+   public ChefApiMetadataTest() {
+      super(new ChefApiMetadata(), ImmutableSet.<TypeToken<? extends View>> of());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java
new file mode 100644
index 0000000..434c22c
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java
@@ -0,0 +1,741 @@
+/*
+ * 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 com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.asList;
+import static org.jclouds.reflect.Reflection2.method;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Properties;
+import java.util.Set;
+
+import org.jclouds.Constants;
+import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.apis.ApiMetadata;
+import org.jclouds.chef.config.ChefHttpApiModule;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.domain.Resource;
+import org.jclouds.chef.domain.Role;
+import org.jclouds.chef.filters.SignedHeaderAuth;
+import org.jclouds.chef.filters.SignedHeaderAuthTest;
+import org.jclouds.chef.functions.ParseCookbookVersionsCheckingChefVersion;
+import org.jclouds.chef.functions.ParseKeySetFromJson;
+import org.jclouds.chef.functions.ParseSearchClientsFromJson;
+import org.jclouds.chef.functions.ParseSearchDatabagFromJson;
+import org.jclouds.chef.functions.ParseSearchNodesFromJson;
+import org.jclouds.chef.options.CreateClientOptions;
+import org.jclouds.chef.options.SearchOptions;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.fallbacks.MapHttp4xxCodesToExceptions;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.functions.ParseFirstJsonValueNamed;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.http.functions.ReleasePayloadAndReturn;
+import org.jclouds.http.functions.ReturnInputStream;
+import org.jclouds.io.Payload;
+import org.jclouds.io.payloads.StringPayload;
+import org.jclouds.reflect.Invocation;
+import org.jclouds.rest.ConfiguresRestClient;
+import org.jclouds.rest.internal.BaseAsyncApiTest;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.Invokable;
+import com.google.inject.Module;
+
+/**
+ * Tests annotation parsing of {@code ChefApi}.
+ */
+@Test(groups = { "unit" })
+public class ChefApiTest extends BaseAsyncApiTest<ChefApi> {
+
+   public void testCommitSandbox() throws SecurityException, NoSuchMethodException, IOException {
+
+      Invokable<?, ?> method = method(ChefApi.class, "commitSandbox", String.class, boolean.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("0189e76ccc476701d6b374e5a1a27347", true)));
+      assertRequestLineEquals(httpRequest,
+            "PUT http://localhost:4000/sandboxes/0189e76ccc476701d6b374e5a1a27347 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"is_completed\":true}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateUploadSandboxForChecksums() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createUploadSandboxForChecksums", Set.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList
+            .<Object> of(ImmutableSet.of(asList(base16().lowerCase().decode("0189e76ccc476701d6b374e5a1a27347")),
+                  asList(base16().lowerCase().decode("0c5ecd7788cf4f6c7de2a57193897a6c")), asList(base16().lowerCase()
+                        .decode("1dda05ed139664f1f89b9dec482b77c0"))))));
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/sandboxes HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest,
+            "{\"checksums\":{\"0189e76ccc476701d6b374e5a1a27347\":null,\"0c5ecd7788cf4f6c7de2a57193897a6c\":null,"
+                  + "\"1dda05ed139664f1f89b9dec482b77c0\":null}}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+   }
+
+   public void testUploadContent() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "uploadContent", URI.class, Payload.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(URI.create("http://foo/bar"), new StringPayload("{\"foo\": \"bar\"}"))));
+      assertRequestLineEquals(httpRequest, "PUT http://foo/bar HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"foo\": \"bar\"}", "application/x-binary", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testGetCookbook() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "getCookbook", String.class, String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("cookbook", "1.0.0")));
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/cookbooks/cookbook/1.0.0 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteCookbook() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteCookbook", String.class, String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("cookbook", "1.0.0")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/cookbooks/cookbook/1.0.0 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testUpdateCookbook() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "updateCookbook", String.class, String.class,
+            CookbookVersion.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("cookbook", "1.0.1", CookbookVersion.builder("cookbook", "1.0.1").build())));
+
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/cookbooks/cookbook/1.0.1 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest,
+            "{\"name\":\"cookbook-1.0.1\",\"definitions\":[],\"attributes\":[],\"files\":[],"
+                  + "\"metadata\":{\"suggestions\":{},\"dependencies\":{},\"conflicting\":{},\"providing\":{},"
+                  + "\"platforms\":{},\"recipes\":{},\"replacing\":{},"
+                  + "\"groupings\":{},\"attributes\":{},\"recommendations\":{}},"
+                  + "\"providers\":[],\"cookbook_name\":\"cookbook\",\"resources\":[],\"templates\":[],"
+                  + "\"libraries\":[],\"version\":\"1.0.1\","
+                  + "\"recipes\":[],\"root_files\":[],\"json_class\":\"Chef::CookbookVersion\","
+                  + "\"chef_type\":\"cookbook_version\"}", "application/json", false);
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testListVersionsOfCookbook() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "listVersionsOfCookbook", String.class);
+      GeneratedHttpRequest httpRequest = processor
+            .apply(Invocation.create(method, ImmutableList.<Object> of("apache2")));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/cookbooks/apache2 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseCookbookVersionsCheckingChefVersion.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteClient() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteClient", String.class);
+      GeneratedHttpRequest httpRequest = processor
+            .apply(Invocation.create(method, ImmutableList.<Object> of("client")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/clients/client HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateApi() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createClient", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("api")));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/clients HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"api\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateAdminApi() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createClient", String.class, CreateClientOptions.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("api", CreateClientOptions.Builder.admin())));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/clients HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"api\",\"admin\":true}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testGenerateKeyForClient() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "generateKeyForClient", String.class);
+      GeneratedHttpRequest httpRequest = processor
+            .apply(Invocation.create(method, ImmutableList.<Object> of("client")));
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/clients/client HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"client\", \"private_key\": true}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteNode() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteNode", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("node")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/nodes/node HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateNode() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createNode", Node.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(
+            method,
+            ImmutableList.<Object> of(Node.builder().name("testnode").runListElement("recipe[java]")
+                  .environment("_default").build())));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/nodes HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest,
+            "{\"name\":\"testnode\",\"normal\":{},\"override\":{},\"default\":{},\"automatic\":{},"
+                  + "\"run_list\":[\"recipe[java]\"],\"chef_environment\":\"_default\",\"json_class\":\"Chef::Node\","
+                  + "\"chef_type\":\"node\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testUpdateNode() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "updateNode", Node.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(
+            method,
+            ImmutableList.<Object> of(Node.builder().name("testnode").runListElement("recipe[java]")
+                  .environment("_default").build())));
+
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/nodes/testnode HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest,
+            "{\"name\":\"testnode\",\"normal\":{},\"override\":{},\"default\":{},\"automatic\":{},"
+                  + "\"run_list\":[\"recipe[java]\"],\"chef_environment\":\"_default\",\"json_class\":\"Chef::Node\","
+                  + "\"chef_type\":\"node\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteRole() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteRole", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("role")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/roles/role HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateRole() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createRole", Role.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(Role.builder().name("testrole").runListElement("recipe[java]").build())));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/roles HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"testrole\",\"override_attributes\":{},\"default_attributes\":{},"
+            + "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::Role\",\"chef_type\":\"role\"}",
+            "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testUpdateRole() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "updateRole", Role.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(Role.builder().name("testrole").runListElement("recipe[java]").build())));
+
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/roles/testrole HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"testrole\",\"override_attributes\":{},\"default_attributes\":{},"
+            + "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::Role\",\"chef_type\":\"role\"}",
+            "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteDatabag() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteDatabag", String.class);
+      GeneratedHttpRequest httpRequest = processor
+            .apply(Invocation.create(method, ImmutableList.<Object> of("databag")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/data/databag HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, VoidOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateDatabag() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabag", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("name")));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"name\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteDatabagItem() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteDatabagItem", String.class, String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", "databagItem")));
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseFirstJsonValueNamed.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testCreateDatabagItemThrowsIllegalArgumentOnPrimitive() throws SecurityException, NoSuchMethodException,
+         IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "100"))));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(
+            httpRequest,
+            "{\"name\":\"testdatabagItem\",\"override_attributes\":{},\"default_attributes\":{},"
+                  + "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::DatabagItem\",\"chef_type\":\"databagItem\"}",
+            "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testCreateDatabagItemThrowsIllegalArgumentOnWrongId() throws SecurityException, NoSuchMethodException,
+         IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "{\"id\": \"item1\",\"my_key\": \"my_data\"}"))));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(
+            httpRequest,
+            "{\"name\":\"testdatabagItem\",\"override_attributes\":{},\"default_attributes\":{},"
+                  + "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::DatabagItem\",\"chef_type\":\"databagItem\"}",
+            "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateDatabagItem() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "{\"id\": \"id\",\"my_key\": \"my_data\"}"))));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"id\": \"id\",\"my_key\": \"my_data\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateDatabagItemEvenWhenUserForgotId() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "{\"my_key\": \"my_data\"}"))));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"id\":\"id\",\"my_key\": \"my_data\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testUpdateDatabagItem() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "updateDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "{\"my_key\": \"my_data\"}"))));
+
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/data/name/id HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+
+      assertPayloadEquals(httpRequest, "{\"id\":\"id\",\"my_key\": \"my_data\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testListDatabagItems() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "listDatabagItems", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("name")));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseKeySetFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testListSearchIndexes() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "listSearchIndexes");
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.of()));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseKeySetFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+
+   public void testSearchClients() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchClients");
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.of()));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/client HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchClientsFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchClientsWithOptions() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchClients", SearchOptions.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(SearchOptions.Builder.query("text").rows(5))));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/client?q=text&rows=5 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchClientsFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchNodes() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchNodes");
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.of()));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/node HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchNodesFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchNodesWithOptions() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchNodes", SearchOptions.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(SearchOptions.Builder.query("foo:foo").start(3))));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/node?q=foo%3Afoo&start=3 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchNodesFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchDatabagItems() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchDatabagItems", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("foo")));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/foo HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchDatabagFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchDatabagItemsWithOptions() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchDatabagItems", String.class, SearchOptions.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("foo", SearchOptions.Builder.query("bar").sort("name DESC"))));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/foo?q=bar&sort=name%20DESC HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchDatabagFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testGetResourceContents() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "getResourceContents", Resource.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(Resource.builder().name("test").url(URI.create("http://foo/bar")).build())));
+
+      assertRequestLineEquals(httpRequest, "GET http://foo/bar HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReturnInputStream.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   @Override
+   protected void checkFilters(HttpRequest request) {
+      assertEquals(request.getFilters().size(), 1);
+      assertEquals(request.getFilters().get(0).getClass(), SignedHeaderAuth.class);
+   }
+
+   @Override
+   protected Module createModule() {
+      return new TestChefRestClientModule();
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      props.put(Constants.PROPERTY_API_VERSION, ChefApiMetadata.DEFAULT_API_VERSION + "-test");
+      return props;
+   }
+
+   @ConfiguresRestClient
+   static class TestChefRestClientModule extends ChefHttpApiModule {
+      @Override
+      protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
+         return "timestamp";
+      }
+   }
+
+   @Override
+   public ApiMetadata createApiMetadata() {
+      identity = "user";
+      credential = SignedHeaderAuthTest.PRIVATE_KEY;
+      return new ChefApiMetadata();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java b/apis/chef/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java
new file mode 100644
index 0000000..7648732
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.binders;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.asList;
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+
+import javax.ws.rs.HttpMethod;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+@Test(groups = { "unit" })
+public class BindHexEncodedMD5sToJsonPayloadTest {
+
+   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());
+
+   BindChecksumsToJsonPayload binder = injector.getInstance(BindChecksumsToJsonPayload.class);
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testMustBeIterable() {
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost").build();
+      binder.bindToRequest(request, new File("foo"));
+   }
+
+   public void testCorrect() {
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost").build();
+      binder.bindToRequest(request,
+            ImmutableSet.of(asList(base16().lowerCase().decode("abddef")), asList(base16().lowerCase().decode("1234"))));
+      assertEquals(request.getPayload().getRawContent(), "{\"checksums\":{\"abddef\":null,\"1234\":null}}");
+   }
+
+   @Test(expectedExceptions = { NullPointerException.class, IllegalStateException.class })
+   public void testNullIsBad() {
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost").build();
+      binder.bindToRequest(request, null);
+   }
+
+}