Issue 191: added chef bootstrap

This commit is contained in:
Adrian Cole 2010-09-03 01:27:51 -07:00
parent 8afc0a833e
commit 0b11e7d652
22 changed files with 1000 additions and 201 deletions

View File

@ -20,11 +20,7 @@
package org.jclouds.chef.compute; package org.jclouds.chef.compute;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.newHashSet; import static org.testng.Assert.assertEquals;
import static org.jclouds.io.Payloads.newStringPayload;
import static org.jclouds.scriptbuilder.domain.Statements.createFile;
import static org.jclouds.scriptbuilder.domain.Statements.exec;
import static org.jclouds.scriptbuilder.domain.Statements.newStatementList;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -32,25 +28,18 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import org.jclouds.chef.ChefContext; import org.jclouds.chef.ChefContext;
import org.jclouds.chef.ChefContextFactory; import org.jclouds.chef.ChefContextFactory;
import org.jclouds.compute.BaseComputeServiceLiveTest;
import org.jclouds.compute.ComputeServiceContext; import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.ComputeServiceContextFactory; import org.jclouds.compute.ComputeServiceContextFactory;
import org.jclouds.compute.RunNodesException; import org.jclouds.compute.RunNodesException;
import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.options.TemplateOptions; import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.compute.predicates.NodePredicates; import org.jclouds.compute.predicates.NodePredicates;
import org.jclouds.crypto.Pems; import org.jclouds.io.Payload;
import org.jclouds.json.Json;
import org.jclouds.logging.log4j.config.Log4JLoggingModule; import org.jclouds.logging.log4j.config.Log4JLoggingModule;
import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.ssh.jsch.config.JschSshClientModule; import org.jclouds.ssh.jsch.config.JschSshClientModule;
import org.jclouds.util.Utils; import org.jclouds.util.Utils;
import org.testng.annotations.AfterGroups; import org.testng.annotations.AfterGroups;
@ -58,15 +47,10 @@ import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.TypeLiteral;
/** /**
* *
@ -80,7 +64,6 @@ public class ChefComputeServiceLiveTest {
private String tag; private String tag;
private String clientName; private String clientName;
private String chefEndpoint; private String chefEndpoint;
private Map<String, String> keyPair;
private Iterable<? extends NodeMetadata> nodes; private Iterable<? extends NodeMetadata> nodes;
@BeforeGroups(groups = { "live" }) @BeforeGroups(groups = { "live" })
@ -97,7 +80,6 @@ public class ChefComputeServiceLiveTest {
"jclouds.compute.credential"); "jclouds.compute.credential");
computeContext = new ComputeServiceContextFactory().createContext(computeProvider, computeIdentity, computeContext = new ComputeServiceContextFactory().createContext(computeProvider, computeIdentity,
computeCredential, ImmutableSet.of(new Log4JLoggingModule(), getSshModule()), props); computeCredential, ImmutableSet.of(new Log4JLoggingModule(), getSshModule()), props);
keyPair = BaseComputeServiceLiveTest.setupKeyPair();
} }
@BeforeGroups(groups = { "live" }) @BeforeGroups(groups = { "live" })
@ -113,69 +95,21 @@ public class ChefComputeServiceLiveTest {
Charsets.UTF_8), ImmutableSet.<Module> of(new Log4JLoggingModule()), props); Charsets.UTF_8), ImmutableSet.<Module> of(new Log4JLoggingModule()), props);
} }
public static class InstallChefGems implements Statement {
public String render(OsFamily family) {
try {
return Utils.toStringAndClose(InstallChefGems.class.getClassLoader().getResourceAsStream(
"install-chef-gems.sh"));
} catch (IOException e) {
Throwables.propagate(e);
return null;
}
}
@Override
public Iterable<String> functionDependecies(OsFamily family) {
return Collections.emptyList();
}
}
protected Module getSshModule() { protected Module getSshModule() {
return new JschSshClientModule(); return new JschSshClientModule();
} }
@Test @Test
public void test() throws IOException, InterruptedException { public void testCanUpdateRunList() throws IOException {
clientName = findNextClientName(chefContext, tag + "-validator-%d"); chefContext.getChefService().updateRunListForTag(Collections.singleton("recipe[apache2]"), tag);
assertEquals(chefContext.getChefService().getRunListForTag(tag), Collections.singleton("recipe[apache2]"));
}
System.out.println("created new client: " + clientName); @Test(dependsOnMethods = "testCanUpdateRunList")
public void testRunNodesWithBootstrap() throws IOException {
List<String> runList = ImmutableList.of("recipe[apache2]"); Payload bootstrap = chefContext.getChefService().createClientAndBootstrapScriptForTag(tag);
Json json = computeContext.utils().json(); TemplateOptions options = computeContext.getComputeService().templateOptions().runScript(bootstrap);
String clientKey = Pems.pem(chefContext.getApi().createClient(clientName).getPrivateKey());
Statement installChefGems = new InstallChefGems();
String chefConfigDir = "{root}etc{fs}chef";
Statement createChefConfigDir = exec("{md} " + chefConfigDir);
Statement createClientRb = createFile(chefConfigDir + "{fs}client.rb", ImmutableList.of("require 'rubygems'",
"require 'ohai'", "o = Ohai::System.new", "o.all_plugins", String.format(
"node_name \"%s-\" + o[:ipaddress]", tag), "log_level :info", "log_location STDOUT", String
.format("validation_client_name \"%s\"", clientName), String.format("chef_server_url \"%s\"",
chefEndpoint)));
Statement createValidationPem = createFile(chefConfigDir + "{fs}validation.pem", Splitter.on('\n').split(
clientKey));
String chefBootFile = chefConfigDir + "{fs}first-boot.json";
Statement createFirstBoot = createFile(chefBootFile, Collections.singleton(json.toJson(ImmutableMap
.<String, List<String>> of("run_list", runList), new TypeLiteral<Map<String, List<String>>>() {
}.getType())));
Statement runChef = exec("chef-client -j " + chefBootFile);
Statement bootstrapAndRunChef = newStatementList(installChefGems, createChefConfigDir, createClientRb,
createValidationPem, createFirstBoot, runChef);
String runScript = bootstrapAndRunChef.render(OsFamily.UNIX);
System.out.println(runScript);
TemplateOptions options = computeContext.getComputeService().templateOptions().//
installPrivateKey(newStringPayload(keyPair.get("private"))).//
authorizePublicKey(newStringPayload(keyPair.get("public"))).//
runScript(newStringPayload(runScript));
try { try {
nodes = computeContext.getComputeService().runNodesWithTag(tag, 1, options); nodes = computeContext.getComputeService().runNodesWithTag(tag, 1, options);
@ -192,19 +126,6 @@ public class ChefComputeServiceLiveTest {
} }
private String findNextClientName(ChefContext context, String pattern) {
Set<String> nodes = context.getApi().listClients();
String nodeName;
Set<String> names = newHashSet(nodes);
int index = 0;
while (true) {
nodeName = String.format(pattern, index++);
if (!names.contains(nodeName))
break;
}
return nodeName;
}
@AfterGroups(groups = { "live" }) @AfterGroups(groups = { "live" })
public void teardownCompute() { public void teardownCompute() {
if (computeContext != null) { if (computeContext != null) {

View File

@ -1,107 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
Copyright (C) 2010 Cloud Conscious, LLC <info@cloudconscious.com> Copyright (C) 2010 Cloud Conscious, LLC
<info@cloudconscious.com>
==================================================================== ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more Licensed to the Apache Software Foundation (ASF) under one or
contributor license agreements. See the NOTICE file distributed with more contributor license agreements. See the NOTICE file
this work for additional information regarding copyright ownership. distributed with this work for additional information regarding
The ASF licenses this file to you under the Apache License, Version copyright ownership. The ASF licenses this file to you under the
2.0 (the "License"); you may not use this file except in compliance Apache License, Version 2.0 (the "License"); you may not use
with the License. You may obtain a copy of the License at 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.html Unless required by http://www.apache.org/licenses/LICENSE-2.0.html Unless required
applicable law or agreed to in writing, software distributed under the by applicable law or agreed to in writing, software distributed
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR under the License is distributed on an "AS IS" BASIS, WITHOUT
CONDITIONS OF ANY KIND, either express or implied. See the License for WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
the specific language governing permissions and limitations under the See the License for the specific language governing permissions
License. and limitations under the License.
==================================================================== ====================================================================
--> -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.jclouds</groupId> <groupId>org.jclouds</groupId>
<artifactId>jclouds-chef-project</artifactId> <artifactId>jclouds-chef-project</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>org.jclouds</groupId> <groupId>org.jclouds</groupId>
<artifactId>jclouds-chef</artifactId> <artifactId>jclouds-chef</artifactId>
<name>jclouds Chef core</name> <name>jclouds Chef core</name>
<description>jclouds components to access Chef</description> <description>jclouds components to access Chef</description>
<scm> <scm>
<connection>scm:svn:http://jclouds.googlecode.com/svn/trunk/chef</connection> <connection>scm:svn:http://jclouds.googlecode.com/svn/trunk/chef</connection>
<developerConnection>scm:svn:https://jclouds.googlecode.com/svn/trunk/chef</developerConnection> <developerConnection>scm:svn:https://jclouds.googlecode.com/svn/trunk/chef</developerConnection>
<url>http://jclouds.googlecode.com/svn/trunk/chef</url> <url>http://jclouds.googlecode.com/svn/trunk/chef</url>
</scm> </scm>
<!-- bootstrapping: need to fetch the project POM --> <!-- bootstrapping: need to fetch the project POM -->
<repositories> <repositories>
<repository> <repository>
<id>jclouds-googlecode-deploy</id> <id>jclouds-googlecode-deploy</id>
<url>http://jclouds.googlecode.com/svn/repo</url> <url>http://jclouds.googlecode.com/svn/repo</url>
<snapshots> <snapshots>
<enabled>false</enabled> <enabled>false</enabled>
</snapshots> </snapshots>
</repository> </repository>
<repository> <repository>
<id>jclouds-rimu-snapshots-nexus</id> <id>jclouds-rimu-snapshots-nexus</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url> <url>https://oss.sonatype.org/content/repositories/snapshots</url>
<snapshots> <snapshots>
<enabled>true</enabled> <enabled>true</enabled>
</snapshots> </snapshots>
</repository> </repository>
</repositories> </repositories>
<properties> <properties>
<jclouds.chef.identity>chef-validator</jclouds.chef.identity> <jclouds.chef.identity>chef-validator</jclouds.chef.identity>
<jclouds.chef.rsa-key>/etc/chef/validation.pem</jclouds.chef.rsa-key> <jclouds.chef.rsa-key>/etc/chef/validation.pem</jclouds.chef.rsa-key>
<jclouds.chef.endpoint>http://localhost:4000</jclouds.chef.endpoint> <jclouds.chef.endpoint>http://localhost:4000</jclouds.chef.endpoint>
<jclouds.test.identity>${jclouds.chef.identity}</jclouds.test.identity> <jclouds.test.identity>${jclouds.chef.identity}</jclouds.test.identity>
<jclouds.test.credential>${jclouds.chef.rsa-key}</jclouds.test.credential> <jclouds.test.credential>${jclouds.chef.rsa-key}</jclouds.test.credential>
<jclouds.test.endpoint>${jclouds.chef.endpoint}</jclouds.test.endpoint> <jclouds.test.endpoint>${jclouds.chef.endpoint}</jclouds.test.endpoint>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
<artifactId>jclouds-core</artifactId> <artifactId>jclouds-core</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <!-- only required for Pems.java and only writing a private key file -->
<groupId>${project.groupId}</groupId> <dependency>
<artifactId>jclouds-core</artifactId> <groupId>org.bouncycastle</groupId>
<version>${project.version}</version> <artifactId>bcprov-jdk15</artifactId>
<type>test-jar</type> <version>1.44</version>
<scope>test</scope> </dependency>
</dependency> <dependency>
<dependency> <groupId>${project.groupId}</groupId>
<groupId>log4j</groupId> <artifactId>jclouds-scriptbuilder</artifactId>
<artifactId>log4j</artifactId> <version>${project.version}</version>
<version>1.2.14</version> </dependency>
<scope>test</scope> <dependency>
</dependency> <groupId>${project.groupId}</groupId>
<dependency> <artifactId>jclouds-core</artifactId>
<groupId>${project.groupId}</groupId> <version>${project.version}</version>
<artifactId>jclouds-log4j</artifactId> <type>test-jar</type>
<version>${project.version}</version> <scope>test</scope>
<scope>test</scope> </dependency>
</dependency> <dependency>
<dependency> <groupId>log4j</groupId>
<groupId>org.danlarkin</groupId> <artifactId>log4j</artifactId>
<artifactId>clojure-json</artifactId> <version>1.2.14</version>
<version>1.1</version> <scope>test</scope>
<optional>true</optional> </dependency>
</dependency> <dependency>
<!-- for transient chef provider --> <groupId>${project.groupId}</groupId>
<dependency> <artifactId>jclouds-log4j</artifactId>
<groupId>${project.groupId}</groupId> <version>${project.version}</version>
<artifactId>jclouds-blobstore</artifactId> <scope>test</scope>
<version>${project.version}</version> </dependency>
<optional>true</optional> <dependency>
</dependency> <groupId>org.danlarkin</groupId>
</dependencies> <artifactId>clojure-json</artifactId>
<version>1.1</version>
<optional>true</optional>
</dependency>
<!-- for transient chef provider -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-blobstore</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</project> </project>

View File

@ -131,6 +131,24 @@ unit testing"
([#^ChefService chef] ([#^ChefService chef]
(seq (.listNodesDetails chef)))) (seq (.listNodesDetails chef))))
(defn update-run-list
"Updates the run-list associated with a tag"
([run-list tag] (update-run-list run-list tag *chef*))
([run-list tag #^ChefService chef]
(.updateRunListForTag chef run-list tag)))
(defn run-list
"Retrieves the run-list associated with a tag"
([tag] (run-list tag *chef*))
([tag #^ChefService chef]
(seq (.getRunListForTag chef tag))))
(defn create-bootstrap
"creates a client and bootstrap script associated with a tag"
([tag] (create-bootstrap tag *chef*))
([tag #^ChefService chef]
(.createClientAndBootstrapScriptForTag chef tag)))
(defn databags (defn databags
"Retrieve the names of the existing data bags in your chef server." "Retrieve the names of the existing data bags in your chef server."
([] (databags *chef*)) ([] (databags *chef*))

View File

@ -21,10 +21,12 @@ package org.jclouds.chef;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import org.jclouds.chef.domain.Client; import org.jclouds.chef.domain.Client;
import org.jclouds.chef.domain.Node; import org.jclouds.chef.domain.Node;
import org.jclouds.chef.internal.BaseChefService; import org.jclouds.chef.internal.BaseChefService;
import org.jclouds.io.Payload;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.io.InputSupplier; import com.google.common.io.InputSupplier;
@ -56,6 +58,43 @@ public interface ChefService {
*/ */
Node createNodeAndPopulateAutomaticAttributes(String nodeName, Iterable<String> runList); Node createNodeAndPopulateAutomaticAttributes(String nodeName, Iterable<String> runList);
/**
* Creates all steps necessary to bootstrap and run the chef client.
*
* @param tag
* corresponds to a configured
* {@link org.jclouds.chef.reference.ChefConstants#CHEF_BOOTSTRAP_DATABAG databag}
* where run_list and other information are stored
* @return boot script
* @see #updateRunListForTag
*/
Payload createClientAndBootstrapScriptForTag(String tag);
/**
* assigns a run list to all nodes bootstrapped with a certain tag
*
* @param runList
* list of recipes or roles to assign. syntax is {@code recipe[name]} and {@code
* role[name]}
*
* @param tag
* corresponds to a configured
* {@link org.jclouds.chef.reference.ChefConstants#CHEF_BOOTSTRAP_DATABAG databag}
* where run_list and other information are stored
* @see #makeChefClientBootstrapScriptForTag
*/
void updateRunListForTag(Iterable<String> runList, String tag);
/**
* @param tag
* corresponds to a configured
* {@link org.jclouds.chef.reference.ChefConstants#CHEF_BOOTSTRAP_DATABAG databag}
* where run_list and other information are stored
* @return run list for all nodes bootstrapped with a certain tag
* @see #updateRunListForTag
*/
List<String> getRunListForTag(String tag);
void deleteAllNodesInList(Iterable<String> names); void deleteAllNodesInList(Iterable<String> names);
Iterable<? extends Node> listNodesDetails(); Iterable<? extends Node> listNodesDetails();
@ -63,7 +102,7 @@ public interface ChefService {
Iterable<? extends Node> listNodesDetailsMatching(Predicate<String> nodeNameSelector); Iterable<? extends Node> listNodesDetailsMatching(Predicate<String> nodeNameSelector);
Iterable<? extends Node> listNodesNamed(Iterable<String> names); Iterable<? extends Node> listNodesNamed(Iterable<String> names);
void deleteAllClientsInList(Iterable<String> names); void deleteAllClientsInList(Iterable<String> names);
Iterable<? extends Client> listClientsDetails(); Iterable<? extends Client> listClientsDetails();

View File

@ -65,7 +65,7 @@ public class BaseChefRestClientModule<S, A> extends RestClientModule<S, A> {
} }
protected BaseChefRestClientModule(Class<S> syncClientType, Class<A> asyncClientType, protected BaseChefRestClientModule(Class<S> syncClientType, Class<A> asyncClientType,
Map<Class<?>, Class<?>> delegates) { Map<Class<?>, Class<?>> delegates) {
super(syncClientType, asyncClientType, delegates); super(syncClientType, asyncClientType, delegates);
} }
@ -91,10 +91,10 @@ public class BaseChefRestClientModule<S, A> extends RestClientModule<S, A> {
@Provides @Provides
@Singleton @Singleton
public PrivateKey provideKey(Crypto crypto, @Named(PROPERTY_CREDENTIAL) String pem) throws InvalidKeySpecException, public PrivateKey provideKey(Crypto crypto, @Named(PROPERTY_CREDENTIAL) String pem) throws InvalidKeySpecException,
IOException { IOException {
return crypto.rsaKeyFactory().generatePrivate(Pems.privateKeySpec(InputSuppliers.of(pem))); return crypto.rsaKeyFactory().generatePrivate(Pems.privateKeySpec(InputSuppliers.of(pem)));
} }
@Override @Override
protected void bindErrorHandlers() { protected void bindErrorHandlers() {
bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ChefErrorHandler.class); bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ChefErrorHandler.class);

View File

@ -19,10 +19,24 @@
package org.jclouds.chef.config; package org.jclouds.chef.config;
import java.util.List;
import java.util.Map;
import javax.inject.Singleton;
import org.jclouds.chef.ChefAsyncClient; import org.jclouds.chef.ChefAsyncClient;
import org.jclouds.chef.ChefClient; import org.jclouds.chef.ChefClient;
import org.jclouds.chef.domain.Client;
import org.jclouds.chef.functions.ClientForTag;
import org.jclouds.chef.functions.RunListForTag;
import org.jclouds.chef.statements.InstallChefGems;
import org.jclouds.http.RequiresHttp; import org.jclouds.http.RequiresHttp;
import org.jclouds.rest.ConfiguresRestClient; import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.scriptbuilder.domain.Statement;
import com.google.common.collect.MapMaker;
import com.google.inject.Provides;
import com.google.inject.name.Names;
/** /**
* Configures the Chef connection. * Configures the Chef connection.
@ -37,4 +51,22 @@ public class ChefRestClientModule extends BaseChefRestClientModule<ChefClient, C
super(ChefClient.class, ChefAsyncClient.class); super(ChefClient.class, ChefAsyncClient.class);
} }
@Provides
@Singleton
Map<String, List<String>> runListForTag(RunListForTag runListForTag) {
return new MapMaker().makeComputingMap(runListForTag);
}
@Provides
@Singleton
Map<String, Client> tagToClient(ClientForTag tagToClient) {
return new MapMaker().makeComputingMap(tagToClient);
}
@Override
protected void configure() {
bind(Statement.class).annotatedWith(Names.named("installChefGems")).to(InstallChefGems.class);
super.configure();
}
} }

View File

@ -0,0 +1,69 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*/
package org.jclouds.chef.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.newHashSet;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.chef.ChefClient;
import org.jclouds.chef.domain.Client;
import com.google.common.base.Function;
/**
*
* Generates a client relevant for a particular tag
*
* @author Adrian Cole
*/
@Singleton
public class ClientForTag implements Function<String, Client> {
private final ChefClient chefClient;
@Inject
public ClientForTag(ChefClient chefClient) {
this.chefClient = checkNotNull(chefClient, "chefClient");
}
@Override
public Client apply(String from) {
String clientName = findNextClientName(chefClient.listClients(), from + "-validator-%02d");
Client client = chefClient.createClient(clientName);
// response from create only includes the key
return new Client(null, null, clientName, clientName, false, client.getPrivateKey());
}
private static String findNextClientName(Set<String> clients, String pattern) {
String clientName;
Set<String> names = newHashSet(clients);
int index = 0;
while (true) {
clientName = String.format(pattern,index++);
if (!names.contains(clientName))
break;
}
return clientName;
}
}

View File

@ -0,0 +1,71 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*/
package org.jclouds.chef.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.chef.reference.ChefConstants.CHEF_BOOTSTRAP_DATABAG;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.chef.ChefClient;
import org.jclouds.chef.domain.DatabagItem;
import org.jclouds.json.Json;
import com.google.common.base.Function;
import com.google.inject.TypeLiteral;
/**
*
* Retrieves the run-list for a specific tag
*
*
* @author Adrian Cole
*/
@Singleton
public class RunListForTag implements Function<String, List<String>> {
public static final Type RUN_LIST_TYPE = new TypeLiteral<Map<String, List<String>>>() {
}.getType();
private final ChefClient client;
private final Json json;
private final String databag;
@Inject
public RunListForTag(@Named(CHEF_BOOTSTRAP_DATABAG) String databag, ChefClient client, Json json) {
this.databag = checkNotNull(databag, "databag");
this.client = checkNotNull(client, "client");
this.json = checkNotNull(json, "json");
}
@SuppressWarnings("unchecked")
@Override
public List<String> apply(String from) {
DatabagItem list = client.getDatabagItem(databag, from);
checkState(list != null, "databag item %s/%s not found", databag, from);
return ((Map<String, List<String>>) json.fromJson(list.toString(), RUN_LIST_TYPE)).get("run_list");
}
}

View File

@ -0,0 +1,118 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*/
package org.jclouds.chef.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.io.Payloads.newStringPayload;
import static org.jclouds.scriptbuilder.domain.Statements.createFile;
import static org.jclouds.scriptbuilder.domain.Statements.exec;
import static org.jclouds.scriptbuilder.domain.Statements.newStatementList;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.chef.domain.Client;
import org.jclouds.crypto.Pems;
import org.jclouds.io.Payload;
import org.jclouds.json.Json;
import org.jclouds.rest.annotations.Provider;
import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.Statement;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.inject.TypeLiteral;
/**
*
* Generates a bootstrap script relevant for a particular tag
*
* @author Adrian Cole
*/
@Singleton
public class TagToBootScript implements Function<String, Payload> {
@VisibleForTesting
static final Type RUN_LIST_TYPE = new TypeLiteral<Map<String, List<String>>>() {
}.getType();
private final URI endpoint;
private final Json json;
private final Map<String, Client> tagToClient;
private final Map<String, List<String>> runListForTag;
private final Statement installChefGems;
@Inject
public TagToBootScript(@Provider URI endpoint, Json json, Map<String, Client> tagToClient,
Map<String, List<String>> runListForTag, @Named("installChefGems") Statement installChefGems) {
this.endpoint = checkNotNull(endpoint, "endpoint");
this.json = checkNotNull(json, "json");
this.tagToClient = checkNotNull(tagToClient, "tagToClient");
this.runListForTag = checkNotNull(runListForTag, "runListForTag");
this.installChefGems = checkNotNull(installChefGems, "installChefGems");
}
public Payload apply(String tag) {
checkNotNull(tag, "tag");
Client client = tagToClient.get(tag);
checkState(client != null, "could not get a client for tag %s", tag);
checkState(client.getClientname() != null, "clientname null for %s", client);
checkState(client.getPrivateKey() != null, "privatekey null for %s", client);
List<String> runList = runListForTag.get(tag);
checkState(runList != null, "runList for %s was not found", tag);
checkState(runList.size() > 0, "runList for %s was empty", tag);
String chefConfigDir = "{root}etc{fs}chef";
Statement createChefConfigDir = exec("{md} " + chefConfigDir);
Statement createClientRb = createFile(chefConfigDir + "{fs}client.rb", ImmutableList.of("require 'rubygems'",
"require 'ohai'", "o = Ohai::System.new", "o.all_plugins", String.format(
"node_name \"%s-\" + o[:ipaddress]", tag), "log_level :info", "log_location STDOUT", String
.format("validation_client_name \"%s\"", client.getClientname()), String.format(
"chef_server_url \"%s\"", endpoint)));
Statement createValidationPem = createFile(chefConfigDir + "{fs}validation.pem", Splitter.on('\n').split(
Pems.pem(client.getPrivateKey())));
String chefBootFile = chefConfigDir + "{fs}first-boot.json";
Statement createFirstBoot = createFile(chefBootFile, Collections.singleton(json.toJson(ImmutableMap
.<String, List<String>> of("run_list", runList), RUN_LIST_TYPE)));
Statement runChef = exec("chef-client -j " + chefBootFile);
Statement bootstrapAndRunChef = newStatementList(installChefGems, createChefConfigDir, createClientRb,
createValidationPem, createFirstBoot, runChef);
String runScript = bootstrapAndRunChef.render(OsFamily.UNIX);
return newStringPayload(runScript);
}
}

View File

@ -21,6 +21,7 @@ package org.jclouds.chef.internal;
import static org.jclouds.Constants.PROPERTY_API_VERSION; import static org.jclouds.Constants.PROPERTY_API_VERSION;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.chef.reference.ChefConstants.CHEF_BOOTSTRAP_DATABAG;
import java.util.Properties; import java.util.Properties;
@ -38,6 +39,7 @@ public abstract class BaseChefPropertiesBuilder extends PropertiesBuilder {
Properties properties = super.defaultProperties(); Properties properties = super.defaultProperties();
properties.setProperty(PROPERTY_SESSION_INTERVAL, "1"); properties.setProperty(PROPERTY_SESSION_INTERVAL, "1");
properties.setProperty(PROPERTY_API_VERSION, ChefAsyncClient.VERSION); properties.setProperty(PROPERTY_API_VERSION, ChefAsyncClient.VERSION);
properties.setProperty(CHEF_BOOTSTRAP_DATABAG, "bootstrap");
return properties; return properties;
} }

