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

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

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

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java
new file mode 100644
index 0000000..46c765b
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/strategy/internal/CleanupStaleNodesAndClientsImpl.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.and;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+import static org.jclouds.chef.util.ChefUtils.fromOhaiTime;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.strategy.CleanupStaleNodesAndClients;
+import org.jclouds.chef.strategy.DeleteAllClientsInList;
+import org.jclouds.chef.strategy.DeleteAllNodesInList;
+import org.jclouds.chef.strategy.ListNodes;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+/**
+ * 
+ * Cleans up nodes and apis who have been hanging around too long.
+ */
+@Singleton
+public class CleanupStaleNodesAndClientsImpl implements CleanupStaleNodesAndClients {
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final ListNodes nodeLister;
+   private final DeleteAllNodesInList nodeDeleter;
+   private final DeleteAllClientsInList clientDeleter;
+
+   @Inject
+   public CleanupStaleNodesAndClientsImpl(DeleteAllNodesInList nodeDeleter, DeleteAllClientsInList clientDeleter,
+         ListNodes nodeLister) {
+      this.nodeLister = checkNotNull(nodeLister, "nodeLister");
+      this.nodeDeleter = checkNotNull(nodeDeleter, "nodeDeleter");
+      this.clientDeleter = checkNotNull(clientDeleter, "clientDeleter");
+   }
+
+   @Override
+   public void execute(final String prefix, int secondsStale) {
+      final Calendar expired = Calendar.getInstance();
+      expired.setTime(new Date());
+      expired.add(Calendar.SECOND, -secondsStale);
+      Iterable<? extends Node> staleNodes = filter(
+         nodeLister.execute(), and(notNull(), new Predicate<Node>() {
+               @Override
+               public boolean apply(Node input) {
+                  return input.getName().startsWith(prefix);
+               }
+         },
+         new Predicate<Node>() {
+             @Override
+             public boolean apply(Node input) {
+                JsonBall dateLong = input.getAutomaticAttributes().get("ohai_time");
+                if (dateLong == null)
+                   return true;
+                Calendar nodeUpdate = Calendar.getInstance();
+                nodeUpdate.setTime(fromOhaiTime(dateLong));
+                return expired.after(nodeUpdate);
+             }
+         }));
+      Iterable<String> nodeNames = transform(staleNodes, new Function<Node, String>() {
+
+         @Override
+         public String apply(Node from) {
+            return from.getName();
+         }
+
+      });
+      nodeDeleter.execute(nodeNames);
+      clientDeleter.execute(nodeNames);
+   }
+}

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

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

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

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

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

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

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

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

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

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

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/suppliers/ChefVersionSupplier.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/suppliers/ChefVersionSupplier.java b/apis/chef/src/main/java/org/jclouds/chef/suppliers/ChefVersionSupplier.java
new file mode 100644
index 0000000..a0d8b04
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/suppliers/ChefVersionSupplier.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.suppliers;
+
+import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.chef.config.ChefProperties;
+import org.jclouds.logging.Logger;
+import org.jclouds.rest.annotations.ApiVersion;
+
+import com.google.common.base.Supplier;
+
+/**
+ * Properly supply the version of the Chef Server.
+ */
+@Singleton
+public class ChefVersionSupplier implements Supplier<Integer> {
+
+   /** The default version to assume in case we can not parse it. */
+   public static final Integer FALLBACK_VERSION = 10;
+
+   @Resource
+   @Named(ChefProperties.CHEF_LOGGER)
+   private Logger logger = Logger.NULL;
+
+   /** The configured version of the Chef Server API. */
+   private final String apiVersion;
+
+   @Inject
+   ChefVersionSupplier(@ApiVersion String apiVersion) {
+      this.apiVersion = checkNotNull(apiVersion, "apiVersion must not be null");
+   }
+
+   @Override
+   public Integer get() {
+      // Old versions of Chef have versions like 0.9.x, 0.10.x, but newer
+      // versions are in the format 10.x.y, 11.x.y
+      Pattern versionPattern = Pattern.compile("(?:0\\.(\\d+)|(\\d+)\\.\\d+)(?:\\.\\d)*");
+
+      Matcher m = versionPattern.matcher(apiVersion);
+      if (!m.matches()) {
+         logger.warn("Configured version does not match the standard version pattern. Assuming version %s",
+               FALLBACK_VERSION);
+         return FALLBACK_VERSION;
+      }
+
+      return Integer.valueOf(firstNonNull(m.group(1), m.group(2)));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApi.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApi.java b/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApi.java
new file mode 100644
index 0000000..c2c5f10
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/test/TransientChefApi.java
@@ -0,0 +1,387 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.test;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Sets.newLinkedHashSet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.blobstore.config.LocalBlobStore;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.domain.PageSet;
+import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.chef.domain.Environment;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.domain.Resource;
+import org.jclouds.chef.domain.Role;
+import org.jclouds.chef.domain.Sandbox;
+import org.jclouds.chef.domain.SearchResult;
+import org.jclouds.chef.domain.UploadSandbox;
+import org.jclouds.chef.options.CreateClientOptions;
+import org.jclouds.chef.options.SearchOptions;
+import org.jclouds.io.Payload;
+import org.jclouds.lifecycle.Closer;
+import org.jclouds.util.Strings2;
+
+import com.google.common.base.Function;
+
+/**
+ * In-memory chef simulator.
+ */
+public class TransientChefApi implements ChefApi {
+   @Singleton
+   private static class StorageMetadataToName implements Function<PageSet<? extends StorageMetadata>, Set<String>> {
+      @Override
+      public Set<String> apply(PageSet<? extends StorageMetadata> from) {
+         return newLinkedHashSet(transform(from, new Function<StorageMetadata, String>() {
+
+            @Override
+            public String apply(StorageMetadata from) {
+               return from.getName();
+            }
+         }));
+      }
+   }
+
+   @Singleton
+   private static class BlobToDatabagItem implements Function<Blob, DatabagItem> {
+      @Override
+      public DatabagItem apply(Blob from) {
+         try {
+            return from == null ? null : new DatabagItem(from.getMetadata().getName(), Strings2.toStringAndClose(from
+                  .getPayload().getInput()));
+         } catch (IOException e) {
+            propagate(e);
+            return null;
+         }
+      }
+   }
+
+   private final LocalBlobStore databags;
+   private final BlobToDatabagItem blobToDatabagItem;
+   private final StorageMetadataToName storageMetadataToName;
+   private final Closer closer;
+
+   @Inject
+   TransientChefApi(@Named("databags") LocalBlobStore databags, StorageMetadataToName storageMetadataToName,
+         BlobToDatabagItem blobToDatabagItem, Closer closer) {
+      this.databags = checkNotNull(databags, "databags");
+      this.storageMetadataToName = checkNotNull(storageMetadataToName, "storageMetadataToName");
+      this.blobToDatabagItem = checkNotNull(blobToDatabagItem, "blobToDatabagItem");
+      this.closer = checkNotNull(closer, "closer");
+   }
+
+   @Override
+   public Sandbox commitSandbox(String id, boolean isCompleted) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client createClient(String clientName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client createClient(String clientName, CreateClientOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void createDatabag(String databagName) {
+      databags.createContainerInLocation(null, databagName);
+   }
+
+   @Override
+   public DatabagItem createDatabagItem(String databagName, DatabagItem databagItem) {
+      Blob blob = databags.blobBuilder(databagItem.getId()).payload(databagItem.toString()).build();
+      databags.putBlob(databagName, blob);
+      return databagItem;
+   }
+
+   @Override
+   public void createNode(Node node) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void createRole(Role role) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client deleteClient(String clientName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookVersion deleteCookbook(String cookbookName, String version) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void deleteDatabag(String databagName) {
+      databags.deleteContainer(databagName);
+   }
+
+   @Override
+   public DatabagItem deleteDatabagItem(String databagName, String databagItemId) {
+      DatabagItem item = blobToDatabagItem.apply(databags.getBlob(databagName, databagItemId));
+      databags.removeBlob(databagName, databagItemId);
+      return item;
+   }
+
+   @Override
+   public Node deleteNode(String nodeName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Role deleteRole(String rolename) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client generateKeyForClient(String clientName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Client getClient(String clientName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookVersion getCookbook(String cookbookName, String version) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public DatabagItem getDatabagItem(String databagName, String databagItemId) {
+      return blobToDatabagItem.apply(databags.getBlob(databagName, databagItemId));
+   }
+
+   @Override
+   public Node getNode(String nodeName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Role getRole(String roleName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public UploadSandbox createUploadSandboxForChecksums(Set<List<Byte>> md5s) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listVersionsOfCookbook(String cookbookName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listClients() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listCookbooks() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listDatabagItems(String databagName) {
+      return storageMetadataToName.apply(databags.list(databagName));
+   }
+
+   @Override
+   public Set<String> listDatabags() {
+      return storageMetadataToName.apply(databags.list());
+   }
+
+   @Override
+   public Set<String> listNodes() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listRoles() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listSearchIndexes() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Client> searchClients() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Client> searchClients(SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends DatabagItem> searchDatabagItems(String databagName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends DatabagItem> searchDatabagItems(String databagName, SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Node> searchNodes() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Node> searchNodes(SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Role> searchRoles() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Role> searchRoles(SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookVersion updateCookbook(String cookbookName, String version, CookbookVersion cookbook) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public DatabagItem updateDatabagItem(String databagName, DatabagItem item) {
+      return createDatabagItem(databagName, item);
+   }
+
+   @Override
+   public Node updateNode(Node node) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Role updateRole(Role role) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void uploadContent(URI location, Payload content) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public InputStream getResourceContents(Resource resource) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listEnvironments() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public void createEnvironment(Environment environment) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Environment deleteEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Environment getEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Environment updateEnvironment(Environment environment) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<CookbookDefinition> listCookbooksInEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<CookbookDefinition> listCookbooksInEnvironment(String environmentName, String numVersions) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookDefinition getCookbookInEnvironment(String environmentName, String cookbookName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public CookbookDefinition getCookbookInEnvironment(String environmentName, String cookbookName, String numVersions) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Environment> searchEnvironments() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public SearchResult<? extends Environment> searchEnvironments(SearchOptions options) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listRecipesInEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public Set<String> listNodesInEnvironment(String environmentName) {
+      throw new UnsupportedOperationException();
+   }
+  
+   @Override
+   public void close() throws IOException {
+      closer.close();
+   }
+}

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

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

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

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

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/chef/util/RunListBuilder.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/chef/util/RunListBuilder.java b/apis/chef/src/main/java/org/jclouds/chef/util/RunListBuilder.java
new file mode 100644
index 0000000..27cc377
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/chef/util/RunListBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.addAll;
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Lists.transform;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * builds a run list in the correct syntax for chef.
+ */
+public class RunListBuilder {
+   private List<String> list = newArrayList();
+
+   /**
+    * Add the following recipe to the run list
+    */
+   public RunListBuilder addRecipe(String recipe) {
+      return addRecipes(checkNotNull(recipe, "recipe"));
+   }
+
+   /**
+    * Add the following recipes to the run list
+    */
+   public RunListBuilder addRecipes(String... recipes) {
+      addAll(list, transform(Arrays.asList(checkNotNull(recipes, "recipes")), new Function<String, String>() {
+
+         @Override
+         public String apply(String from) {
+            return "recipe[" + from + "]";
+         }
+
+      }));
+      return this;
+   }
+
+   /**
+    * Add the following role to the run list
+    */
+   public RunListBuilder addRole(String role) {
+      return addRoles(checkNotNull(role, "role"));
+   }
+
+   /**
+    * Add the following roles to the run list
+    */
+   public RunListBuilder addRoles(String... roles) {
+      addAll(list, transform(Arrays.asList(checkNotNull(roles, "roles")), new Function<String, String>() {
+
+         @Override
+         public String apply(String from) {
+            return "role[" + from + "]";
+         }
+
+      }));
+      return this;
+   }
+
+   public List<String> build() {
+      return ImmutableList.copyOf(list);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/Automatic.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/Automatic.java b/apis/chef/src/main/java/org/jclouds/ohai/Automatic.java
new file mode 100644
index 0000000..9b3d134
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/Automatic.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.ohai;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+@Retention(RUNTIME)
+@Target({ TYPE, METHOD, PARAMETER })
+@Qualifier
+public @interface Automatic {
+}

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

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/config/ConfiguresOhai.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/config/ConfiguresOhai.java b/apis/chef/src/main/java/org/jclouds/ohai/config/ConfiguresOhai.java
new file mode 100644
index 0000000..1ca0df4
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/config/ConfiguresOhai.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.ohai.config;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Retention(RUNTIME)
+@Target(TYPE)
+public @interface ConfiguresOhai {
+}

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