diff --git a/compute/src/main/java/org/jclouds/compute/callables/RunScriptOnNodeAsInitScriptUsingSsh.java b/compute/src/main/java/org/jclouds/compute/callables/RunScriptOnNodeAsInitScriptUsingSsh.java index e4cefc4b50..34459c8eb7 100644 --- a/compute/src/main/java/org/jclouds/compute/callables/RunScriptOnNodeAsInitScriptUsingSsh.java +++ b/compute/src/main/java/org/jclouds/compute/callables/RunScriptOnNodeAsInitScriptUsingSsh.java @@ -28,14 +28,17 @@ import javax.inject.Named; import org.jclouds.compute.domain.ExecResponse; import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadataBuilder; import org.jclouds.compute.options.RunScriptOptions; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; import org.jclouds.scriptbuilder.InitBuilder; +import org.jclouds.scriptbuilder.domain.AdminAccessVisitor; import org.jclouds.scriptbuilder.domain.AppendFile; import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statements; +import org.jclouds.scriptbuilder.statements.login.AdminAccess; import org.jclouds.ssh.SshClient; import com.google.common.annotations.VisibleForTesting; @@ -52,15 +55,16 @@ import com.google.inject.assistedinject.AssistedInject; */ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode { public static final String PROPERTY_PUSH_INIT_SCRIPT_VIA_SFTP = "jclouds.compute.push-init-script-via-sftp"; + public static final String PROPERTY_INIT_SCRIPT_PATTERN = "jclouds.compute.init-script-pattern"; @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; protected final Function sshFactory; - protected final NodeMetadata node; - protected final Statement init; - protected final String name; + protected NodeMetadata node; + protected final InitBuilder init; protected final boolean runAsRoot; + protected final String initFile; protected SshClient ssh; @@ -72,6 +76,15 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode { @Named(PROPERTY_PUSH_INIT_SCRIPT_VIA_SFTP) private boolean pushInitViaSftp = true; + /** + * determines the naming convention of init scripts. + * + * ex. {@code /tmp/init-%s} + */ + @Inject(optional = true) + @Named(PROPERTY_INIT_SCRIPT_PATTERN) + private String initScriptPattern = "/tmp/init-%s"; + @AssistedInject public RunScriptOnNodeAsInitScriptUsingSsh(Function sshFactory, @Assisted NodeMetadata node, @Assisted Statement script, @Assisted RunScriptOptions options) { @@ -84,9 +97,9 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode { else name = "jclouds-script-" + System.currentTimeMillis(); } - this.name = checkNotNull(name, "name"); this.init = checkNotNull(script, "script") instanceof InitBuilder ? InitBuilder.class.cast(script) : createInitScript(name, script); + this.initFile = String.format(initScriptPattern, name); this.runAsRoot = options.shouldRunAsRoot(); } @@ -113,22 +126,48 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode { return this; } + public void refreshSshIfNewAdminCredentialsConfigured(AdminAccess input) { + if (input.getAdminCredentials() != null && input.shouldGrantSudoToAdminUser()) { + ssh.disconnect(); + logger.debug(">> reconnecting as %s@%s", input.getAdminCredentials().identity, ssh.getHostAddress()); + ssh = sshFactory.apply(node = NodeMetadataBuilder.fromNodeMetadata(node).adminPassword(null).credentials( + input.getAdminCredentials()).build()); + ssh.connect(); + setupLinkToInitFile(); + } + } + /** * ssh client is initialized through this call. */ protected ExecResponse doCall() { if (pushInitViaSftp) { - ssh.put(name, init.render(OsFamily.UNIX)); + ssh.put(initFile, init.render(OsFamily.UNIX)); } else { - ssh.exec("rm " + name); - ssh.exec(Statements.appendFile(name, Splitter.on('\n').split(init.render(OsFamily.UNIX)), - AppendFile.MARKER + "_" + name).render(OsFamily.UNIX)); + ssh.exec("rm " + initFile); + ssh.exec(Statements.appendFile(initFile, Splitter.on('\n').split(init.render(OsFamily.UNIX)), + AppendFile.MARKER + "_" + init.getInstanceName()).render(OsFamily.UNIX)); } - ssh.exec("chmod 755 " + name); + + ssh.exec("chmod 755 " + initFile); + setupLinkToInitFile(); + runAction("init"); + init.getInitStatement().accept(new AdminAccessVisitor() { + + @Override + public void visit(AdminAccess input) { + refreshSshIfNewAdminCredentialsConfigured(input); + } + + }); return runAction("start"); } + protected void setupLinkToInitFile() { + ssh.exec(String.format("ln -fs %s %s", initFile, init.getInstanceName())); + } + protected ExecResponse runAction(String action) { ExecResponse returnVal; String command = (runAsRoot) ? execScriptAsRoot(action) : execScriptAsDefaultUser(action); @@ -152,17 +191,17 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode { public String execScriptAsRoot(String action) { String command; if (node.getCredentials().identity.equals("root")) { - command = "./" + name + " " + action; + command = "./" + init.getInstanceName() + " " + action; } else if (node.getAdminPassword() != null) { - command = String.format("echo '%s'|sudo -S ./%s %s", node.getAdminPassword(), name, action); + command = String.format("echo '%s'|sudo -S ./%s %s", node.getAdminPassword(), init.getInstanceName(), action); } else { - command = "sudo ./" + name + " " + action; + command = "sudo ./" + init.getInstanceName() + " " + action; } return command; } protected String execScriptAsDefaultUser(String action) { - return "./" + name + " " + action; + return "./" + initFile + " " + action; } public NodeMetadata getNode() { @@ -171,7 +210,8 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode { @Override public String toString() { - return Objects.toStringHelper(this).add("node", node).add("name", name).add("runAsRoot", runAsRoot).toString(); + return Objects.toStringHelper(this).add("node", node).add("name", init.getInstanceName()).add("runAsRoot", + runAsRoot).toString(); } @Override diff --git a/compute/src/main/java/org/jclouds/compute/callables/RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete.java b/compute/src/main/java/org/jclouds/compute/callables/RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete.java index 3f68bcb094..db0eb0b5ac 100644 --- a/compute/src/main/java/org/jclouds/compute/callables/RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete.java +++ b/compute/src/main/java/org/jclouds/compute/callables/RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete.java @@ -53,13 +53,14 @@ public class RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete extends Ru @Override public ExecResponse doCall() { ExecResponse returnVal = super.doCall(); - boolean complete = runScriptNotRunning.apply(new CommandUsingClient("./" + name + " status", ssh)); + boolean complete = runScriptNotRunning.apply(new CommandUsingClient("./" + init.getInstanceName() + " status", + ssh)); logger.debug("<< complete(%s)", complete); if (logger.isDebugEnabled() || returnVal.getExitCode() != 0) { - logger.debug("<< stdout from %s as %s@%s\n%s", name, ssh.getUsername(), ssh.getHostAddress(), ssh.exec( - "./" + name + " tail").getOutput()); - logger.debug("<< stderr from %s as %s@%s\n%s", name, ssh.getUsername(), ssh.getHostAddress(), ssh.exec( - "./" + name + " tailerr").getOutput()); + logger.debug("<< stdout from %s as %s@%s\n%s", init.getInstanceName(), ssh.getUsername(), + ssh.getHostAddress(), ssh.exec("./" + init.getInstanceName() + " tail").getOutput()); + logger.debug("<< stderr from %s as %s@%s\n%s", init.getInstanceName(), ssh.getUsername(), + ssh.getHostAddress(), ssh.exec("./" + init.getInstanceName() + " tailerr").getOutput()); } return returnVal; } diff --git a/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java b/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java index e4f641cfa4..5e078a5e20 100644 --- a/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java +++ b/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java @@ -224,7 +224,7 @@ public class BaseComputeService implements ComputeService { Multimap customizationResponses = LinkedHashMultimap.create(); if (template.getOptions().getRunScript() != null) - template.getOptions().runScript(initAdminAccess.apply(template.getOptions().getRunScript())); + initAdminAccess.visit(template.getOptions().getRunScript()); Map> responses = runNodesAndAddToSetStrategy.execute(group, count, template, goodNodes, badNodes, customizationResponses); @@ -550,7 +550,7 @@ public class BaseComputeService implements ComputeService { Map> responses = newLinkedHashMap(); Map exceptions = ImmutableMap. of(); - runScript = initAdminAccess.apply(runScript); + initAdminAccess.visit(runScript); Iterable scriptRunners = transformNodesIntoInitializedScriptRunners( nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter), runScript, options, badNodes); diff --git a/compute/src/test/java/org/jclouds/compute/BaseComputeServiceLiveTest.java b/compute/src/test/java/org/jclouds/compute/BaseComputeServiceLiveTest.java index c65d49e8bf..057888db70 100644 --- a/compute/src/test/java/org/jclouds/compute/BaseComputeServiceLiveTest.java +++ b/compute/src/test/java/org/jclouds/compute/BaseComputeServiceLiveTest.java @@ -502,7 +502,7 @@ public abstract class BaseComputeServiceLiveTest { // note this is a dependency on the template resolution template.getOptions().runScript( - RunScriptData.createScriptInstallAndStartJBoss(keyPair.get("public"), template.getImage() + RunScriptData.createScriptInstallAndStartJBoss(template.getImage() .getOperatingSystem())); try { NodeMetadata node = getOnlyElement(client.createNodesInGroup(group, 1, template)); diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/InitBuilder.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/InitBuilder.java index 3c84b3026c..1986feb5b9 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/InitBuilder.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/InitBuilder.java @@ -30,6 +30,7 @@ import static org.jclouds.scriptbuilder.domain.Statements.switchArg; import java.util.Map; +import org.jclouds.scriptbuilder.domain.CreateRunScript; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.StatementList; @@ -47,38 +48,40 @@ public class InitBuilder extends ScriptBuilder { private final String instanceName; private final String instanceHome; private final String logDir; + private final StatementList initStatement; + private final CreateRunScript createRunScript; public InitBuilder(String instanceName, String instanceHome, String logDir, Map variables, Iterable statements) { - this(instanceName, instanceHome, logDir, variables, ImmutableSet.of(), statements); + this(instanceName, instanceHome, logDir, variables, ImmutableSet. of(), statements); } public InitBuilder(String instanceName, String instanceHome, String logDir, Map variables, Iterable initStatements, Iterable statements) { + Map defaultVariables = ImmutableMap.of("instanceName", instanceName, "instanceHome", + instanceHome, "logDir", logDir); + this.initStatement = new StatementList(initStatements); + this.createRunScript = createRunScript(instanceName,// TODO: convert + // so + // that + // createRunScript + // can take from a + // variable + Iterables.concat(variables.keySet(), defaultVariables.keySet()), "{varl}INSTANCE_HOME{varr}", statements); this.instanceName = checkNotNull(instanceName, "instanceName"); this.instanceHome = checkNotNull(instanceHome, "instanceHome"); this.logDir = checkNotNull(logDir, "logDir"); - Map defaultVariables = ImmutableMap.of("instanceName", instanceName, "instanceHome", - instanceHome, "logDir", logDir); + addEnvironmentVariableScope("default", defaultVariables) .addEnvironmentVariableScope(instanceName, variables) .addStatement( switchArg( 1, - new ImmutableMap.Builder() + new ImmutableMap.Builder() .put( "init", - newStatementList(call("default"), call(instanceName), - new StatementList(initStatements), createRunScript( - instanceName,// TODO: convert - // so - // that - // createRunScript - // can take from a - // variable - Iterables.concat(variables.keySet(), - defaultVariables.keySet()), - "{varl}INSTANCE_HOME{varr}", statements))) + newStatementList(call("default"), call(instanceName), initStatement, + createRunScript)) .put( "status", newStatementList(call("default"), @@ -165,4 +168,12 @@ public class InitBuilder extends ScriptBuilder { public String toString() { return "[instanceName=" + instanceName + ", instanceHome=" + instanceHome + ", logDir=" + logDir + "]"; } + + public StatementList getInitStatement() { + return initStatement; + } + + public CreateRunScript getCreateRunScript() { + return createRunScript; + } } \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/ScriptBuilder.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/ScriptBuilder.java index 2d46badfbc..999adbfca3 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/ScriptBuilder.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/ScriptBuilder.java @@ -24,9 +24,11 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.jclouds.scriptbuilder.domain.AcceptsStatementVisitor; import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.ShellToken; import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.scriptbuilder.domain.StatementVisitor; import org.jclouds.scriptbuilder.util.Utils; import com.google.common.annotations.VisibleForTesting; @@ -41,7 +43,7 @@ import com.google.common.collect.Maps; * * @author Adrian Cole */ -public class ScriptBuilder implements Statement { +public class ScriptBuilder implements Statement, AcceptsStatementVisitor { @VisibleForTesting List statements = Lists.newArrayList(); @@ -147,4 +149,11 @@ public class ScriptBuilder implements Statement { public Iterable functionDependencies(OsFamily family) { return ImmutableSet. of(); } + + @Override + public void accept(StatementVisitor visitor) { + for (Statement statement : statements) { + visitor.visit(statement); + } + } } \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AcceptsStatementVisitor.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AcceptsStatementVisitor.java new file mode 100644 index 0000000000..80da8ff02e --- /dev/null +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AcceptsStatementVisitor.java @@ -0,0 +1,28 @@ +/** + * + * Copyright (C) 2011 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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; + +/** + * + * @author Adrian Cole + */ +public interface AcceptsStatementVisitor { + + void accept(StatementVisitor visitor); +} \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AdminAccessVisitor.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AdminAccessVisitor.java new file mode 100644 index 0000000000..bf06c65a2a --- /dev/null +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AdminAccessVisitor.java @@ -0,0 +1,40 @@ +/** + * + * Copyright (C) 2011 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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; + +import org.jclouds.scriptbuilder.statements.login.AdminAccess; + +/** + * + * @author Adrian Cole + */ +public abstract class AdminAccessVisitor implements StatementVisitor { + public abstract void visit(AdminAccess input); + + @Override + public void visit(Statement input) { + if (input == null) + return; + if (input instanceof AcceptsStatementVisitor) { + AcceptsStatementVisitor.class.cast(input).accept(this); + } else if (input instanceof AdminAccess) { + visit(AdminAccess.class.cast(input)); + } + } +} \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/CreateRunScript.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/CreateRunScript.java index f27268c03a..571e814515 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/CreateRunScript.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/CreateRunScript.java @@ -41,18 +41,17 @@ import com.google.common.collect.Maps; * * @author Adrian Cole */ -public class CreateRunScript implements Statement { +public class CreateRunScript extends StatementList { public final static String MARKER = "END_OF_SCRIPT"; final String instanceName; final Iterable exports; final String pwd; - final Iterable statements; public CreateRunScript(String instanceName, Iterable exports, String pwd, Iterable statements) { + super(statements); this.instanceName = checkNotNull(instanceName, "instanceName"); this.exports = checkNotNull(exports, "exports"); this.pwd = checkNotNull(pwd, "pwd").replaceAll("[/\\\\]", "{fs}"); - this.statements = checkNotNull(statements, "statements"); } public static class AddTitleToFile implements Statement { diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/StatementList.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/StatementList.java index 4cdd1d99e8..9a895a3fd0 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/StatementList.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/StatementList.java @@ -30,7 +30,7 @@ import com.google.common.collect.ImmutableList.Builder; * * @author Adrian Cole */ -public class StatementList implements Statement { +public class StatementList implements Statement, AcceptsStatementVisitor { public final List statements; @@ -84,7 +84,10 @@ public class StatementList implements Statement { return true; } - public List getStatements() { - return statements; + @Override + public void accept(StatementVisitor visitor) { + for (Statement statement : statements) { + visitor.visit(statement); + } } } \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/StatementVisitor.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/StatementVisitor.java new file mode 100644 index 0000000000..784a479516 --- /dev/null +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/StatementVisitor.java @@ -0,0 +1,27 @@ +/** + * + * Copyright (C) 2011 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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; + +/** + * + * @author Adrian Cole + */ +public interface StatementVisitor { + void visit(Statement in); +} \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statements.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statements.java index bcac021f98..4951f871e0 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statements.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statements.java @@ -71,7 +71,7 @@ public class Statements { return new AppendFile(path, lines, marker); } - public static Statement createRunScript(String instanceName, Iterable exports, String pwd, + public static CreateRunScript createRunScript(String instanceName, Iterable exports, String pwd, Iterable statements) {// TODO: convert so // that // createRunScript diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/SwitchArg.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/SwitchArg.java index 665ba63106..1582c828e8 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/SwitchArg.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/SwitchArg.java @@ -38,7 +38,7 @@ import com.google.common.collect.Lists; * * @author Adrian Cole */ -public class SwitchArg implements Statement { +public class SwitchArg implements Statement, AcceptsStatementVisitor { private static final String INDENT = " "; @@ -161,4 +161,11 @@ public class SwitchArg implements Statement { return false; return true; } + + @Override + public void accept(StatementVisitor visitor) { + for (Statement statement : valueToActions.values()) { + visitor.visit(statement); + } + } } \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/functions/CredentialsFromAdminAccess.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/functions/CredentialsFromAdminAccess.java index 123f4e0ef8..a66b928422 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/functions/CredentialsFromAdminAccess.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/functions/CredentialsFromAdminAccess.java @@ -18,18 +18,17 @@ */ package org.jclouds.scriptbuilder.functions; -import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import org.jclouds.domain.Credentials; +import org.jclouds.scriptbuilder.domain.AcceptsStatementVisitor; import org.jclouds.scriptbuilder.domain.Statement; -import org.jclouds.scriptbuilder.domain.StatementList; +import org.jclouds.scriptbuilder.domain.StatementVisitor; import org.jclouds.scriptbuilder.statements.login.AdminAccess; import com.google.common.base.Function; -import com.google.common.base.Predicates; -import com.google.common.collect.Iterables; /** * @@ -41,13 +40,21 @@ public enum CredentialsFromAdminAccess implements Function credsHolder = new AtomicReference(); + AcceptsStatementVisitor.class.cast(input).accept(new StatementVisitor() { + + @Override + public void visit(Statement in) { + if (credsHolder.get() == null) { + Credentials creds = apply(in); + if (creds != null) + credsHolder.set(creds); + } + } + + }); + return credsHolder.get(); } else if (input instanceof AdminAccess) { return AdminAccess.class.cast(input).getAdminCredentials(); } else { diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/functions/InitAdminAccess.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/functions/InitAdminAccess.java index f6f8d5c773..c159bd43ce 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/functions/InitAdminAccess.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/functions/InitAdminAccess.java @@ -20,21 +20,15 @@ package org.jclouds.scriptbuilder.functions; import javax.inject.Inject; -import org.jclouds.scriptbuilder.domain.Statement; -import org.jclouds.scriptbuilder.domain.StatementList; +import org.jclouds.scriptbuilder.domain.AdminAccessVisitor; import org.jclouds.scriptbuilder.statements.login.AdminAccess; import org.jclouds.scriptbuilder.statements.login.AdminAccess.Configuration; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; - /** - * Statement used in a shell script * * @author Adrian Cole */ -public class InitAdminAccess implements Function { +public class InitAdminAccess extends AdminAccessVisitor { private final AdminAccess.Configuration adminAccessConfiguration; @Inject @@ -43,16 +37,7 @@ public class InitAdminAccess implements Function { } @Override - public Statement apply(Statement input) { - if (input instanceof StatementList) { - Builder statements = ImmutableList. builder(); - for (Statement statement : StatementList.class.cast(input).getStatements()) - statements.add(apply(statement)); - return new StatementList(statements.build()); - } else if (input instanceof AdminAccess) { - return AdminAccess.class.cast(input).apply(adminAccessConfiguration); - } else { - return input; - } + public void visit(AdminAccess input) { + input.init(adminAccessConfiguration); } } \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/AdminAccess.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/AdminAccess.java index e08d0fd3c1..749eb94e0c 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/AdminAccess.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/AdminAccess.java @@ -32,7 +32,6 @@ import org.jclouds.domain.Credentials; import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.StatementList; -import org.jclouds.scriptbuilder.statements.login.AdminAccess.Configuration; import org.jclouds.scriptbuilder.statements.ssh.SshStatements; import com.google.common.base.Function; @@ -44,27 +43,24 @@ import com.google.common.io.Files; import com.google.inject.ImplementedBy; /** - * Controls the administrative access to a node. By default, it will perform the - * following: + * Controls the administrative access to a node. By default, it will perform the following: * *
    - *
  • setup a new admin user which folks should use as opposed to the built-in - * vcloud account
  • + *
  • setup a new admin user which folks should use as opposed to the built-in vcloud account
  • *
      *
    • associate a random password to account
    • *
        - *
      • securely ( use sha 512 on client side and literally rewrite the shadow - * entry, rather than pass password to OS in a script )
      • + *
      • securely ( use sha 512 on client side and literally rewrite the shadow entry, rather than + * pass password to OS in a script )
      • *
      *
    • associate the users' ssh public key with the account for login
    • - * associate it with the os group wheel
  • create os group wheel
  • - *
  • add sudoers for nopassword access to root by group wheel
  • reset - * root password securely
  • lockdown sshd_config for no root login, nor - * passwords allowed
+ * associate it with the os group wheel
  • create os group wheel
  • add sudoers + * for nopassword access to root by group wheel
  • reset root password securely
  • + * lockdown sshd_config for no root login, nor passwords allowed
  • * * @author Adrian Cole */ -public class AdminAccess implements Statement, Function { +public class AdminAccess implements Statement { public static AdminAccess.Builder builder() { return new Builder(); } @@ -177,6 +173,10 @@ public class AdminAccess implements Statement, Function cryptFunction; - private final Credentials adminCredentials; + protected static class Config { + private final String adminUsername; + private final String adminPublicKey; + private final String adminPrivateKey; + private final String adminPassword; + private final String loginPassword; + private final boolean lockSsh; + private final boolean grantSudoToAdminUser; + private final boolean authorizeAdminPublicKey; + private final boolean installAdminPrivateKey; + private final boolean resetLoginPassword; + private final Function cryptFunction; + private final Credentials adminCredentials; - protected AdminAccess(@Nullable String adminUsername, @Nullable String adminPublicKey, - @Nullable String adminPrivateKey, @Nullable String adminPassword, @Nullable String loginPassword, - boolean lockSsh, boolean grantSudoToAdminUser, boolean authorizeAdminPublicKey, - boolean installAdminPrivateKey, boolean resetLoginPassword, Function cryptFunction) { - this.adminUsername = adminUsername; - this.adminPublicKey = adminPublicKey; - this.adminPrivateKey = adminPrivateKey; - this.adminPassword = adminPassword; - this.loginPassword = loginPassword; - this.lockSsh = lockSsh; - this.grantSudoToAdminUser = grantSudoToAdminUser; - this.authorizeAdminPublicKey = authorizeAdminPublicKey; - this.installAdminPrivateKey = installAdminPrivateKey; - this.resetLoginPassword = resetLoginPassword; - this.cryptFunction = cryptFunction; - if (adminUsername != null && authorizeAdminPublicKey && adminPrivateKey != null) - this.adminCredentials = new Credentials(adminUsername, adminPrivateKey); - else - this.adminCredentials = null; + protected Config(@Nullable String adminUsername, @Nullable String adminPublicKey, + @Nullable String adminPrivateKey, @Nullable String adminPassword, @Nullable String loginPassword, + boolean lockSsh, boolean grantSudoToAdminUser, boolean authorizeAdminPublicKey, + boolean installAdminPrivateKey, boolean resetLoginPassword, Function cryptFunction) { + this.adminUsername = adminUsername; + this.adminPublicKey = adminPublicKey; + this.adminPrivateKey = adminPrivateKey; + this.adminPassword = adminPassword; + this.loginPassword = loginPassword; + this.lockSsh = lockSsh; + this.grantSudoToAdminUser = grantSudoToAdminUser; + this.authorizeAdminPublicKey = authorizeAdminPublicKey; + this.installAdminPrivateKey = installAdminPrivateKey; + this.resetLoginPassword = resetLoginPassword; + this.cryptFunction = cryptFunction; + if (adminUsername != null && authorizeAdminPublicKey && adminPrivateKey != null) + this.adminCredentials = new Credentials(adminUsername, adminPrivateKey); + else + this.adminCredentials = null; + } + + public String getAdminUsername() { + return adminUsername; + } + + public String getAdminPublicKey() { + return adminPublicKey; + } + + public String getAdminPrivateKey() { + return adminPrivateKey; + } + + public String getAdminPassword() { + return adminPassword; + } + + public String getLoginPassword() { + return loginPassword; + } + + public boolean shouldLockSsh() { + return lockSsh; + } + + public boolean shouldGrantSudoToAdminUser() { + return grantSudoToAdminUser; + } + + public boolean shouldAuthorizeAdminPublicKey() { + return authorizeAdminPublicKey; + } + + public boolean shouldInstallAdminPrivateKey() { + return installAdminPrivateKey; + } + + public boolean shouldResetLoginPassword() { + return resetLoginPassword; + } + + public Function getCryptFunction() { + return cryptFunction; + } + + public Credentials getAdminCredentials() { + return adminCredentials; + } + } + + private Config config; + + protected AdminAccess(Config in) { + this.config = checkNotNull(in, "in"); } /** @@ -234,7 +290,16 @@ public class AdminAccess implements Statement, Function adminSshKeys = (adminPublicKey != null && adminPrivateKey != null) ? ImmutableMap.of( - "public", adminPublicKey, "private", adminPrivateKey) : configuration.defaultAdminSshKeys().get(); + builder.adminUsername(config.getAdminUsername() != null ? config.getAdminUsername() : configuration + .defaultAdminUsername().get()); + builder.adminPassword(config.getAdminPassword() != null ? config.getAdminPassword() : configuration + .passwordGenerator().get()); + Map adminSshKeys = (config.getAdminPublicKey() != null && config.getAdminPrivateKey() != null) ? ImmutableMap + .of("public", config.getAdminPublicKey(), "private", config.getAdminPrivateKey()) + : configuration.defaultAdminSshKeys().get(); builder.adminPublicKey(adminSshKeys.get("public")); builder.adminPrivateKey(adminSshKeys.get("private")); - builder.loginPassword(this.loginPassword != null ? this.loginPassword : configuration.passwordGenerator().get()); - builder.grantSudoToAdminUser(this.grantSudoToAdminUser); - builder.authorizeAdminPublicKey(this.authorizeAdminPublicKey); - builder.installAdminPrivateKey(this.installAdminPrivateKey); - builder.lockSsh(this.lockSsh); - builder.resetLoginPassword(this.resetLoginPassword); - return builder.build(); + builder.loginPassword(config.getLoginPassword() != null ? config.getLoginPassword() : configuration + .passwordGenerator().get()); + builder.grantSudoToAdminUser(config.shouldGrantSudoToAdminUser()); + builder.authorizeAdminPublicKey(config.shouldAuthorizeAdminPublicKey()); + builder.installAdminPrivateKey(config.shouldInstallAdminPrivateKey()); + builder.lockSsh(config.shouldLockSsh()); + builder.resetLoginPassword(config.shouldResetLoginPassword()); + this.config = builder.buildConfig(); + return this; } @Override @@ -266,29 +334,30 @@ public class AdminAccess implements Statement, Function statements = ImmutableList. builder(); UserAdd.Builder userBuilder = UserAdd.builder(); - userBuilder.login(adminUsername); - if (authorizeAdminPublicKey) - userBuilder.authorizeRSAPublicKey(adminPublicKey); - userBuilder.password(adminPassword); - if (installAdminPrivateKey) - userBuilder.installRSAPrivateKey(adminPrivateKey); - if (grantSudoToAdminUser) { + userBuilder.login(config.getAdminUsername()); + if (config.shouldAuthorizeAdminPublicKey()) + userBuilder.authorizeRSAPublicKey(config.getAdminPublicKey()); + userBuilder.password(config.getAdminPassword()); + if (config.shouldInstallAdminPrivateKey()) + userBuilder.installRSAPrivateKey(config.getAdminPrivateKey()); + if (config.shouldGrantSudoToAdminUser()) { statements.add(SudoStatements.createWheel()); userBuilder.group("wheel"); } - statements.add(userBuilder.build().cryptFunction(cryptFunction)); - if (lockSsh) + statements.add(userBuilder.build().cryptFunction(config.getCryptFunction())); + if (config.shouldLockSsh()) statements.add(SshStatements.lockSshd()); - if (resetLoginPassword) { - statements.add(ShadowStatements.resetLoginUserPasswordTo(loginPassword).cryptFunction(cryptFunction)); + if (config.shouldResetLoginPassword()) { + statements.add(ShadowStatements.resetLoginUserPasswordTo(config.getLoginPassword()).cryptFunction( + config.getCryptFunction())); } return new StatementList(statements.build()).render(family); } diff --git a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/functions/CredentialsFromAdminAccessTest.java b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/functions/CredentialsFromAdminAccessTest.java index ff055b7d22..45a3360932 100644 --- a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/functions/CredentialsFromAdminAccessTest.java +++ b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/functions/CredentialsFromAdminAccessTest.java @@ -22,14 +22,20 @@ import static org.easymock.EasyMock.expect; import static org.easymock.classextension.EasyMock.createMock; import static org.easymock.classextension.EasyMock.replay; import static org.easymock.classextension.EasyMock.verify; +import static org.jclouds.scriptbuilder.domain.Statements.appendFile; +import static org.jclouds.scriptbuilder.domain.Statements.call; import static org.testng.Assert.assertEquals; import org.jclouds.domain.Credentials; +import org.jclouds.scriptbuilder.InitBuilder; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statements; import org.jclouds.scriptbuilder.statements.login.AdminAccess; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + /** * @author Adrian Cole */ @@ -81,4 +87,25 @@ public class CredentialsFromAdminAccessTest { verify(statement); verify(creds); } + + public void testWhenAdminAccessInsideInitBuilder() { + AdminAccess.Configuration configuration = createMock(AdminAccess.Configuration.class); + AdminAccess statement = createMock(AdminAccess.class); + Credentials creds = createMock(Credentials.class); + + expect(statement.getAdminCredentials()).andReturn(creds); + + replay(configuration); + replay(statement); + replay(creds); + + InitBuilder testInitBuilder = new InitBuilder("mkebsboot", "/mnt/tmp", "/mnt/tmp", ImmutableMap.of("tmpDir", + "/mnt/tmp"), ImmutableList. of(statement)); + + assertEquals(CredentialsFromAdminAccess.INSTANCE.apply(testInitBuilder), creds); + + verify(configuration); + verify(statement); + verify(creds); + } } diff --git a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/functions/InitAdminAccessTest.java b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/functions/InitAdminAccessTest.java index 3af9ba94d2..ab3f3ae466 100644 --- a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/functions/InitAdminAccessTest.java +++ b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/functions/InitAdminAccessTest.java @@ -24,11 +24,16 @@ import static org.easymock.classextension.EasyMock.replay; import static org.easymock.classextension.EasyMock.verify; import static org.testng.Assert.assertEquals; +import org.jclouds.domain.Credentials; +import org.jclouds.scriptbuilder.InitBuilder; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statements; import org.jclouds.scriptbuilder.statements.login.AdminAccess; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + /** * @author Adrian Cole */ @@ -36,13 +41,15 @@ import org.testng.annotations.Test; public class InitAdminAccessTest { public void testWhenNotAdminAccess() { + AdminAccess.Configuration configuration = createMock(AdminAccess.Configuration.class); - InitAdminAccess initAdminAccess = new InitAdminAccess(createMock(AdminAccess.Configuration.class)); - Statement statement = Statements.exec("echo hello"); - assertEquals(initAdminAccess.apply(statement), statement); + InitAdminAccess initAdminAccess = new InitAdminAccess(configuration); + replay(configuration); - Statement statementList = Statements.newStatementList(statement); - assertEquals(initAdminAccess.apply(statementList), statementList); + initAdminAccess.visit(Statements.exec("echo hello")); + + initAdminAccess.visit(Statements.newStatementList(Statements.exec("echo hello"))); + verify(configuration); } @@ -51,14 +58,14 @@ public class InitAdminAccessTest { AdminAccess statement = createMock(AdminAccess.class); AdminAccess newStatement = createMock(AdminAccess.class); - expect(statement.apply(configuration)).andReturn(newStatement); + expect(statement.init(configuration)).andReturn(newStatement); replay(configuration); replay(statement); replay(newStatement); InitAdminAccess initAdminAccess = new InitAdminAccess(configuration); - assertEquals(initAdminAccess.apply(statement), newStatement); + initAdminAccess.visit(statement); verify(configuration); verify(statement); @@ -70,15 +77,38 @@ public class InitAdminAccessTest { AdminAccess statement = createMock(AdminAccess.class); AdminAccess newStatement = createMock(AdminAccess.class); - expect(statement.apply(configuration)).andReturn(newStatement); + expect(statement.init(configuration)).andReturn(newStatement); replay(configuration); replay(statement); replay(newStatement); InitAdminAccess initAdminAccess = new InitAdminAccess(configuration); - assertEquals(initAdminAccess.apply(Statements.newStatementList(statement)), - Statements.newStatementList(newStatement)); + initAdminAccess.visit(Statements.newStatementList(statement)); + + verify(configuration); + verify(statement); + verify(newStatement); + } + + + public void testWhenAdminAccessInsideInitBuilder() { + AdminAccess.Configuration configuration = createMock(AdminAccess.Configuration.class); + AdminAccess statement = createMock(AdminAccess.class); + AdminAccess newStatement = createMock(AdminAccess.class); + + expect(statement.init(configuration)).andReturn(newStatement); + + replay(configuration); + replay(statement); + replay(newStatement); + + InitBuilder testInitBuilder = new InitBuilder("mkebsboot", "/mnt/tmp", "/mnt/tmp", ImmutableMap.of("tmpDir", + "/mnt/tmp"), ImmutableList. of(statement)); + + InitAdminAccess initAdminAccess = new InitAdminAccess(configuration); + + initAdminAccess.visit(testInitBuilder); verify(configuration); verify(statement); diff --git a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/AdminAccessTest.java b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/AdminAccessTest.java index 078ac63065..b8466ffe17 100644 --- a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/AdminAccessTest.java +++ b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/AdminAccessTest.java @@ -38,7 +38,7 @@ public class AdminAccessTest { public void testStandardUNIX() throws IOException { TestConfiguration.INSTANCE.reset(); try { - assertEquals(AdminAccess.standard().apply(TestConfiguration.INSTANCE).render(OsFamily.UNIX), + assertEquals(AdminAccess.standard().init(TestConfiguration.INSTANCE).render(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(Resources.getResource("test_adminaccess_standard.sh"), Charsets.UTF_8))); } finally { @@ -51,7 +51,7 @@ public class AdminAccessTest { try { assertEquals( AdminAccess.builder().adminPassword("bar").adminPrivateKey("fooPrivateKey") - .adminPublicKey("fooPublicKey").adminUsername("foo").build().apply(TestConfiguration.INSTANCE) + .adminPublicKey("fooPublicKey").adminUsername("foo").build().init(TestConfiguration.INSTANCE) .render(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier( Resources.getResource("test_adminaccess_params.sh"), Charsets.UTF_8))); } finally { @@ -65,7 +65,7 @@ public class AdminAccessTest { assertEquals( AdminAccess.builder().grantSudoToAdminUser(false).authorizeAdminPublicKey(true) .installAdminPrivateKey(true).lockSsh(false).resetLoginPassword(false).build() - .apply(TestConfiguration.INSTANCE).render(OsFamily.UNIX), CharStreams.toString(Resources + .init(TestConfiguration.INSTANCE).render(OsFamily.UNIX), CharStreams.toString(Resources .newReaderSupplier(Resources.getResource("test_adminaccess_plainuser.sh"), Charsets.UTF_8))); } finally { TestConfiguration.INSTANCE.reset(); @@ -74,6 +74,6 @@ public class AdminAccessTest { @Test(expectedExceptions = UnsupportedOperationException.class) public void testCreateWheelWindowsNotSupported() { - AdminAccess.standard().apply(TestConfiguration.INSTANCE).render(OsFamily.WINDOWS); + AdminAccess.standard().init(TestConfiguration.INSTANCE).render(OsFamily.WINDOWS); } }