JCLOUDS-245/JCLOUDS-254: Fix live tests

Refactored the domain model to be immutable and addressed some
inconsistences with the Chef Server API model.

Removed all HEAD methods, as they have been removed from newer versions
of Chef. They were used to test the existance of a given resource, and
with newer versions the only way to do that is via a GET operation.

Now all live tests are passing for Community Chef 0.10.8, 11.0.6 and
Enterprise Chef.
diff --git a/README.md b/README.md
index ed2c40e..cb1987f 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
 * [Chef community](http://www.opscode.com/chef/)
 * [Enterprise Chef](http://www.opscode.com/enterprise-chef/)
 
-It currently supports versions **0.9** and **0.10** of the standard Chef server apis, and an initial
+It currently supports versions **0.9**, **0.10** and **11** of the standard Chef server apis, and an initial
 and very basic (still in progress) implementation of the user and organization api of the Enterprise Chef.
 
 Also provides a set of utility methods to combine Chef features with the jclouds Compute service, allowing
diff --git a/compute/src/test/java/org/jclouds/chef/compute/ChefComputeServiceLiveTest.java b/compute/src/test/java/org/jclouds/chef/compute/ChefComputeServiceLiveTest.java
index 71290d7..0c8fcc5 100644
--- a/compute/src/test/java/org/jclouds/chef/compute/ChefComputeServiceLiveTest.java
+++ b/compute/src/test/java/org/jclouds/chef/compute/ChefComputeServiceLiveTest.java
@@ -108,7 +108,7 @@
       if (context != null) {
          view.getChefService().cleanupStaleNodesAndClients(group + "-", 1);
          ChefApi api = view.unwrapApi(ChefApi.class);
-         if (clientName != null && api.clientExists(clientName)) {
+         if (clientName != null && api.getClient(clientName) != null) {
             api.deleteClient(clientName);
          }
          context.close();
diff --git a/core/src/main/java/org/jclouds/chef/ChefApi.java b/core/src/main/java/org/jclouds/chef/ChefApi.java
index 3c3d2bc..faa189c 100644
--- a/core/src/main/java/org/jclouds/chef/ChefApi.java
+++ b/core/src/main/java/org/jclouds/chef/ChefApi.java
@@ -26,7 +26,6 @@
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
-import javax.ws.rs.HEAD;
 import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
@@ -36,7 +35,6 @@
 
 import org.jclouds.Constants;
 import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
-import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
 import org.jclouds.Fallbacks.NullOnNotFoundOr404;
 import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
 import org.jclouds.chef.binders.BindChecksumsToJsonPayload;
@@ -86,6 +84,7 @@
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.ResponseParser;
 import org.jclouds.rest.annotations.SinceApiVersion;
+import org.jclouds.rest.annotations.SkipEncoding;
 import org.jclouds.rest.binders.BindToJsonPayload;
 
 /**
@@ -295,20 +294,6 @@
    Set<String> listClients();
 
    /**
-    * @return true if the specified client name exists.
-    * @throws AuthorizationException
-    *            <p/>
-    *            "401 Unauthorized" if you are not a recognized user.
-    *            <p/>
-    *            "403 Forbidden" if you do not have rights to view the client.
-    */
-   @Named("client:exists")
-   @HEAD
-   @Path("/clients/{clientname}")
-   @Fallback(FalseOnNotFoundOr404.class)
-   boolean clientExists(@PathParam("clientname") String clientname);
-
-   /**
     * deletes an existing client.
     * 
     * @return last state of the client you deleted or null, if not found
@@ -385,20 +370,6 @@
    Set<String> listNodes();
 
    /**
-    * @return true if the specified node name exists.
-    * @throws AuthorizationException
-    *            <p/>
-    *            "401 Unauthorized" if you are not a recognized user.
-    *            <p/>
-    *            "403 Forbidden" if you do not have rights to view the node.
-    */
-   @Named("node:exists")
-   @HEAD
-   @Path("/nodes/{nodename}")
-   @Fallback(FalseOnNotFoundOr404.class)
-   boolean nodeExists(@PathParam("nodename") String nodename);
-
-   /**
     * deletes an existing node.
     * 
     * @return last state of the node you deleted or null, if not found
@@ -474,20 +445,6 @@
    Set<String> listRoles();
 
    /**
-    * @return true if the specified role name exists.
-    * @throws AuthorizationException
-    *            <p/>
-    *            "401 Unauthorized" if you are not a recognized user.
-    *            <p/>
-    *            "403 Forbidden" if you do not have rights to view the role.
-    */
-   @Named("role:exists")
-   @HEAD
-   @Path("/roles/{rolename}")
-   @Fallback(FalseOnNotFoundOr404.class)
-   boolean roleExists(@PathParam("rolename") String rolename);
-
-   /**
     * deletes an existing role.
     * 
     * @return last state of the role you deleted or null, if not found
@@ -549,21 +506,6 @@
    void createDatabag(@BinderParam(BindNameToJsonPayload.class) String databagName);
 
    /**
-    * true is a databag exists
-    * 
-    * @throws AuthorizationException
-    *            <p/>
-    *            "401 Unauthorized" if you are not a recognized user.
-    *            <p/>
-    *            "403 Forbidden" if you do not have view rights on the databag.
-    */
-   @Named("databag:exists")
-   @HEAD
-   @Path("/data/{name}")
-   @Fallback(FalseOnNotFoundOr404.class)
-   boolean databagExists(@PathParam("name") String databagName);
-
-   /**
     * Delete a data bag, including its items
     * 
     * @throws AuthorizationException
@@ -629,22 +571,6 @@
          @PathParam("databagItemId") @ParamParser(DatabagItemId.class) @BinderParam(BindToJsonPayload.class) DatabagItem item);
 
    /**
-    * determines if a databag item exists
-    * 
-    * @throws AuthorizationException
-    *            <p/>
-    *            "401 Unauthorized" if you are not a recognized user.
-    *            <p/>
-    *            "403 Forbidden" if you do not have view rights on the databag.
-    */
-   @Named("databag:itemexists")
-   @HEAD
-   @Path("/data/{databagName}/{databagItemId}")
-   @Fallback(FalseOnNotFoundOr404.class)
-   boolean databagItemExists(@PathParam("databagName") String databagName,
-         @PathParam("databagItemId") String databagItemId);
-
-   /**
     * gets an existing databag item.
     * 
     * @throws AuthorizationException
@@ -853,6 +779,7 @@
    @Named("content:get")
    @GET
    @Fallback(NullOnNotFoundOr404.class)
+   @SkipEncoding({'+', ' ', '/', '=', ':', ';'})
    InputStream getResourceContents(@EndpointParam(parser = UriForResource.class) Resource resource);
 
    /**
diff --git a/core/src/main/java/org/jclouds/chef/config/ChefParserModule.java b/core/src/main/java/org/jclouds/chef/config/ChefParserModule.java
index 7a92064..f7457fe 100644
--- a/core/src/main/java/org/jclouds/chef/config/ChefParserModule.java
+++ b/core/src/main/java/org/jclouds/chef/config/ChefParserModule.java
@@ -16,6 +16,8 @@
  */
 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;
 
@@ -45,13 +47,16 @@
 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 org.jclouds.rest.annotations.ApiVersion;
 
 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.ByteStreams;
 import com.google.gson.Gson;
 import com.google.gson.JsonDeserializationContext;
@@ -59,6 +64,10 @@
 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;
@@ -211,6 +220,82 @@
       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,
@@ -252,5 +337,6 @@
    @Override
    protected void configure() {
       bind(DateAdapter.class).to(Iso8601DateAdapter.class);
+      bind(MapTypeAdapterFactory.class).to(KeepLastRepeatedKeyMapTypeAdapterFactory.class);
    }
 }
diff --git a/core/src/main/java/org/jclouds/chef/domain/Attribute.java b/core/src/main/java/org/jclouds/chef/domain/Attribute.java
index 38d10c6..a5fb25d 100644
--- a/core/src/main/java/org/jclouds/chef/domain/Attribute.java
+++ b/core/src/main/java/org/jclouds/chef/domain/Attribute.java
@@ -16,46 +16,120 @@
  */
 package org.jclouds.chef.domain;
 
+import static org.jclouds.chef.util.CollectionUtils.*;
+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.Iterables;
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.gson.annotations.SerializedName;
 
 /**
- * Cookbook object.
+ * An attribute in a cookbook metadata.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class Attribute {
-
-   private String required;
-   private boolean calculated;
-   private List<String> choice = Lists.newArrayList();
-   @SerializedName("default")
-   private JsonBall defaultValue;
-   private String type;
-   private List<String> recipes = Lists.newArrayList();
-   @SerializedName("display_name")
-   private String displayName;
-   private String description;
-
-   public Attribute(String required, boolean calculated, Set<String> choice, JsonBall defaultValue, String type,
-         List<String> recipes, String displayName, String description) {
-      this.required = required;
-      this.calculated = calculated;
-      Iterables.addAll(this.choice, choice);
-      this.defaultValue = defaultValue;
-      this.type = type;
-      Iterables.addAll(this.recipes, recipes);
-      this.displayName = displayName;
-      this.description = description;
+   public static Builder builder() {
+      return new Builder();
    }
 
-   public Attribute() {
+   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() {
@@ -66,7 +140,7 @@
       return calculated;
    }
 
-   public List<String> getChoice() {
+   public Set<String> getChoice() {
       return choice;
    }
 
@@ -162,4 +236,3 @@
    }
 
 }
-
diff --git a/core/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java b/core/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java
index 2c16514..22d8f36 100644
--- a/core/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java
+++ b/core/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java
@@ -17,14 +17,13 @@
 package org.jclouds.chef.domain;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.addAll;
 
 import java.util.List;
 
 import org.jclouds.domain.JsonBall;
 
 import com.google.common.base.Optional;
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableList;
 
 /**
  * Configures how the nodes in a group will bootstrap.
@@ -33,13 +32,12 @@
  * @since 1.7
  */
 public class BootstrapConfig {
-
    public static Builder builder() {
       return new Builder();
    }
 
    public static class Builder {
-      private List<String> runList = Lists.newArrayList();
+      private ImmutableList.Builder<String> runList = ImmutableList.builder();
       private String environment;
       private JsonBall attribtues;
 
@@ -47,7 +45,7 @@
        * Sets the run list that will be executed in the nodes of the group.
        */
       public Builder runList(Iterable<String> runList) {
-         addAll(this.runList, checkNotNull(runList, "runList"));
+         this.runList.addAll(checkNotNull(runList, "runList"));
          return this;
       }
 
@@ -68,13 +66,14 @@
       }
 
       public BootstrapConfig build() {
-         return new BootstrapConfig(runList, Optional.fromNullable(environment), Optional.fromNullable(attribtues));
+         return new BootstrapConfig(runList.build(), Optional.fromNullable(environment),
+               Optional.fromNullable(attribtues));
       }
    }
 
-   private List<String> runList = Lists.newArrayList();
-   private Optional<String> environment;
-   private Optional<JsonBall> 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");
diff --git a/core/src/main/java/org/jclouds/chef/domain/ChecksumStatus.java b/core/src/main/java/org/jclouds/chef/domain/ChecksumStatus.java
index ffe7e64..e1c77e9 100644
--- a/core/src/main/java/org/jclouds/chef/domain/ChecksumStatus.java
+++ b/core/src/main/java/org/jclouds/chef/domain/ChecksumStatus.java
@@ -16,26 +16,50 @@
  */
 package org.jclouds.chef.domain;
 
+import static com.google.common.base.Preconditions.*;
+import java.beans.ConstructorProperties;
 import java.net.URI;
 
 import com.google.gson.annotations.SerializedName;
 
 /**
+ * The checksum of an uploaded resource.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class ChecksumStatus {
-   private URI url;
-   @SerializedName("needs_upload")
-   private boolean needsUpload;
-
-   public ChecksumStatus(URI url, boolean needsUpload) {
-      this.url = url;
-      this.needsUpload = needsUpload;
+   public static Builder builder() {
+      return new Builder();
    }
 
-   public ChecksumStatus() {
+   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() {
@@ -47,11 +71,6 @@
    }
 
    @Override
-   public String toString() {
-      return "ChecksumStatus [needsUpload=" + needsUpload + ", url=" + url + "]";
-   }
-
-   @Override
    public int hashCode() {
       final int prime = 31;
       int result = 1;
@@ -78,5 +97,9 @@
          return false;
       return true;
    }
-}
 
+   @Override
+   public String toString() {
+      return "ChecksumStatus [needsUpload=" + needsUpload + ", url=" + url + "]";
+   }
+}
diff --git a/core/src/main/java/org/jclouds/chef/domain/Client.java b/core/src/main/java/org/jclouds/chef/domain/Client.java
index 47dc4e0..101638c 100644
--- a/core/src/main/java/org/jclouds/chef/domain/Client.java
+++ b/core/src/main/java/org/jclouds/chef/domain/Client.java
@@ -16,6 +16,9 @@
  */
 package org.jclouds.chef.domain;
 
+import static com.google.common.base.Preconditions.*;
+
+import java.beans.ConstructorProperties;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
 
@@ -27,19 +30,73 @@
  * Client object.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class Client {
-   private X509Certificate certificate;
+   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 PrivateKey privateKey;
-   private String orgname;
-   private String clientname;
-   private String name;
-   private boolean validator;
+   private final PrivateKey privateKey;
+   private final String orgname;
+   private final String clientname;
+   private final String name;
+   private final boolean validator;
 
-   // only for deserialization
-   Client() {
-
+   @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() {
@@ -67,12 +124,6 @@
    }
 
    @Override
-   public String toString() {
-      return "[name=" + name + ", clientname=" + clientname + ", orgname=" + orgname + ", isValidator=" + validator
-            + ", certificate=" + certificate + ", privateKey=" + (privateKey != null) + "]";
-   }
-
-   @Override
    public int hashCode() {
       final int prime = 31;
       int result = 1;
@@ -124,14 +175,11 @@
       return true;
    }
 
-   public Client(X509Certificate certificate, String orgname, String clientname, String name, boolean isValidator,
-         @Nullable PrivateKey privateKey) {
-      this.certificate = certificate;
-      this.orgname = orgname;
-      this.clientname = clientname;
-      this.name = name;
-      this.validator = isValidator;
-      this.privateKey = privateKey;
+   @Override
+   public String toString() {
+      return "Client [name=" + name + ", clientname=" + clientname + ", orgname=" + orgname + ", isValidator="
+            + validator + ", certificate=" + certificate + ", privateKey=" + (privateKey == null ? "not " : "")
+            + "present]";
    }
 
 }
diff --git a/core/src/main/java/org/jclouds/chef/domain/CookbookDefinition.java b/core/src/main/java/org/jclouds/chef/domain/CookbookDefinition.java
index 7b0dea2..6531f03 100644
--- a/core/src/main/java/org/jclouds/chef/domain/CookbookDefinition.java
+++ b/core/src/main/java/org/jclouds/chef/domain/CookbookDefinition.java
@@ -16,10 +16,16 @@
  */
 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 com.google.common.collect.Sets;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableSet;
 
 /**
  * Cookbook definition as returned by the Chef server >= 0.10.8.
@@ -27,18 +33,41 @@
  * @author Ignasi Barrera
  */
 public class CookbookDefinition {
-
-   private URI url;
-   private Set<Version> versions = Sets.newLinkedHashSet();
-
-   // only for deserialization
-   CookbookDefinition() {
-
+   public static Builder builder() {
+      return new Builder();
    }
 
-   public CookbookDefinition(URI url, Set<Version> versions) {
+   public static class Builder {
+      private URI url;
+      private ImmutableSet.Builder<Version> versions = ImmutableSet.builder();
+
+      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 CookbookDefinition build() {
+         return new CookbookDefinition(url, versions.build());
+      }
+   }
+
+   private final URI url;
+   private final Set<Version> versions;
+
+   @ConstructorProperties({ "url", "versions" })
+   protected CookbookDefinition(URI url, @Nullable Set<Version> versions) {
       this.url = url;
-      this.versions = versions;
+      this.versions = copyOfOrEmpty(versions);
    }
 
    public URI getUrl() {
@@ -86,15 +115,34 @@
    }
 
    public static class Version {
-      private URI url;
-      private String version;
-
-      // only for deserialization
-      Version() {
-
+      public static Builder builder() {
+         return new Builder();
       }
 
-      public Version(URI url, String version) {
+      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;
       }
diff --git a/core/src/main/java/org/jclouds/chef/domain/CookbookVersion.java b/core/src/main/java/org/jclouds/chef/domain/CookbookVersion.java
index 9251bd1..5af929e 100644
--- a/core/src/main/java/org/jclouds/chef/domain/CookbookVersion.java
+++ b/core/src/main/java/org/jclouds/chef/domain/CookbookVersion.java
@@ -16,34 +16,174 @@
  */
 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 com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableSet;
 import com.google.gson.annotations.SerializedName;
 
 /**
  * Cookbook object.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class CookbookVersion {
+   public static Builder builder(String name, String version) {
+      return new Builder(name, version);
+   }
 
-   private String name;
-   private Set<Resource> definitions = Sets.newLinkedHashSet();
-   private Set<Attribute> attributes = Sets.newLinkedHashSet();
-   private Set<Resource> files = Sets.newLinkedHashSet();
-   private Metadata metadata = new Metadata();
-   private Set<Resource> providers = Sets.newLinkedHashSet();
+   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 String cookbookName;
-   private Set<Resource> resources = Sets.newLinkedHashSet();
-   private Set<Resource> templates = Sets.newLinkedHashSet();
-   private Set<Resource> libraries = Sets.newLinkedHashSet();
-   private String version;
-   private Set<Resource> recipes = Sets.newLinkedHashSet();
+   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 Set<Resource> rootFiles = Sets.newLinkedHashSet();
+   private final Set<Resource> rootFiles;
 
    // internal
    @SerializedName("json_class")
@@ -51,34 +191,25 @@
    @SerializedName("chef_type")
    private String _chefType = "cookbook_version";
 
-   public CookbookVersion(String cookbookName, String version) {
-      this.cookbookName = cookbookName;
-      this.version = version;
-      this.name = cookbookName + "-" + version;
-   }
-
-   public CookbookVersion(String name, Set<Resource> definitions, Set<Attribute> attributes, Set<Resource> files,
-         Metadata metadata, Set<Resource> providers, String cookbookName, Set<Resource> resources,
-         Set<Resource> templates, Set<Resource> libraries, String version, Set<Resource> recipes,
-         Set<Resource> rootFiles) {
+   @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;
-      Iterables.addAll(this.definitions, definitions);
-      Iterables.addAll(this.attributes, attributes);
-      Iterables.addAll(this.files, files);
+      this.definitions = copyOfOrEmpty(definitions);
+      this.attributes = copyOfOrEmpty(attributes);
+      this.files = copyOfOrEmpty(files);
       this.metadata = metadata;
-      Iterables.addAll(this.providers, providers);
+      this.providers = copyOfOrEmpty(providers);
       this.cookbookName = cookbookName;
-      Iterables.addAll(this.resources, resources);
-      Iterables.addAll(this.templates, templates);
-      Iterables.addAll(this.libraries, libraries);
+      this.resources = copyOfOrEmpty(resources);
+      this.templates = copyOfOrEmpty(templates);
+      this.libraries = copyOfOrEmpty(libraries);
       this.version = version;
-      Iterables.addAll(this.recipes, recipes);
-      Iterables.addAll(this.rootFiles, rootFiles);
-   }
-
-   // hidden but needs to be here for json deserialization to work
-   CookbookVersion() {
-
+      this.recipes = copyOfOrEmpty(recipes);
+      this.rootFiles = copyOfOrEmpty(rootFiles);
    }
 
    public String getName() {
diff --git a/core/src/main/java/org/jclouds/chef/domain/DatabagItem.java b/core/src/main/java/org/jclouds/chef/domain/DatabagItem.java
index 8d253a5..9138b9f 100644
--- a/core/src/main/java/org/jclouds/chef/domain/DatabagItem.java
+++ b/core/src/main/java/org/jclouds/chef/domain/DatabagItem.java
@@ -21,8 +21,10 @@
 import org.jclouds.domain.JsonBall;
 
 /**
+ * An item in a data bag.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class DatabagItem extends JsonBall {
 
@@ -62,4 +64,3 @@
       return id;
    }
 }
-
diff --git a/core/src/main/java/org/jclouds/chef/domain/Environment.java b/core/src/main/java/org/jclouds/chef/domain/Environment.java
index d96caa7..f54208e 100644
--- a/core/src/main/java/org/jclouds/chef/domain/Environment.java
+++ b/core/src/main/java/org/jclouds/chef/domain/Environment.java
@@ -16,48 +16,105 @@
  */
 package org.jclouds.chef.domain;
 
-import com.google.common.collect.Maps;
-import com.google.gson.annotations.SerializedName;
-import org.jclouds.domain.JsonBall;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
 
+import java.beans.ConstructorProperties;
 import java.util.Map;
 
-public class Environment {
+import org.jclouds.domain.JsonBall;
+import org.jclouds.javax.annotation.Nullable;
 
-   private String name;
-    @SerializedName("default_attributes")
-   private Map<String, JsonBall> attributes = Maps.newLinkedHashMap();
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * An environment.
+ * 
+ * @author Ignasi Barrera
+ */
+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 Map<String, JsonBall> overrideAttributes = Maps.newLinkedHashMap();
-   private String description = "";
+   private final Map<String, JsonBall> overrideAttributes;
+   private final String description;
    @SerializedName("cookbook_versions")
-   private Map<String, String> cookbookVersions = Maps.newLinkedHashMap();
+   private final Map<String, String> cookbookVersions;
+
    // internal
    @SerializedName("json_class")
-   private String _jsonClass = "Chef::Environment";
+   private final String _jsonClass = "Chef::Environment";
    @SerializedName("chef_type")
-   private String _chefType = "environment";
+   private final String _chefType = "environment";
 
-   public Environment(String name, Map<String, JsonBall> attributes, Map<String, JsonBall> overrideAttributes,
-                      String description, Map<String, String> cookbookVersions) {
+   @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.putAll(attributes);
-      this.overrideAttributes.putAll(overrideAttributes);
+      this.attributes = copyOfOrEmpty(attributes);
+      this.overrideAttributes = copyOfOrEmpty(overrideAttributes);
       this.description = description;
-      this.cookbookVersions.putAll(cookbookVersions);
-   }
-
-   public Environment(String name, String description) {
-      this.name = name;
-      this.description = description;
-   }
-
-   public Environment(String name) {
-      this(name, null);
-   }
-
-   // hidden but needs to be here for json deserialization to work
-   Environment() {
+      this.cookbookVersions = copyOfOrEmpty(cookbookVersions);
    }
 
    public String getName() {
@@ -82,17 +139,23 @@
 
    @Override
    public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
+      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 (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)
+      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;
@@ -110,12 +173,8 @@
 
    @Override
    public String toString() {
-      return "[" +
-            "name='" + name + '\'' +
-            ", attributes=" + attributes +
-            ", overrideAttributes=" + overrideAttributes +
-            ", description='" + description + '\'' +
-            ", cookbookVersions=" + cookbookVersions +
-            ']';
+      return "Environment [" + "name='" + name + '\'' + ", attributes=" + attributes + ", overrideAttributes="
+            + overrideAttributes + ", description='" + description + '\'' + ", cookbookVersions=" + cookbookVersions
+            + ']';
    }
 }
diff --git a/core/src/main/java/org/jclouds/chef/domain/Metadata.java b/core/src/main/java/org/jclouds/chef/domain/Metadata.java
index 2bc1044..0cfb66f 100644
--- a/core/src/main/java/org/jclouds/chef/domain/Metadata.java
+++ b/core/src/main/java/org/jclouds/chef/domain/Metadata.java
@@ -16,63 +16,236 @@
  */
 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 com.google.common.collect.Maps;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableMap;
 import com.google.gson.annotations.SerializedName;
 
 /**
- * Cookbook object.
+ * A metadata object.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class Metadata {
-
-   private String license;
-   private String maintainer;
-   private Map<String, String> suggestions = Maps.newLinkedHashMap();
-   private Map<String, String> dependencies = Maps.newLinkedHashMap();
-   @SerializedName("maintainer_email")
-   private String maintainerEmail;
-   private Map<String, String> conflicting = Maps.newLinkedHashMap();
-   private String description;
-   private Map<String, String> providing = Maps.newLinkedHashMap();
-   private Map<String, String> platforms = Maps.newLinkedHashMap();
-   private String version;
-   private Map<String, String> recipes = Maps.newLinkedHashMap();
-   private Map<String, String> replacing = Maps.newLinkedHashMap();
-   private String name;
-   private Map<String, String> groupings = Maps.newLinkedHashMap();
-   @SerializedName("long_description")
-   private String longDescription;
-   private Map<String, Attribute> attributes = Maps.newLinkedHashMap();
-   private Map<String, String> recommendations = Maps.newLinkedHashMap();
-
-   public Metadata(String license, String maintainer, Map<String, String> suggestions,
-         Map<String, String> dependencies, String maintainerEmail, Map<String, String> conflicting, String description,
-         Map<String, String> providing, Map<String, String> platforms, String version, Map<String, String> recipes,
-         Map<String, String> replacing, String name, Map<String, String> groupings, String longDescription,
-         Map<String, Attribute> attributes, Map<String, String> recommendations) {
-      this.license = license;
-      this.maintainer = maintainer;
-      this.suggestions.putAll(suggestions);
-      this.dependencies.putAll(dependencies);
-      this.maintainerEmail = maintainerEmail;
-      this.conflicting.putAll(conflicting);
-      this.description = description;
-      this.providing.putAll(providing);
-      this.platforms.putAll(platforms);
-      this.version = version;
-      this.recipes.putAll(recipes);
-      this.replacing.putAll(replacing);
-      this.name = name;
-      this.groupings.putAll(groupings);
-      this.longDescription = longDescription;
-      this.attributes.putAll(attributes);
-      this.recommendations.putAll(recommendations);
+   public static Builder builder() {
+      return new Builder();
    }
 
-   public Metadata() {
+   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() {
@@ -275,4 +448,3 @@
    }
 
 }
-
diff --git a/core/src/main/java/org/jclouds/chef/domain/Node.java b/core/src/main/java/org/jclouds/chef/domain/Node.java
index 83cd5a8..4d4fa0f 100644
--- a/core/src/main/java/org/jclouds/chef/domain/Node.java
+++ b/core/src/main/java/org/jclouds/chef/domain/Node.java
@@ -16,109 +16,160 @@
  */
 package org.jclouds.chef.domain;
 
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.gson.annotations.SerializedName;
-import org.jclouds.domain.JsonBall;
-import org.jclouds.javax.annotation.Nullable;
+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;
+
 /**
- * Sandbox object.
+ * Node object.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class Node {
+   public static Builder builder() {
+      return new Builder();
+   }
 
-   private String name;
-   private Map<String, JsonBall> normal = Maps.newLinkedHashMap();
-   private Map<String, JsonBall> override = Maps.newLinkedHashMap();
+   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 Map<String, JsonBall> defaultA = Maps.newLinkedHashMap();
-   private Map<String, JsonBall> automatic = Maps.newLinkedHashMap();
+   private final Map<String, JsonBall> defaultAttributes;
+   @SerializedName("automatic")
+   private final Map<String, JsonBall> automaticAttributes;
    @SerializedName("run_list")
-   private List<String> runList = Lists.newArrayList();
-
-   /**
-    * @since chef 0.10
-    */
+   private final List<String> runList;
    @SerializedName("chef_environment")
-   @Nullable
-   private String chefEnvironment;
+   private final String environment;
 
    // internal
    @SerializedName("json_class")
-   private String _jsonClass = "Chef::Node";
+   private final String _jsonClass = "Chef::Node";
+   @SerializedName("chef_type")
+   private final String _chefType = "node";
 
-
-    @SerializedName("chef_type")
-    private String _chefType = "node";
-
-   public Node(String name, Map<String, JsonBall> normal, Map<String, JsonBall> override,
-         Map<String, JsonBall> defaultA, Map<String, JsonBall> automatic, Iterable<String> runList) {
-      this(name, normal, override, defaultA, automatic, runList, null);
-   }
-
-   /**
-    * @since chef 0.10
-    */
-   public Node(String name, Map<String, JsonBall> normal, Map<String, JsonBall> override,
-         Map<String, JsonBall> defaultA, Map<String, JsonBall> automatic, Iterable<String> runList,
-         String chefEnvironment) {
+   @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.chefEnvironment = chefEnvironment;
-      this.normal.putAll(normal);
-      this.override.putAll(override);
-      this.defaultA.putAll(defaultA);
-      this.automatic.putAll(automatic);
-      Iterables.addAll(this.runList, runList);
-   }
-
-   @Override
-   public String toString() {
-      return "Node [name=" + name + ", runList=" + runList + ", normal=" + normal + ", default=" + defaultA
-            + ", override=" + override + ", chefEnvironment=" + chefEnvironment + ", automatic=" + automatic + "]";
-   }
-
-   public Node(String name, Iterable<String> runList) {
-      this(name, runList, "_default");
-   }
-
-   /**
-    * @since chef 0.10
-    */
-   public Node(String name, Iterable<String> runList, String chefEnvironment) {
-      this.name = name;
-      this.chefEnvironment = chefEnvironment;
-      Iterables.addAll(this.runList, runList);
-   }
-
-   // hidden but needs to be here for json deserialization to work
-   Node() {
-
+      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> getNormal() {
-      return normal;
+   public Map<String, JsonBall> getNormalAttributes() {
+      return normalAttributes;
    }
 
-   public Map<String, JsonBall> getOverride() {
-      return override;
+   public Map<String, JsonBall> getOverrideAttributes() {
+      return overrideAttributes;
    }
 
-   public Map<String, JsonBall> getDefault() {
-      return defaultA;
+   public Map<String, JsonBall> getDefaultAttributes() {
+      return defaultAttributes;
    }
 
-   public Map<String, JsonBall> getAutomatic() {
-      return automatic;
+   public Map<String, JsonBall> getAutomaticAttributes() {
+      return automaticAttributes;
    }
 
    public List<String> getRunList() {
@@ -126,10 +177,10 @@
    }
 
    /**
-    * @since chef 0.10
+    * @since Chef 0.10
     */
-   public String getChefEnvironment() {
-      return chefEnvironment;
+   public String getEnvironment() {
+      return environment;
    }
 
    @Override
@@ -138,13 +189,13 @@
       int result = 1;
       result = prime * result + ((_chefType == null) ? 0 : _chefType.hashCode());
       result = prime * result + ((_jsonClass == null) ? 0 : _jsonClass.hashCode());
-      result = prime * result + ((automatic == null) ? 0 : automatic.hashCode());
-      result = prime * result + ((defaultA == null) ? 0 : defaultA.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 + ((normal == null) ? 0 : normal.hashCode());
-      result = prime * result + ((override == null) ? 0 : override.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 + ((chefEnvironment == null) ? 0 : chefEnvironment.hashCode());
+      result = prime * result + ((environment == null) ? 0 : environment.hashCode());
       return result;
    }
 
@@ -167,42 +218,49 @@
             return false;
       } else if (!_jsonClass.equals(other._jsonClass))
          return false;
-      if (automatic == null) {
-         if (other.automatic != null)
+      if (automaticAttributes == null) {
+         if (other.automaticAttributes != null)
             return false;
-      } else if (!automatic.equals(other.automatic))
+      } else if (!automaticAttributes.equals(other.automaticAttributes))
          return false;
-      if (defaultA == null) {
-         if (other.defaultA != null)
+      if (defaultAttributes == null) {
+         if (other.defaultAttributes != null)
             return false;
-      } else if (!defaultA.equals(other.defaultA))
+      } 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 (normal == null) {
-         if (other.normal != null)
+      if (normalAttributes == null) {
+         if (other.normalAttributes != null)
             return false;
-      } else if (!normal.equals(other.normal))
+      } else if (!normalAttributes.equals(other.normalAttributes))
          return false;
-      if (override == null) {
-         if (other.override != null)
+      if (overrideAttributes == null) {
+         if (other.overrideAttributes != null)
             return false;
-      } else if (!override.equals(other.override))
+      } 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 (chefEnvironment == null) {
-         if (other.chefEnvironment != null)
+      if (environment == null) {
+         if (other.environment != null)
             return false;
-      } else if (!chefEnvironment.equals(other.chefEnvironment))
+      } 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 + "]";
+   }
+
 }
diff --git a/core/src/main/java/org/jclouds/chef/domain/Resource.java b/core/src/main/java/org/jclouds/chef/domain/Resource.java
index 687a468..15ac613 100644
--- a/core/src/main/java/org/jclouds/chef/domain/Resource.java
+++ b/core/src/main/java/org/jclouds/chef/domain/Resource.java
@@ -16,6 +16,9 @@
  */
 package org.jclouds.chef.domain;
 
+import static com.google.common.base.Preconditions.*;
+
+import java.beans.ConstructorProperties;
 import java.net.URI;
 import java.util.Arrays;
 
@@ -24,28 +27,69 @@
 import com.google.common.primitives.Bytes;
 
 /**
- * Cookbook object.
+ * Resource object.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class Resource {
-
-   private String name;
-   private URI url;
-   private byte[] checksum;
-   private String path;
-   private String specificity;
-
-   public Resource(FilePayload payload) {
-      this(payload.getRawContent().getName(), null, payload.getContentMetadata().getContentMD5(), payload
-            .getRawContent().getPath(), "default");
+   public static Builder builder() {
+      return new Builder();
    }
 
-   public Resource(String name, byte[] checksum, String path) {
-      this(name, null, checksum, path, "default");
+   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);
+      }
    }
 
-   public Resource(String name, URI url, byte[] checksum, String path, String 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;
@@ -53,10 +97,6 @@
       this.specificity = specificity;
    }
 
-   // hidden but needs to be here for json deserialization to work
-   Resource() {
-   }
-
    public String getName() {
       return name;
    }
@@ -130,4 +170,3 @@
    }
 
 }
-
diff --git a/core/src/main/java/org/jclouds/chef/domain/Role.java b/core/src/main/java/org/jclouds/chef/domain/Role.java
index 95d91f1..1b73609 100644
--- a/core/src/main/java/org/jclouds/chef/domain/Role.java
+++ b/core/src/main/java/org/jclouds/chef/domain/Role.java
@@ -16,55 +16,106 @@
  */
 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.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.gson.annotations.SerializedName;
 
 /**
- * Sandbox object.
+ * Role object.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class Role {
+   public static Builder builder() {
+      return new Builder();
+   }
 
-   private String name;
-   private String description;
+   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 Map<String, JsonBall> override = Maps.newLinkedHashMap();
+   private final Map<String, JsonBall> overrideAttributes;
    @SerializedName("default_attributes")
-   private Map<String, JsonBall> defaultA = Maps.newLinkedHashMap();
+   private final Map<String, JsonBall> defaultAttributes;
    @SerializedName("run_list")
-   private List<String> runList = Lists.newArrayList();
+   private final List<String> runList;
 
    // internal
    @SerializedName("json_class")
-   private String _jsonClass = "Chef::Role";
+   private final String _jsonClass = "Chef::Role";
    @SerializedName("chef_type")
-   private String _chefType = "role";
+   private final String _chefType = "role";
 
-   public Role(String name, String description, Map<String, JsonBall> defaultA, List<String> runList,
-         Map<String, JsonBall> override) {
+   @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.defaultA = defaultA;
-      this.runList = runList;
-      this.override = override;
-   }
-
-   public Role(String name, Iterable<String> runList) {
-      this.name = name;
-      Iterables.addAll(this.runList, runList);
-   }
-
-   // hidden but needs to be here for json deserialization to work
-   Role() {
-
+      this.defaultAttributes = copyOfOrEmpty(defaultAttributes);
+      this.runList = copyOfOrEmpty(runList);
+      this.overrideAttributes = copyOfOrEmpty(overrideAttributes);
    }
 
    public String getName() {
@@ -75,12 +126,12 @@
       return description;
    }
 
-   public Map<String, JsonBall> getOverride() {
-      return override;
+   public Map<String, JsonBall> getOverrideAttributes() {
+      return overrideAttributes;
    }
 
-   public Map<String, JsonBall> getDefault() {
-      return defaultA;
+   public Map<String, JsonBall> getDefaultAttributes() {
+      return defaultAttributes;
    }
 
    public List<String> getRunList() {
@@ -93,10 +144,10 @@
       int result = 1;
       result = prime * result + ((_chefType == null) ? 0 : _chefType.hashCode());
       result = prime * result + ((_jsonClass == null) ? 0 : _jsonClass.hashCode());
-      result = prime * result + ((defaultA == null) ? 0 : defaultA.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 + ((override == null) ? 0 : override.hashCode());
+      result = prime * result + ((overrideAttributes == null) ? 0 : overrideAttributes.hashCode());
       result = prime * result + ((runList == null) ? 0 : runList.hashCode());
       return result;
    }
@@ -120,10 +171,10 @@
             return false;
       } else if (!_jsonClass.equals(other._jsonClass))
          return false;
-      if (defaultA == null) {
-         if (other.defaultA != null)
+      if (defaultAttributes == null) {
+         if (other.defaultAttributes != null)
             return false;
-      } else if (!defaultA.equals(other.defaultA))
+      } else if (!defaultAttributes.equals(other.defaultAttributes))
          return false;
       if (description == null) {
          if (other.description != null)
@@ -135,10 +186,10 @@
             return false;
       } else if (!name.equals(other.name))
          return false;
-      if (override == null) {
-         if (other.override != null)
+      if (overrideAttributes == null) {
+         if (other.overrideAttributes != null)
             return false;
-      } else if (!override.equals(other.override))
+      } else if (!overrideAttributes.equals(other.overrideAttributes))
          return false;
       if (runList == null) {
          if (other.runList != null)
@@ -150,8 +201,8 @@
 
    @Override
    public String toString() {
-      return "[name=" + name + ", description=" + description + ", defaultA=" + defaultA + ", override=" + override
-            + ", runList=" + runList + "]";
+      return "Role [name=" + name + ", description=" + description + ", defaultAttributes=" + defaultAttributes
+            + ", overrideAttributes=" + overrideAttributes + ", runList=" + runList + "]";
    }
 
 }
diff --git a/core/src/main/java/org/jclouds/chef/domain/Sandbox.java b/core/src/main/java/org/jclouds/chef/domain/Sandbox.java
index 98c8c96..774aa4a 100644
--- a/core/src/main/java/org/jclouds/chef/domain/Sandbox.java
+++ b/core/src/main/java/org/jclouds/chef/domain/Sandbox.java
@@ -16,49 +16,104 @@
  */
 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 com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableSet;
 import com.google.gson.annotations.SerializedName;
 
 /**
  * Sandbox object.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 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 String rev;
+   private final String rev;
    @SerializedName("is_completed")
-   private boolean isCompleted;
+   private final boolean isCompleted;
    @SerializedName("create_time")
-   private Date createTime;
-   private Set<String> checksums = Sets.newLinkedHashSet();
-   private String name;
-   private String guid;
+   private final Date createTime;
+   private final Set<String> checksums;
+   private final String name;
+   private final String guid;
 
    // internal
    @SerializedName("json_class")
-   private String _jsonClass = "Chef::Sandbox";
+   private final String _jsonClass = "Chef::Sandbox";
    @SerializedName("chef_type")
-   private String _chefType = "sandbox";
+   private final String _chefType = "sandbox";
 
-   public Sandbox(String rev, boolean isCompleted, Date createTime, Iterable<String> checksums, String name, String guid) {
+   @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;
-      Iterables.addAll(this.checksums, checksums);
+      this.checksums = copyOfOrEmpty(checksums);
       this.name = name;
       this.guid = guid;
    }
 
-   public Sandbox() {
-
-   }
-
    public String getRev() {
       return rev;
    }
@@ -141,4 +196,3 @@
             + isCompleted + ", name=" + name + ", rev=" + rev + "]";
    }
 }
-
diff --git a/core/src/main/java/org/jclouds/chef/domain/SearchResult.java b/core/src/main/java/org/jclouds/chef/domain/SearchResult.java
index 91222a2..1e2cced 100644
--- a/core/src/main/java/org/jclouds/chef/domain/SearchResult.java
+++ b/core/src/main/java/org/jclouds/chef/domain/SearchResult.java
@@ -21,11 +21,13 @@
 import com.google.common.collect.Iterables;
 
 /**
+ * A result of a search.
  * 
  * @author Adrian Cole
- * 
+ * @author Ignasi Barrera
  */
 public class SearchResult<T> extends LinkedHashSet<T> {
+   private static final long serialVersionUID = 4000610660948065287L;
    private long start;
 
    SearchResult() {
@@ -36,8 +38,6 @@
       Iterables.addAll(this, results);
    }
 
-   private static final long serialVersionUID = 4000610660948065287L;
-
    /**
     * 
     * @return the result position this started from from
@@ -47,4 +47,3 @@
    }
 
 }
-
diff --git a/core/src/main/java/org/jclouds/chef/domain/UploadSandbox.java b/core/src/main/java/org/jclouds/chef/domain/UploadSandbox.java
index f9bd6f1..868c147 100644
--- a/core/src/main/java/org/jclouds/chef/domain/UploadSandbox.java
+++ b/core/src/main/java/org/jclouds/chef/domain/UploadSandbox.java
@@ -16,31 +16,70 @@
  */
 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 com.google.common.collect.Maps;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableMap;
 import com.google.gson.annotations.SerializedName;
 
 /**
+ * An upload sandbox.
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 public class UploadSandbox {
-   private URI uri;
-   private Map<List<Byte>, ChecksumStatus> checksums = Maps.newLinkedHashMap();
-   @SerializedName("sandbox_id")
-   private String sandboxId;
-
-   public UploadSandbox(URI uri, Map<List<Byte>, ChecksumStatus> checksums, String sandboxId) {
-      this.uri = uri;
-      this.checksums.putAll(checksums);
-      this.sandboxId = sandboxId;
+   public static Builder builder() {
+      return new Builder();
    }
 
-   public UploadSandbox() {
+   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() {
@@ -94,8 +133,7 @@
 
    @Override
    public String toString() {
-      return "UploadSite [checksums=" + checksums + ", id=" + sandboxId + ", uri=" + uri + "]";
+      return "UploadSandbox [checksums=" + checksums + ", id=" + sandboxId + ", uri=" + uri + "]";
    }
 
 }
-
diff --git a/core/src/main/java/org/jclouds/chef/functions/ClientForGroup.java b/core/src/main/java/org/jclouds/chef/functions/ClientForGroup.java
index f51b7a2..a9c1c33 100644
--- a/core/src/main/java/org/jclouds/chef/functions/ClientForGroup.java
+++ b/core/src/main/java/org/jclouds/chef/functions/ClientForGroup.java
@@ -49,7 +49,12 @@
       String clientName = findNextClientName(chefApi.listClients(), from + "-client-%02d");
       Client client = chefApi.createClient(clientName);
       // response from create only includes the key
-      return new Client(null, null, clientName, clientName, false, client.getPrivateKey());
+      return Client.builder() //
+            .clientname(clientName) //
+            .name(clientName) //
+            .isValidator(false) //
+            .privateKey(client.getPrivateKey()) //
+            .build();
    }
 
    private static String findNextClientName(Set<String> clients, String pattern) {
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java
index 28d4e1b..a3ef7c2 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java
@@ -82,7 +82,7 @@
       }), and(notNull(), new Predicate<Node>() {
          @Override
          public boolean apply(Node input) {
-            JsonBall dateLong = input.getAutomatic().get("ohai_time");
+            JsonBall dateLong = input.getAutomaticAttributes().get("ohai_time");
             if (dateLong == null)
                return true;
             Calendar nodeUpdate = Calendar.getInstance();
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java
index 54de5e7..d6336b2 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java
@@ -61,14 +61,25 @@
    @Override
    public Node execute(Node node) {
       logger.trace("creating node %s", node.getName());
-      node.getAutomatic().putAll(automaticSupplier.get());
-      chef.createNode(node);
-      logger.debug("created 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(new Node(nodeName, runList, "_default"));
+      return execute(Node.builder().name(nodeName).runList(runList).environment("_default").build());
    }
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java
index e1af597..4d9bea4 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java
@@ -61,9 +61,17 @@
    public void execute(String nodeName) {
       logger.trace("updating node %s", nodeName);
       Node node = chef.getNode(nodeName);
-      Node mutable = new Node(node.getName(), node.getNormal(), node.getOverride(), node.getDefault(),
-            automaticSupplier.get(), node.getRunList(), node.getChefEnvironment());
-      chef.updateNode(mutable);
+      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);
    }
 }
diff --git a/core/src/main/java/org/jclouds/chef/test/TransientChefApi.java b/core/src/main/java/org/jclouds/chef/test/TransientChefApi.java
index 70a17d9..908ae2f 100644
--- a/core/src/main/java/org/jclouds/chef/test/TransientChefApi.java
+++ b/core/src/main/java/org/jclouds/chef/test/TransientChefApi.java
@@ -104,11 +104,6 @@
    }
 
    @Override
-   public boolean clientExists(String clientname) {
-      throw new UnsupportedOperationException();
-   }
-
-   @Override
    public Sandbox commitSandbox(String id, boolean isCompleted) {
       throw new UnsupportedOperationException();
    }
@@ -146,16 +141,6 @@
    }
 
    @Override
-   public boolean databagExists(String databagName) {
-      return databags.containerExists(databagName);
-   }
-
-   @Override
-   public boolean databagItemExists(String databagName, String databagItemId) {
-      return databags.blobExists(databagName, databagItemId);
-   }
-
-   @Override
    public Client deleteClient(String clientname) {
       throw new UnsupportedOperationException();
    }
@@ -263,16 +248,6 @@
    }
 
    @Override
-   public boolean nodeExists(String nodename) {
-      throw new UnsupportedOperationException();
-   }
-
-   @Override
-   public boolean roleExists(String rolename) {
-      throw new UnsupportedOperationException();
-   }
-
-   @Override
    public SearchResult<? extends Client> searchClients() {
       throw new UnsupportedOperationException();
    }
diff --git a/core/src/main/java/org/jclouds/chef/util/CollectionUtils.java b/core/src/main/java/org/jclouds/chef/util/CollectionUtils.java
new file mode 100644
index 0000000..0e62804
--- /dev/null
+++ b/core/src/main/java/org/jclouds/chef/util/CollectionUtils.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 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.
+ * 
+ * @author Ignasi Barrera
+ */
+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);
+   }
+}
diff --git a/core/src/test/java/org/jclouds/chef/ChefApiTest.java b/core/src/test/java/org/jclouds/chef/ChefApiTest.java
index bdb059a..33d6a98 100644
--- a/core/src/test/java/org/jclouds/chef/ChefApiTest.java
+++ b/core/src/test/java/org/jclouds/chef/ChefApiTest.java
@@ -28,7 +28,6 @@
 
 import org.jclouds.Constants;
 import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
-import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
 import org.jclouds.Fallbacks.NullOnNotFoundOr404;
 import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
 import org.jclouds.apis.ApiMetadata;
@@ -55,7 +54,6 @@
 import org.jclouds.http.functions.ParseJson;
 import org.jclouds.http.functions.ReleasePayloadAndReturn;
 import org.jclouds.http.functions.ReturnInputStream;
-import org.jclouds.http.functions.ReturnTrueIf2xx;
 import org.jclouds.io.Payload;
 import org.jclouds.io.payloads.StringPayload;
 import org.jclouds.reflect.Invocation;
@@ -172,7 +170,7 @@
       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", new CookbookVersion("cookbook", "1.0.1"))));
+            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: " + ChefApi.VERSION
@@ -229,22 +227,6 @@
 
    }
 
-   public void testApiExists() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(ChefApi.class, "clientExists", String.class);
-      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("api")));
-      assertRequestLineEquals(httpRequest, "HEAD http://localhost:4000/clients/api HTTP/1.1");
-      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApi.VERSION
-            + "-test\n");
-      assertPayloadEquals(httpRequest, null, null, false);
-
-      assertResponseParserClassEquals(method, httpRequest, ReturnTrueIf2xx.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, FalseOnNotFoundOr404.class);
-
-      checkFilters(httpRequest);
-
-   }
-
    public void testDeleteClient() throws SecurityException, NoSuchMethodException, IOException {
       Invokable<?, ?> method = method(ChefApi.class, "deleteClient", String.class);
       GeneratedHttpRequest httpRequest = processor
@@ -331,21 +313,6 @@
 
    }
 
-   public void testNodeExists() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(ChefApi.class, "nodeExists", String.class);
-      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("node")));
-      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApi.VERSION
-            + "-test\n");
-      assertPayloadEquals(httpRequest, null, null, false);
-
-      assertResponseParserClassEquals(method, httpRequest, ReturnTrueIf2xx.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, FalseOnNotFoundOr404.class);
-
-      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")));
@@ -364,8 +331,10 @@
 
    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(new Node("testnode", ImmutableSet.of("recipe[java]"), "_default"))));
+      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: " + ChefApi.VERSION
@@ -385,8 +354,10 @@
 
    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(new Node("testnode", ImmutableSet.of("recipe[java]"), "_default"))));
+      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: " + ChefApi.VERSION
@@ -421,22 +392,6 @@
 
    }
 
