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 836e495f41..0e19659fbb 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 @@ -33,6 +33,7 @@ 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.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; @@ -48,38 +49,163 @@ import com.google.common.collect.Lists; */ public class ChefSolo implements Statement { + public static final String DEFAULT_SOLO_PATH = "/var/chef"; + public static Builder builder() { return new Builder(); } public static class Builder { + private String fileCachePath = DEFAULT_SOLO_PATH; + private String rolePath; + private String databagPath; + private List cookbookPath = Lists.newArrayList(); private String cookbooksArchiveLocation; private String jsonAttributes; + private String group; + private Integer interval; + private String logLevel; + private String logFile; + private String nodeName; + private Integer splay; + private String user; private List roles = Lists.newArrayList(); private List databags = Lists.newArrayList(); private List runlist = Lists.newArrayList(); + /** + * Directory where Chef Solo will store files. + */ + public Builder fileCachePath(String fileCachePath) { + this.fileCachePath = checkNotNull(fileCachePath, "fileCachePath"); + return this; + } + + /** + * Directory where Chef Solo will store roles. + */ + public Builder rolePath(String rolePath) { + this.rolePath = checkNotNull(rolePath, "rolePath"); + return this; + } + + /** + * Directory where Chef Solo will store data bags. + */ + public Builder dataBagPath(String dataBagPath) { + this.databagPath = checkNotNull(dataBagPath, "dataBagPath"); + return this; + } + + /** + * Directory where Chef Solo will look for cookbooks. + */ + public Builder cookbookPath(String cookbookPath) { + this.cookbookPath.add(checkNotNull(cookbookPath, "cookbookPath")); + return this; + } + + /** + * Directories where Chef Solo will look for cookbooks. + */ + public Builder cookbookPaths(Iterable cookbookPaths) { + this.cookbookPath = ImmutableList. copyOf(checkNotNull(cookbookPaths, "cookbookPaths")); + return this; + } + + /** + * Local file path or remote URL of a cookbook tar file. Chef solo will + * download and unpack its contents in the {@link #fileCachePath} + * directory. + */ public Builder cookbooksArchiveLocation(String cookbooksArchiveLocation) { this.cookbooksArchiveLocation = checkNotNull(cookbooksArchiveLocation, "cookbooksArchiveLocation"); return this; } + /** + * JSON attributes to customize cookbook values. + */ public Builder jsonAttributes(String jsonAttributes) { this.jsonAttributes = checkNotNull(jsonAttributes, "jsonAttributes"); return this; } + /** + * The goup to set privilege to. + */ + public Builder group(String group) { + this.group = checkNotNull(group, "group"); + return this; + } + + /** + * Run chef-client periodically, in seconds. + */ + public Builder interval(Integer interval) { + this.interval = checkNotNull(interval, "interval"); + return this; + } + + /** + * Set he Log level (debug, info, warn, error, fatal). + */ + public Builder logLevel(String logLevel) { + this.logLevel = checkNotNull(logLevel, "logLevel"); + return this; + } + + /** + * Set the log file location, by default STDOUT. + */ + public Builder logFile(String logFile) { + this.logFile = checkNotNull(logFile, "logFile"); + return this; + } + + /** + * Set the name for the node. By default the hostname will be used. + */ + public Builder nodeName(String nodeName) { + this.nodeName = checkNotNull(nodeName, "nodeName"); + return this; + } + + /** + * The splay time for running at intervals, in seconds. + */ + public Builder splay(Integer splay) { + this.splay = checkNotNull(splay, "splay"); + return this; + } + + /** + * The user to set privilege to. + */ + public Builder user(String user) { + this.user = checkNotNull(user, "user"); + return this; + } + + /** + * Creates a role. + */ public Builder defineRole(Role role) { this.roles.add(checkNotNull(role, "role")); return this; } + /** + * Creates a set of roles. + */ public Builder defineRoles(Iterable roles) { this.roles = ImmutableList. copyOf(checkNotNull(roles, "roles")); return this; } /** + * Creates a data bag. + * * @since Chef 0.10.4 */ public Builder defineDataBag(DataBag dataBag) { @@ -88,6 +214,8 @@ public class ChefSolo implements Statement { } /** + * Creates a set of data bags. + * * @since Chef 0.10.4 */ public Builder defineDataBags(Iterable databags) { @@ -95,11 +223,17 @@ public class ChefSolo implements Statement { return this; } + /** + * Adds the given recipe to the run list of the node. + */ public Builder installRecipe(String recipe) { this.runlist.add("recipe[" + checkNotNull(recipe, "recipe") + "]"); return this; } + /** + * Adds the given recipes to the run list of the node. + */ public Builder installRecipes(Iterable recipes) { this.runlist.addAll(Lists.newArrayList(transform(checkNotNull(recipes, "recipes"), new Function() { @@ -111,11 +245,17 @@ public class ChefSolo implements Statement { return this; } + /** + * Adds the given role to the run list of the node. + */ public Builder installRole(String role) { this.runlist.add("role[" + checkNotNull(role, "role") + "]"); return this; } + /** + * Adds the given roles to the run list of the node. + */ public Builder installRoles(Iterable roles) { this.runlist.addAll(Lists.newArrayList(transform(checkNotNull(roles, "roles"), new Function() { @Override @@ -127,26 +267,59 @@ public class ChefSolo implements Statement { } public ChefSolo build() { - return new ChefSolo(cookbooksArchiveLocation, Optional.fromNullable(jsonAttributes), Optional.of(roles), - Optional.of(databags), runlist); + if (cookbookPath.isEmpty()) { + cookbookPath.add(fileCachePath + "/cookbooks"); + } + return new ChefSolo(Optional.of(fileCachePath), Optional.fromNullable(rolePath), + Optional.fromNullable(databagPath), Optional.of(cookbookPath), + Optional.fromNullable(cookbooksArchiveLocation), Optional.fromNullable(jsonAttributes), + Optional.fromNullable(group), Optional.fromNullable(interval), Optional.fromNullable(logLevel), + Optional.fromNullable(logFile), Optional.fromNullable(nodeName), Optional.fromNullable(splay), + Optional.fromNullable(user), Optional.of(roles), Optional.of(databags), runlist); } } - private String cookbooksArchiveLocation; + private String fileCachePath; + private String rolePath; + private String databagPath; + private List cookbookPath; + private Optional cookbooksArchiveLocation; private Optional jsonAttributes; + private Optional group; + private Optional interval; + private Optional logLevel; + private Optional logFile; + private Optional nodeName; + private Optional splay; + private Optional user; private Optional> roles; private Optional> databags; private List runlist; private final InstallChefGems installChefGems = new InstallChefGems(); - public ChefSolo(String cookbooksArchiveLocation, Optional jsonAttributes, Optional> roles, - Optional> databags, List runlist) { + public ChefSolo(Optional fileCachePath, Optional rolePath, Optional databagPath, + Optional> cookbookPath, Optional cookbooksArchiveLocation, + Optional jsonAttributes, Optional group, Optional interval, + Optional logLevel, Optional logFile, Optional nodeName, Optional splay, + Optional user, Optional> roles, Optional> databags, List runlist) { + this.fileCachePath = checkNotNull(fileCachePath, "fileCachePath must be set").or(DEFAULT_SOLO_PATH); + this.rolePath = checkNotNull(rolePath, "rolePath must be set").or(this.fileCachePath + "/roles"); + this.databagPath = checkNotNull(databagPath, "databagPath must be set").or(this.fileCachePath + "/data_bags"); + this.cookbookPath = checkNotNull(cookbookPath, "cookbookPath must be set").or( + Lists. newArrayList(this.fileCachePath + "/cookbooks")); this.cookbooksArchiveLocation = checkNotNull(cookbooksArchiveLocation, "cookbooksArchiveLocation must be set"); + this.jsonAttributes = checkNotNull(jsonAttributes, "jsonAttributes must be set"); + this.group = checkNotNull(group, "group must be set"); + this.interval = checkNotNull(interval, "interval must be set"); + this.logLevel = checkNotNull(logLevel, "logLevel must be set"); + this.logFile = checkNotNull(logFile, "logFile must be set"); + this.nodeName = checkNotNull(nodeName, "nodeName must be set"); + this.splay = checkNotNull(splay, "splay must be set"); + this.user = checkNotNull(user, "user 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"); } @Override @@ -157,46 +330,41 @@ public class ChefSolo implements Statement { ImmutableList.Builder statements = ImmutableList.builder(); statements.add(installChefGems); - statements.add(exec("{md} /var/chef")); - // The roles directory must contain one file for each role definition - if (roles.isPresent() && !roles.get().isEmpty()) { - statements.add(exec("{md} /var/chef/roles")); - for (Role role : roles.get()) { - statements.add(createOrOverwriteFile("/var/chef/roles/" + role.getName() + ".json", - ImmutableSet.of(role.toJsonString()))); - } + createSoloConfiguration(statements); + createRolesIfNecessary(statements); + createDatabagsIfNecessary(statements); + createNodeConfiguration(statements); + + ImmutableMap.Builder options = ImmutableMap.builder(); + options.put("-c", fileCachePath + "/solo.rb"); + options.put("-j", fileCachePath + "/node.json"); + options.put("-N", nodeName.or("`hostname`")); + + if (group.isPresent()) { + options.put("-g", group.get()); + } + if (interval.isPresent()) { + options.put("-i", interval.get().toString()); + } + if (logLevel.isPresent()) { + options.put("-l", logLevel.get()); + } + if (logFile.isPresent()) { + options.put("-L", logFile.get()); + } + if (cookbooksArchiveLocation.isPresent()) { + options.put("-r", cookbooksArchiveLocation.get()); + } + if (splay.isPresent()) { + options.put("-s", splay.get().toString()); + } + if (user.isPresent()) { + options.put("-u", user.get()); } - // 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); - - if (jsonAttributes.isPresent()) { - statements.add(createOrOverwriteFile("/var/chef/node.json", jsonAttributes.asSet())); - chefSoloOptions.put("-j", "/var/chef/node.json"); - } - - if (!runlist.isEmpty()) { - chefSoloOptions.put("-o", Joiner.on(',').join(runlist)); - } - - String options = Joiner.on(' ').withKeyValueSeparator(" ").join(chefSoloOptions.build()); - statements.add(Statements.exec(String.format("chef-solo %s", options))); + String strOptions = Joiner.on(' ').withKeyValueSeparator(" ").join(options.build()); + statements.add(Statements.exec(String.format("chef-solo %s", strOptions))); return new StatementList(statements.build()).render(family); } @@ -206,4 +374,74 @@ public class ChefSolo implements Statement { return installChefGems.functionDependencies(family); } + @VisibleForTesting + void createSoloConfiguration(ImmutableList.Builder statements) { + statements.add(exec("{md} " + fileCachePath)); + for (String path : cookbookPath) { + statements.add(exec("{md} " + path)); + } + String cookbookPathJoined = Joiner.on(',').join(transform(cookbookPath, quote())); + statements.add(createOrOverwriteFile( + fileCachePath + "/solo.rb", + ImmutableSet.of("file_cache_path \"" + fileCachePath + "\"", // + "cookbook_path [" + cookbookPathJoined + "]", "role_path \"" + rolePath + "\"", "data_bag_path \"" + + databagPath + "\""))); + } + + @VisibleForTesting + void createNodeConfiguration(ImmutableList.Builder statements) { + StringBuffer json = new StringBuffer(); + if (jsonAttributes.isPresent()) { + // Start the node configuration with the attributes, but remove the + // last bracket to append the run list to the json configuration + json.append(jsonAttributes.get().substring(0, jsonAttributes.get().lastIndexOf('}'))); + json.append(","); + } else { + json.append("{"); + } + json.append("\"run_list\":["); + json.append(Joiner.on(',').join(transform(runlist, quote()))); + json.append("]"); + json.append("}"); + + statements.add(createOrOverwriteFile(fileCachePath + "/node.json", ImmutableSet.of(json.toString()))); + } + + @VisibleForTesting + void createRolesIfNecessary(ImmutableList.Builder statements) { + // The roles directory must contain one file for each role definition + if (roles.isPresent() && !roles.get().isEmpty()) { + statements.add(exec("{md} " + rolePath)); + for (Role role : roles.get()) { + statements.add(createOrOverwriteFile(rolePath + "/" + role.getName() + ".json", + ImmutableSet.of(role.toJsonString()))); + } + } + } + + @VisibleForTesting + void createDatabagsIfNecessary(ImmutableList.Builder statements) { + // Each data bag item must be defined in a file inside the data bag + // directory, and each data bag item must have its own JSON file. + if (databags.isPresent() && !databags.get().isEmpty()) { + statements.add(exec("{md} " + databagPath)); + for (DataBag databag : databags.get()) { + String databagFolder = databagPath + "/" + databag.getName(); + statements.add(exec("{md} " + databagFolder)); + for (Item item : databag.getItems()) { + statements.add(createOrOverwriteFile(databagFolder + "/" + item.getName() + ".json", + ImmutableSet.of(item.getJsonData()))); + } + } + } + } + + private static Function quote() { + return new Function() { + @Override + public String apply(String input) { + return "\"" + input + "\""; + } + }; + } } 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 535eaad6d3..54ebfc1113 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 @@ -18,26 +18,303 @@ */ package org.jclouds.scriptbuilder.statements.chef; +import static org.jclouds.scriptbuilder.domain.Statements.createOrOverwriteFile; +import static org.jclouds.scriptbuilder.domain.Statements.exec; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; import java.io.IOException; import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.ShellToken; +import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.chef.DataBag; import org.jclouds.scriptbuilder.domain.chef.Role; import org.testng.annotations.Test; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.io.Resources; +/** + * Unit tests for the {@link ChefSoloTest} statement. + * + * @author Ignasi Barrera + */ @Test(groups = "unit", testName = "ChefSoloTest") public class ChefSoloTest { - @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "cookbooksArchiveLocation must be set") - public void testChefSoloWithoutCookbooksLocation() { - ChefSolo.builder().build(); + public void testCreateDefaultSoloConfiguration() { + ImmutableList.Builder statements = ImmutableList.builder(); + ChefSolo solo = ChefSolo.builder().build(); + + solo.createSoloConfiguration(statements); + ImmutableList statementList = statements.build(); + + assertEquals(statementList.size(), 3); + assertEquals(statementList.get(0), exec("{md} " + ChefSolo.DEFAULT_SOLO_PATH)); + assertEquals(statementList.get(1), exec("{md} " + ChefSolo.DEFAULT_SOLO_PATH + "/cookbooks")); + + Statement expected = createOrOverwriteFile( + ChefSolo.DEFAULT_SOLO_PATH + "/solo.rb", + ImmutableSet.of("file_cache_path \"" + ChefSolo.DEFAULT_SOLO_PATH + "\"", // + "cookbook_path [\"" + ChefSolo.DEFAULT_SOLO_PATH + "/cookbooks\"]", "role_path \"" + + ChefSolo.DEFAULT_SOLO_PATH + "/roles\"", "data_bag_path \"" + ChefSolo.DEFAULT_SOLO_PATH + + "/data_bags\"")); + + assertEquals(statementList.get(2).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateCustomSoloConfiguration() { + ImmutableList.Builder statements = ImmutableList.builder(); + ChefSolo solo = ChefSolo.builder().fileCachePath("/tmp").cookbookPath("/tmp/foo").cookbookPath("/tmp/bar") + .rolePath("/tmp/roles").dataBagPath("/tmp/databags").build(); + + solo.createSoloConfiguration(statements); + ImmutableList statementList = statements.build(); + + assertEquals(statementList.size(), 4); + assertEquals(statementList.get(0), exec("{md} /tmp")); + assertEquals(statementList.get(1), exec("{md} /tmp/foo")); + assertEquals(statementList.get(2), exec("{md} /tmp/bar")); + + Statement expected = createOrOverwriteFile("/tmp/solo.rb", ImmutableSet.of("file_cache_path \"/tmp\"", // + "cookbook_path [\"/tmp/foo\",\"/tmp/bar\"]", "role_path \"/tmp/roles\"", "data_bag_path \"/tmp/databags\"")); + + assertEquals(statementList.get(3).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateDefaultNodeConfiguration() { + ImmutableList.Builder statements = ImmutableList.builder(); + ChefSolo solo = ChefSolo.builder().build(); + + solo.createNodeConfiguration(statements); + ImmutableList statementList = statements.build(); + + Statement expected = createOrOverwriteFile(ChefSolo.DEFAULT_SOLO_PATH + "/node.json", + ImmutableSet.of("{\"run_list\":[]}")); + + assertEquals(statementList.size(), 1); + assertEquals(statementList.get(0).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateNodeConfigurationWithJsonAttributes() { + ImmutableList.Builder statements = ImmutableList.builder(); + ChefSolo solo = ChefSolo.builder().jsonAttributes("{\"foo\":\"bar\"}").build(); + + solo.createNodeConfiguration(statements); + ImmutableList statementList = statements.build(); + + Statement expected = createOrOverwriteFile(ChefSolo.DEFAULT_SOLO_PATH + "/node.json", + ImmutableSet.of("{\"foo\":\"bar\",\"run_list\":[]}")); + + assertEquals(statementList.size(), 1); + assertEquals(statementList.get(0).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateNodeConfigurationWithRunList() { + ImmutableList.Builder statements = ImmutableList.builder(); + ChefSolo solo = ChefSolo.builder().installRecipe("foo").installRole("bar").build(); + + solo.createNodeConfiguration(statements); + ImmutableList statementList = statements.build(); + + Statement expected = createOrOverwriteFile(ChefSolo.DEFAULT_SOLO_PATH + "/node.json", + ImmutableSet.of("{\"run_list\":[\"recipe[foo]\",\"role[bar]\"]}")); + + assertEquals(statementList.size(), 1); + assertEquals(statementList.get(0).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateNodeConfigurationWithJsonAttributesAndRunList() { + ImmutableList.Builder statements = ImmutableList.builder(); + ChefSolo solo = ChefSolo.builder().jsonAttributes("{\"foo\":\"bar\"}").installRecipe("foo").installRole("bar") + .build(); + + solo.createNodeConfiguration(statements); + ImmutableList statementList = statements.build(); + + Statement expected = createOrOverwriteFile(ChefSolo.DEFAULT_SOLO_PATH + "/node.json", + ImmutableSet.of("{\"foo\":\"bar\",\"run_list\":[\"recipe[foo]\",\"role[bar]\"]}")); + + assertEquals(statementList.size(), 1); + assertEquals(statementList.get(0).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateRolesIfNecessaryWithDefaultValues() { + ImmutableList.Builder statements = ImmutableList.builder(); + ChefSolo solo = ChefSolo.builder().build(); + + solo.createRolesIfNecessary(statements); + ImmutableList statementList = statements.build(); + + assertTrue(statementList.isEmpty()); + } + + public void testCreateRolesIfNecessaryWithOneRole() { + ImmutableList.Builder statements = ImmutableList.builder(); + Role role = Role.builder().name("foo").installRecipe("bar").build(); + ChefSolo solo = ChefSolo.builder().defineRole(role).build(); + + solo.createRolesIfNecessary(statements); + ImmutableList statementList = statements.build(); + + Statement expected = createOrOverwriteFile(ChefSolo.DEFAULT_SOLO_PATH + "/roles/" + role.getName() + ".json", + ImmutableSet.of(role.toJsonString())); + + assertEquals(statementList.size(), 2); + assertEquals(statementList.get(0), exec("{md} " + ChefSolo.DEFAULT_SOLO_PATH + "/roles")); + assertEquals(statementList.get(1).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateRolesIfNecessaryWithOneRoleAndCustomPath() { + ImmutableList.Builder statements = ImmutableList.builder(); + Role role = Role.builder().name("foo").installRecipe("bar").build(); + ChefSolo solo = ChefSolo.builder().rolePath("/tmp/roles").defineRole(role).build(); + + solo.createRolesIfNecessary(statements); + ImmutableList statementList = statements.build(); + + Statement expected = createOrOverwriteFile("/tmp/roles/" + role.getName() + ".json", + ImmutableSet.of(role.toJsonString())); + + assertEquals(statementList.size(), 2); + assertEquals(statementList.get(0), exec("{md} /tmp/roles")); + assertEquals(statementList.get(1).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateRolesIfNecessaryWithMultipleRoleAndCustomPath() { + ImmutableList.Builder statements = ImmutableList.builder(); + Role roleFoo = Role.builder().name("foo").installRecipe("bar").build(); + Role roleBar = Role.builder().name("bar").installRecipe("foo").build(); + ChefSolo solo = ChefSolo.builder().rolePath("/tmp/roles").defineRole(roleFoo).defineRole(roleBar).build(); + + solo.createRolesIfNecessary(statements); + ImmutableList statementList = statements.build(); + + Statement expectedFoo = createOrOverwriteFile("/tmp/roles/" + roleFoo.getName() + ".json", + ImmutableSet.of(roleFoo.toJsonString())); + Statement expectedBar = createOrOverwriteFile("/tmp/roles/" + roleBar.getName() + ".json", + ImmutableSet.of(roleBar.toJsonString())); + + assertEquals(statementList.size(), 3); + assertEquals(statementList.get(0), exec("{md} /tmp/roles")); + assertEquals(statementList.get(1).render(OsFamily.UNIX), expectedFoo.render(OsFamily.UNIX)); + assertEquals(statementList.get(2).render(OsFamily.UNIX), expectedBar.render(OsFamily.UNIX)); + } + + public void testCreateDatabagsIfNecessaryWithDefaultValues() { + ImmutableList.Builder statements = ImmutableList.builder(); + ChefSolo solo = ChefSolo.builder().build(); + + solo.createDatabagsIfNecessary(statements); + ImmutableList statementList = statements.build(); + + assertTrue(statementList.isEmpty()); + } + + public void testCreateDatabagsIfNecessaryWithOneDatabag() { + ImmutableList.Builder statements = ImmutableList.builder(); + DataBag databag = DataBag.builder().name("foo").item("item", "{\"foo\":\"bar\"}").build(); + ChefSolo solo = ChefSolo.builder().defineDataBag(databag).build(); + + solo.createDatabagsIfNecessary(statements); + ImmutableList statementList = statements.build(); + + Statement expected = createOrOverwriteFile(ChefSolo.DEFAULT_SOLO_PATH + "/data_bags/foo/item.json", + ImmutableSet.of("{\"foo\":\"bar\"}")); + + assertEquals(statementList.size(), 3); + assertEquals(statementList.get(0), exec("{md} " + ChefSolo.DEFAULT_SOLO_PATH + "/data_bags")); + assertEquals(statementList.get(1), exec("{md} " + ChefSolo.DEFAULT_SOLO_PATH + "/data_bags/" + databag.getName())); + assertEquals(statementList.get(2).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateDatabagsIfNecessaryWithOneDatabagAndCustomPath() { + ImmutableList.Builder statements = ImmutableList.builder(); + DataBag databag = DataBag.builder().name("foo").item("item", "{\"foo\":\"bar\"}").build(); + ChefSolo solo = ChefSolo.builder().dataBagPath("/tmp/databags").defineDataBag(databag).build(); + + solo.createDatabagsIfNecessary(statements); + ImmutableList statementList = statements.build(); + + Statement expected = createOrOverwriteFile("/tmp/databags/foo/item.json", ImmutableSet.of("{\"foo\":\"bar\"}")); + + assertEquals(statementList.size(), 3); + assertEquals(statementList.get(0), exec("{md} /tmp/databags")); + assertEquals(statementList.get(1), exec("{md} /tmp/databags/" + databag.getName())); + assertEquals(statementList.get(2).render(OsFamily.UNIX), expected.render(OsFamily.UNIX)); + } + + public void testCreateDatabagsIfNecessaryWithOneDatabagWithMultipleItemsAndCustomPath() { + ImmutableList.Builder statements = ImmutableList.builder(); + DataBag databag = DataBag.builder().name("foo").item("item1", "{\"foo\":\"bar\"}") + .item("item2", "{\"bar\":\"foo\"}").build(); + ChefSolo solo = ChefSolo.builder().dataBagPath("/tmp/databags").defineDataBag(databag).build(); + + solo.createDatabagsIfNecessary(statements); + ImmutableList statementList = statements.build(); + + Statement expectedItem1 = createOrOverwriteFile("/tmp/databags/foo/item1.json", + ImmutableSet.of("{\"foo\":\"bar\"}")); + Statement expectedItem2 = createOrOverwriteFile("/tmp/databags/foo/item2.json", + ImmutableSet.of("{\"bar\":\"foo\"}")); + + assertEquals(statementList.size(), 4); + assertEquals(statementList.get(0), exec("{md} /tmp/databags")); + assertEquals(statementList.get(1), exec("{md} /tmp/databags/" + databag.getName())); + assertEquals(statementList.get(2).render(OsFamily.UNIX), expectedItem1.render(OsFamily.UNIX)); + assertEquals(statementList.get(3).render(OsFamily.UNIX), expectedItem2.render(OsFamily.UNIX)); + } + + public void testCreateDatabagsIfNecessaryWithMultipleDatabagsAndCustomPath() { + ImmutableList.Builder statements = ImmutableList.builder(); + DataBag databagFoo = DataBag.builder().name("foo").item("itemFoo", "{\"foo\":\"bar\"}").build(); + DataBag databagBar = DataBag.builder().name("bar").item("itemBar", "{\"bar\":\"foo\"}").build(); + ChefSolo solo = ChefSolo.builder().dataBagPath("/tmp/databags").defineDataBag(databagFoo) + .defineDataBag(databagBar).build(); + + solo.createDatabagsIfNecessary(statements); + ImmutableList statementList = statements.build(); + + Statement expectedFoo = createOrOverwriteFile("/tmp/databags/foo/itemFoo.json", + ImmutableSet.of("{\"foo\":\"bar\"}")); + Statement expectedBar = createOrOverwriteFile("/tmp/databags/bar/itemBar.json", + ImmutableSet.of("{\"bar\":\"foo\"}")); + + assertEquals(statementList.size(), 5); + assertEquals(statementList.get(0), exec("{md} /tmp/databags")); + assertEquals(statementList.get(1), exec("{md} /tmp/databags/" + databagFoo.getName())); + assertEquals(statementList.get(2).render(OsFamily.UNIX), expectedFoo.render(OsFamily.UNIX)); + assertEquals(statementList.get(3), exec("{md} /tmp/databags/" + databagBar.getName())); + assertEquals(statementList.get(4).render(OsFamily.UNIX), expectedBar.render(OsFamily.UNIX)); + } + + public void testCreateDatabagsIfNecessaryWithMultipleDatabagsAndMultipleItemsAndCustomPath() { + ImmutableList.Builder statements = ImmutableList.builder(); + DataBag databagFoo = DataBag.builder().name("foo").item("itemFoo", "{\"foo\":\"bar\"}").build(); + DataBag databagBar = DataBag.builder().name("bar").item("itemBar", "{\"bar\":\"foo\"}") + .item("extra", "{\"extra\":[]}").build(); + ChefSolo solo = ChefSolo.builder().dataBagPath("/tmp/databags").defineDataBag(databagFoo) + .defineDataBag(databagBar).build(); + + solo.createDatabagsIfNecessary(statements); + ImmutableList statementList = statements.build(); + + Statement expectedFoo = createOrOverwriteFile("/tmp/databags/foo/itemFoo.json", + ImmutableSet.of("{\"foo\":\"bar\"}")); + Statement expectedBar = createOrOverwriteFile("/tmp/databags/bar/itemBar.json", + ImmutableSet.of("{\"bar\":\"foo\"}")); + Statement expectedExtra = createOrOverwriteFile("/tmp/databags/bar/extra.json", ImmutableSet.of("{\"extra\":[]}")); + + assertEquals(statementList.size(), 6); + assertEquals(statementList.get(0), exec("{md} /tmp/databags")); + assertEquals(statementList.get(1), exec("{md} /tmp/databags/" + databagFoo.getName())); + assertEquals(statementList.get(2).render(OsFamily.UNIX), expectedFoo.render(OsFamily.UNIX)); + assertEquals(statementList.get(3), exec("{md} /tmp/databags/" + databagBar.getName())); + assertEquals(statementList.get(4).render(OsFamily.UNIX), expectedBar.render(OsFamily.UNIX)); + assertEquals(statementList.get(5).render(OsFamily.UNIX), expectedExtra.render(OsFamily.UNIX)); } @Test(expectedExceptions = UnsupportedOperationException.class, expectedExceptionsMessageRegExp = "windows not yet implemented") @@ -45,136 +322,73 @@ public class ChefSoloTest { ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").build().render(OsFamily.WINDOWS); } + public void testChefWoloWithDefaultConfiguration() throws IOException { + String script = ChefSolo.builder().build().render(OsFamily.UNIX); + assertEquals(script, installChefGems() + createConfigFile() + createNodeFile() + + "chef-solo -c /var/chef/solo.rb -j /var/chef/node.json -N `hostname`\n"); + } + + public void testChefWoloWithNodeName() throws IOException { + String script = ChefSolo.builder().nodeName("foo").build().render(OsFamily.UNIX); + assertEquals(script, installChefGems() + createConfigFile() + createNodeFile() + + "chef-solo -c /var/chef/solo.rb -j /var/chef/node.json -N foo\n"); + } + + public void testChefSoloWithGroup() throws IOException { + String script = ChefSolo.builder().group("foo").build().render(OsFamily.UNIX); + assertEquals(script, installChefGems() + createConfigFile() + createNodeFile() + + "chef-solo -c /var/chef/solo.rb -j /var/chef/node.json -N `hostname` -g foo\n"); + } + + public void testChefSoloWithInterval() throws IOException { + String script = ChefSolo.builder().interval(15).build().render(OsFamily.UNIX); + assertEquals(script, installChefGems() + createConfigFile() + createNodeFile() + + "chef-solo -c /var/chef/solo.rb -j /var/chef/node.json -N `hostname` -i 15\n"); + } + + public void testChefSoloWithLogLevel() throws IOException { + String script = ChefSolo.builder().logLevel("debug").build().render(OsFamily.UNIX); + assertEquals(script, installChefGems() + createConfigFile() + createNodeFile() + + "chef-solo -c /var/chef/solo.rb -j /var/chef/node.json -N `hostname` -l debug\n"); + } + + public void testChefSoloWithLogFile() throws IOException { + String script = ChefSolo.builder().logFile("/var/log/solo.log").build().render(OsFamily.UNIX); + assertEquals(script, installChefGems() + createConfigFile() + createNodeFile() + + "chef-solo -c /var/chef/solo.rb -j /var/chef/node.json -N `hostname` -L /var/log/solo.log\n"); + } + public void testChefSoloWithCookbooksLocation() throws IOException { String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").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\nchef-solo -N `hostname` -r /tmp/cookbooks\n"); + assertEquals(script, installChefGems() + createConfigFile() + createNodeFile() + + "chef-solo -c /var/chef/solo.rb -j /var/chef/node.json -N `hostname` -r /tmp/cookbooks\n"); } - public void testChefSoloWithCookbooksLocationAndSingleRecipe() throws IOException { - String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").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" - + "chef-solo -N `hostname` -r /tmp/cookbooks -o recipe[apache2]\n"); + public void testChefSoloWithSplay() throws IOException { + String script = ChefSolo.builder().splay(15).build().render(OsFamily.UNIX); + assertEquals(script, installChefGems() + createConfigFile() + createNodeFile() + + "chef-solo -c /var/chef/solo.rb -j /var/chef/node.json -N `hostname` -s 15\n"); } - public void testChefSoloWithCookbooksLocationAndMultipleRecipes() throws IOException { - String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").installRecipe("apache2") - .installRecipe("mysql").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" - + "chef-solo -N `hostname` -r /tmp/cookbooks -o recipe[apache2],recipe[mysql]\n"); + public void testChefSoloWithUser() throws IOException { + String script = ChefSolo.builder().user("foo").build().render(OsFamily.UNIX); + assertEquals(script, installChefGems() + createConfigFile() + createNodeFile() + + "chef-solo -c /var/chef/solo.rb -j /var/chef/node.json -N `hostname` -u foo\n"); } - public void testChefSoloWithCookbooksLocationAndMultipleRecipesInList() throws IOException { - String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks") - .installRecipes(ImmutableList. of("apache2", "mysql")).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" - + "chef-solo -N `hostname` -r /tmp/cookbooks -o recipe[apache2],recipe[mysql]\n"); + private static String installChefGems() throws IOException { + return Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)), + Charsets.UTF_8) + "installChefGems || return 1\n"; } - public void testChefSoloWithCookbooksLocationAndAttributes() throws IOException { - String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").jsonAttributes("{\"foo\":\"bar\"}") - .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\n" - + "mkdir -p /var/chef\n" - + "cat > /var/chef/node.json <<-'END_OF_JCLOUDS_FILE'\n\t{\"foo\":\"bar\"}\nEND_OF_JCLOUDS_FILE\n" - + "chef-solo -N `hostname` -r /tmp/cookbooks -j /var/chef/node.json -o recipe[apache2]\n"); + private static String createConfigFile() { + return "mkdir -p /var/chef\nmkdir -p /var/chef/cookbooks\ncat > /var/chef/solo.rb <<-'END_OF_JCLOUDS_FILE'\n" + + "\tfile_cache_path \"/var/chef\"\n\tcookbook_path [\"/var/chef/cookbooks\"]\n" + + "\trole_path \"/var/chef/roles\"\n\tdata_bag_path \"/var/chef/data_bags\"\nEND_OF_JCLOUDS_FILE\n"; } - public void testChefSoloWithRoleDefinitionAndRecipe() throws IOException { - Role role = Role.builder().name("foo").installRecipe("apache2").build(); - String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").defineRole(role) - .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\nmkdir -p /var/chef/roles\n" - + "cat > /var/chef/roles/foo.json <<-'END_OF_JCLOUDS_FILE'\n" - + "\t{\"name\": \"foo\",\"description\":\"\",\"default_attributes\":{},\"override_attributes\":{}," - + "\"json_class\":\"Chef::Role\",\"chef_type\":\"role\",\"run_list\":[\"recipe[apache2]\"]}" - + "\nEND_OF_JCLOUDS_FILE\nchef-solo -N `hostname` -r /tmp/cookbooks -o recipe[apache2]\n"); + private static String createNodeFile() { + return "cat > /var/chef/node.json <<-'END_OF_JCLOUDS_FILE'\n\t{\"run_list\":[]}\nEND_OF_JCLOUDS_FILE\n"; } - public void testChefSoloWithRoleDefinitionAndRole() throws IOException { - Role role = Role.builder().name("foo").installRecipe("apache2").build(); - String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").defineRole(role).installRole("foo") - .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\nmkdir -p /var/chef/roles\n" - + "cat > /var/chef/roles/foo.json <<-'END_OF_JCLOUDS_FILE'\n" - + "\t{\"name\": \"foo\",\"description\":\"\",\"default_attributes\":{},\"override_attributes\":{}," - + "\"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]\n"); - } - - public void testChefSoloWithRoleDefinitionAndRoleAndRecipe() throws IOException { - Role role = Role.builder().name("foo").installRecipe("apache2").build(); - String script = ChefSolo.builder().cookbooksArchiveLocation("/tmp/cookbooks").defineRole(role).installRole("foo") - .installRecipe("git").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\nmkdir -p /var/chef/roles\n" - + "cat > /var/chef/roles/foo.json <<-'END_OF_JCLOUDS_FILE'\n" - + "\t{\"name\": \"foo\",\"description\":\"\",\"default_attributes\":{},\"override_attributes\":{}," - + "\"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"); - } }