View File

@ -20,10 +20,12 @@
package org.jclouds.chef.internal; package org.jclouds.chef.internal;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.chef.reference.ChefConstants.CHEF_BOOTSTRAP_DATABAG;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.util.List;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.inject.Inject; import javax.inject.Inject;
@ -34,7 +36,10 @@ import javax.inject.Singleton;
import org.jclouds.chef.ChefContext; import org.jclouds.chef.ChefContext;
import org.jclouds.chef.ChefService; import org.jclouds.chef.ChefService;
import org.jclouds.chef.domain.Client; import org.jclouds.chef.domain.Client;
import org.jclouds.chef.domain.DatabagItem;
import org.jclouds.chef.domain.Node; import org.jclouds.chef.domain.Node;
import org.jclouds.chef.functions.RunListForTag;
import org.jclouds.chef.functions.TagToBootScript;
import org.jclouds.chef.reference.ChefConstants; import org.jclouds.chef.reference.ChefConstants;
import org.jclouds.chef.strategy.CleanupStaleNodesAndClients; import org.jclouds.chef.strategy.CleanupStaleNodesAndClients;
import org.jclouds.chef.strategy.CreateNodeAndPopulateAutomaticAttributes; import org.jclouds.chef.strategy.CreateNodeAndPopulateAutomaticAttributes;
@ -43,12 +48,15 @@ import org.jclouds.chef.strategy.DeleteAllNodesInList;
import org.jclouds.chef.strategy.ListClients; import org.jclouds.chef.strategy.ListClients;
import org.jclouds.chef.strategy.ListNodes; import org.jclouds.chef.strategy.ListNodes;
import org.jclouds.chef.strategy.UpdateAutomaticAttributesOnNode; import org.jclouds.chef.strategy.UpdateAutomaticAttributesOnNode;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.io.payloads.RSADecryptingPayload; import org.jclouds.io.payloads.RSADecryptingPayload;
import org.jclouds.io.payloads.RSAEncryptingPayload; import org.jclouds.io.payloads.RSAEncryptingPayload;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.google.common.io.InputSupplier; import com.google.common.io.InputSupplier;
@ -72,13 +80,17 @@ public class BaseChefService implements ChefService {
private final ListClients listClients; private final ListClients listClients;
private final UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode; private final UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode;
private final Provider<PrivateKey> privateKey; private final Provider<PrivateKey> privateKey;
private final TagToBootScript tagToBootScript;
private final String databag;
private final RunListForTag runListForTag;
@Inject @Inject
protected BaseChefService(ChefContext chefContext, CleanupStaleNodesAndClients cleanupStaleNodesAndClients, protected BaseChefService(ChefContext chefContext, CleanupStaleNodesAndClients cleanupStaleNodesAndClients,
CreateNodeAndPopulateAutomaticAttributes createNodeAndPopulateAutomaticAttributes, CreateNodeAndPopulateAutomaticAttributes createNodeAndPopulateAutomaticAttributes,
DeleteAllNodesInList deleteAllNodesInList, ListNodes listNodes, DeleteAllNodesInList deleteAllNodesInList, ListNodes listNodes,
DeleteAllClientsInList deleteAllClientsInList, ListClients listClients, DeleteAllClientsInList deleteAllClientsInList, ListClients listClients,
UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode, Provider<PrivateKey> privateKey) { UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode, Provider<PrivateKey> privateKey,
@Named(CHEF_BOOTSTRAP_DATABAG) String databag, TagToBootScript tagToBootScript, RunListForTag runListForTag) {
this.chefContext = checkNotNull(chefContext, "chefContext"); this.chefContext = checkNotNull(chefContext, "chefContext");
this.cleanupStaleNodesAndClients = checkNotNull(cleanupStaleNodesAndClients, "cleanupStaleNodesAndClients"); this.cleanupStaleNodesAndClients = checkNotNull(cleanupStaleNodesAndClients, "cleanupStaleNodesAndClients");
this.createNodeAndPopulateAutomaticAttributes = checkNotNull(createNodeAndPopulateAutomaticAttributes, this.createNodeAndPopulateAutomaticAttributes = checkNotNull(createNodeAndPopulateAutomaticAttributes,
@ -90,6 +102,9 @@ public class BaseChefService implements ChefService {
this.updateAutomaticAttributesOnNode = checkNotNull(updateAutomaticAttributesOnNode, this.updateAutomaticAttributesOnNode = checkNotNull(updateAutomaticAttributesOnNode,
"updateAutomaticAttributesOnNode"); "updateAutomaticAttributesOnNode");
this.privateKey = checkNotNull(privateKey, "privateKey"); this.privateKey = checkNotNull(privateKey, "privateKey");
this.tagToBootScript = checkNotNull(tagToBootScript, "tagToBootScript");
this.databag = checkNotNull(databag, "databag");
this.runListForTag = checkNotNull(runListForTag, "runListForTag");
} }
@Override @Override
@ -152,6 +167,30 @@ public class BaseChefService implements ChefService {
return chefContext; return chefContext;
} }
@Override
public Payload createClientAndBootstrapScriptForTag(String tag) {
return tagToBootScript.apply(tag);
}
@Override
public void updateRunListForTag(Iterable<String> runList, String tag) {
try {
chefContext.getApi().createDatabag(databag);
} catch (IllegalStateException e) {
}
chefContext.getApi().updateDatabagItem(
databag,
new DatabagItem(tag, chefContext.utils().json().toJson(
ImmutableMap.<String, List<String>> of("run_list", Lists.newArrayList(runList)),
RunListForTag.RUN_LIST_TYPE)));
}
@Override
public List<String> getRunListForTag(String tag) {
return runListForTag.apply(tag);
}
@Override @Override
public byte[] decrypt(InputSupplier<? extends InputStream> supplier) throws IOException { public byte[] decrypt(InputSupplier<? extends InputStream> supplier) throws IOException {
return ByteStreams.toByteArray(new RSADecryptingPayload(Payloads.newPayload(supplier.getInput()), privateKey return ByteStreams.toByteArray(new RSADecryptingPayload(Payloads.newPayload(supplier.getInput()), privateKey

View File

@ -28,8 +28,7 @@ public interface ChefConstants {
/** /**
* There are generally 3 types of identities * There are generally 3 types of identities
* <ul> * <ul>
* <li>validator - used to create clients within an organization; {@code * <li>validator - used to create clients within an organization; {@code orgname}-validator</li>
* orgname}-validator</li>
* <li>client - scoped to an organization, used on nodes to run chef</li> * <li>client - scoped to an organization, used on nodes to run chef</li>
* <li>user - used to run commands like knife and access cookbook sites</li> * <li>user - used to run commands like knife and access cookbook sites</li>
* </ul> * </ul>
@ -49,4 +48,11 @@ public interface ChefConstants {
public static final String CHEF_NODE = "chef.node"; public static final String CHEF_NODE = "chef.node";
public static final String CHEF_NODE_PATTERN = "chef.node-pattern"; public static final String CHEF_NODE_PATTERN = "chef.node-pattern";
public static final String CHEF_RUN_LIST = "chef.run-list"; public static final String CHEF_RUN_LIST = "chef.run-list";
/**
* databag that holds chef bootstrap hints, should be a json ball in the following format:
* <p/>
* {"tag":{"run_list":["recipe[apache2]"]}}
*/
public static final String CHEF_BOOTSTRAP_DATABAG = "chef.bootstrap-databag";
} }

View File

@ -0,0 +1,54 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.statements;
import java.io.IOException;
import java.util.Collections;
import javax.inject.Singleton;
import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.util.Utils;
import com.google.common.base.Throwables;
/**
* @author Adrian Cole
*/
@Singleton
public class InstallChefGems implements Statement {
@Override
public String render(OsFamily family) {
try {
return Utils.toStringAndClose(InstallChefGems.class.getClassLoader().getResourceAsStream(
"install-chef-gems.sh"));
} catch (IOException e) {
Throwables.propagate(e);
return null;
}
}
@Override
public Iterable<String> functionDependecies(OsFamily family) {
return Collections.emptyList();
}
}

View File

@ -18,16 +18,25 @@
*/ */
package org.jclouds.chef.test.config; package org.jclouds.chef.test.config;
import java.util.List;
import java.util.Map;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.jclouds.blobstore.TransientAsyncBlobStore; import org.jclouds.blobstore.TransientAsyncBlobStore;
import org.jclouds.chef.ChefAsyncClient; import org.jclouds.chef.ChefAsyncClient;
import org.jclouds.chef.ChefClient; import org.jclouds.chef.ChefClient;
import org.jclouds.chef.config.BaseChefRestClientModule; import org.jclouds.chef.config.BaseChefRestClientModule;
import org.jclouds.chef.domain.Client;
import org.jclouds.chef.functions.ClientForTag;
import org.jclouds.chef.functions.RunListForTag;
import org.jclouds.chef.statements.InstallChefGems;
import org.jclouds.chef.test.TransientChefAsyncClient; import org.jclouds.chef.test.TransientChefAsyncClient;
import org.jclouds.chef.test.TransientChefClient; import org.jclouds.chef.test.TransientChefClient;
import org.jclouds.rest.RestContextFactory; import org.jclouds.rest.RestContextFactory;
import org.jclouds.scriptbuilder.domain.Statement;
import com.google.common.collect.MapMaker;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.name.Names; import com.google.inject.name.Names;
@ -44,8 +53,9 @@ public class TransientChefClientModule extends BaseChefRestClientModule<Transien
@Override @Override
protected void configure() { protected void configure() {
bind(TransientAsyncBlobStore.class).annotatedWith(Names.named("databags")).toInstance( bind(TransientAsyncBlobStore.class).annotatedWith(Names.named("databags")).toInstance(
new RestContextFactory().createContextBuilder("transient", "foo", "bar").buildInjector().getInstance( new RestContextFactory().createContextBuilder("transient", "foo", "bar").buildInjector().getInstance(
TransientAsyncBlobStore.class)); TransientAsyncBlobStore.class));
bind(Statement.class).annotatedWith(Names.named("installChefGems")).to(InstallChefGems.class);
super.configure(); super.configure();
} }
@ -60,4 +70,16 @@ public class TransientChefClientModule extends BaseChefRestClientModule<Transien
return in; return in;
} }
@Provides
@Singleton
Map<String, List<String>> runListForTag(RunListForTag runListForTag) {
return new MapMaker().makeComputingMap(runListForTag);
}
@Provides
@Singleton
Map<String, Client> tagToClient(ClientForTag tagToClient) {
return new MapMaker().makeComputingMap(tagToClient);
}
} }

View File

@ -67,3 +67,6 @@
(is (create-databag-item "databag" {:id "databag-item2" :value "databag-value2"})) (is (create-databag-item "databag" {:id "databag-item2" :value "databag-value2"}))
(is (= {:id "databag-item2" :value "databag-value2"} (databag-item "databag" "databag-item2")))) (is (= {:id "databag-item2" :value "databag-value2"} (databag-item "databag" "databag-item2"))))
(deftest run-list-test
(update-run-list #{"recipe[foo]"} "tag")
(is (= ["recipe[foo]"] (run-list "tag"))))

View File

@ -39,7 +39,7 @@ import com.google.inject.Injector;
/** /**
* @author Adrian Cole * @author Adrian Cole
*/ */
@Test(groups = "unit", testName = "chef.BindHexEncodedMD5sToJsonPayloadTest") @Test(groups = "unit", testName = "chef.BootstrapChefClientTest")
public class BindHexEncodedMD5sToJsonPayloadTest { public class BindHexEncodedMD5sToJsonPayloadTest {
Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule()); Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule());

View File

@ -0,0 +1,112 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*/
package org.jclouds.chef.functions;
import static org.easymock.EasyMock.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.testng.Assert.assertEquals;
import java.io.IOException;
import java.security.PrivateKey;
import org.jclouds.chef.ChefClient;
import org.jclouds.chef.domain.Client;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
/**
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "chef.ClientForTagTest")
public class ClientForTagTest {
public void testWhenNoClientsInList() throws IOException {
ChefClient chefClient = createMock(ChefClient.class);
Client client = createMock(Client.class);
PrivateKey privateKey = createMock(PrivateKey.class);
ClientForTag fn = new ClientForTag(chefClient);
expect(chefClient.listClients()).andReturn(ImmutableSet.<String> of());
expect(chefClient.createClient("foo-validator-00")).andReturn(client);
expect(client.getPrivateKey()).andReturn(privateKey);
replay(client);
replay(chefClient);
Client compare = fn.apply("foo");
assertEquals(compare.getClientname(), "foo-validator-00");
assertEquals(compare.getName(), "foo-validator-00");
assertEquals(compare.getPrivateKey(), privateKey);
verify(client);
verify(chefClient);
}
public void testWhenClientsInListAddsToEnd() throws IOException {
ChefClient chefClient = createMock(ChefClient.class);
Client client = createMock(Client.class);
PrivateKey privateKey = createMock(PrivateKey.class);
ClientForTag fn = new ClientForTag(chefClient);
expect(chefClient.listClients()).andReturn(
ImmutableSet.<String> of("foo-validator-00", "foo-validator-01", "foo-validator-02"));
expect(chefClient.createClient("foo-validator-03")).andReturn(client);
expect(client.getPrivateKey()).andReturn(privateKey);
replay(client);
replay(chefClient);
Client compare = fn.apply("foo");
assertEquals(compare.getClientname(), "foo-validator-03");
assertEquals(compare.getName(), "foo-validator-03");
assertEquals(compare.getPrivateKey(), privateKey);
verify(client);
verify(chefClient);
}
public void testWhenClientsInListReplacesMissing() throws IOException {
ChefClient chefClient = createMock(ChefClient.class);
Client client = createMock(Client.class);
PrivateKey privateKey = createMock(PrivateKey.class);
ClientForTag fn = new ClientForTag(chefClient);
expect(chefClient.listClients()).andReturn(ImmutableSet.<String> of("foo-validator-00", "foo-validator-02"));
expect(chefClient.createClient("foo-validator-01")).andReturn(client);
expect(client.getPrivateKey()).andReturn(privateKey);
replay(client);
replay(chefClient);
Client compare = fn.apply("foo");
assertEquals(compare.getClientname(), "foo-validator-01");
assertEquals(compare.getName(), "foo-validator-01");
assertEquals(compare.getPrivateKey(), privateKey);
verify(client);
verify(chefClient);
}
}

View File

@ -0,0 +1,106 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*/
package org.jclouds.chef.functions;
import static org.easymock.EasyMock.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.testng.Assert.assertEquals;
import java.io.IOException;
import org.jclouds.chef.ChefClient;
import org.jclouds.chef.config.ChefParserModule;
import org.jclouds.chef.domain.Client;
import org.jclouds.chef.domain.DatabagItem;
import org.jclouds.json.Json;
import org.jclouds.json.config.GsonModule;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.inject.Guice;
import com.google.inject.Injector;
/**
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "chef.RunListForTagTest")
public class RunListForTagTest {
Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule());
Json json = injector.getInstance(Json.class);
@Test(expectedExceptions = IllegalStateException.class)
public void testWhenNoDatabagItem() throws IOException {
ChefClient chefClient = createMock(ChefClient.class);
Client client = createMock(Client.class);
RunListForTag fn = new RunListForTag("jclouds", chefClient, json);
expect(chefClient.getDatabagItem("jclouds", "foo")).andReturn(null);
replay(client);
replay(chefClient);
fn.apply("foo");
verify(client);
verify(chefClient);
}
@Test
public void testOneRecipe() throws IOException {
ChefClient chefClient = createMock(ChefClient.class);
Client client = createMock(Client.class);
RunListForTag fn = new RunListForTag("jclouds", chefClient, json);
expect(chefClient.getDatabagItem("jclouds", "foo")).andReturn(
new DatabagItem("foo", "{\"run_list\":[\"recipe[apache2]\"]}"));
replay(client);
replay(chefClient);
assertEquals(fn.apply("foo"), ImmutableList.of("recipe[apache2]"));
verify(client);
verify(chefClient);
}
@Test
public void testTwoRecipes() throws IOException {
ChefClient chefClient = createMock(ChefClient.class);
Client client = createMock(Client.class);
RunListForTag fn = new RunListForTag("jclouds", chefClient, json);
expect(chefClient.getDatabagItem("jclouds", "foo")).andReturn(
new DatabagItem("foo", "{\"run_list\":[\"recipe[apache2]\",\"recipe[mysql]\"]}"));
replay(client);
replay(chefClient);
assertEquals(fn.apply("foo"), ImmutableList.of("recipe[apache2]", "recipe[mysql]"));
verify(client);
verify(chefClient);
}
}

View File

@ -0,0 +1,104 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*/
package org.jclouds.chef.functions;
import static org.easymock.EasyMock.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.testng.Assert.assertEquals;
import java.io.IOException;
import java.net.URI;
import java.security.PrivateKey;
import java.util.List;
import org.jclouds.chef.config.ChefParserModule;
import org.jclouds.chef.domain.Client;
import org.jclouds.chef.statements.InstallChefGems;
import org.jclouds.crypto.PemsTest;
import org.jclouds.json.Json;
import org.jclouds.json.config.GsonModule;
import org.jclouds.scriptbuilder.domain.Statement;
import org.testng.annotations.Test;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.CharStreams;
import com.google.common.io.Resources;
import com.google.inject.Guice;
import com.google.inject.Injector;
/**
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "chef.TagToBootScriptTest")
public class TagToBootScriptTest {
Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule());
Json json = injector.getInstance(Json.class);
Statement installChefGems = new InstallChefGems();
@Test(expectedExceptions = IllegalStateException.class)
public void testMustHaveClients() {
TagToBootScript fn = new TagToBootScript(URI.create("http://localhost:4000"), json, ImmutableMap
.<String, Client> of(), ImmutableMap.<String, List<String>> of("foo", ImmutableList
.of("recipe[apache2]")), installChefGems);
fn.apply("foo");
}
@Test(expectedExceptions = IllegalStateException.class)
public void testMustHaveRunScripts() {
TagToBootScript fn = new TagToBootScript(URI.create("http://localhost:4000"), json, ImmutableMap
.<String, Client> of("foo", createMock(Client.class)), ImmutableMap.<String, List<String>> of(), installChefGems);
fn.apply("foo");
}
@Test(expectedExceptions = IllegalStateException.class)
public void testMustHaveRunScriptsValue() {
TagToBootScript fn = new TagToBootScript(URI.create("http://localhost:4000"), json, ImmutableMap
.<String, Client> of("foo", createMock(Client.class)), ImmutableMap.<String, List<String>> of("foo",
ImmutableList.<String> of()), installChefGems);
fn.apply("foo");
}
public void testOneRecipe() throws IOException {
Client client = createMock(Client.class);
PrivateKey privateKey = createMock(PrivateKey.class);
TagToBootScript fn = new TagToBootScript(URI.create("http://localhost:4000"), json, ImmutableMap
.<String, Client> of("foo", client), ImmutableMap.<String, List<String>> of("foo", ImmutableList
.<String> of("recipe[apache2]")), installChefGems);
expect(client.getClientname()).andReturn("fooclient").atLeastOnce();
expect(client.getPrivateKey()).andReturn(privateKey).atLeastOnce();
expect(privateKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
replay(client);
replay(privateKey);
assertEquals(fn.apply("foo").getRawContent(), CharStreams.toString(Resources.newReaderSupplier(Resources
.getResource("one-recipe.sh"), Charsets.UTF_8)));
verify(client);
verify(privateKey);
}
}

View File

@ -0,0 +1,70 @@
if [ ! -f /usr/bin/chef-client ]; then
apt-get update
apt-get install -y ruby ruby1.8-dev build-essential wget libruby-extras libruby1.8-extras
mkdir -p /tmp/bootchef
(
cd /tmp/bootchef
wget http://rubyforge.org/frs/download.php/69365/rubygems-1.3.6.tgz
tar xvf rubygems-1.3.6.tgz
cd rubygems-1.3.6
ruby setup.rb
cp /usr/bin/gem1.8 /usr/bin/gem
)
rm -rf /tmp/bootchef
gem install chef ohai --no-rdoc --no-ri --verbose
fi
mkdir -p /etc/chef
cat > /etc/chef/client.rb <<'END_OF_FILE'
require 'rubygems'
require 'ohai'
o = Ohai::System.new
o.all_plugins
node_name "foo-" + o[:ipaddress]
log_level :info
log_location STDOUT
validation_client_name "fooclient"
chef_server_url "http://localhost:4000"
END_OF_FILE
cat > /etc/chef/validation.pem <<'END_OF_FILE'
-----BEGIN PRIVATE KEY-----
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVB
eWIyWkpKcUdtMEtLUis4bmZRSk5zU2QrRjl0WE5NVjdDZk9jVzZqc3FzOEVaZ2lW
ClIwOWhEMUlZT2o0WXFNMHFKT05sZ3lnNHhSV2V3ZFNHN1FUUGoxbEpwVkFpZGE5
c1h5MitrenlhZ1pBMUFtME8KWmNicWI1aG9lSURnY1grZURhNzlzMHUwRG9tamNm
TzlFS2h2SExCeit6TSszUXFQUmtQVjhuWVRiZnMrSGpWegp6T1U2RDFCMFhSMytJ
UFpabDJBbldzMmQwcWhuU3RIY0RVdm5SVlEwUDQ4Mll3TjlWZ2NlT1p0cFB6MERD
S0VKCjVUeDVTVHViOGswL3p0L1ZBTUhRYWZMU3VRTUxkMnM0Wkx1T1pwdE4vL3VB
c1RteGlyZXFkMzd6KzhaVGRCYkoKOExFcEoraUNYdVNmbTVhVWg3aXc2b3h2VG9Z
MkFMNTMraksyVVFJREFRQUJBb0lCQVFEQTg4QjNpL3hXbjB2WApCVnhGYW1DWW9l
Y3VOakd3WFhrU3laZXc2MTZBK0VPQ3U0N2JoNGFUdXJkRmJZTDBZRmFBdGFXdnps
YU4yZUhnCkRiK0hEdVRlZkUyOStXa2NHazZTc2hQbWl6NVQwWE9DQUlDV3c2d1NW
RGtIbUd3UzRqWnZiQUZtN1c4bndHazkKWWh4Z3hGaVJuZ3N3SlpGb3BPTG9GNVdY
czJ0ZDhndUlZTnNsTXBvN3R1NTBpRm5CSHdLTzJac1BBazh0OW5uUwp4bERhdkty
dXltRW1xSENyMytkdGlvNWVhZW5KY3AzZmpvWEJRT0tVazNpcElJMjlYUkI4TnFl
Q1ZWLzdLeHdxCmNrcU9CRWJSd0JjbGNreUliRCtSaUFnS3ZPZWxPUmpFaUU5UjQy
dnVxdnhSQTZrOWtkOW83dXRsWDBBVXRwRW4KM2daYzZMZXBBb0dCQVA5YWVsNVk3
NStzSzJKSlVOT09oTzhhZTQ1Y2RzaWxwMnlJMFgrVUJhU3VRczIrZHlQcAprcEVI
QXhkNHBtbVN2bi84YzlUbEVaaHIrcVliQUJYVlBsRG5jeHBJdXcyQWpiazdzL1M0
WGFTS3NScXBYTDU3CnpqL1FPcUxrUms4K09WVjlxNmxNZVFOcUx0RWoxdTZKUHZp
WDcwUm8rRlF0UnR0Tk9ZYmZkUC9mQW9HQkFNcEEKWGpSNXdvVjVzVWIrUkVnOXZF
dVlvOFJTeU9hcnhxS0ZDSVhWVU5zTE94KzIyK0FLNCtDUXBidWVXTjdqb3RybApZ
RDZ1VDZzdldpM0FBQzdraVkwVUkvZmpWUFJDVWk4dFZvUVVFMFRhVTVWTElUYVlP
QitXL2JCYURFNE05NTYwCjFOdURXTzkwYmFBNWRmVTQ0aXV6dmEwMnJHSlhLOStu
UzNvOG5rL1BBb0dCQUxPTDZkam5EZTRtd0FhRzZKY28KY2Q0eHI4amt5UHpDUlp1
eUJDU0Jid3BoSVVYTGM3aERwclBreTA2NG5jSkQxVURtd0lka1hkL2ZwTWtnMlFt
QQovQ1VrNkxFRmpNaXNxSG9qT2FDTDlnUVpKUGhMTjVRVU4yeDFQSldHanMxdlFo
OFRreDBpVVVDT2E4YlFQWE5SCiszNE9Uc1c2VFVuYTRDU1pBeWNMZmhmZkFvR0JB
SWdnVnNlZkJDdnVRa0YwTmVVaG1EQ1JaZmhuZDh5NTVSSFIKMUhDdnFLSWxwdity
aGNYL3pteUJMdXRlb3BZeVJKUnNPaUUyRlcwMGk4K3JJUFJ1NFozUTVueWJ4N3cz
UHpWOQpvSE41UjViYUU5T3lJNEtwWld6dHBZWWl0WkY2N05jbkF2VlVMSEhPdlZK
UUduS1lmTEhKWW1ySkY3R0Exb2pNCkF1TWRGYmpGQW9HQVB4VWh4d0Z5OGdhcUJh
aEtVRVpuNEY4MUhGUDVpaEdoa1Q0UUw2QUZQTzJlK0poSUdqdVIKMjcrODVoY0Zx
UStISFZ0RnNtODFiL2ErUjdQNFV1Q1JnYzhlQ2p4UU1vSjFYbDRuN1ZialBiSE1u
SU4wUnl2ZApPNFpwV0RXWW5DTzAyMUpUT1VVT0o0Si95MDQxNkJ2a3cwejU5eTdz
Tlg3d0RCQkhIYksvWENjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
-----END PRIVATE KEY-----
END_OF_FILE
cat > /etc/chef/first-boot.json <<'END_OF_FILE'
{"run_list":["recipe[apache2]"]}
END_OF_FILE
chef-client -j /etc/chef/first-boot.json

View File

@ -267,7 +267,7 @@ public class Pems {
* @throws IOException * @throws IOException
* @throws CertificateEncodingException * @throws CertificateEncodingException
*/ */
public static String pem(X509Certificate cert) throws IOException, CertificateEncodingException { public static String pem(X509Certificate cert) throws CertificateEncodingException {
String marker = CERTIFICATE_X509_MARKER; String marker = CERTIFICATE_X509_MARKER;
return pem(cert.getEncoded(), marker); return pem(cert.getEncoded(), marker);
} }
@ -281,7 +281,7 @@ public class Pems {
* @throws IOException * @throws IOException
* @throws CertificateEncodingException * @throws CertificateEncodingException
*/ */
public static String pem(PublicKey key) throws IOException { public static String pem(PublicKey key) {
String marker = key instanceof RSAPublicKey ? PUBLIC_PKCS1_MARKER : PUBLIC_X509_MARKER; String marker = key instanceof RSAPublicKey ? PUBLIC_PKCS1_MARKER : PUBLIC_X509_MARKER;
return pem(key.getEncoded(), marker); return pem(key.getEncoded(), marker);
} }
@ -295,7 +295,7 @@ public class Pems {
* @throws IOException * @throws IOException
* @throws CertificateEncodingException * @throws CertificateEncodingException
*/ */
public static String pem(PrivateKey key) throws IOException { public static String pem(PrivateKey key) {
String marker = key instanceof RSAPrivateCrtKey ? PRIVATE_PKCS1_MARKER : PRIVATE_PKCS8_MARKER; String marker = key instanceof RSAPrivateCrtKey ? PRIVATE_PKCS1_MARKER : PRIVATE_PKCS8_MARKER;
return pem(key instanceof RSAPrivateCrtKey ? getEncoded(RSAPrivateCrtKey.class.cast(key)) : key.getEncoded(), return pem(key instanceof RSAPrivateCrtKey ? getEncoded(RSAPrivateCrtKey.class.cast(key)) : key.getEncoded(),
marker); marker);
@ -320,7 +320,7 @@ public class Pems {
return bOut.toByteArray(); return bOut.toByteArray();
} }
private static String pem(byte[] key, String marker) throws IOException { private static String pem(byte[] key, String marker) {
return new StringBuilder(marker + "\n").append( return new StringBuilder(marker + "\n").append(
Joiner.on('\n').join(Splitter.fixedLength(64).split(CryptoStreams.base64(key)))).append( Joiner.on('\n').join(Splitter.fixedLength(64).split(CryptoStreams.base64(key)))).append(
"\n" + marker.replace("BEGIN", "END") + "\n").toString().trim(); "\n" + marker.replace("BEGIN", "END") + "\n").toString().trim();