-   public void testRoleExists() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(ChefApi.class, "roleExists", String.class);
-      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("role")));
-      assertRequestLineEquals(httpRequest, "HEAD http://localhost:4000/roles/role HTTP/1.1");
-      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApi.VERSION
-            + "-test\n");
-      assertPayloadEquals(httpRequest, null, null, false);
-
-      assertResponseParserClassEquals(method, httpRequest, ReturnTrueIf2xx.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, FalseOnNotFoundOr404.class);
-
-      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")));
@@ -456,7 +411,7 @@
    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(new Role("testrole", ImmutableSet.of("recipe[java]")))));
+            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: " + ChefApi.VERSION
@@ -476,7 +431,7 @@
    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(new Role("testrole", ImmutableSet.of("recipe[java]")))));
+            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: " + ChefApi.VERSION
@@ -510,23 +465,6 @@
 
    }
 
-   public void testDatabagExists() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(ChefApi.class, "databagExists", String.class);
-      GeneratedHttpRequest httpRequest = processor
-            .apply(Invocation.create(method, ImmutableList.<Object> of("databag")));
-      assertRequestLineEquals(httpRequest, "HEAD http://localhost:4000/data/databag HTTP/1.1");
-      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApi.VERSION
-            + "-test\n");
-      assertPayloadEquals(httpRequest, null, null, false);
-
-      assertResponseParserClassEquals(method, httpRequest, ReturnTrueIf2xx.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, FalseOnNotFoundOr404.class);
-
-      checkFilters(httpRequest);
-
-   }
-
    public void testDeleteDatabag() throws SecurityException, NoSuchMethodException, IOException {
       Invokable<?, ?> method = method(ChefApi.class, "deleteDatabag", String.class);
       GeneratedHttpRequest httpRequest = processor
@@ -578,23 +516,6 @@
 
    }
 
