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"); + } }