From 125d93aa582530f5e790dd20c1c28c4fc07083f4 Mon Sep 17 00:00:00 2001 From: William Chu Date: Tue, 2 Dec 2014 14:52:09 -0800 Subject: [PATCH] JCLOUDS-788 Added support for specifying a custom node name when generating the chef bootstrap node script --- .../java/org/jclouds/chef/ChefService.java | 12 ++++ .../chef/functions/GroupToBootScript.java | 16 ++++-- .../chef/internal/BaseChefService.java | 8 ++- .../chef/functions/GroupToBootScriptTest.java | 36 +++++++++--- .../src/test/resources/bootstrap-node-env.sh | 56 +++++++++++++++++++ 5 files changed, 113 insertions(+), 15 deletions(-) create mode 100755 apis/chef/src/test/resources/bootstrap-node-env.sh diff --git a/apis/chef/src/main/java/org/jclouds/chef/ChefService.java b/apis/chef/src/main/java/org/jclouds/chef/ChefService.java index 040107a170..6f136fa9a3 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/ChefService.java +++ b/apis/chef/src/main/java/org/jclouds/chef/ChefService.java @@ -25,6 +25,7 @@ import org.jclouds.chef.domain.Environment; import org.jclouds.chef.domain.Node; import org.jclouds.chef.internal.BaseChefService; import org.jclouds.domain.JsonBall; +import org.jclouds.javax.annotation.Nullable; import org.jclouds.rest.annotations.SinceApiVersion; import org.jclouds.scriptbuilder.domain.Statement; @@ -78,6 +79,17 @@ public interface ChefService { */ Statement createBootstrapScriptForGroup(String group); + /** + * Creates all steps necessary to bootstrap the node. + * + * @param group corresponds to a configured + * {@link ChefProperties#CHEF_BOOTSTRAP_DATABAG} data bag where + * run_list and other information are stored. + * @param nodeName The name of the node to create. + * @return The script used to bootstrap the node. + */ + Statement createBootstrapScriptForGroup(String group, @Nullable String nodeName); + /** * Configures how the nodes of a certain group will be bootstrapped * diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java b/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java index 516d9f98c8..60103533d4 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java +++ b/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java @@ -37,13 +37,13 @@ import org.jclouds.chef.config.InstallChef; import org.jclouds.chef.config.Validator; import org.jclouds.crypto.Pems; import org.jclouds.domain.JsonBall; +import org.jclouds.javax.annotation.Nullable; import org.jclouds.json.Json; import org.jclouds.location.Provider; import org.jclouds.scriptbuilder.ExitInsteadOfReturn; 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; @@ -58,7 +58,7 @@ import com.google.inject.TypeLiteral; * Generates a bootstrap script relevant for a particular group */ @Singleton -public class GroupToBootScript implements Function { +public class GroupToBootScript { private static final Pattern newLinePattern = Pattern.compile("(\\r\\n)|(\\n)"); @VisibleForTesting @@ -84,8 +84,7 @@ public class GroupToBootScript implements Function { this.validatorCredential = checkNotNull(validatorCredential, validatorCredential); } - @Override - public Statement apply(String group) { + public Statement apply(String group, @Nullable String nodeName) { checkNotNull(group, "group"); String validatorClientName = validatorName.get(); PrivateKey validatorKey = validatorCredential.get(); @@ -103,9 +102,14 @@ public class GroupToBootScript implements Function { String chefConfigDir = "{root}etc{fs}chef"; Statement createChefConfigDir = exec("{md} " + chefConfigDir); + String createNodeName; + if (nodeName != null) { + createNodeName = String.format("node_name \"%s\"", nodeName); + } else { + createNodeName = String.format("node_name \"%s-\" + o[:ipaddress]", group); + } Statement createClientRb = appendFile(chefConfigDir + "{fs}client.rb", ImmutableList.of("require 'rubygems'", - "require 'ohai'", "o = Ohai::System.new", "o.all_plugins", - String.format("node_name \"%s-\" + o[:ipaddress]", group), "log_level :info", "log_location STDOUT", + "require 'ohai'", "o = Ohai::System.new", "o.all_plugins", createNodeName, "log_level :info", "log_location STDOUT", String.format("validation_client_name \"%s\"", validatorClientName), String.format("chef_server_url \"%s\"", endpoint.get()))); diff --git a/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java b/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java index 1b6fcfe34c..3e20c12a2c 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java +++ b/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java @@ -61,6 +61,7 @@ import org.jclouds.io.ByteStreams2; import org.jclouds.io.Payloads; import org.jclouds.io.payloads.RSADecryptingPayload; import org.jclouds.io.payloads.RSAEncryptingPayload; +import org.jclouds.javax.annotation.Nullable; import org.jclouds.json.Json; import org.jclouds.logging.Logger; import org.jclouds.scriptbuilder.domain.Statement; @@ -170,9 +171,14 @@ public class BaseChefService implements ChefService { return json.toJson(configMap); } + @Override + public Statement createBootstrapScriptForGroup(String group, @Nullable String nodeName) { + return groupToBootScript.apply(group, nodeName); + } + @Override public Statement createBootstrapScriptForGroup(String group) { - return groupToBootScript.apply(group); + return groupToBootScript.apply(group, null); } @Override diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java index 87920250f7..89b6ec0039 100644 --- a/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java +++ b/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java @@ -100,7 +100,7 @@ public class GroupToBootScriptTest { GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, CacheLoader.from(Functions.forMap(ImmutableMap. of())), installChefGems, Optional. absent(), validatorCredential); - fn.apply("foo"); + fn.apply("foo", null); } @Test(expectedExceptions = IllegalStateException.class) @@ -108,7 +108,7 @@ public class GroupToBootScriptTest { GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, CacheLoader.from(Functions.forMap(ImmutableMap. of())), installChefGems, validatorName, Optional. absent()); - fn.apply("foo"); + fn.apply("foo", null); } @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Key 'foo' not present in map") @@ -117,7 +117,7 @@ public class GroupToBootScriptTest { GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, CacheLoader.from(Functions.forMap(ImmutableMap. of())), installChefGems, validatorName, validatorCredential); - fn.apply("foo"); + fn.apply("foo", null); } @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "null value in entry: foo=null") @@ -126,7 +126,7 @@ public class GroupToBootScriptTest { GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, CacheLoader.from(Functions.forMap(ImmutableMap. of("foo", (DatabagItem) null))), installChefGems, validatorName, validatorCredential); - fn.apply("foo"); + fn.apply("foo", null); } public void testOneRecipe() throws IOException { @@ -141,7 +141,7 @@ public class GroupToBootScriptTest { replay(validatorKey); assertEquals( - fn.apply("foo").render(OsFamily.UNIX), + fn.apply("foo", null).render(OsFamily.UNIX), exitInsteadOfReturn( OsFamily.UNIX, Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)), @@ -168,7 +168,7 @@ public class GroupToBootScriptTest { replay(validatorKey); assertEquals( - fn.apply("foo").render(OsFamily.UNIX), + fn.apply("foo", null).render(OsFamily.UNIX), exitInsteadOfReturn( OsFamily.UNIX, Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)), @@ -194,7 +194,7 @@ public class GroupToBootScriptTest { replay(validatorKey); assertEquals( - fn.apply("foo").render(OsFamily.UNIX), + fn.apply("foo", null).render(OsFamily.UNIX), "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 " + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" + Resources.toString(Resources.getResource("bootstrap.sh"), Charsets.UTF_8)); @@ -215,7 +215,7 @@ public class GroupToBootScriptTest { replay(validatorKey); assertEquals( - fn.apply("foo").render(OsFamily.UNIX), + fn.apply("foo", null).render(OsFamily.UNIX), "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 " + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" + Resources.toString(Resources.getResource("bootstrap-env.sh"), Charsets.UTF_8)); @@ -227,4 +227,24 @@ public class GroupToBootScriptTest { return input.replaceAll(ShellToken.RETURN.to(family), ShellToken.EXIT.to(family)); } + public void testCustomNodeName() throws IOException { + Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); + GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, + CacheLoader.from(Functions.forMap(ImmutableMap. of("foo", new JsonBall( + "{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\"," + + "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), installChefOmnibus, + validatorName, validatorCredential); + + PrivateKey validatorKey = validatorCredential.get(); + expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes()); + replay(validatorKey); + + assertEquals( + fn.apply("foo", "bar").render(OsFamily.UNIX), + "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 " + + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" + + Resources.toString(Resources.getResource("bootstrap-node-env.sh"), Charsets.UTF_8)); + + verify(validatorKey); + } } diff --git a/apis/chef/src/test/resources/bootstrap-node-env.sh b/apis/chef/src/test/resources/bootstrap-node-env.sh new file mode 100755 index 0000000000..84713d6564 --- /dev/null +++ b/apis/chef/src/test/resources/bootstrap-node-env.sh @@ -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 "bar" + 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"