-   public void testDatabagItemExists() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(ChefApi.class, "databagItemExists", String.class, String.class);
-      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
-            ImmutableList.<Object> of("name", "databagItem")));
-      assertRequestLineEquals(httpRequest, "HEAD http://localhost:4000/data/name/databagItem HTTP/1.1");
-      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApi.VERSION
-            + "-test\n");
-      assertPayloadEquals(httpRequest, null, null, false);
-
-      assertResponseParserClassEquals(method, httpRequest, ReturnTrueIf2xx.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, FalseOnNotFoundOr404.class);
-
-      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,
@@ -891,7 +812,7 @@
    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(new Resource("test", URI.create("http://foo/bar"), new byte[] {}, null, null))));
+            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: " + ChefApi.VERSION
diff --git a/core/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java b/core/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java
index c703090..282e68e 100644
--- a/core/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java
+++ b/core/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java
@@ -17,6 +17,7 @@
 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;
@@ -56,11 +57,10 @@
       binder.bindToRequest(request, new File("foo"));
    }
 
-   @Test(enabled = false)
    public void testCorrect() {
       HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost").build();
       binder.bindToRequest(request,
-            ImmutableSet.of(base16().lowerCase().decode("abddef"), base16().lowerCase().decode("1234")));
+            ImmutableSet.of(asList(base16().lowerCase().decode("abddef")), asList(base16().lowerCase().decode("1234"))));
       assertEquals(request.getPayload().getRawContent(), "{\"checksums\":{\"abddef\":null,\"1234\":null}}");
    }
 
