Issue 614:Add Visitor pattern support for scriptbuilder Statements

This commit is contained in:
Adrian Cole 2011-07-01 17:40:20 -07:00
parent 0a28bbdb8a
commit b56f08b9a5
19 changed files with 450 additions and 167 deletions

View File

@ -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<NodeMetadata, SshClient> 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<NodeMetadata, SshClient> 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

View File

@ -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;
}

View File

@ -224,7 +224,7 @@ public class BaseComputeService implements ComputeService {
Multimap<NodeMetadata, CustomizationResponse> customizationResponses = LinkedHashMultimap.create();
if (template.getOptions().getRunScript() != null)
template.getOptions().runScript(initAdminAccess.apply(template.getOptions().getRunScript()));
initAdminAccess.visit(template.getOptions().getRunScript());
Map<?, Future<Void>> responses = runNodesAndAddToSetStrategy.execute(group, count, template, goodNodes, badNodes,
customizationResponses);
@ -550,7 +550,7 @@ public class BaseComputeService implements ComputeService {
Map<NodeMetadata, Future<ExecResponse>> responses = newLinkedHashMap();
Map<?, Exception> exceptions = ImmutableMap.<Object, Exception> of();
runScript = initAdminAccess.apply(runScript);
initAdminAccess.visit(runScript);
Iterable<? extends RunScriptOnNode> scriptRunners = transformNodesIntoInitializedScriptRunners(
nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter), runScript, options, badNodes);

View File

@ -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));

View File

