Added data bag support to Chef Solo

Since Chef 0.10.4, Solo also allows to define data bags that can be used
in cookbooks. This commit enables dataBag support, allowing users to
define custom databags before executing the runlist of the node.
This commit is contained in:
Ignasi Barrera 2012-11-07 19:12:39 +01:00
parent ee8e82f57b
commit 12453a66a1
3 changed files with 251 additions and 7 deletions

View File

@ -0,0 +1,174 @@
/**
* 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.scriptbuilder.domain.chef;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* A Data bag to be configured for a Chef Solo run.
*
* @author Ignasi Barrera
* @since Chef 0.10.4
*/
public class DataBag {
public static class Item {
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String name;
private String jsonData;
public Builder name(String name) {
this.name = checkNotNull(name, "name must be set");
return this;
}
public Builder jsonData(String jsonData) {
this.jsonData = checkNotNull(jsonData, "jsonData must be set");
return this;
}
public Item build() {
return new Item(name, jsonData);
}
}
private String name;
private String jsonData;
public Item(String name, String jsonData) {
this.name = checkNotNull(name, "name must be set");
this.jsonData = checkNotNull(jsonData, "jsonData must be set");
}
public String getName() {
return name;
}
public String getJsonData() {
return jsonData;
}
@Override
public int hashCode() {
return Objects.hashCode(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Item other = (Item) obj;
return Objects.equal(name, other.name);
}
@Override
public String toString() {
return Objects.toStringHelper(this).add("name", name).toString();
}
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String name;
private List<Item> items = Lists.newArrayList();
public Builder name(String name) {
this.name = checkNotNull(name, "name must be set");
return this;
}
public Builder item(String name, String jsonData) {
Item item = Item.builder().name(checkNotNull(name, "name must be set"))
.jsonData(checkNotNull(jsonData, "jsonData must be set")).build();
this.items.add(item);
return this;
}
public Builder items(Iterable<Item> items) {
this.items = ImmutableList.copyOf(checkNotNull(items, "items must be set"));
return this;
}
public DataBag build() {
return new DataBag(name, items);
}
}
private String name;
private List<Item> items;
public DataBag(String name, List<Item> items) {
this.name = checkNotNull(name, "name must be set");
this.items = ImmutableList.copyOf(checkNotNull(items, "items must be set"));
}
public String getName() {
return name;
}
public List<Item> getItems() {
return items;
}
@Override
public int hashCode() {
return Objects.hashCode(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Item other = (Item) obj;
return Objects.equal(name, other.name);
}
@Override
public String toString() {
return Objects.toStringHelper(this).add("name", name).toString();
}
}

View File

@ -29,6 +29,8 @@ import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.StatementList; import org.jclouds.scriptbuilder.domain.StatementList;
import org.jclouds.scriptbuilder.domain.Statements; import org.jclouds.scriptbuilder.domain.Statements;
import org.jclouds.scriptbuilder.domain.chef.DataBag;
import org.jclouds.scriptbuilder.domain.chef.DataBag.Item;
import org.jclouds.scriptbuilder.domain.chef.Role; import org.jclouds.scriptbuilder.domain.chef.Role;
import com.google.common.base.Function; import com.google.common.base.Function;
@ -54,6 +56,7 @@ public class ChefSolo implements Statement {
private String cookbooksArchiveLocation; private String cookbooksArchiveLocation;
private String jsonAttributes; private String jsonAttributes;
private List<Role> roles = Lists.newArrayList(); private List<Role> roles = Lists.newArrayList();
private List<DataBag> databags = Lists.newArrayList();
private List<String> runlist = Lists.newArrayList(); private List<String> runlist = Lists.newArrayList();
public Builder cookbooksArchiveLocation(String cookbooksArchiveLocation) { public Builder cookbooksArchiveLocation(String cookbooksArchiveLocation) {
@ -76,6 +79,22 @@ public class ChefSolo implements Statement {
return this; return this;
} }
/**
* @since Chef 0.10.4
*/
public Builder defineDataBag(DataBag dataBag) {
this.databags.add(checkNotNull(dataBag, "dataBag"));
return this;
}
/**
* @since Chef 0.10.4
*/
public Builder defineDataBags(Iterable<DataBag> databags) {
this.databags = ImmutableList.<DataBag> copyOf(checkNotNull(databags, "databags"));
return this;
}
public Builder installRecipe(String recipe) { public Builder installRecipe(String recipe) {
this.runlist.add("recipe[" + checkNotNull(recipe, "recipe") + "]"); this.runlist.add("recipe[" + checkNotNull(recipe, "recipe") + "]");
return this; return this;
@ -108,21 +127,24 @@ public class ChefSolo implements Statement {
} }
public ChefSolo build() { public ChefSolo build() {
return new ChefSolo(cookbooksArchiveLocation, Optional.fromNullable(jsonAttributes), roles, runlist); return new ChefSolo(cookbooksArchiveLocation, Optional.fromNullable(jsonAttributes), Optional.of(roles),
Optional.of(databags), runlist);
} }
} }
private String cookbooksArchiveLocation; private String cookbooksArchiveLocation;
private Optional<String> jsonAttributes; private Optional<String> jsonAttributes;
private List<Role> roles; private Optional<List<Role>> roles;
private Optional<List<DataBag>> databags;
private List<String> runlist; private List<String> runlist;
private final InstallChefGems installChefGems = new InstallChefGems(); private final InstallChefGems installChefGems = new InstallChefGems();
public ChefSolo(String cookbooksArchiveLocation, Optional<String> jsonAttributes, List<Role> roles, public ChefSolo(String cookbooksArchiveLocation, Optional<String> jsonAttributes, Optional<List<Role>> roles,
List<String> runlist) { Optional<List<DataBag>> databags, List<String> runlist) {
this.cookbooksArchiveLocation = checkNotNull(cookbooksArchiveLocation, "cookbooksArchiveLocation must be set"); this.cookbooksArchiveLocation = checkNotNull(cookbooksArchiveLocation, "cookbooksArchiveLocation must be set");
this.roles = ImmutableList.copyOf(checkNotNull(roles, "roles must be set")); this.roles = checkNotNull(roles, "roles must be set");
this.databags = checkNotNull(databags, "databags must be set");
this.runlist = ImmutableList.copyOf(checkNotNull(runlist, "runlist must be set")); this.runlist = ImmutableList.copyOf(checkNotNull(runlist, "runlist must be set"));
this.jsonAttributes = checkNotNull(jsonAttributes, "jsonAttributes must be set"); this.jsonAttributes = checkNotNull(jsonAttributes, "jsonAttributes must be set");
} }
@ -138,14 +160,28 @@ public class ChefSolo implements Statement {
statements.add(exec("{md} /var/chef")); statements.add(exec("{md} /var/chef"));
// The roles directory must contain one file for each role definition // The roles directory must contain one file for each role definition
if (!roles.isEmpty()) { if (roles.isPresent() && !roles.get().isEmpty()) {
statements.add(exec("{md} /var/chef/roles")); statements.add(exec("{md} /var/chef/roles"));
for (Role role : roles) { for (Role role : roles.get()) {
statements.add(createOrOverwriteFile("/var/chef/roles/" + role.getName() + ".json", statements.add(createOrOverwriteFile("/var/chef/roles/" + role.getName() + ".json",
ImmutableSet.of(role.toJsonString()))); ImmutableSet.of(role.toJsonString())));
} }
} }
// Each data bag item must be defined in a file inside the data bag
// directory
if (databags.isPresent() && !databags.get().isEmpty()) {
statements.add(exec("{md} /var/chef/data_bags"));
for (DataBag databag : databags.get()) {
String databagFolder = "/var/chef/data_bags/" + databag.getName();
statements.add(exec("{md} " + databagFolder));
for (Item item : databag.getItems()) {
statements.add(createOrOverwriteFile(databagFolder + "/" + item.getName() + ".json",
ImmutableSet.of(item.getJsonData())));
}
}
}
ImmutableMap.Builder<String, String> chefSoloOptions = ImmutableMap.builder(); ImmutableMap.Builder<String, String> chefSoloOptions = ImmutableMap.builder();
chefSoloOptions.put("-N", "`hostname`"); chefSoloOptions.put("-N", "`hostname`");
chefSoloOptions.put("-r", cookbooksArchiveLocation); chefSoloOptions.put("-r", cookbooksArchiveLocation);

View File

@ -24,6 +24,7 @@ import java.io.IOException;
import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.ShellToken; import org.jclouds.scriptbuilder.domain.ShellToken;
import org.jclouds.scriptbuilder.domain.chef.DataBag;
import org.jclouds.scriptbuilder.domain.chef.Role; import org.jclouds.scriptbuilder.domain.chef.Role;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -143,4 +144,37 @@ public class ChefSoloTest {
+ "\"json_class\":\"Chef::Role\",\"chef_type\":\"role\",\"run_list\":[\"recipe[apache2]\"]}" + "\"json_class\":\"Chef::Role\",\"chef_type\":\"role\",\"run_list\":[\"recipe[apache2]\"]}"
+ "\nEND_OF_JCLOUDS_FILE\nchef-solo -N `hostname` -r /tmp/cookbooks -o role[foo],recipe[git]\n"); + "\nEND_OF_JCLOUDS_FILE\nchef-solo -N `hostname` -r /tmp/cookbooks -o role[foo],recipe[git]\n");
} }
public void testChefSoloWithDataBag() throws IOException {
DataBag databag = DataBag.builder().name("foo").item("item", "{\"foo\":\"bar\"}").build();
String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").defineDataBag(databag)
.installRecipe("apache2").build().render(OsFamily.UNIX);
assertEquals(
script,
Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)),
Charsets.UTF_8)
+ "installChefGems || return 1\nmkdir -p /var/chef\n"
+ "mkdir -p /var/chef/data_bags\nmkdir -p /var/chef/data_bags/foo\n"
+ "cat > /var/chef/data_bags/foo/item.json <<-'END_OF_JCLOUDS_FILE'\n"
+ "\t{\"foo\":\"bar\"}\nEND_OF_JCLOUDS_FILE\n"
+ "chef-solo -N `hostname` -r /tmp/cookbooks -o recipe[apache2]\n");
}
public void testChefSoloWithDataBagAndMultipleItems() throws IOException {
DataBag databag = DataBag.builder().name("foo").item("item", "{\"foo\":\"bar\"}")
.item("item2", "{\"foo2\":\"bar2\"}").build();
String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").defineDataBag(databag)
.installRecipe("apache2").build().render(OsFamily.UNIX);
assertEquals(
script,
Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)),
Charsets.UTF_8)
+ "installChefGems || return 1\nmkdir -p /var/chef\n"
+ "mkdir -p /var/chef/data_bags\nmkdir -p /var/chef/data_bags/foo\n"
+ "cat > /var/chef/data_bags/foo/item.json <<-'END_OF_JCLOUDS_FILE'\n"
+ "\t{\"foo\":\"bar\"}\nEND_OF_JCLOUDS_FILE\n"
+ "cat > /var/chef/data_bags/foo/item2.json <<-'END_OF_JCLOUDS_FILE'\n"
+ "\t{\"foo2\":\"bar2\"}\nEND_OF_JCLOUDS_FILE\n"
+ "chef-solo -N `hostname` -r /tmp/cookbooks -o recipe[apache2]\n");
}
} }