diff --git a/core/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java b/core/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java
new file mode 100644
index 0000000..76db791
--- /dev/null
+++ b/core/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.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.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.
+ * 
+ * @author Ignasi Barrera
+ */
+@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")));
+   }
+}
diff --git a/core/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java b/core/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java
index 6fd7449..355d9a4 100644
--- a/core/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java
+++ b/core/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java
@@ -79,8 +79,8 @@
 
    public void test() throws IOException {
 
-      Client user = new Client(certificate, "jclouds", "adriancole-jcloudstest", "adriancole-jcloudstest", false,
-            privateKey);
+      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(Payloads.newPayload("fooya"), user
             .getCertificate().getPublicKey()));
diff --git a/core/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java b/core/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java
index e806fa2..987e45a 100644
--- a/core/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java
+++ b/core/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java
@@ -31,12 +31,11 @@
 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)
+@Test(groups = { "unit" }, singleThreaded = true)
 public class ParseCookbookDefinitionFromJsonv10Test {
 
    private ParseCookbookDefinitionFromJsonv10 handler;
@@ -54,6 +53,13 @@
    }
 
    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()
+            .url(new URI("http://localhost:4000/cookbooks/apache2")).version(v510).version(v420).build();
+
       assertEquals(handler.apply(HttpResponse
             .builder()
             .statusCode(200)
@@ -63,9 +69,6 @@
                         + "\"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()),
-            new CookbookDefinition(new URI("http://localhost:4000/cookbooks/apache2"),
-                  ImmutableSet.of(new CookbookDefinition.Version(new URI("http://localhost:4000/cookbooks/apache2/5.1.0"), "5.1.0"),
-                        new CookbookDefinition.Version(new URI("http://localhost:4000/cookbooks/apache2/4.2.0"), "4.2.0"))));
+                        + "\"version\" => \"4.2.0\"}" + "]" + "}" + "}").build()), definition);
    }
 }
diff --git a/core/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java b/core/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java
index 95b26ea..d2cc882 100644
--- a/core/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java
+++ b/core/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java
@@ -54,6 +54,20 @@
    }
 
    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()
+            .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 v130 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/nginx/0.3.0")).version("0.3.0").build();
+      CookbookDefinition nginx = CookbookDefinition.builder()
+            .url(new URI("http://localhost:4000/cookbooks/nginx")).version(v100).version(v130).build();
+      
       assertEquals(handler.apply(HttpResponse
             .builder()
             .statusCode(200)
@@ -73,11 +87,6 @@
                         + "\"version\" => \"0.3.0\"}"
                         + "]}" +
                         "}").build()),
-            ImmutableSet.of(new CookbookDefinition(new URI("http://localhost:4000/cookbooks/apache2"),
-                  ImmutableSet.of(new CookbookDefinition.Version(new URI("http://localhost:4000/cookbooks/apache2/5.1.0"), "5.1.0"),
-                        new CookbookDefinition.Version(new URI("http://localhost:4000/cookbooks/apache2/4.2.0"), "4.2.0"))),
-                  new CookbookDefinition(new URI("http://localhost:4000/cookbooks/nginx"),
-                        ImmutableSet.of(new CookbookDefinition.Version(new URI("http://localhost:4000/cookbooks/nginx/1.0.0"), "1.0.0"),
-                              new CookbookDefinition.Version(new URI("http://localhost:4000/cookbooks/nginx/0.3.0"), "0.3.0")))));
+            ImmutableSet.of(apache2, nginx));
    }
 }
diff --git a/core/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java b/core/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java
index f5d0f1f..c1f21de 100644
--- a/core/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java
+++ b/core/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java
@@ -24,7 +24,6 @@
 
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.config.ChefParserModule;
-import org.jclouds.chef.domain.Attribute;
 import org.jclouds.chef.domain.CookbookVersion;
 import org.jclouds.chef.domain.Metadata;
 import org.jclouds.chef.domain.Resource;
@@ -36,8 +35,6 @@
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
 
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
@@ -48,6 +45,7 @@
  * Tests behavior of {@code ParseCookbookVersionFromJson}
  * 
  * @author Adrian Cole
+ * @author Ignasi Barrera
  */
 @Test(groups = { "unit" }, singleThreaded = true)
 public class ParseCookbookVersionFromJsonTest {
@@ -70,7 +68,6 @@
       }));
    }
 
-   @Test(enabled = false)
    public void testBrew() throws IOException {
       CookbookVersion cookbook = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
             .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/brew-cookbook.json")).build());
@@ -79,7 +76,6 @@
             handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json.toJson(cookbook)).build()));
    }
 
-   @Test(enabled = false)
    public void testTomcat() {
       CookbookVersion cookbook = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
             .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/tomcat-cookbook.json")).build());
@@ -88,7 +84,6 @@
             handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json.toJson(cookbook)).build()));
    }
 
-   @Test(enabled = false)
    public void testMysql() throws IOException {
       CookbookVersion cookbook = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
             .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/mysql-cookbook.json")).build());
@@ -97,44 +92,41 @@
             handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json.toJson(cookbook)).build()));
    }
 
-   @Test(enabled = false)
    public void testApache() {
+      CookbookVersion fromJson = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/apache-chef-demo-cookbook.json"))
+            .build());
 
-      assertEquals(
-            handler.apply(HttpResponse
-                  .builder()
-                  .statusCode(200)
-                  .message("ok")
-                  .payload(
-                        ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/apache-chef-demo-cookbook.json"))
-                  .build()),
-            new CookbookVersion(
-                  "apache-chef-demo-0.0.0",
-                  ImmutableSet.<Resource> of(),
-                  ImmutableSet.<Attribute> of(),
-                  ImmutableSet.<Resource> of(),
-                  new Metadata("Apache v2.0", "Your Name", ImmutableMap.<String, String> of(), ImmutableMap
-                        .<String, String> of(), "youremail@example.com", ImmutableMap.<String, String> of(),
-                        "A fabulous new cookbook", ImmutableMap.<String, String> of(), ImmutableMap
-                              .<String, String> of(), "0.0.0", ImmutableMap.<String, String> of(), ImmutableMap
-                              .<String, String> of(), "apache-chef-demo", ImmutableMap.<String, String> of(), "",
-                        ImmutableMap.<String, Attribute> of(), ImmutableMap.<String, String> of()),
-                  ImmutableSet.<Resource> of(),
-                  "apache-chef-demo",
-                  ImmutableSet.<Resource> of(),
-                  ImmutableSet.<Resource> of(),
-                  ImmutableSet.<Resource> of(),
-                  "0.0.0",
-                  ImmutableSet.<Resource> of(),
-                  ImmutableSet.<Resource> of(
-                        new Resource(
-                              "README",
-                              URI.create("https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-11637f98942eafbf49c71b7f2f048b78?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277766181&Signature=zgpNl6wSxjTNovqZu2nJq0JztU8%3D"),
-                              base16().lowerCase().decode("11637f98942eafbf49c71b7f2f048b78"), "README", "default"),
-                        new Resource(
-                              "Rakefile",
-                              URI.create("https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-ebcf925a1651b4e04b9cd8aac2bc54eb?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277766181&Signature=EFzzDSKKytTl7b%2FxrCeNLh05zj4%3D"),
-                              base16().lowerCase().decode("ebcf925a1651b4e04b9cd8aac2bc54eb"), "Rakefile", "default"))));
+      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);
    }
 }
