JCLOUDS-3: Bootstrap nodes in a custom environment

Added the environment where the nodes in a group must be created in the
bootstrap data bag. This way the configuration is persisted and can be
used between runs, such as the node run list.

To accomodate the new bootstrap parameter, the method in the ChefService
used to create the data bag has been refactored and a the new
BootstrapConfig object has been created. This way, we'll be able to
extend the information in the bootstrap databag in the future without
changing the ChefService interface.
This commit is contained in:
Ignasi Barrera 2013-05-14 08:28:56 +02:00
parent 6ab817766a
commit 66dd885280
7 changed files with 288 additions and 72 deletions

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.jclouds.chef.domain.BootstrapConfig;
import org.jclouds.chef.domain.Client;
import org.jclouds.chef.domain.CookbookVersion;
import org.jclouds.chef.domain.Environment;
@ -74,22 +75,15 @@ public interface ChefService {
Statement createBootstrapScriptForGroup(String group);
/**
* assigns a run list to all nodes bootstrapped with a certain group
*
* @param runList
* list of recipes or roles to assign. syntax is
* {@code recipe[name]} and {@code role[name]}
* Configures how the nodes of a certain group will be bootstrapped
*
* @param group
* corresponds to a configured
* {@link org.jclouds.chef.config.ChefProperties#CHEF_BOOTSTRAP_DATABAG
* databag} where run_list and other information are stored
* @deprecated use
* {@link ChefService#updateBootstrapConfigForGroup(Iterable, String)
* The group where the given bootstrap configuration will be
* applied.
* @param bootstrapConfig
* The configuration to be applied to the nodes in the group.
*/
@Deprecated
void updateRunListForGroup(Iterable<String> runList, String group);
void updateBootstrapConfigForGroup(String group, BootstrapConfig bootstrapConfig);
/**
* assigns a run list to all nodes bootstrapped with a certain group
@ -102,9 +96,11 @@ public interface ChefService {
* corresponds to a configured
* {@link org.jclouds.chef.config.ChefProperties#CHEF_BOOTSTRAP_DATABAG
* databag} where run_list and other information are stored
* @see #makeChefApiBootstrapScriptForTag
* @deprecated Use {link
* {@link #updateBootstrapConfigForGroup(String, BootstrapConfig)}
*/
public void updateBootstrapConfigForGroup(Iterable<String> runList, String group);
@Deprecated
void updateBootstrapConfigForGroup(Iterable<String> runList, String group);
/**
* assigns a run list to all nodes bootstrapped with a certain group, and
@ -123,9 +119,11 @@ public interface ChefService {
* corresponds to a configured
* {@link org.jclouds.chef.config.ChefProperties#CHEF_BOOTSTRAP_DATABAG
* databag} where run_list and other information are stored
* @see #makeChefApiBootstrapScriptForTag
* @deprecated Use {link
* {@link #updateBootstrapConfigForGroup(String, BootstrapConfig)}
*/
public void updateBootstrapConfigForGroup(Iterable<String> runList, JsonBall jsonAttributes, String group);
@Deprecated
void updateBootstrapConfigForGroup(Iterable<String> runList, JsonBall jsonAttributes, String group);
/**
* @param group

View File

@ -0,0 +1,99 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.chef.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.addAll;
import java.util.List;
import org.jclouds.domain.JsonBall;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
/**
* Configures how the nodes in a group will bootstrap.
*
* @author Ignasi Barrera
* @since 1.7
*/
public class BootstrapConfig {
public static Builder builder() {
return new Builder();
}
public static class Builder {
private List<String> runList = Lists.newArrayList();
private String environment;
private JsonBall attribtues;
/**
* Sets the run list that will be executed in the nodes of the group.
*/
public Builder runList(Iterable<String> runList) {
addAll(this.runList, checkNotNull(runList, "runList"));
return this;
}
/**
* Sets the environment where the nodes in the group will be deployed.
*/
public Builder environment(String environment) {
this.environment = checkNotNull(environment, "environment");
return this;
}
/**
* Sets the attributes that will be populated to the deployed nodes.
*/
public Builder attributes(JsonBall attributes) {
this.attribtues = checkNotNull(attributes, "attributes");
return this;
}
public BootstrapConfig build() {
return new BootstrapConfig(runList, Optional.fromNullable(environment), Optional.fromNullable(attribtues));
}
}
private List<String> runList = Lists.newArrayList();
private Optional<String> environment;
private Optional<JsonBall> attribtues;
protected BootstrapConfig(List<String> runList, Optional<String> environment, Optional<JsonBall> attribtues) {
this.runList = checkNotNull(runList, "runList");
this.environment = checkNotNull(environment, "environment");
this.attribtues = checkNotNull(attribtues, "attributes");
}
public List<String> getRunList() {
return runList;
}
public Optional<String> getEnvironment() {
return environment;
}
public Optional<JsonBall> getAttribtues() {
return attribtues;
}
}

View File

@ -44,11 +44,13 @@ import org.jclouds.scriptbuilder.domain.Statement;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.cache.CacheLoader;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.inject.TypeLiteral;
/**
@ -82,6 +84,7 @@ public class GroupToBootScript implements Function<String, Statement> {
this.validatorCredential = checkNotNull(validatorCredential, validatorCredential);
}
@Override
public Statement apply(String group) {
checkNotNull(group, "group");
String validatorClientName = validatorName.get();
@ -94,6 +97,10 @@ public class GroupToBootScript implements Function<String, Statement> {
throw propagate(e);
}
Map<String, JsonBall> config = json.fromJson(bootstrapConfig.toString(),
BootstrapConfigForGroup.BOOTSTRAP_CONFIG_TYPE);
Optional<JsonBall> environment = Optional.fromNullable(config.get("environment"));
String chefConfigDir = "{root}etc{fs}chef";
Statement createChefConfigDir = exec("{md} " + chefConfigDir);
Statement createClientRb = appendFile(chefConfigDir + "{fs}client.rb", ImmutableList.of("require 'rubygems'",
@ -106,10 +113,15 @@ public class GroupToBootScript implements Function<String, Statement> {
Splitter.on('\n').split(Pems.pem(validatorKey)));
String chefBootFile = chefConfigDir + "{fs}first-boot.json";
Statement createFirstBoot = appendFile(chefBootFile, Collections.singleton(json.toJson(bootstrapConfig)));
Statement runChef = exec("chef-client -j " + chefBootFile);
ImmutableMap.Builder<String, String> options = ImmutableMap.builder();
options.put("-j", chefBootFile);
if (environment.isPresent()) {
options.put("-E", environment.get().toString());
}
String strOptions = Joiner.on(' ').withKeyValueSeparator(" ").join(options.build());
Statement runChef = exec("chef-client " + strOptions);
return newStatementList(installChefGems, createChefConfigDir, createClientRb, createValidationPem,
createFirstBoot, runChef);

View File

@ -36,6 +36,7 @@ import org.jclouds.chef.ChefApi;
import org.jclouds.chef.ChefContext;
import org.jclouds.chef.ChefService;
import org.jclouds.chef.config.ChefProperties;
import org.jclouds.chef.domain.BootstrapConfig;
import org.jclouds.chef.domain.Client;
import org.jclouds.chef.domain.CookbookVersion;
import org.jclouds.chef.domain.DatabagItem;
@ -63,10 +64,8 @@ import org.jclouds.logging.Logger;
import org.jclouds.scriptbuilder.domain.Statement;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;
import com.google.common.io.InputSupplier;
@ -210,26 +209,15 @@ public class BaseChefService implements ChefService {
}
@Override
@Deprecated
public void updateRunListForGroup(Iterable<String> runList, String group) {
updateBootstrapConfigForGroup(runList, group);
}
@Override
public void updateBootstrapConfigForGroup(Iterable<String> runList, String group) {
updateBootstrapConfigForGroup(runList, null, group);
}
@Override
public void updateBootstrapConfigForGroup(Iterable<String> runList, @Nullable JsonBall jsonAttributes, String group) {
public void updateBootstrapConfigForGroup(String group, BootstrapConfig bootstrapConfig) {
try {
api.createDatabag(databag);
} catch (IllegalStateException e) {
}
String bootstrapConfig = buildBootstrapConfiguration(runList, Optional.fromNullable(jsonAttributes));
DatabagItem runlist = new DatabagItem(group, bootstrapConfig);
String jsonConfig = buildBootstrapConfiguration(bootstrapConfig);
DatabagItem runlist = new DatabagItem(group, jsonConfig);
if (api.getDatabagItem(databag, group) == null) {
api.createDatabagItem(databag, runlist);
@ -238,6 +226,19 @@ public class BaseChefService implements ChefService {
}
}
@Override
@Deprecated
public void updateBootstrapConfigForGroup(Iterable<String> runList, String group) {
updateBootstrapConfigForGroup(runList, null, group);
}
@Override
@Deprecated
public void updateBootstrapConfigForGroup(Iterable<String> runList, @Nullable JsonBall jsonAttributes, String group) {
updateBootstrapConfigForGroup(group, BootstrapConfig.builder().runList(runList).attributes(jsonAttributes)
.build());
}
@Override
public List<String> getRunListForGroup(String group) {
return runListForGroup.apply(group);
@ -261,18 +262,23 @@ public class BaseChefService implements ChefService {
}
@VisibleForTesting
String buildBootstrapConfiguration(Iterable<String> runList, Optional<JsonBall> jsonAttributes) {
checkNotNull(runList, "runList must not be null");
checkNotNull(jsonAttributes, "jsonAttributes must not be null");
String buildBootstrapConfiguration(BootstrapConfig bootstrapConfig) {
checkNotNull(bootstrapConfig, "bootstrapConfig must not be null");
Map<String, Object> bootstrapConfig = Maps.newHashMap();
bootstrapConfig.put("run_list", Lists.newArrayList(runList));
if (jsonAttributes.isPresent()) {
Map<String, Object> attributes = json.fromJson(jsonAttributes.get().toString(),
BootstrapConfigForGroup.BOOTSTRAP_CONFIG_TYPE);
bootstrapConfig.putAll(attributes);
Map<String, Object> configMap = Maps.newHashMap();
configMap.put("run_list", bootstrapConfig.getRunList());
if (bootstrapConfig.getEnvironment().isPresent()) {
configMap.put("environment", bootstrapConfig.getEnvironment().get());
}
return json.toJson(bootstrapConfig);
if (bootstrapConfig.getAttribtues().isPresent()) {
Map<String, Object> attributes = json.fromJson(bootstrapConfig.getAttribtues().get().toString(),
BootstrapConfigForGroup.BOOTSTRAP_CONFIG_TYPE);
configMap.putAll(attributes);
}
return json.toJson(configMap);
}
@Override
@ -289,4 +295,5 @@ public class BaseChefService implements ChefService {
public Iterable<? extends Environment> listEnvironmentsNamed(Iterable<String> names) {
return listEnvironments.execute(names);
}
}

View File

@ -42,6 +42,7 @@ import org.jclouds.rest.annotations.ApiVersion;
import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.ShellToken;
import org.jclouds.scriptbuilder.domain.Statement;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.base.Charsets;
@ -63,22 +64,29 @@ import com.google.inject.name.Names;
@Test(groups = "unit", testName = "GroupToBootScriptTest")
public class GroupToBootScriptTest {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApi.VERSION);
bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEM_SYSTEM)).toInstance("true");
bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEMS)).toInstance("true");
}
}, new ChefParserModule(), new GsonModule(), new ChefBootstrapModule());
private Json json;
private Statement installChefGems;
private Optional<String> validatorName;
Json json = injector.getInstance(Json.class);
Statement installChefGems = injector.getInstance(Key.get(Statement.class, Names.named("installChefGems")));
Optional<String> validatorName = Optional.<String> of("chef-validator");
Optional<PrivateKey> validatorCredential = Optional.<PrivateKey> of(createMock(PrivateKey.class));
@BeforeClass
public void setup() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApi.VERSION);
bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEM_SYSTEM)).toInstance("true");
bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEMS)).toInstance("true");
}
}, new ChefParserModule(), new GsonModule(), new ChefBootstrapModule());
json = injector.getInstance(Json.class);
installChefGems = injector.getInstance(Key.get(Statement.class, Names.named("installChefGems")));
validatorName = Optional.<String> of("chef-validator");
}
@Test(expectedExceptions = IllegalStateException.class)
public void testMustHaveValidatorName() {
Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of())), installChefGems,
Optional.<String> absent(), validatorCredential);
@ -95,6 +103,7 @@ public class GroupToBootScriptTest {
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Key 'foo' not present in map")
public void testMustHaveRunScriptsName() {
Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of())), installChefGems,
validatorName, validatorCredential);
@ -103,6 +112,7 @@ public class GroupToBootScriptTest {
@Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "null value in entry: foo=null")
public void testMustHaveRunScriptsValue() {
Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of("foo", (DatabagItem) null))),
installChefGems, validatorName, validatorCredential);
@ -110,6 +120,7 @@ public class GroupToBootScriptTest {
}
public void testOneRecipe() throws IOException {
Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
"{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))),
@ -131,4 +142,29 @@ public class GroupToBootScriptTest {
verify(validatorKey);
}
public void testOneRecipeAndEnvironment() throws IOException {
Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
"{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\","
+ "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), installChefGems,
validatorName, validatorCredential);
PrivateKey validatorKey = validatorCredential.get();
expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
replay(validatorKey);
assertEquals(
fn.apply("foo").render(OsFamily.UNIX),
Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)),
Charsets.UTF_8)
+ Resources.toString(
Resources.getResource("test_install_rubygems." + ShellToken.SH.to(OsFamily.UNIX)),
Charsets.UTF_8)
+ "gem install chef --no-rdoc --no-ri\n"
+ Resources.toString(Resources.getResource("bootstrap-env.sh"), Charsets.UTF_8));
verify(validatorKey);
}
}

View File

@ -24,6 +24,7 @@ import java.util.List;
import org.jclouds.ContextBuilder;
import org.jclouds.chef.ChefApiMetadata;
import org.jclouds.chef.domain.BootstrapConfig;
import org.jclouds.chef.filters.SignedHeaderAuthTest;
import org.jclouds.chef.util.RunListBuilder;
import org.jclouds.domain.JsonBall;
@ -32,7 +33,6 @@ import org.jclouds.rest.internal.BaseRestApiTest.MockModule;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Injector;
@ -57,39 +57,47 @@ public class BaseChefServiceTest {
chefService = injector.getInstance(BaseChefService.class);
}
@Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "runList must not be null")
public void testBuildBootstrapConfigurationWithNullRunlist() {
chefService.buildBootstrapConfiguration(null, null);
}
@Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "jsonAttributes must not be null")
public void testBuildBootstrapConfigurationWithNullJsonAttributes() {
chefService.buildBootstrapConfiguration(ImmutableList.<String> of(), null);
@Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "bootstrapConfig must not be null")
public void testBuildBootstrapConfigurationWithNullConfig() {
chefService.buildBootstrapConfiguration(null);
}
public void testBuildBootstrapConfigurationWithEmptyRunlist() {
String config = chefService
.buildBootstrapConfiguration(ImmutableList.<String> of(), Optional.<JsonBall> absent());
BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(ImmutableList.<String> of()).build();
String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
assertEquals(config, "{\"run_list\":[]}");
}
public void testBuildBootstrapConfigurationWithRunlist() {
List<String> runlist = new RunListBuilder().addRecipe("apache2").addRole("webserver").build();
String config = chefService.buildBootstrapConfiguration(runlist, Optional.<JsonBall> absent());
BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist).build();
String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
assertEquals(config, "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
}
public void testBuildBootstrapConfigurationWithRunlistAndEmptyAttributes() {
List<String> runlist = new RunListBuilder().addRecipe("apache2").addRole("webserver").build();
String config = chefService.buildBootstrapConfiguration(runlist, Optional.of(new JsonBall("{}")));
BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist).attributes(new JsonBall("{}"))
.build();
String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
assertEquals(config, "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
}
public void testBuildBootstrapConfigurationWithRunlistAndAttributes() {
List<String> runlist = new RunListBuilder().addRecipe("apache2").addRole("webserver").build();
String config = chefService.buildBootstrapConfiguration(runlist,
Optional.of(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")));
BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist)
.attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")).build();
String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
assertEquals(config, "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
}
public void testBuildBootstrapConfigurationWithRunlistAndAttributesAndEnvironment() {
List<String> runlist = new RunListBuilder().addRecipe("apache2").addRole("webserver").build();
BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist)
.attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")).environment("env").build();
String config = chefService.buildBootstrapConfiguration(bootstrapConfig);
assertEquals(config,
"{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\",\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
}
}

View File

@ -0,0 +1,56 @@
mkdir -p /etc/chef
cat >> /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE'
require 'rubygems'
require 'ohai'
o = Ohai::System.new
o.all_plugins
node_name "foo-" + o[:ipaddress]
log_level :info
log_location STDOUT
validation_client_name "chef-validator"
chef_server_url "http://localhost:4000"
END_OF_JCLOUDS_FILE
cat >> /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE'
-----BEGIN PRIVATE KEY-----
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVB
eWIyWkpKcUdtMEtLUis4bmZRSk5zU2QrRjl0WE5NVjdDZk9jVzZqc3FzOEVaZ2lW
ClIwOWhEMUlZT2o0WXFNMHFKT05sZ3lnNHhSV2V3ZFNHN1FUUGoxbEpwVkFpZGE5
c1h5MitrenlhZ1pBMUFtME8KWmNicWI1aG9lSURnY1grZURhNzlzMHUwRG9tamNm
TzlFS2h2SExCeit6TSszUXFQUmtQVjhuWVRiZnMrSGpWegp6T1U2RDFCMFhSMytJ
UFpabDJBbldzMmQwcWhuU3RIY0RVdm5SVlEwUDQ4Mll3TjlWZ2NlT1p0cFB6MERD
S0VKCjVUeDVTVHViOGswL3p0L1ZBTUhRYWZMU3VRTUxkMnM0Wkx1T1pwdE4vL3VB
c1RteGlyZXFkMzd6KzhaVGRCYkoKOExFcEoraUNYdVNmbTVhVWg3aXc2b3h2VG9Z
MkFMNTMraksyVVFJREFRQUJBb0lCQVFEQTg4QjNpL3hXbjB2WApCVnhGYW1DWW9l
Y3VOakd3WFhrU3laZXc2MTZBK0VPQ3U0N2JoNGFUdXJkRmJZTDBZRmFBdGFXdnps
YU4yZUhnCkRiK0hEdVRlZkUyOStXa2NHazZTc2hQbWl6NVQwWE9DQUlDV3c2d1NW
RGtIbUd3UzRqWnZiQUZtN1c4bndHazkKWWh4Z3hGaVJuZ3N3SlpGb3BPTG9GNVdY
czJ0ZDhndUlZTnNsTXBvN3R1NTBpRm5CSHdLTzJac1BBazh0OW5uUwp4bERhdkty
dXltRW1xSENyMytkdGlvNWVhZW5KY3AzZmpvWEJRT0tVazNpcElJMjlYUkI4TnFl
Q1ZWLzdLeHdxCmNrcU9CRWJSd0JjbGNreUliRCtSaUFnS3ZPZWxPUmpFaUU5UjQy
dnVxdnhSQTZrOWtkOW83dXRsWDBBVXRwRW4KM2daYzZMZXBBb0dCQVA5YWVsNVk3
NStzSzJKSlVOT09oTzhhZTQ1Y2RzaWxwMnlJMFgrVUJhU3VRczIrZHlQcAprcEVI
QXhkNHBtbVN2bi84YzlUbEVaaHIrcVliQUJYVlBsRG5jeHBJdXcyQWpiazdzL1M0
WGFTS3NScXBYTDU3CnpqL1FPcUxrUms4K09WVjlxNmxNZVFOcUx0RWoxdTZKUHZp
WDcwUm8rRlF0UnR0Tk9ZYmZkUC9mQW9HQkFNcEEKWGpSNXdvVjVzVWIrUkVnOXZF
dVlvOFJTeU9hcnhxS0ZDSVhWVU5zTE94KzIyK0FLNCtDUXBidWVXTjdqb3RybApZ
RDZ1VDZzdldpM0FBQzdraVkwVUkvZmpWUFJDVWk4dFZvUVVFMFRhVTVWTElUYVlP
QitXL2JCYURFNE05NTYwCjFOdURXTzkwYmFBNWRmVTQ0aXV6dmEwMnJHSlhLOStu
UzNvOG5rL1BBb0dCQUxPTDZkam5EZTRtd0FhRzZKY28KY2Q0eHI4amt5UHpDUlp1
eUJDU0Jid3BoSVVYTGM3aERwclBreTA2NG5jSkQxVURtd0lka1hkL2ZwTWtnMlFt
QQovQ1VrNkxFRmpNaXNxSG9qT2FDTDlnUVpKUGhMTjVRVU4yeDFQSldHanMxdlFo
OFRreDBpVVVDT2E4YlFQWE5SCiszNE9Uc1c2VFVuYTRDU1pBeWNMZmhmZkFvR0JB
SWdnVnNlZkJDdnVRa0YwTmVVaG1EQ1JaZmhuZDh5NTVSSFIKMUhDdnFLSWxwdity
aGNYL3pteUJMdXRlb3BZeVJKUnNPaUUyRlcwMGk4K3JJUFJ1NFozUTVueWJ4N3cz
UHpWOQpvSE41UjViYUU5T3lJNEtwWld6dHBZWWl0WkY2N05jbkF2VlVMSEhPdlZK
UUduS1lmTEhKWW1ySkY3R0Exb2pNCkF1TWRGYmpGQW9HQVB4VWh4d0Z5OGdhcUJh
aEtVRVpuNEY4MUhGUDVpaEdoa1Q0UUw2QUZQTzJlK0poSUdqdVIKMjcrODVoY0Zx
UStISFZ0RnNtODFiL2ErUjdQNFV1Q1JnYzhlQ2p4UU1vSjFYbDRuN1ZialBiSE1u
SU4wUnl2ZApPNFpwV0RXWW5DTzAyMUpUT1VVT0o0Si95MDQxNkJ2a3cwejU5eTdz
Tlg3d0RCQkhIYksvWENjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
-----END PRIVATE KEY-----
END_OF_JCLOUDS_FILE
cat >> /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE'
{"tomcat6":{"ssl_port":8433},"environment":"env","run_list":["recipe[apache2]","role[webserver]"]}
END_OF_JCLOUDS_FILE
chef-client -j /etc/chef/first-boot.json -E "env"