@ -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<String, String> variables,
Iterable<Statement> statements) {
this(instanceName, instanceHome, logDir, variables, ImmutableSet.<Statement>of(), statements);
this(instanceName, instanceHome, logDir, variables, ImmutableSet.<Statement> of(), statements);
}
public InitBuilder(String instanceName, String instanceHome, String logDir, Map<String, String> variables,
Iterable<Statement> initStatements, Iterable<Statement> statements) {
Map<String, String> 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<String, String> defaultVariables = ImmutableMap.of("instanceName", instanceName, "instanceHome",
instanceHome, "logDir", logDir);
addEnvironmentVariableScope("default", defaultVariables)
.addEnvironmentVariableScope(instanceName, variables)
.addStatement(
switchArg(
1,
new ImmutableMap.Builder<String,Statement>()
new ImmutableMap.Builder<String, Statement>()
.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;
}
}

View File

@ -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<Statement> statements = Lists.newArrayList();
@ -147,4 +149,11 @@ public class ScriptBuilder implements Statement {
public Iterable<String> functionDependencies(OsFamily family) {
return ImmutableSet.<String> of();
}
@Override
public void accept(StatementVisitor visitor) {
for (Statement statement : statements) {
visitor.visit(statement);
}
}
}

View File

@ -0,0 +1,28 @@
/**
*
* Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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);
}

View File

@ -0,0 +1,40 @@
/**
*
* Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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));
}
}
}

View File

@ -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<String> exports;
final String pwd;
final Iterable<Statement> statements;
public CreateRunScript(String instanceName, Iterable<String> exports, String pwd, Iterable<Statement> 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 {

View File

@ -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<Statement> statements;
@ -84,7 +84,10 @@ public class StatementList implements Statement {
return true;
}
public List<Statement> getStatements() {
return statements;
@Override
public void accept(StatementVisitor visitor) {
for (Statement statement : statements) {
visitor.visit(statement);
}
}
}

View File

@ -0,0 +1,27 @@
/**
*
* Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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);
}

View File

@ -71,7 +71,7 @@ public class Statements {
return new AppendFile(path, lines, marker);
}
public static Statement createRunScript(String instanceName, Iterable<String> exports, String pwd,
public static CreateRunScript createRunScript(String instanceName, Iterable<String> exports, String pwd,
Iterable<Statement> statements) {// TODO: convert so
// that
// createRunScript

View File

@ -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);
}
}
}

View File

@ -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<Statement, Credential
public Credentials apply(@Nullable Statement input) {
if (input == null)
return null;
if (input instanceof StatementList) {
try {
return apply(Iterables.find(StatementList.class.cast(input).getStatements(),
Predicates.instanceOf(AdminAccess.class)));
} catch (NoSuchElementException e) {
return null;
}
if (input instanceof AcceptsStatementVisitor) {
final AtomicReference<Credentials> credsHolder = new AtomicReference<Credentials>();
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 {

View File

@ -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<Statement, Statement> {
public class InitAdminAccess extends AdminAccessVisitor {
private final AdminAccess.Configuration adminAccessConfiguration;
@Inject
@ -43,16 +37,7 @@ public class InitAdminAccess implements Function<Statement, Statement> {
}
@Override
public Statement apply(Statement input) {
if (input instanceof StatementList) {
Builder<Statement> statements = ImmutableList.<Statement> 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);
}
}

View File

@ -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:
*
* <ul>
* <li>setup a new admin user which folks should use as opposed to the built-in
* vcloud account</li>
* <li>setup a new admin user which folks should use as opposed to the built-in vcloud account</li>
* <ul>
* <li>associate a random password to account</li>
* <ul>
* <li>securely ( use sha 512 on client side and literally rewrite the shadow
* entry, rather than pass password to OS in a script )</li>
* <li>securely ( use sha 512 on client side and literally rewrite the shadow entry, rather than
* pass password to OS in a script )</li>
* </ul>
* <li>associate the users' ssh public key with the account for login</li> <li>
* associate it with the os group wheel</li> </ul> <li>create os group wheel</li>
* <li>add sudoers for nopassword access to root by group wheel</li> <li>reset
* root password securely</li> <li>lockdown sshd_config for no root login, nor
* passwords allowed</li> </ul>
* associate it with the os group wheel</li> </ul> <li>create os group wheel</li> <li>add sudoers
* for nopassword access to root by group wheel</li> <li>reset root password securely</li> <li>
* lockdown sshd_config for no root login, nor passwords allowed</li> </ul>
*
* @author Adrian Cole
*/
public class AdminAccess implements Statement, Function<Configuration, AdminAccess> {
public class AdminAccess implements Statement {
public static AdminAccess.Builder builder() {
return new Builder();
}
@ -177,6 +173,10 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
}
public AdminAccess build() {
return new AdminAccess(buildConfig());
}
protected Config buildConfig() {
try {
String adminPublicKey = this.adminPublicKey;
if (adminPublicKey == null && adminPublicKeyFile != null)
@ -184,9 +184,9 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
String adminPrivateKey = this.adminPrivateKey;
if (adminPrivateKey == null && adminPrivateKeyFile != null)
adminPrivateKey = Files.toString(adminPrivateKeyFile, UTF_8);
return new AdminAccess(adminUsername, adminPublicKey, adminPrivateKey, adminPassword, loginPassword,
lockSsh, grantSudoToAdminUser, authorizeAdminPublicKey, installAdminPrivateKey, resetLoginPassword,
cryptFunction);
return new Config(adminUsername, adminPublicKey, adminPrivateKey, adminPassword, loginPassword, lockSsh,
grantSudoToAdminUser, authorizeAdminPublicKey, installAdminPrivateKey, resetLoginPassword,
cryptFunction);
} catch (IOException e) {
Throwables.propagate(e);
return null;
@ -194,38 +194,94 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
}
}
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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<Configuration, AdminAcce
*/
@Nullable
public Credentials getAdminCredentials() {
return adminCredentials;
return config.getAdminCredentials();
}
@Nullable
public String getAdminPassword() {
return config.getAdminPassword();
}
public boolean shouldGrantSudoToAdminUser() {
return config.shouldGrantSudoToAdminUser();
}
@Override
@ -242,23 +307,26 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
return ImmutableList.of();
}
@Override
public AdminAccess apply(Configuration configuration) {
public AdminAccess init(Configuration configuration) {
Builder builder = AdminAccess.builder(configuration.cryptFunction());
builder.adminUsername(this.adminUsername != null ? this.adminUsername : configuration.defaultAdminUsername()
.get());
builder.adminPassword(this.adminPassword != null ? this.adminPassword : configuration.passwordGenerator().get());
Map<String, String> 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<String, String> 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<Configuration, AdminAcce
checkNotNull(family, "family");
if (family == OsFamily.WINDOWS)
throw new UnsupportedOperationException("windows not yet implemented");
checkNotNull(adminUsername, "adminUsername");
checkNotNull(adminPassword, "adminPassword");
checkNotNull(adminPublicKey, "adminPublicKey");
checkNotNull(adminPrivateKey, "adminPrivateKey");
checkNotNull(loginPassword, "loginPassword");
checkNotNull(config.getAdminUsername(), "adminUsername");
checkNotNull(config.getAdminPassword(), "adminPassword");
checkNotNull(config.getAdminPublicKey(), "adminPublicKey");
checkNotNull(config.getAdminPrivateKey(), "adminPrivateKey");
checkNotNull(config.getLoginPassword(), "loginPassword");
ImmutableList.Builder<Statement> statements = ImmutableList.<Statement> 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);
}

View File

@ -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.<Statement> of(statement));
assertEquals(CredentialsFromAdminAccess.INSTANCE.apply(testInitBuilder), creds);
verify(configuration);
verify(statement);
verify(creds);
}
}

View File

@ -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.<Statement> of(statement));
InitAdminAccess initAdminAccess = new InitAdminAccess(configuration);
initAdminAccess.visit(testInitBuilder);
verify(configuration);
verify(statement);

View File

@ -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);
}
}