diff --git a/core/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java b/core/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java
index d0d9070..5f825dd 100644
--- a/core/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java
+++ b/core/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java
@@ -19,7 +19,6 @@
 import static org.testng.Assert.assertEquals;
 
 import java.io.IOException;
-import java.util.Collections;
 
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.config.ChefParserModule;
@@ -32,7 +31,6 @@
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
@@ -63,10 +61,12 @@
    }
 
    public void test() {
-
-      Node node = new Node("adrian-jcloudstest", ImmutableMap.<String, JsonBall> of("tomcat6", new JsonBall(
-            "{\"ssl_port\":8433}")), ImmutableMap.<String, JsonBall> of(), ImmutableMap.<String, JsonBall> of(),
-            ImmutableMap.<String, JsonBall> of(), Collections.singleton("recipe[java]"), "prod");
+      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")
diff --git a/core/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java b/core/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java
index 76edffd..13843ee 100644
--- a/core/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java
+++ b/core/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java
@@ -31,7 +31,6 @@
 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;
@@ -67,8 +66,8 @@
       assertEquals(
             handler.apply(HttpResponse.builder().statusCode(200).message("ok")
                   .payload(ParseSandboxFromJsonTest.class.getResourceAsStream("/sandbox.json")).build()),
-            new Sandbox("1-8c27b0ea4c2b7aaedbb44cfbdfcc11b2", false, dateService
-                  .iso8601SecondsDateParse("2010-07-07T03:36:00+00:00"), ImmutableSet.<String> of(),
-                  "f9d6d9b72bae465890aae87969f98a9c", "f9d6d9b72bae465890aae87969f98a9c"));
+            Sandbox.builder().rev("1-8c27b0ea4c2b7aaedbb44cfbdfcc11b2").isCompleted(false)
+                  .createTime(dateService.iso8601SecondsDateParse("2010-07-07T03:36:00+00:00"))
+                  .name("f9d6d9b72bae465890aae87969f98a9c").guid("f9d6d9b72bae465890aae87969f98a9c").build());
    }
 }
diff --git a/core/src/test/java/org/jclouds/chef/functions/ParseUploadSandboxFromJsonTest.java b/core/src/test/java/org/jclouds/chef/functions/ParseUploadSandboxFromJsonTest.java
index e992e79..03e61ca 100644
--- a/core/src/test/java/org/jclouds/chef/functions/ParseUploadSandboxFromJsonTest.java
+++ b/core/src/test/java/org/jclouds/chef/functions/ParseUploadSandboxFromJsonTest.java
@@ -22,7 +22,6 @@
 
 import java.io.IOException;
 import java.net.URI;
-import java.util.List;
 
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.config.ChefParserModule;
@@ -35,7 +34,6 @@
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
@@ -70,14 +68,22 @@
       assertEquals(
             handler.apply(HttpResponse.builder().statusCode(200).message("ok")
                   .payload(ParseUploadSandboxFromJsonTest.class.getResourceAsStream("/upload-site.json")).build()),
-            new UploadSandbox(
-                  URI.create("https://api.opscode.com/organizations/jclouds/sandboxes/d454f71e2a5f400c808d0c5d04c2c88c"),
-                  ImmutableMap.<List<Byte>, ChecksumStatus> of(
+            UploadSandbox
+                  .builder()
+                  .uri(URI
+                        .create("https://api.opscode.com/organizations/jclouds/sandboxes/d454f71e2a5f400c808d0c5d04c2c88c"))
+                  .checksum(
                         asList(base16().lowerCase().decode("0c5ecd7788cf4f6c7de2a57193897a6c")),
-                        new ChecksumStatus(
-                              URI.create("https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/sandbox-d454f71e2a5f400c808d0c5d04c2c88c/checksum-0c5ecd7788cf4f6c7de2a57193897a6c?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277344702&Signature=FtKyqvYEjhhEKmRY%2B0M8aGPMM7g%3D"),
-                              true), asList(base16().lowerCase().decode("0189e76ccc476701d6b374e5a1a27347")),
-                        new ChecksumStatus(), asList(base16().lowerCase().decode("1dda05ed139664f1f89b9dec482b77c0")),
-                        new ChecksumStatus()), "d454f71e2a5f400c808d0c5d04c2c88c"));
+                        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()
+
+      );
    }
 }
diff --git a/core/src/test/java/org/jclouds/chef/functions/UriForResourceTest.java b/core/src/test/java/org/jclouds/chef/functions/UriForResourceTest.java
index 2504b53..98c7d07 100644
--- a/core/src/test/java/org/jclouds/chef/functions/UriForResourceTest.java
+++ b/core/src/test/java/org/jclouds/chef/functions/UriForResourceTest.java
@@ -48,7 +48,7 @@
    @Test
    public void testWithValidResource() {
       Function<Object, URI> function = new UriForResource();
-      Resource res = new Resource("test", URI.create("http://foo/bar"), null, null, null);
+      Resource res = Resource.builder().name("test").url(URI.create("http://foo/bar")).build();
       URI result = function.apply(res);
       assertEquals(res.getUrl().toString(), result.toString());
    }
diff --git a/core/src/test/java/org/jclouds/chef/internal/BaseChefApiLiveTest.java b/core/src/test/java/org/jclouds/chef/internal/BaseChefApiLiveTest.java
index adf11cf..529df68 100644
--- a/core/src/test/java/org/jclouds/chef/internal/BaseChefApiLiveTest.java
+++ b/core/src/test/java/org/jclouds/chef/internal/BaseChefApiLiveTest.java
@@ -17,6 +17,7 @@
 package org.jclouds.chef.internal;
 
 import static com.google.common.base.Throwables.propagate;
+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.io.ByteSources.asByteSource;
@@ -25,6 +26,7 @@
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 import java.io.File;
 import java.io.IOException;
@@ -40,6 +42,7 @@
 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;
