From 12453a66a15a32f445b09eb5e6d333738aa4088c Mon Sep 17 00:00:00 2001 From: Ignasi Barrera Date: Wed, 7 Nov 2012 19:12:39 +0100 Subject: [PATCH] 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. --- .../scriptbuilder/domain/chef/DataBag.java | 174 ++++++++++++++++++ .../statements/chef/ChefSolo.java | 50 ++++- .../statements/chef/ChefSoloTest.java | 34 ++++ 3 files changed, 251 insertions(+), 7 deletions(-) create mode 100644 scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/chef/DataBag.java diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/chef/DataBag.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/chef/DataBag.java new file mode 100644 index 0000000000..07fea12110 --- /dev/null +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/chef/DataBag.java @@ -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 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 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 items; + + public DataBag(String name, List 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 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(); + } +} diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/chef/ChefSolo.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/chef/ChefSolo.java index ed902a25b7..836e495f41 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/chef/ChefSolo.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/chef/ChefSolo.java @@ -29,6 +29,8 @@ import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.StatementList; 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 com.google.common.base.Function; @@ -54,6 +56,7 @@ public class ChefSolo implements Statement { private String cookbooksArchiveLocation; private String jsonAttributes; private List roles = Lists.newArrayList(); + private List databags = Lists.newArrayList(); private List runlist = Lists.newArrayList(); public Builder cookbooksArchiveLocation(String cookbooksArchiveLocation) { @@ -76,6 +79,22 @@ public class ChefSolo implements Statement { 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 databags) { + this.databags = ImmutableList. copyOf(checkNotNull(databags, "databags")); + return this; + } + public Builder installRecipe(String recipe) { this.runlist.add("recipe[" + checkNotNull(recipe, "recipe") + "]"); return this; @@ -108,21 +127,24 @@ public class ChefSolo implements Statement { } 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 Optional jsonAttributes; - private List roles; + private Optional> roles; + private Optional> databags; private List runlist; private final InstallChefGems installChefGems = new InstallChefGems(); - public ChefSolo(String cookbooksArchiveLocation, Optional jsonAttributes, List roles, - List runlist) { + public ChefSolo(String cookbooksArchiveLocation, Optional jsonAttributes, Optional> roles, + Optional> databags, List runlist) { 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.jsonAttributes = checkNotNull(jsonAttributes, "jsonAttributes must be set"); } @@ -138,14 +160,28 @@ public class ChefSolo implements Statement { statements.add(exec("{md} /var/chef")); // 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")); - for (Role role : roles) { + for (Role role : roles.get()) { statements.add(createOrOverwriteFile("/var/chef/roles/" + role.getName() + ".json", 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 chefSoloOptions = ImmutableMap.builder(); chefSoloOptions.put("-N", "`hostname`"); chefSoloOptions.put("-r", cookbooksArchiveLocation); diff --git a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/chef/ChefSoloTest.java b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/chef/ChefSoloTest.java index fd8293dc83..535eaad6d3 100644 --- a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/chef/ChefSoloTest.java +++ b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/chef/ChefSoloTest.java @@ -24,6 +24,7 @@ import java.io.IOException; import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.ShellToken; +import org.jclouds.scriptbuilder.domain.chef.DataBag; import org.jclouds.scriptbuilder.domain.chef.Role; import org.testng.annotations.Test; @@ -143,4 +144,37 @@ public class ChefSoloTest { + "\"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"); } + + 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"); + } }