@@ -78,71 +81,88 @@
    protected DatabagItem databagItem;
 
    public void testCreateNewCookbook() throws Exception {
-
-      // define the file you want in the cookbook
+      // Define the file you want in the cookbook
       FilePayload content = Payloads.newFilePayload(new File(System.getProperty("user.dir"), "pom.xml"));
       content.getContentMetadata().setContentType("application/x-binary");
 
-      // get an md5 so that you can see if the server already has it or not
+      // Get an md5 so that you can see if the server already has it or not
       Payloads.calculateMD5(content);
 
       // Note that java collections cannot effectively do equals or hashcodes on
-      // byte arrays,
-      // so let's convert to a list of bytes.
+      // 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
+      // Request an upload site for this file
       UploadSandbox site = api.getUploadSandboxForChecksums(ImmutableSet.of(md5));
+      assertTrue(site.getChecksums().containsKey(md5), md5 + " not in " + site.getChecksums());
 
       try {
-         assert site.getChecksums().containsKey(md5) : md5 + " not in " + site.getChecksums();
-
+         // Upload the file contents, if still not uploaded
          ChecksumStatus status = site.getChecksums().get(md5);
          if (status.needsUpload()) {
-            // context.utils().http().put(status.getUrl(), content);
             api.uploadContent(status.getUrl(), content);
          }
-
          api.commitSandbox(site.getSandboxId(), true);
-
       } catch (RuntimeException e) {
          api.commitSandbox(site.getSandboxId(), false);
+         fail("Could not upload content");
       }
 
-      // create a new cookbook
-      CookbookVersion cookbook = new CookbookVersion(PREFIX, "0.0.0");
-      cookbook.getRootFiles().add(new Resource(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);
    }
 
-   @Test(dependsOnMethods = "testCreateClient")
-   public void testGenerateKeyForClient() throws Exception {
-      String credential = Pems.pem(api.generateKeyForClient(PREFIX).getPrivateKey());
-      assertClientCreated(PREFIX, credential);
-   }
-
-   @Test
    public void testListCookbooks() throws Exception {
       Set<String> cookbookNames = api.listCookbooks();
-      assertFalse(cookbookNames.isEmpty());
+      assertFalse(cookbookNames.isEmpty(), "No cookbooks were found");
 
-      for (String cookbook : cookbookNames) {
-         for (String version : api.getVersionsOfCookbook(cookbook)) {
-            CookbookVersion cookbookO = api.getCookbook(cookbook, version);
-            for (Resource resource : ImmutableList.<Resource> builder().addAll(cookbookO.getDefinitions())
-                  .addAll(cookbookO.getFiles()).addAll(cookbookO.getLibraries()).addAll(cookbookO.getSuppliers())
-                  .addAll(cookbookO.getRecipes()).addAll(cookbookO.getResources()).addAll(cookbookO.getRootFiles())
-                  .addAll(cookbookO.getTemplates()).build()) {
-               try {
-                  InputStream stream = api.getResourceContents(resource);
-                  byte[] md5 = asByteSource(stream).hash(md5()).asBytes();
-                  assertEquals(md5, resource.getChecksum());
-               } catch (NullPointerException e) {
-                  assert false : "resource not found: " + resource;
-               }
-            }
+      for (String cookbookName : cookbookNames) {
+         Set<String> versions = api.getVersionsOfCookbook(cookbookName);
+         assertFalse(versions.isEmpty(), "There are no versions of the cookbook: " + cookbookName);
+
+         for (String version : api.getVersionsOfCookbook(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 = asByteSource(stream).hash(md5()).asBytes();
+            assertEquals(md5, resource.getChecksum());
          }
       }
    }
@@ -150,12 +170,13 @@
    @Test(dependsOnMethods = "testCreateNewCookbook")
    public void testUpdateCookbook() throws Exception {
       CookbookVersion cookbook = api.getCookbook(PREFIX, "0.0.0");
-      assertNotNull(api.updateCookbook(PREFIX, "0.0.0", cookbook));
+      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"));
+      assertNotNull(api.deleteCookbook(PREFIX, "0.0.0"), "Deleted cookbook was null");
    }
 
    @Test
@@ -172,34 +193,30 @@
       assertClientCreated(ADMIN_PREFIX, credential);
    }
 
-   @Test
-   public void testClientExists() throws Exception {
-      assertNotNull(api.clientExists(identity));
+   @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);
+      assertNotNull(nodes, "No nodes were found");
    }
 
    @Test(dependsOnMethods = "testCreateRole")
    public void testCreateNode() throws Exception {
       api.deleteNode(PREFIX);
-      api.createNode(new Node(PREFIX, Collections.singleton("role[" + PREFIX + "]"), "_default"));
+      api.createNode(Node.builder().name(PREFIX).runListElement("role[" + PREFIX + "]").environment("_default").build());
       node = api.getNode(PREFIX);
       // TODO check recipes
-      assertNotNull(node);
+      assertNotNull(node, "Created node should not be null");
       Set<String> nodes = api.listNodes();
-      assert nodes.contains(PREFIX) : String.format("node %s not in %s", PREFIX, nodes);
+      assertTrue(nodes.contains(PREFIX), String.format("node %s not in %s", PREFIX, nodes));
    }
 
    @Test(dependsOnMethods = "testCreateNode")
-   public void testNodeExists() throws Exception {
-      assertNotNull(api.nodeExists(PREFIX));
-   }
-
-   @Test(dependsOnMethods = "testNodeExists")
    public void testUpdateNode() throws Exception {
       for (String nodename : api.listNodes()) {
          Node node = api.getNode(nodename);
@@ -210,25 +227,20 @@
    @Test
    public void testListRoles() throws Exception {
       Set<String> roles = api.listRoles();
-      assertNotNull(roles);
+      assertNotNull(roles, "Role list was null");
    }
 
    @Test
    public void testCreateRole() throws Exception {
       api.deleteRole(PREFIX);
-      api.createRole(new Role(PREFIX, Collections.singleton("recipe[java]")));
+      api.createRole(Role.builder().name(PREFIX).runListElement("recipe[java]").build());
       role = api.getRole(PREFIX);
-      assertNotNull(role);
+      assertNotNull(role, "Created role should not be null");
       assertEquals(role.getName(), PREFIX);
       assertEquals(role.getRunList(), Collections.singleton("recipe[java]"));
    }
 
    @Test(dependsOnMethods = "testCreateRole")
-   public void testRoleExists() throws Exception {
-      assertNotNull(api.roleExists(PREFIX));
-   }
-
-   @Test(dependsOnMethods = "testRoleExists")
    public void testUpdateRole() throws Exception {
       for (String rolename : api.listRoles()) {
          Role role = api.getRole(rolename);
@@ -239,7 +251,7 @@
    @Test
    public void testListDatabags() throws Exception {
       Set<String> databags = api.listDatabags();
-      assertNotNull(databags);
+      assertNotNull(databags, "Data bag list was null");
    }
 
    @Test
@@ -248,15 +260,10 @@
       api.createDatabag(PREFIX);
    }
 
-   @Test(dependsOnMethods = "testCreateDatabag")
-   public void testDatabagExists() throws Exception {
-      assertNotNull(api.databagExists(PREFIX));
-   }
-
    @Test(dependsOnMethods = "testCreateDatabagItem")
    public void testListDatabagItems() throws Exception {
       Set<String> databagItems = api.listDatabagItems(PREFIX);
-      assertNotNull(databagItems);
+      assertNotNull(databagItems, "Data bag item list was null");
    }
 
    @Test(dependsOnMethods = "testCreateDatabag")
@@ -265,7 +272,7 @@
       config.setProperty("foo", "bar");
       api.deleteDatabagItem(PREFIX, PREFIX);
       databagItem = api.createDatabagItem(PREFIX, new DatabagItem("config", json.toJson(config)));
-      assertNotNull(databagItem);
+      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
@@ -278,11 +285,6 @@
    }
 
    @Test(dependsOnMethods = "testCreateDatabagItem")
-   public void testDatabagItemExists() throws Exception {
-      assertNotNull(api.databagItemExists(PREFIX, PREFIX));
-   }
-
-   @Test(dependsOnMethods = "testDatabagItemExists")
    public void testUpdateDatabagItem() throws Exception {
       for (String databagItemId : api.listDatabagItems(PREFIX)) {
          DatabagItem databagItem = api.getDatabagItem(PREFIX, databagItemId);
@@ -293,16 +295,16 @@
    @Test
    public void testListSearchIndexes() throws Exception {
       Set<String> indexes = api.listSearchIndexes();
-      assertNotNull(indexes);
-      assert indexes.contains("node") : indexes;
-      assert indexes.contains("client") : indexes;
-      assert indexes.contains("role") : indexes;
+      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);
+      assertNotNull(results, "Node result list should not be null");
    }
 
    @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateNode" })
@@ -330,7 +332,7 @@
    @Test
    public void testSearchClients() throws Exception {
       SearchResult<? extends Client> results = api.searchClients();
-      assertNotNull(results);
+      assertNotNull(results, "Client result list should not be null");
    }
 
    @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateClient" })
@@ -358,7 +360,7 @@
    @Test
    public void testSearchRoles() throws Exception {
       SearchResult<? extends Role> results = api.searchRoles();
-      assertNotNull(results);
+      assertNotNull(results, "Role result list should not be null");
    }
 
    @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateRole" })
@@ -383,13 +385,13 @@
       assertTrue(waitForIndex.apply(options));
    }
 
-   @Test(dependsOnMethods = { "testListSearchIndexes", "testDatabagItemExists" })
+   @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateDatabagItem" })
    public void testSearchDatabag() throws Exception {
       SearchResult<? extends DatabagItem> results = api.searchDatabag(PREFIX);
-      assertNotNull(results);
+      assertNotNull(results, "Data bag item result list should not be null");
    }
 
-   @Test(dependsOnMethods = { "testListSearchIndexes", "testDatabagItemExists" })
+   @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateDatabagItem" })
    public void testSearchDatabagWithOptions() throws Exception {
       Predicate<SearchOptions> waitForIndex = retry(new Predicate<SearchOptions>() {
          @Override
@@ -414,15 +416,15 @@
    @Test(expectedExceptions = ResourceNotFoundException.class, dependsOnMethods = "testListSearchIndexes")
    public void testSearchDatabagNotFound() throws Exception {
       SearchResult<? extends DatabagItem> results = api.searchDatabag("whoopie");
-      assertNotNull(results);
+      assertNotNull(results, "Data bag item result list should not be null");
    }
 
    @Test
    public void testCreateEnvironment() {
       api.deleteEnvironment(PREFIX);
-      api.createEnvironment(new Environment(PREFIX, PREFIX));
+      api.createEnvironment(Environment.builder().name(PREFIX).description(PREFIX).build());
       Environment env = api.getEnvironment(PREFIX);
-      assertNotNull(env);
+      assertNotNull(env, "Created environment should not be null");
       assertEquals(env.getName(), PREFIX);
       assertEquals(env.getDescription(), PREFIX);
    }
@@ -430,14 +432,14 @@
    @Test(dependsOnMethods = "testCreateEnvironment")
    public void testListEnvironment() {
       Set<String> envList = api.listEnvironments();
-      assertNotNull(envList);
+      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);
+      assertNotNull(results, "Environment result list was null");
    }
 
    @Test(dependsOnMethods = { "testListSearchIndexes", "testCreateEnvironment" })
@@ -462,12 +464,6 @@
       assertTrue(waitForIndex.apply(options));
    }
 
-   @Test
-   public void testListCookbookVersionsWithChefService() throws Exception {
-      Iterable<? extends CookbookVersion> cookbooks = chefService.listCookbookVersions();
-      assertNotNull(cookbooks);
-   }
-
    @AfterClass(groups = { "live", "integration" })
    @Override
    public void tearDown() {
@@ -489,7 +485,7 @@
 
       try {
          Client client = clientApi.getClient(identity);
-         assertNotNull(client);
+         assertNotNull(client, "Client not found: " + identity);
       } finally {
          try {
             Closeables.close(clientApi, true);
diff --git a/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java b/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java
index a904569..4b55efd 100644
--- a/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java
+++ b/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java
@@ -55,7 +55,7 @@
          Node node = api.getNode(prefix);
          assertEquals(node.getName(), prefix);
          assertEquals(node.getRunList(), runList);
-         assertEquals(node.getAutomatic().get("current_user").toString(), currentUserProvider.get().toString());
+         assertEquals(node.getAutomaticAttributes().get("current_user").toString(), currentUserProvider.get().toString());
       } finally {
          api.deleteNode(prefix);
       }
diff --git a/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplTest.java b/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplTest.java
index 776247f..ace63e1 100644
--- a/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplTest.java
+++ b/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplTest.java
@@ -44,17 +44,11 @@
    public void testWithNoRunlist() {
       ChefApi chef = createMock(ChefApi.class);
 
-      Map<String, JsonBall> automatic = ImmutableMap.<String, JsonBall> of();
+      Supplier<Map<String, JsonBall>> automaticSupplier = Suppliers.<Map<String, JsonBall>> ofInstance(ImmutableMap.<String, JsonBall> of());
 
-      Node node = new Node("name", ImmutableSet.<String> of(), "_default");
+      Node nodeWithAutomatic = Node.builder().name("name").environment("_default")
+            .automaticAttributes(automaticSupplier.get()).build();
 
-      Supplier<Map<String, JsonBall>> automaticSupplier = Suppliers.<Map<String, JsonBall>> ofInstance(automatic);
-
-      Node nodeWithAutomatic = new Node("name", ImmutableMap.<String, JsonBall> of(),
-            ImmutableMap.<String, JsonBall> of(), ImmutableMap.<String, JsonBall> of(), automatic,
-            ImmutableSet.<String> of(), "_default");
-
-      node.getAutomatic().putAll(automaticSupplier.get());
       chef.createNode(nodeWithAutomatic);
 
       replay(chef);
diff --git a/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java b/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java
index 755576c..e8681d2 100644
--- a/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java
+++ b/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java
@@ -50,12 +50,12 @@
    public void testExecute() {
       Set<String> runList = ImmutableSet.of("role[" + prefix + "]");
       try {
-         api.createNode(new Node(prefix, runList, "_default"));
+         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.getAutomatic().get("current_user").toString(), currentUserProvider.get().toString());
+         assertEquals(node.getAutomaticAttributes().get("current_user").toString(), currentUserProvider.get().toString());
       } finally {
          api.deleteNode(prefix);
       }
diff --git a/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplTest.java b/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplTest.java
index bf4a0e4..792e391 100644
--- a/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplTest.java
+++ b/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplTest.java
@@ -16,10 +16,10 @@
  */
 package org.jclouds.chef.strategy.internal;
 
+import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
-import static org.easymock.classextension.EasyMock.createMock;
-import static org.easymock.classextension.EasyMock.replay;
-import static org.easymock.classextension.EasyMock.verify;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
 
 import java.util.Map;
 
@@ -31,7 +31,6 @@
 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 UpdateAutomaticAttributesOnNodeImpl}
@@ -46,17 +45,13 @@
       ChefApi chef = createMock(ChefApi.class);
 
       Map<String, JsonBall> automatic = ImmutableMap.<String, JsonBall> of();
-
-      Node node = new Node("name", ImmutableSet.<String> of(), "_default");
-
       Supplier<Map<String, JsonBall>> automaticSupplier = Suppliers.<Map<String, JsonBall>> ofInstance(automatic);
 
-      Node nodeWithAutomatic = new Node("name", ImmutableMap.<String, JsonBall> of(),
-            ImmutableMap.<String, JsonBall> of(), ImmutableMap.<String, JsonBall> of(), automatic,
-            ImmutableSet.<String> of(), "_default");
+      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);
-      node.getAutomatic().putAll(automaticSupplier.get());
       expect(chef.updateNode(nodeWithAutomatic)).andReturn(null);
 
       replay(chef);
diff --git a/core/src/test/java/org/jclouds/chef/test/TransientChefApiIntegrationTest.java b/core/src/test/java/org/jclouds/chef/test/TransientChefApiIntegrationTest.java
index d11b1cf..13c002e 100644
--- a/core/src/test/java/org/jclouds/chef/test/TransientChefApiIntegrationTest.java
+++ b/core/src/test/java/org/jclouds/chef/test/TransientChefApiIntegrationTest.java
@@ -50,12 +50,7 @@
       api.createDatabag(PREFIX);
    }
 
-   @Test(dependsOnMethods = "testCreateDatabag")
-   public void testDatabagExists() {
-      assertNotNull(api.databagExists(PREFIX));
-   }
-
-   @Test(dependsOnMethods = { "testDatabagExists" })
+   @Test(dependsOnMethods = { "testCreateDatabag" })
    public void testCreateDatabagItem() {
       Properties config = new Properties();
       config.setProperty("foo", "bar");
@@ -66,11 +61,6 @@
    }
 
    @Test(dependsOnMethods = "testCreateDatabagItem")
-   public void testDatabagItemExists() {
-      assertNotNull(api.databagItemExists(PREFIX, PREFIX));
-   }
-
-   @Test(dependsOnMethods = "testDatabagItemExists")
    public void testUpdateDatabagItem() {
       for (String databagItemId : api.listDatabagItems(PREFIX)) {
          DatabagItem databagItem = api.getDatabagItem(PREFIX, databagItemId);
diff --git a/core/src/test/resources/apache-chef-demo-cookbook.json b/core/src/test/resources/apache-chef-demo-cookbook.json
index 0a3e384..228a3c0 100644
--- a/core/src/test/resources/apache-chef-demo-cookbook.json
+++ b/core/src/test/resources/apache-chef-demo-cookbook.json
@@ -6,14 +6,23 @@
     "json_class": "Chef::CookbookVersion",
     "providers": [],
     "metadata": {
-        "dependencies": {}, "name": "apache-chef-demo",
+        "dependencies": {},
+        "name": "apache-chef-demo",
         "maintainer_email": "youremail@example.com",
-        "attributes": {}, "license": "Apache v2.0",
+        "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": {}
+        "suggestions": {},
+        "platforms": {},
+        "long_description": "",
+        "recommendations": {},
+        "version": "0.0.0",
+        "groupings": {},
+        "recipes": {},
+        "conflicting": {},
+        "description": "A fabulous new cookbook",
+        "replacing": {},
+        "providing": {}
     }, "libraries": [],
     "resources": [],
     "templates": [],
diff --git a/core/src/test/resources/brew-cookbook.json b/core/src/test/resources/brew-cookbook.json
index f0b09ed..dcf7aca 100644
--- a/core/src/test/resources/brew-cookbook.json
+++ b/core/src/test/resources/brew-cookbook.json
@@ -1 +1,48 @@
-{"name":"brew-0.0.0","definitions":[],"json_class":"Chef::CookbookVersion","attributes":[],"files":[],"metadata":{"dependencies":{},"name":"brew","maintainer_email":"youremail@example.com","license":"Apache v2.0","attributes":{},"maintainer":"Your Name","suggestions":{},"platforms":{},"long_description":"","version":"0.0.0","recommendations":{},"conflicting":{},"recipes":{"brew":""},"groupings":{},"description":"A fabulous new cookbook","replacing":{},"providing":{"brew":[]}},"providers":[{"name":"brew.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-0c5ecd7788cf4f6c7de2a57193897a6c?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774465&Signature=brTA3YkBF7iDnjPGCCHxgm7AHko%3D","checksum":"0c5ecd7788cf4f6c7de2a57193897a6c","path":"providers/brew.rb","specificity":"default"}],"cookbook_name":"brew","resources":[{"name":"brew.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-0189e76ccc476701d6b374e5a1a27347?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774465&Signature=ufrI1k6pKJ1%2FBRMAaIGr6icJlpc%3D","checksum":"0189e76ccc476701d6b374e5a1a27347","path":"resources/brew.rb","specificity":"default"}],"templates":[],"libraries":[],"version":"0.0.0","recipes":[{"name":"default.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-1dda05ed139664f1f89b9dec482b77c0?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774465&Signature=dOzPk64at92zOfZlxt1suDpGuPs%3D","checksum":"1dda05ed139664f1f89b9dec482b77c0","path":"recipes/default.rb","specificity":"default"}],"root_files":[],"chef_type":"cookbook_version"}
\ No newline at end of file
+{ "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
diff --git a/core/src/test/resources/client.json b/core/src/test/resources/client.json
index 15ff8d0..eef7c71 100644
--- a/core/src/test/resources/client.json
+++ b/core/src/test/resources/client.json
@@ -1 +1,8 @@
-{"orgname":"jclouds","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","uri":"https://api.opscode.com/organizations/jclouds/clients/adriancole-jcloudstest","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+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","clientname":"adriancole-jcloudstest","name":"adriancole-jcloudstest","validator":false}
\ No newline at end of file
+{ "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+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",
+  "uri" : "https://api.opscode.com/organizations/jclouds/clients/adriancole-jcloudstest",
+  "validator" : false
+}
\ No newline at end of file
diff --git a/core/src/test/resources/client.txt b/core/src/test/resources/client.txt
deleted file mode 100644
index 5023370..0000000
--- a/core/src/test/resources/client.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-    "certificate": "-----BEGIN CERTIFICATE-----\ndXQ==\n-----END CERTIFICATE-----\n",
-    "orgname": "jclouds",
-    "clientname": "adrian-jcloudstest",
-    "name": "adrian-jcloudstest",
-    "validator": false
-}
\ No newline at end of file
diff --git a/core/src/test/resources/logback.xml b/core/src/test/resources/logback.xml
index 50b810a..f9c023f 100644
--- a/core/src/test/resources/logback.xml
+++ b/core/src/test/resources/logback.xml
@@ -2,15 +2,12 @@
 <configuration scan="false">

     <appender name="FILE" class="ch.qos.logback.core.FileAppender">

         <file>target/test-data/jclouds.log</file>

-

         <encoder>

             <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>

         </encoder>

     </appender>

-

     <appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">

         <file>target/test-data/jclouds-wire.log</file>

-

         <encoder>

             <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>

         </encoder>

@@ -24,12 +21,10 @@
         <level value="DEBUG" />

         <appender-ref ref="FILE" />

     </logger>

-

     <logger name="jclouds.wire">

         <level value="DEBUG" />

         <appender-ref ref="WIREFILE" />

     </logger>

-

     <logger name="jclouds.headers">

         <level value="DEBUG" />

         <appender-ref ref="WIREFILE" />

diff --git a/core/src/test/resources/mysql-cookbook.json b/core/src/test/resources/mysql-cookbook.json
index b0f8bc4..b0450ae 100644
--- a/core/src/test/resources/mysql-cookbook.json
+++ b/core/src/test/resources/mysql-cookbook.json
@@ -1 +1,268 @@
-{"definitions":[],"name":"mysql-0.21.2","attributes":[{"name":"server.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-548fa4bc548b8b59ac98fffee8e81f4a?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=MsgggSKS0T1q1Lc72nJpHORBQX0%3D","checksum":"548fa4bc548b8b59ac98fffee8e81f4a","path":"attributes/server.rb","specificity":"default"}],"files":[],"json_class":"Chef::CookbookVersion","providers":[{"name":"database.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-b994881a2aba60e32c4b6408ffba993d?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=2XMRbryCmEqirCWLCvrXoenYubw%3D","checksum":"b994881a2aba60e32c4b6408ffba993d","path":"providers/database.rb","specificity":"default"}],"metadata":{"dependencies":{"openssl":[]},"name":"mysql","maintainer_email":"cookbooks@opscode.com","attributes":{"mysql/server_root_password":{"required":"optional","calculated":false,"default":"randomly generated","choice":[],"type":"string","recipes":[],"display_name":"MySQL Server Root Password","description":"Randomly generated password for the mysqld root user"},"mysql/tunable/max_heap_table_size":{"required":"optional","calculated":false,"default":"32M","choice":[],"type":"string","recipes":[],"display_name":"MySQL Tunable Max Heap Table Size"},"mysql/bind_address":{"required":"optional","calculated":false,"default":"ipaddress","choice":[],"type":"string","recipes":[],"display_name":"MySQL Bind Address","description":"Address that mysqld should listen on"},"mysql/datadir":{"required":"optional","calculated":false,"default":"/var/lib/mysql","choice":[],"type":"string","recipes":[],"display_name":"MySQL Data Directory","description":"Location of mysql databases"},"mysql/tunable/wait_timeout":{"required":"optional","calculated":false,"default":"180","choice":[],"type":"string","recipes":[],"display_name":"MySQL Tunable Wait Timeout"},"mysql/tunable/back_log":{"required":"optional","calculated":false,"default":"128","choice":[],"type":"string","recipes":[],"display_name":"MySQL Tunable Back Log"},"mysql/tunable/net_read_timeout":{"required":"optional","calculated":false,"default":"30","choice":[],"type":"string","recipes":[],"display_name":"MySQL Tunable Net Read Timeout"},"mysql/tunable/max_connections":{"required":"optional","calculated":false,"default":"800","choice":[],"type":"string","recipes":[],"display_name":"MySQL Tunable Max Connections"},"mysql/tunable":{"required":"optional","calculated":false,"choice":[],"type":"hash","recipes":[],"display_name":"MySQL Tunables","description":"Hash of MySQL tunable attributes"},"mysql/tunable/table_cache":{"required":"optional","calculated":false,"default":"128","choice":[],"type":"string","recipes":[],"display_name":"MySQL Tunable Table Cache"},"mysql/ec2_path":{"required":"optional","calculated":false,"default":"/mnt/mysql","choice":[],"type":"string","recipes":[],"display_name":"MySQL EC2 Path","description":"Location of mysql directory on EC2 instance EBS volumes"},"mysql/tunable/key_buffer":{"required":"optional","calculated":false,"default":"250M","choice":[],"type":"string","recipes":[],"display_name":"MySQL Tuntable Key Buffer"},"mysql/tunable/net_write_timeout":{"required":"optional","calculated":false,"default":"30","choice":[],"type":"string","recipes":[],"display_name":"MySQL Tunable Net Write Timeout"}},"license":"Apache 2.0","maintainer":"Opscode, Inc.","suggestions":{},"platforms":{"debian":[],"ubuntu":[]},"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 randomly 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 (<joshua@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","recommendations":{},"version":"0.21.2","groupings":{},"recipes":{"mysql::client":"Installs packages required for mysql clients using run_action magic","mysql::server_ec2":"Performs EC2-specific mountpoint manipulation","mysql::server":"Installs packages required for mysql servers w/o manual intervention"},"conflicting":{},"description":"Installs and configures mysql for client or server","replacing":{},"providing":{}},"libraries":[{"name":"database.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-b2eb0760c07734be9c637dcffc86175a?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=CvVbxzrA%2Fuxc59ZjLR5BsMozfxk%3D","checksum":"b2eb0760c07734be9c637dcffc86175a","path":"libraries/database.rb","specificity":"default"}],"resources":[{"name":"database.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-8aa8e2cafe54c2932c7aa65d62ec2695?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=6URS94f1HpRibHC%2FhBkr7Eg3dVA%3D","checksum":"8aa8e2cafe54c2932c7aa65d62ec2695","path":"resources/database.rb","specificity":"default"}],"templates":[{"name":"my.cnf.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-689c1b6fbb242b6c508384e56646341d?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=2ugp0XVvvUktYdBxfC9bCZBjOs4%3D","checksum":"689c1b6fbb242b6c508384e56646341d","path":"templates/ubuntu-9.10/my.cnf.erb","specificity":"ubuntu-9.10"},{"name":"mysql-server.seed.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-16b036a0bb31957a77e9b825cf616cc5?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=50bRvt6GQqFEcfYtaIsq1d4a4c8%3D","checksum":"16b036a0bb31957a77e9b825cf616cc5","path":"templates/default/mysql-server.seed.erb","specificity":"default"},{"name":"grants.sql.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-932b51ddddcbd24ee10a76ecae33b8ba?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=RHR0obLFcsN1M9tL28IH4Tcur5g%3D","checksum":"932b51ddddcbd24ee10a76ecae33b8ba","path":"templates/default/grants.sql.erb","specificity":"default"},{"name":"my.cnf.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-7746560b37ac8d4a0cf68befbecbd8a3?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=W7Nqkvhw2TBrlDvcM2rlA4Oj5%2Bk%3D","checksum":"7746560b37ac8d4a0cf68befbecbd8a3","path":"templates/default/my.cnf.erb","specificity":"default"},{"name":"my.cnf.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-63bd67fae6d297e8f658e9c0ad01a411?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=oFcRya56f%2F4oJBGrIvk4XcWQFm4%3D","checksum":"63bd67fae6d297e8f658e9c0ad01a411","path":"templates/centos/my.cnf.erb","specificity":"centos"},{"name":"my.cnf.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-689c1b6fbb242b6c508384e56646341d?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=2ugp0XVvvUktYdBxfC9bCZBjOs4%3D","checksum":"689c1b6fbb242b6c508384e56646341d","path":"templates/ubuntu-10.04/my.cnf.erb","specificity":"ubuntu-10.04"},{"name":"my.cnf.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-63bd67fae6d297e8f658e9c0ad01a411?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=oFcRya56f%2F4oJBGrIvk4XcWQFm4%3D","checksum":"63bd67fae6d297e8f658e9c0ad01a411","path":"templates/redhat/my.cnf.erb","specificity":"redhat"},{"name":"port_mysql.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-d2244150a145b3f658cd37c13269fafc?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=23iHcjwpNOvlNf%2BlrKcV2pkU7uo%3D","checksum":"d2244150a145b3f658cd37c13269fafc","path":"templates/default/port_mysql.erb","specificity":"default"},{"name":"debian.cnf.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-2e08553db526f5f80c28b343f6a616cb?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=VdcFYxnLUkj1tS3k8DovrzZEA7E%3D","checksum":"2e08553db526f5f80c28b343f6a616cb","path":"templates/default/debian.cnf.erb","specificity":"default"},{"name":"my.cnf.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-1e5068eec65b51f5a327580fb0af4677?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=D5lPeu1UQvA9pEXZm5nVOwj3WIo%3D","checksum":"1e5068eec65b51f5a327580fb0af4677","path":"templates/ubuntu-8.04/my.cnf.erb","specificity":"ubuntu-8.04"}],"cookbook_name":"mysql","version":"0.21.2","recipes":[{"name":"server_ec2.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-f51bd8122b7dccc9f4656319fef3252a?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=BUR2mosCvmoOKq4Mkh3JUG0MY38%3D","checksum":"f51bd8122b7dccc9f4656319fef3252a","path":"recipes/server_ec2.rb","specificity":"default"},{"name":"server.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-80daa897597560372d017c58c4df0e3c?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=bU3j9Nw%2BnuIroXKrlJZe7tjaugA%3D","checksum":"80daa897597560372d017c58c4df0e3c","path":"recipes/server.rb","specificity":"default"},{"name":"default.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-bd3ba2d05dea6a8cf0dc2a45f540cc32?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=7haumT5EtEM2h8l32efqV%2Fik%2BdY%3D","checksum":"bd3ba2d05dea6a8cf0dc2a45f540cc32","path":"recipes/default.rb","specificity":"default"},{"name":"client.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-a1d679c7480267cd9b69e3194c7e45ab?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=ZhfTiwv1aXC7HZMnW8A7i4vkMCM%3D","checksum":"a1d679c7480267cd9b69e3194c7e45ab","path":"recipes/client.rb","specificity":"default"}],"root_files":[{"name":"README.rdoc","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-e9278fc99fd668bdce33d72dc71fade9?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=KPgAGqShEO5SGzz8oRdwIInPUOc%3D","checksum":"e9278fc99fd668bdce33d72dc71fade9","path":"README.rdoc","specificity":"default"},{"name":"metadata.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-8d2f9635f4817ff905a4124e09ec6c59?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=fPF2iY7tNrq%2FuNrCjRLImP9vRbA%3D","checksum":"8d2f9635f4817ff905a4124e09ec6c59","path":"metadata.rb","specificity":"default"},{"name":"metadata.json","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-e6804b8f3e6dfdbbece9d319537ffea1?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277774082&Signature=EVsALLreeAA41%2BiPAfPt%2FxFEIYI%3D","checksum":"e6804b8f3e6dfdbbece9d319537ffea1","path":"metadata.json","specificity":"default"}],"chef_type":"cookbook_version"}
\ No newline at end of file
+{ "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 randomly 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 (<joshua@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
diff --git a/core/src/test/resources/newclient.txt b/core/src/test/resources/newclient.txt
deleted file mode 100644
index c1e9368..0000000
--- a/core/src/test/resources/newclient.txt
+++ /dev/null
@@ -1 +0,0 @@
-{"clientname":"adriancole-jcloudstest","private_key":"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAuzaE6azgUxwESX1rCGdJ5xpdrc1XC311bOGZBCE8NA+CpFh2\npopCBQwjpOnlgpdd/+C+TESl30ojauvVej9AbgJb30Jl7e7dEX4Brncnj03G+mo+\nG4osf7I2PA/6+9Ol7xamK9GL/cs8nOb17cRTWmhTRW7+3Rrli/s6wzqQXjGjWzgz\nthXv7FOPHA87UjJzrePBFta7+S8BxKCG2QaTxzNGytSAy8KBX8BUrSt5+X22QjEM\nQF3zA4TPtoWp/lcDRzCMdffMYoVPZzKqIeEFSexwvNlJ/qU6hbcyAxab1lYawjKU\nRgvPCflVYTIw6teHNqkyvTPX+lpIAVXigSVQXwIDAQABAoIBAHz81xvTSSjzaYFO\n9Gh13QcnuSdSEi0fo4f/zdLOBY2UVVo3nW9umskX46w0ZAAd4qn0d9gfdMZwjtjR\nfoLRO8i2VnPltnt1n64P/DtoXcb03EVPLQvh4feXGVJcMOpz0TKgYmyax+W3DE6M\ne+Az1JplUELo6crgLCSapA63SK85PEuWAcMUQg9s6MnzB/qXz95yJlzgjVMIJUyb\n9jFdq2s0gefTpK2cKeSYWQAFPd41Ea5v/3j0LN8qs/dImNnzxDXu+hi8+16/4PTK\npl+1bJXwE9YkWPdd39EfjVkk6q/HyFijK3VpHnOy7n3iaJTUKwBJLRsFrQ5Eor3U\nvNKyGXECgYEA3RZdFC6MRBAo76GKd9axbA0G9Bmct9pQT4B+od2AGGOdiYzYRWfF\nlyTgctY9dcfsX5DBQFUHNnYXMHHI0wHQk1m20UpFLa7IV3RWkW5JwYkbQHmeP4pn\np8GtJEXC+4PrT0Pc32acfWozArokUju7nLLazCPCDdfc8t9MPX1W230CgYEA2MbB\ndwdwLZx9zEqZ0MciRxjsOA30b6OYPOqMP1ADVeExPN8fHLCAQjwUExQa86+FMV4H\nOtu+DXlisp+TSTRQzpXMfGppupbK1j5cqz2sU37upKuz/uf0XyeyBLOi0y9/DMl5\njG2StLLIMawRqJRUuq/fyA/6oTzADNwoW6LjCgsCgYBGvCj7lAj8nc77HEwZG2+Y\ninJ3Ftq1V/vp88qQLzYUl4qHv7BSRGlLelj1ZOY1EMnnqYCq/IlaO14f+ceu+x2o\nh0OeooyPmSQwFuC7lvWyHhPCBSdEXRvc6HJk8Iz5u7NFoQjB0SqwVZIMhVGpncLg\n17h5J9emZjIi4p6Z7cgkYQKBgHt+/8in3Cif9qrj9S0TxVtrv2dPy+mt8ZUCqlOH\nad8LI9nh4v+dLfSN9YHI+nHJlL/DKatGdMeIV8obTvVtcHvAq3ZVyVYbggL8FB8a\nS4plzd7SUwDtdDKhkrFLBX/6lw7Z2P0/j0ySbaqetJCtsHeKqpp3P/mLen3ZDsTl\nzyJxAoGBAIxl1SGzu3lO3BQ5+EPaprNw3eN3nzG4WLQvnzZwpeAFS+E5pllMkIfs\nu01Vfv68NC4u6LFgdXSY1vQt6hiA5TNqQk0TyVfFAunbXgTekF6XqDPQUf1nq9aZ\nlMvo4vlaLDKBkhG5HJE/pIa0iB+RMZLS0GhxsIWerEDmYdHKM25o\n-----END RSA PRIVATE KEY-----\n","uri":"https://api.opscode.com/organizations/jclouds/clients/adriancole-jcloudstest","certificate":"-----BEGIN CERTIFICATE-----\nMIIClzCCAgCgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxFjAUBgNVBAoM\nDU9wc2NvZGUsIEluYy4xHDAaBgNVBAsME0NlcnRpZmljYXRlIFNlcnZpY2UxMjAw\nBgNVBAMMKW9wc2NvZGUuY29tL2VtYWlsQWRkcmVzcz1hdXRoQG9wc2NvZGUuY29t\nMB4XDTEwMDYwNDIzMzM0NloXDTIwMDYwMTIzMzM0NlowADCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBALs2hOms4FMcBEl9awhnSecaXa3NVwt9dWzhmQQh\nPDQPgqRYdqaKQgUMI6Tp5YKXXf/gvkxEpd9KI2rr1Xo/QG4CW99CZe3u3RF+Aa53\nJ49NxvpqPhuKLH+yNjwP+vvTpe8WpivRi/3LPJzm9e3EU1poU0Vu/t0a5Yv7OsM6\nkF4xo1s4M7YV7+xTjxwPO1Iyc63jwRbWu/kvAcSghtkGk8czRsrUgMvCgV/AVK0r\nefl9tkIxDEBd8wOEz7aFqf5XA0cwjHX3zGKFT2cyqiHhBUnscLzZSf6lOoW3MgMW\nm9ZWGsIylEYLzwn5VWEyMOrXhzapMr0z1/paSAFV4oElUF8CAwEAATANBgkqhkiG\n9w0BAQUFAAOBgQCTllbpWNagYjCiaU5UnjIFXn0YyNfZzqCh8SQ0Asj8MtksVbFG\nAErp03+Cb9a7GTdNE7fIyPsLTnGzFhqTwKN+3jIj4wgxhrbYXF73x1+rDRyHjJu7\na7gdTEYZqWiAHdW47vXj69W1dB5e4vNm1F29gOSL/x7BMAyjLFWbdbKw0w==\n-----END CERTIFICATE-----\n","orgname":"jclouds}
\ No newline at end of file
diff --git a/core/src/test/resources/node.json b/core/src/test/resources/node.json
index ecdb470..c8ddaef 100644
--- a/core/src/test/resources/node.json
+++ b/core/src/test/resources/node.json
@@ -1,2 +1,10 @@
-{"normal":{"tomcat6":{"ssl_port":8433}},"name":"adrian-jcloudstest","override":{},"default":{},"json_class":"Chef::Node","automatic":{},"run_list":["recipe[java]"],"chef_type":"node","chef_environment": "prod"}
-      
+{ "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
diff --git a/core/src/test/resources/tomcat-cookbook.json b/core/src/test/resources/tomcat-cookbook.json
index 6331b51..992f401 100644
--- a/core/src/test/resources/tomcat-cookbook.json
+++ b/core/src/test/resources/tomcat-cookbook.json
@@ -1 +1,121 @@
-{"definitions":[],"name":"tomcat6-0.1.0","files":[{"name":"org.apache.tomcat.tomcat6.plist","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-18e534a72652f3d53b197ca4e5027009?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=M3fBL4t7uuYVXVah2PyZ8eL1QCc%3D","checksum":"18e534a72652f3d53b197ca4e5027009","path":"files/default/org.apache.tomcat.tomcat6.plist","specificity":"default"},{"name":"logging.properties","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-6a35ce92050296862ea63b784529d2e0?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=vRlKoye2%2Fwz8mdQI%2F3Ht916sllE%3D","checksum":"6a35ce92050296862ea63b784529d2e0","path":"files/default/logging.properties","specificity":"default"}],"attributes":[{"name":"tomcat6.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-6e3fd0d16a87a55c569da108194ecb29?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=zvYjsgUXIC7Xj3MT8Wd93esDGJM%3D","checksum":"6e3fd0d16a87a55c569da108194ecb29","path":"attributes/tomcat6.rb","specificity":"default"}],"json_class":"Chef::CookbookVersion","providers":[],"metadata":{"dependencies":{},"name":"tomcat6","maintainer_email":"cookbooks@opscode.com","license":"Apache 2.0","attributes":{"tomcat6/with_native":{"required":"optional","calculated":false,"default":"false","choice":[],"type":"string","recipes":[],"display_name":"Tomcat native support","description":"works for centos, install tomcat-native libraries"}},"maintainer":"Opscode, Inc.","suggestions":{},"platforms":{"debian":[],"centos":[],"ubuntu":[],"redhat":[]},"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 (<edmund@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\nlimitations under the License.\n","version":"0.1.0","recommendations":{},"conflicting":{},"recipes":{"tomcat6":"Main Tomcat 6 configuration"},"groupings":{},"description":"Installs and configures all aspects of tomcat6 using custom local installation","replacing":{},"providing":{}},"libraries":[{"name":"tomcat_manager.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-2b6f7847142bb36823c570899669c54b?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=zxIoUKcGYWo9ir6qf1tTy3wvKZ4%3D","checksum":"2b6f7847142bb36823c570899669c54b","path":"libraries/tomcat_manager.rb","specificity":"default"},{"name":"tomcat.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-24db7b7dd6f04f8da5fa2b282910ac08?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=c4Gbn5kX0ZaPbWvk5LAcR77sITg%3D","checksum":"24db7b7dd6f04f8da5fa2b282910ac08","path":"libraries/tomcat.rb","specificity":"default"}],"templates":[{"name":"tomcat-users.xml.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-107263b81e4700cf0adad7af2a133bbd?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=QC7MNIauR2ox5MVlohf2i73uM1s%3D","checksum":"107263b81e4700cf0adad7af2a133bbd","path":"templates/default/tomcat-users.xml.erb","specificity":"default"},{"name":"sv-tomcat6-run.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-fa4432b353fa57b9da26a4bff44285f2?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=q2rDeJFeh4oyhfv9ExMifGB0wxo%3D","checksum":"fa4432b353fa57b9da26a4bff44285f2","path":"templates/default/sv-tomcat6-run.erb","specificity":"default"},{"name":"manager.xml.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-33fd6f63133e7ebe28bc62e58773c408?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=rIijEhwLPf5PWTJOg%2BElbOquPBM%3D","checksum":"33fd6f63133e7ebe28bc62e58773c408","path":"templates/default/manager.xml.erb","specificity":"default"},{"name":"sv-tomcat6-log-run.erb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-09f2bf988663175cd1b7973198dfb5eb?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=UnfNDP4pDzPM3PoLcLWyTnTa%2FKI%3D","checksum":"09f2bf988663175cd1b7973198dfb5eb","path":"templates/default/sv-tomcat6-log-run.erb","specificity":"default"}],"resources":[],"cookbook_name":"tomcat6","version":"0.1.0","recipes":[{"name":"default.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-d45661e4b50f9677de7b8684af26ff9d?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=r3CvyVe7dcOzB%2F0fNAun5ldGwr8%3D","checksum":"d45661e4b50f9677de7b8684af26ff9d","path":"recipes/default.rb","specificity":"default"}],"root_files":[{"name":"README.rdoc","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-14f6977f68c3674484e8289e361fb5a4?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=VNZxN%2B7CxO7ZbDHJOS%2FaTtpkPaE%3D","checksum":"14f6977f68c3674484e8289e361fb5a4","path":"README.rdoc","specificity":"default"},{"name":"metadata.rb","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-abc416ffba9ea64ca71635191cb87af6?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=pemynt9Q1F%2BxlS26kLaz%2F4NDGO4%3D","checksum":"abc416ffba9ea64ca71635191cb87af6","path":"metadata.rb","specificity":"default"},{"name":"metadata.json","url":"https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-dd8473a8a7f2b446250ecdefb1882a5e?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277771874&Signature=sHpayqP%2Fe4Luv20EMa3q%2FaMN4ms%3D","checksum":"dd8473a8a7f2b446250ecdefb1882a5e","path":"metadata.json","specificity":"default"}],"chef_type":"cookbook_version"}
\ No newline at end of file
+{ "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 (<edmund@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\nlimitations 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
diff --git a/enterprise/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java b/enterprise/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java
index 04a7dda..0e0b2a6 100644
--- a/enterprise/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java
+++ b/enterprise/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java
@@ -29,7 +29,6 @@
 import javax.ws.rs.core.MediaType;
 
 import org.jclouds.Constants;
-import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
 import org.jclouds.Fallbacks.NullOnNotFoundOr404;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.filters.SignedHeaderAuth;
@@ -56,20 +55,6 @@
 @Headers(keys = "X-Chef-Version", values = "{" + Constants.PROPERTY_API_VERSION + "}")
 public interface EnterpriseChefApi extends ChefApi
 {
-
-    /**
-     * Check if there exists a node with the given name.
-     * 
-     * @return <code>true</code> if the specified node name exists.
-     */
-    @Override
-    // Use get instead of HEAD
-    @Named("node:exists")
-    @GET
-    @Path("/nodes/{nodename}")
-    @Fallback(FalseOnNotFoundOr404.class)
-    boolean nodeExists(@PathParam("nodename") String nodename);
-
     /**
      * Retrieves an existing user.
      * 
diff --git a/enterprise/src/main/java/org/jclouds/enterprisechef/domain/Group.java b/enterprise/src/main/java/org/jclouds/enterprisechef/domain/Group.java
index 056acf4..2c0f058 100644
--- a/enterprise/src/main/java/org/jclouds/enterprisechef/domain/Group.java
+++ b/enterprise/src/main/java/org/jclouds/enterprisechef/domain/Group.java
@@ -16,11 +16,15 @@
  */
 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 com.google.common.collect.Sets;
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableSet;
 
 /**
  * Group object.
@@ -28,22 +32,102 @@
  * @author Ignasi Barrera
  */
 public class Group {
-   private String name;
-   private String groupname;
-   private String orgname;
-   private Set<String> actors = Sets.newHashSet();
-   private Set<String> clients = Sets.newHashSet();
-   private Set<String> groups = Sets.newHashSet();
-   private Set<String> users = Sets.newHashSet();
-
-   // When creating groups, only the group name property is considered
-   public Group(String groupname) {
-      this.groupname = checkNotNull(groupname, "groupname");
+   public static Builder builder(String groupname) {
+      return new Builder(groupname);
    }
 
-   // Only for deserialization
-   Group() {
+   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() {
@@ -74,18 +158,6 @@
       return users;
    }
 
-   public void setClients(Set<String> clients) {
-      this.clients = clients;
-   }
-
-   public void setGroups(Set<String> groups) {
-      this.groups = groups;
-   }
-
-   public void setUsers(Set<String> users) {
-      this.users = users;
-   }
-
    @Override
    public int hashCode() {
       final int prime = 31;
diff --git a/enterprise/src/main/java/org/jclouds/enterprisechef/domain/User.java b/enterprise/src/main/java/org/jclouds/enterprisechef/domain/User.java
index 247d1a8..c2c71fa 100644
--- a/enterprise/src/main/java/org/jclouds/enterprisechef/domain/User.java
+++ b/enterprise/src/main/java/org/jclouds/enterprisechef/domain/User.java
@@ -16,6 +16,9 @@
  */
 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;
@@ -26,24 +29,110 @@
  * @author Ignasi Barrera
  */
 public class User {
-   private String username;
+   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 String firstName;
+   private final String firstName;
    @SerializedName("middle_name")
-   private String middleName;
+   private final String middleName;
    @SerializedName("last_name")
-   private String lastName;
+   private final String lastName;
    @SerializedName("display_name")
-   private String displayName;
-   private String email;
+   private final String displayName;
+   private final String email;
    @SerializedName("public_key")
-   private PublicKey publicKey;
+   private final PublicKey publicKey;
 
-   // TODO: Add a constructor to allow creating users. Need an Enterprise Chef instance!
+   @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;
+   }
 
-   // Only for deserialization
-   User() {
+   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
@@ -124,34 +213,6 @@
       return true;
    }
 
-   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 String toString() {
       return "User [username=" + username + ", firstName=" + firstName + ", middleName=" + middleName + ", lastName="
diff --git a/enterprise/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiExpectTest.java b/enterprise/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiExpectTest.java
index e859dc2..1ee2bdf 100644
--- a/enterprise/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiExpectTest.java
+++ b/enterprise/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiExpectTest.java
@@ -27,8 +27,6 @@
 import org.jclouds.chef.BaseChefApiExpectTest;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.date.TimeStamp;
-import org.jclouds.enterprisechef.EnterpriseChefApi;
-import org.jclouds.enterprisechef.EnterpriseChefProviderMetadata;
 import org.jclouds.enterprisechef.config.EnterpriseChefHttpApiModule;
 import org.jclouds.enterprisechef.domain.Group;
 import org.jclouds.enterprisechef.domain.User;
@@ -40,7 +38,6 @@
 import org.testng.annotations.Test;
 
 import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableSet;
 import com.google.inject.Module;
 
 /**
@@ -174,11 +171,7 @@
             .build()), //
             HttpResponse.builder().statusCode(200).build());
 
-      Group group = new Group("admins");
-      group.setClients(ImmutableSet.of("abiquo"));
-      group.setGroups(ImmutableSet.of("admins"));
-      group.setUsers(ImmutableSet.of("nacx"));
-
+      Group group = Group.builder("admins").client("abiquo").group("admins").user("nacx").build();
       api.updateGroup(group);
    }
 
@@ -193,11 +186,7 @@
             .build()), //
             HttpResponse.builder().statusCode(404).build());
 
-      Group group = new Group("admins");
-      group.setClients(ImmutableSet.of("abiquo"));
-      group.setGroups(ImmutableSet.of("admins"));
-      group.setUsers(ImmutableSet.of("nacx"));
-
+      Group group = Group.builder("admins").client("abiquo").group("admins").user("nacx").build();
       api.updateGroup(group);
    }
 
diff --git a/enterprise/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiLiveTest.java b/enterprise/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiLiveTest.java
index 576351a..47148c9 100644
--- a/enterprise/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiLiveTest.java
+++ b/enterprise/src/test/java/org/jclouds/enterprisechef/EnterpriseChefApiLiveTest.java
@@ -26,14 +26,11 @@
 import java.util.UUID;
 
 import org.jclouds.chef.internal.BaseChefApiLiveTest;
-import org.jclouds.enterprisechef.EnterpriseChefApi;
 import org.jclouds.enterprisechef.domain.Group;
 import org.jclouds.enterprisechef.domain.User;
 import org.jclouds.rest.ResourceNotFoundException;
 import org.testng.annotations.Test;
 
-import com.google.common.collect.ImmutableSet;
-
 /**
  * Tests behavior of the EnterpriseChefApi.
  * 
@@ -90,10 +87,16 @@
    @Test(dependsOnMethods = "testCreateGroup")
    public void testUpdateGroup() {
       Group group = api.getGroup(GROUP_NAME);
-      group.setUsers(ImmutableSet.of(identity));
-      group.setClients(ImmutableSet.of(ORG_NAME + "-validator"));
+      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(group);
+      api.updateGroup(updated);
       group = api.getGroup(GROUP_NAME);
 
       assertNotNull(group);
@@ -103,7 +106,7 @@
 
    @Test(expectedExceptions = ResourceNotFoundException.class)
    public void testUpdateUnexistingGroup() {
-      api.updateGroup(new Group(UUID.randomUUID().toString()));
+      api.updateGroup(Group.builder(UUID.randomUUID().toString()).build());
    }
 
    @Test(dependsOnMethods = "testUpdateGroup")
diff --git a/enterprise/src/test/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayloadTest.java b/enterprise/src/test/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayloadTest.java
index 90e84dd..29e120e 100644
--- a/enterprise/src/test/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayloadTest.java
+++ b/enterprise/src/test/java/org/jclouds/enterprisechef/binders/BindGroupToUpdateRequestJsonPayloadTest.java
@@ -23,7 +23,6 @@
 
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.config.ChefParserModule;
-import org.jclouds.enterprisechef.binders.BindGroupToUpdateRequestJsonPayload;
 import org.jclouds.enterprisechef.domain.Group;
 import org.jclouds.http.HttpRequest;
 import org.jclouds.json.config.GsonModule;
@@ -31,7 +30,6 @@
 import org.jclouds.util.Strings2;
 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;
@@ -67,17 +65,14 @@
 
    public void testBindOnlyName() throws IOException {
       HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build();
-      HttpRequest newRequest = binder.bindToRequest(request, new Group("foo"));
+      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 = new Group("foo");
-      group.setClients(ImmutableSet.of("nacx-validator"));
-      group.setGroups(ImmutableSet.of("admins"));
-      group.setUsers(ImmutableSet.of("nacx"));
+      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);
diff --git a/enterprise/src/test/java/org/jclouds/enterprisechef/binders/GroupNameTest.java b/enterprise/src/test/java/org/jclouds/enterprisechef/binders/GroupNameTest.java
index b93c68a..ee8ff46 100644
--- a/enterprise/src/test/java/org/jclouds/enterprisechef/binders/GroupNameTest.java
+++ b/enterprise/src/test/java/org/jclouds/enterprisechef/binders/GroupNameTest.java
@@ -38,6 +38,6 @@
    }
 
    public void testApplyGroupName() throws IOException {
-      assertEquals(new GroupName().apply(new Group("foo")), "foo");
+      assertEquals(new GroupName().apply(Group.builder("foo").build()), "foo");
    }
 }