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.ExecResponse;
import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.compute.options.RunScriptOptions; import org.jclouds.compute.options.RunScriptOptions;
import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.scriptbuilder.InitBuilder; import org.jclouds.scriptbuilder.InitBuilder;
import org.jclouds.scriptbuilder.domain.AdminAccessVisitor;
import org.jclouds.scriptbuilder.domain.AppendFile; import org.jclouds.scriptbuilder.domain.AppendFile;
import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.Statements; import org.jclouds.scriptbuilder.domain.Statements;
import org.jclouds.scriptbuilder.statements.login.AdminAccess;
import org.jclouds.ssh.SshClient; import org.jclouds.ssh.SshClient;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -52,15 +55,16 @@ import com.google.inject.assistedinject.AssistedInject;
*/ */
public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode { 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_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 @Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER) @Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
protected final Function<NodeMetadata, SshClient> sshFactory; protected final Function<NodeMetadata, SshClient> sshFactory;
protected final NodeMetadata node; protected NodeMetadata node;
protected final Statement init; protected final InitBuilder init;
protected final String name;
protected final boolean runAsRoot; protected final boolean runAsRoot;
protected final String initFile;
protected SshClient ssh; protected SshClient ssh;
@ -72,6 +76,15 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode {
@Named(PROPERTY_PUSH_INIT_SCRIPT_VIA_SFTP) @Named(PROPERTY_PUSH_INIT_SCRIPT_VIA_SFTP)
private boolean pushInitViaSftp = true; 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 @AssistedInject
public RunScriptOnNodeAsInitScriptUsingSsh(Function<NodeMetadata, SshClient> sshFactory, public RunScriptOnNodeAsInitScriptUsingSsh(Function<NodeMetadata, SshClient> sshFactory,
@Assisted NodeMetadata node, @Assisted Statement script, @Assisted RunScriptOptions options) { @Assisted NodeMetadata node, @Assisted Statement script, @Assisted RunScriptOptions options) {
@ -84,9 +97,9 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode {
else else
name = "jclouds-script-" + System.currentTimeMillis(); name = "jclouds-script-" + System.currentTimeMillis();
} }
this.name = checkNotNull(name, "name");
this.init = checkNotNull(script, "script") instanceof InitBuilder ? InitBuilder.class.cast(script) this.init = checkNotNull(script, "script") instanceof InitBuilder ? InitBuilder.class.cast(script)
: createInitScript(name, script); : createInitScript(name, script);
this.initFile = String.format(initScriptPattern, name);
this.runAsRoot = options.shouldRunAsRoot(); this.runAsRoot = options.shouldRunAsRoot();
} }
@ -113,22 +126,48 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode {
return this; 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. * ssh client is initialized through this call.
*/ */
protected ExecResponse doCall() { protected ExecResponse doCall() {
if (pushInitViaSftp) { if (pushInitViaSftp) {
ssh.put(name, init.render(OsFamily.UNIX)); ssh.put(initFile, init.render(OsFamily.UNIX));
} else { } else {
ssh.exec("rm " + name); ssh.exec("rm " + initFile);
ssh.exec(Statements.appendFile(name, Splitter.on('\n').split(init.render(OsFamily.UNIX)), ssh.exec(Statements.appendFile(initFile, Splitter.on('\n').split(init.render(OsFamily.UNIX)),
AppendFile.MARKER + "_" + name).render(OsFamily.UNIX)); AppendFile.MARKER + "_" + init.getInstanceName()).render(OsFamily.UNIX));
} }
ssh.exec("chmod 755 " + name);
ssh.exec("chmod 755 " + initFile);
setupLinkToInitFile();
runAction("init"); runAction("init");
init.getInitStatement().accept(new AdminAccessVisitor() {
@Override
public void visit(AdminAccess input) {
refreshSshIfNewAdminCredentialsConfigured(input);
}
});
return runAction("start"); return runAction("start");
} }
protected void setupLinkToInitFile() {
ssh.exec(String.format("ln -fs %s %s", initFile, init.getInstanceName()));
}
protected ExecResponse runAction(String action) { protected ExecResponse runAction(String action) {
ExecResponse returnVal; ExecResponse returnVal;
String command = (runAsRoot) ? execScriptAsRoot(action) : execScriptAsDefaultUser(action); String command = (runAsRoot) ? execScriptAsRoot(action) : execScriptAsDefaultUser(action);
@ -152,17 +191,17 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode {
public String execScriptAsRoot(String action) { public String execScriptAsRoot(String action) {
String command; String command;
if (node.getCredentials().identity.equals("root")) { if (node.getCredentials().identity.equals("root")) {
command = "./" + name + " " + action; command = "./" + init.getInstanceName() + " " + action;
} else if (node.getAdminPassword() != null) { } 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 { } else {
command = "sudo ./" + name + " " + action; command = "sudo ./" + init.getInstanceName() + " " + action;
} }
return command; return command;
} }
protected String execScriptAsDefaultUser(String action) { protected String execScriptAsDefaultUser(String action) {
return "./" + name + " " + action; return "./" + initFile + " " + action;
} }
public NodeMetadata getNode() { public NodeMetadata getNode() {
@ -171,7 +210,8 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode {
@Override @Override
public String toString() { 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 @Override

View File

@ -53,13 +53,14 @@ public class RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete extends Ru
@Override @Override
public ExecResponse doCall() { public ExecResponse doCall() {
ExecResponse returnVal = super.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); logger.debug("<< complete(%s)", complete);
if (logger.isDebugEnabled() || returnVal.getExitCode() != 0) { if (logger.isDebugEnabled() || returnVal.getExitCode() != 0) {
logger.debug("<< stdout from %s as %s@%s\n%s", name, ssh.getUsername(), ssh.getHostAddress(), ssh.exec( logger.debug("<< stdout from %s as %s@%s\n%s", init.getInstanceName(), ssh.getUsername(),
"./" + name + " tail").getOutput()); ssh.getHostAddress(), ssh.exec("./" + init.getInstanceName() + " tail").getOutput());
logger.debug("<< stderr from %s as %s@%s\n%s", name, ssh.getUsername(), ssh.getHostAddress(), ssh.exec( logger.debug("<< stderr from %s as %s@%s\n%s", init.getInstanceName(), ssh.getUsername(),
"./" + name + " tailerr").getOutput()); ssh.getHostAddress(), ssh.exec("./" + init.getInstanceName() + " tailerr").getOutput());
} }
return returnVal; return returnVal;
} }

View File

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

View File

@ -502,7 +502,7 @@ public abstract class BaseComputeServiceLiveTest {
// note this is a dependency on the template resolution // note this is a dependency on the template resolution
template.getOptions().runScript( template.getOptions().runScript(
RunScriptData.createScriptInstallAndStartJBoss(keyPair.get("public"), template.getImage() RunScriptData.createScriptInstallAndStartJBoss(template.getImage()
.getOperatingSystem())); .getOperatingSystem()));
try { try {
NodeMetadata node = getOnlyElement(client.createNodesInGroup(group, 1, template)); 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 java.util.Map;
import org.jclouds.scriptbuilder.domain.CreateRunScript;
import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.StatementList; import org.jclouds.scriptbuilder.domain.StatementList;
@ -47,6 +48,8 @@ public class InitBuilder extends ScriptBuilder {
private final String instanceName; private final String instanceName;
private final String instanceHome; private final String instanceHome;
private final String logDir; private final String logDir;
private final StatementList initStatement;
private final CreateRunScript createRunScript;
public InitBuilder(String instanceName, String instanceHome, String logDir, Map<String, String> variables, public InitBuilder(String instanceName, String instanceHome, String logDir, Map<String, String> variables,
Iterable<Statement> statements) { Iterable<Statement> statements) {
@ -55,11 +58,20 @@ public class InitBuilder extends ScriptBuilder {
public InitBuilder(String instanceName, String instanceHome, String logDir, Map<String, String> variables, public InitBuilder(String instanceName, String instanceHome, String logDir, Map<String, String> variables,
Iterable<Statement> initStatements, Iterable<Statement> statements) { 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.instanceName = checkNotNull(instanceName, "instanceName");
this.instanceHome = checkNotNull(instanceHome, "instanceHome"); this.instanceHome = checkNotNull(instanceHome, "instanceHome");
this.logDir = checkNotNull(logDir, "logDir"); this.logDir = checkNotNull(logDir, "logDir");
Map<String, String> defaultVariables = ImmutableMap.of("instanceName", instanceName, "instanceHome",
instanceHome, "logDir", logDir);
addEnvironmentVariableScope("default", defaultVariables) addEnvironmentVariableScope("default", defaultVariables)
.addEnvironmentVariableScope(instanceName, variables) .addEnvironmentVariableScope(instanceName, variables)
.addStatement( .addStatement(
@ -68,17 +80,8 @@ public class InitBuilder extends ScriptBuilder {
new ImmutableMap.Builder<String, Statement>() new ImmutableMap.Builder<String, Statement>()
.put( .put(
"init", "init",
newStatementList(call("default"), call(instanceName), newStatementList(call("default"), call(instanceName), initStatement,
new StatementList(initStatements), createRunScript( createRunScript))
instanceName,// TODO: convert
// so
// that
// createRunScript
// can take from a
// variable
Iterables.concat(variables.keySet(),
defaultVariables.keySet()),
"{varl}INSTANCE_HOME{varr}", statements)))
.put( .put(
"status", "status",
newStatementList(call("default"), newStatementList(call("default"),
@ -165,4 +168,12 @@ public class InitBuilder extends ScriptBuilder {
public String toString() { public String toString() {
return "[instanceName=" + instanceName + ", instanceHome=" + instanceHome + ", logDir=" + logDir + "]"; 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;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.jclouds.scriptbuilder.domain.AcceptsStatementVisitor;
import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.ShellToken; import org.jclouds.scriptbuilder.domain.ShellToken;
import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.StatementVisitor;
import org.jclouds.scriptbuilder.util.Utils; import org.jclouds.scriptbuilder.util.Utils;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -41,7 +43,7 @@ import com.google.common.collect.Maps;
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class ScriptBuilder implements Statement { public class ScriptBuilder implements Statement, AcceptsStatementVisitor {
@VisibleForTesting @VisibleForTesting
List<Statement> statements = Lists.newArrayList(); List<Statement> statements = Lists.newArrayList();
@ -147,4 +149,11 @@ public class ScriptBuilder implements Statement {
public Iterable<String> functionDependencies(OsFamily family) { public Iterable<String> functionDependencies(OsFamily family) {
return ImmutableSet.<String> of(); 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 * @author Adrian Cole
*/ */
public class CreateRunScript implements Statement { public class CreateRunScript extends StatementList {
public final static String MARKER = "END_OF_SCRIPT"; public final static String MARKER = "END_OF_SCRIPT";
final String instanceName; final String instanceName;
final Iterable<String> exports; final Iterable<String> exports;
final String pwd; final String pwd;
final Iterable<Statement> statements;
public CreateRunScript(String instanceName, Iterable<String> exports, String pwd, Iterable<Statement> statements) { public CreateRunScript(String instanceName, Iterable<String> exports, String pwd, Iterable<Statement> statements) {
super(statements);
this.instanceName = checkNotNull(instanceName, "instanceName"); this.instanceName = checkNotNull(instanceName, "instanceName");
this.exports = checkNotNull(exports, "exports"); this.exports = checkNotNull(exports, "exports");
this.pwd = checkNotNull(pwd, "pwd").replaceAll("[/\\\\]", "{fs}"); this.pwd = checkNotNull(pwd, "pwd").replaceAll("[/\\\\]", "{fs}");
this.statements = checkNotNull(statements, "statements");
} }
public static class AddTitleToFile implements Statement { public static class AddTitleToFile implements Statement {

View File

@ -30,7 +30,7 @@ import com.google.common.collect.ImmutableList.Builder;
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class StatementList implements Statement { public class StatementList implements Statement, AcceptsStatementVisitor {
public final List<Statement> statements; public final List<Statement> statements;
@ -84,7 +84,10 @@ public class StatementList implements Statement {
return true; return true;
} }
public List<Statement> getStatements() { @Override
return statements; 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); 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 Iterable<Statement> statements) {// TODO: convert so
// that // that
// createRunScript // createRunScript

View File

@ -38,7 +38,7 @@ import com.google.common.collect.Lists;
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class SwitchArg implements Statement { public class SwitchArg implements Statement, AcceptsStatementVisitor {
private static final String INDENT = " "; private static final String INDENT = " ";
@ -161,4 +161,11 @@ public class SwitchArg implements Statement {
return false; return false;
return true; 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; package org.jclouds.scriptbuilder.functions;
import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.jclouds.domain.Credentials; import org.jclouds.domain.Credentials;
import org.jclouds.scriptbuilder.domain.AcceptsStatementVisitor;
import org.jclouds.scriptbuilder.domain.Statement; 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 org.jclouds.scriptbuilder.statements.login.AdminAccess;
import com.google.common.base.Function; 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) { public Credentials apply(@Nullable Statement input) {
if (input == null) if (input == null)
return null; return null;
if (input instanceof StatementList) { if (input instanceof AcceptsStatementVisitor) {
try { final AtomicReference<Credentials> credsHolder = new AtomicReference<Credentials>();
return apply(Iterables.find(StatementList.class.cast(input).getStatements(), AcceptsStatementVisitor.class.cast(input).accept(new StatementVisitor() {
Predicates.instanceOf(AdminAccess.class)));
} catch (NoSuchElementException e) { @Override
return null; 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) { } else if (input instanceof AdminAccess) {
return AdminAccess.class.cast(input).getAdminCredentials(); return AdminAccess.class.cast(input).getAdminCredentials();
} else { } else {

View File

@ -20,21 +20,15 @@ package org.jclouds.scriptbuilder.functions;
import javax.inject.Inject; import javax.inject.Inject;
import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.AdminAccessVisitor;
import org.jclouds.scriptbuilder.domain.StatementList;
import org.jclouds.scriptbuilder.statements.login.AdminAccess; import org.jclouds.scriptbuilder.statements.login.AdminAccess;
import org.jclouds.scriptbuilder.statements.login.AdminAccess.Configuration; 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 * @author Adrian Cole
*/ */
public class InitAdminAccess implements Function<Statement, Statement> { public class InitAdminAccess extends AdminAccessVisitor {
private final AdminAccess.Configuration adminAccessConfiguration; private final AdminAccess.Configuration adminAccessConfiguration;
@Inject @Inject
@ -43,16 +37,7 @@ public class InitAdminAccess implements Function<Statement, Statement> {
} }
@Override @Override
public Statement apply(Statement input) { public void visit(AdminAccess input) {
if (input instanceof StatementList) { input.init(adminAccessConfiguration);
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;
}
} }
} }

View File

@ -32,7 +32,6 @@ import org.jclouds.domain.Credentials;
import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.StatementList; import org.jclouds.scriptbuilder.domain.StatementList;
import org.jclouds.scriptbuilder.statements.login.AdminAccess.Configuration;
import org.jclouds.scriptbuilder.statements.ssh.SshStatements; import org.jclouds.scriptbuilder.statements.ssh.SshStatements;
import com.google.common.base.Function; import com.google.common.base.Function;
@ -44,27 +43,24 @@ import com.google.common.io.Files;
import com.google.inject.ImplementedBy; import com.google.inject.ImplementedBy;
/** /**
* Controls the administrative access to a node. By default, it will perform the * Controls the administrative access to a node. By default, it will perform the following:
* following:
* *
* <ul> * <ul>
* <li>setup a new admin user which folks should use as opposed to the built-in * <li>setup a new admin user which folks should use as opposed to the built-in vcloud account</li>
* vcloud account</li>
* <ul> * <ul>
* <li>associate a random password to account</li> * <li>associate a random password to account</li>
* <ul> * <ul>
* <li>securely ( use sha 512 on client side and literally rewrite the shadow * <li>securely ( use sha 512 on client side and literally rewrite the shadow entry, rather than
* entry, rather than pass password to OS in a script )</li> * pass password to OS in a script )</li>
* </ul> * </ul>
* <li>associate the users' ssh public key with the account for login</li> <li> * <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> * associate it with the os group wheel</li> </ul> <li>create os group wheel</li> <li>add sudoers
* <li>add sudoers for nopassword access to root by group wheel</li> <li>reset * for nopassword access to root by group wheel</li> <li>reset root password securely</li> <li>
* root password securely</li> <li>lockdown sshd_config for no root login, nor * lockdown sshd_config for no root login, nor passwords allowed</li> </ul>
* passwords allowed</li> </ul>
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class AdminAccess implements Statement, Function<Configuration, AdminAccess> { public class AdminAccess implements Statement {
public static AdminAccess.Builder builder() { public static AdminAccess.Builder builder() {
return new Builder(); return new Builder();
} }
@ -177,6 +173,10 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
} }
public AdminAccess build() { public AdminAccess build() {
return new AdminAccess(buildConfig());
}
protected Config buildConfig() {
try { try {
String adminPublicKey = this.adminPublicKey; String adminPublicKey = this.adminPublicKey;
if (adminPublicKey == null && adminPublicKeyFile != null) if (adminPublicKey == null && adminPublicKeyFile != null)
@ -184,8 +184,8 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
String adminPrivateKey = this.adminPrivateKey; String adminPrivateKey = this.adminPrivateKey;
if (adminPrivateKey == null && adminPrivateKeyFile != null) if (adminPrivateKey == null && adminPrivateKeyFile != null)
adminPrivateKey = Files.toString(adminPrivateKeyFile, UTF_8); adminPrivateKey = Files.toString(adminPrivateKeyFile, UTF_8);
return new AdminAccess(adminUsername, adminPublicKey, adminPrivateKey, adminPassword, loginPassword, return new Config(adminUsername, adminPublicKey, adminPrivateKey, adminPassword, loginPassword, lockSsh,
lockSsh, grantSudoToAdminUser, authorizeAdminPublicKey, installAdminPrivateKey, resetLoginPassword, grantSudoToAdminUser, authorizeAdminPublicKey, installAdminPrivateKey, resetLoginPassword,
cryptFunction); cryptFunction);
} catch (IOException e) { } catch (IOException e) {
Throwables.propagate(e); Throwables.propagate(e);
@ -194,6 +194,7 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
} }
} }
protected static class Config {
private final String adminUsername; private final String adminUsername;
private final String adminPublicKey; private final String adminPublicKey;
private final String adminPrivateKey; private final String adminPrivateKey;
@ -207,7 +208,7 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
private final Function<String, String> cryptFunction; private final Function<String, String> cryptFunction;
private final Credentials adminCredentials; private final Credentials adminCredentials;
protected AdminAccess(@Nullable String adminUsername, @Nullable String adminPublicKey, protected Config(@Nullable String adminUsername, @Nullable String adminPublicKey,
@Nullable String adminPrivateKey, @Nullable String adminPassword, @Nullable String loginPassword, @Nullable String adminPrivateKey, @Nullable String adminPassword, @Nullable String loginPassword,
boolean lockSsh, boolean grantSudoToAdminUser, boolean authorizeAdminPublicKey, boolean lockSsh, boolean grantSudoToAdminUser, boolean authorizeAdminPublicKey,
boolean installAdminPrivateKey, boolean resetLoginPassword, Function<String, String> cryptFunction) { boolean installAdminPrivateKey, boolean resetLoginPassword, Function<String, String> cryptFunction) {
@ -228,13 +229,77 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
this.adminCredentials = null; 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");
}
/** /**
* *
* @return new credentials or null if unchanged or unavailable * @return new credentials or null if unchanged or unavailable
*/ */
@Nullable @Nullable
public Credentials getAdminCredentials() { public Credentials getAdminCredentials() {
return adminCredentials; return config.getAdminCredentials();
}
@Nullable
public String getAdminPassword() {
return config.getAdminPassword();
}
public boolean shouldGrantSudoToAdminUser() {
return config.shouldGrantSudoToAdminUser();
} }
@Override @Override
@ -242,23 +307,26 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
return ImmutableList.of(); return ImmutableList.of();
} }
@Override public AdminAccess init(Configuration configuration) {
public AdminAccess apply(Configuration configuration) {
Builder builder = AdminAccess.builder(configuration.cryptFunction()); Builder builder = AdminAccess.builder(configuration.cryptFunction());
builder.adminUsername(this.adminUsername != null ? this.adminUsername : configuration.defaultAdminUsername() builder.adminUsername(config.getAdminUsername() != null ? config.getAdminUsername() : configuration
.get()); .defaultAdminUsername().get());
builder.adminPassword(this.adminPassword != null ? this.adminPassword : configuration.passwordGenerator().get()); builder.adminPassword(config.getAdminPassword() != null ? config.getAdminPassword() : configuration
Map<String, String> adminSshKeys = (adminPublicKey != null && adminPrivateKey != null) ? ImmutableMap.of( .passwordGenerator().get());
"public", adminPublicKey, "private", adminPrivateKey) : configuration.defaultAdminSshKeys().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.adminPublicKey(adminSshKeys.get("public"));
builder.adminPrivateKey(adminSshKeys.get("private")); builder.adminPrivateKey(adminSshKeys.get("private"));
builder.loginPassword(this.loginPassword != null ? this.loginPassword : configuration.passwordGenerator().get()); builder.loginPassword(config.getLoginPassword() != null ? config.getLoginPassword() : configuration
builder.grantSudoToAdminUser(this.grantSudoToAdminUser); .passwordGenerator().get());
builder.authorizeAdminPublicKey(this.authorizeAdminPublicKey); builder.grantSudoToAdminUser(config.shouldGrantSudoToAdminUser());
builder.installAdminPrivateKey(this.installAdminPrivateKey); builder.authorizeAdminPublicKey(config.shouldAuthorizeAdminPublicKey());
builder.lockSsh(this.lockSsh); builder.installAdminPrivateKey(config.shouldInstallAdminPrivateKey());
builder.resetLoginPassword(this.resetLoginPassword); builder.lockSsh(config.shouldLockSsh());
return builder.build(); builder.resetLoginPassword(config.shouldResetLoginPassword());
this.config = builder.buildConfig();
return this;
} }
@Override @Override
@ -266,29 +334,30 @@ public class AdminAccess implements Statement, Function<Configuration, AdminAcce
checkNotNull(family, "family"); checkNotNull(family, "family");
if (family == OsFamily.WINDOWS) if (family == OsFamily.WINDOWS)
throw new UnsupportedOperationException("windows not yet implemented"); throw new UnsupportedOperationException("windows not yet implemented");
checkNotNull(adminUsername, "adminUsername"); checkNotNull(config.getAdminUsername(), "adminUsername");
checkNotNull(adminPassword, "adminPassword"); checkNotNull(config.getAdminPassword(), "adminPassword");
checkNotNull(adminPublicKey, "adminPublicKey"); checkNotNull(config.getAdminPublicKey(), "adminPublicKey");
checkNotNull(adminPrivateKey, "adminPrivateKey"); checkNotNull(config.getAdminPrivateKey(), "adminPrivateKey");
checkNotNull(loginPassword, "loginPassword"); checkNotNull(config.getLoginPassword(), "loginPassword");
ImmutableList.Builder<Statement> statements = ImmutableList.<Statement> builder(); ImmutableList.Builder<Statement> statements = ImmutableList.<Statement> builder();
UserAdd.Builder userBuilder = UserAdd.builder(); UserAdd.Builder userBuilder = UserAdd.builder();
userBuilder.login(adminUsername); userBuilder.login(config.getAdminUsername());
if (authorizeAdminPublicKey) if (config.shouldAuthorizeAdminPublicKey())
userBuilder.authorizeRSAPublicKey(adminPublicKey); userBuilder.authorizeRSAPublicKey(config.getAdminPublicKey());
userBuilder.password(adminPassword); userBuilder.password(config.getAdminPassword());
if (installAdminPrivateKey) if (config.shouldInstallAdminPrivateKey())
userBuilder.installRSAPrivateKey(adminPrivateKey); userBuilder.installRSAPrivateKey(config.getAdminPrivateKey());
if (grantSudoToAdminUser) { if (config.shouldGrantSudoToAdminUser()) {
statements.add(SudoStatements.createWheel()); statements.add(SudoStatements.createWheel());
userBuilder.group("wheel"); userBuilder.group("wheel");
} }
statements.add(userBuilder.build().cryptFunction(cryptFunction)); statements.add(userBuilder.build().cryptFunction(config.getCryptFunction()));
if (lockSsh) if (config.shouldLockSsh())
statements.add(SshStatements.lockSshd()); statements.add(SshStatements.lockSshd());
if (resetLoginPassword) { if (config.shouldResetLoginPassword()) {
statements.add(ShadowStatements.resetLoginUserPasswordTo(loginPassword).cryptFunction(cryptFunction)); statements.add(ShadowStatements.resetLoginUserPasswordTo(config.getLoginPassword()).cryptFunction(
config.getCryptFunction()));
} }
return new StatementList(statements.build()).render(family); 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.createMock;
import static org.easymock.classextension.EasyMock.replay; import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify; 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 static org.testng.Assert.assertEquals;
import org.jclouds.domain.Credentials; import org.jclouds.domain.Credentials;
import org.jclouds.scriptbuilder.InitBuilder;
import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.Statements; import org.jclouds.scriptbuilder.domain.Statements;
import org.jclouds.scriptbuilder.statements.login.AdminAccess; import org.jclouds.scriptbuilder.statements.login.AdminAccess;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/** /**
* @author Adrian Cole * @author Adrian Cole
*/ */
@ -81,4 +87,25 @@ public class CredentialsFromAdminAccessTest {
verify(statement); verify(statement);
verify(creds); 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.easymock.classextension.EasyMock.verify;
import static org.testng.Assert.assertEquals; 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.Statement;
import org.jclouds.scriptbuilder.domain.Statements; import org.jclouds.scriptbuilder.domain.Statements;
import org.jclouds.scriptbuilder.statements.login.AdminAccess; import org.jclouds.scriptbuilder.statements.login.AdminAccess;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/** /**
* @author Adrian Cole * @author Adrian Cole
*/ */
@ -36,13 +41,15 @@ import org.testng.annotations.Test;
public class InitAdminAccessTest { public class InitAdminAccessTest {
public void testWhenNotAdminAccess() { public void testWhenNotAdminAccess() {
AdminAccess.Configuration configuration = createMock(AdminAccess.Configuration.class);
InitAdminAccess initAdminAccess = new InitAdminAccess(createMock(AdminAccess.Configuration.class)); InitAdminAccess initAdminAccess = new InitAdminAccess(configuration);
Statement statement = Statements.exec("echo hello"); replay(configuration);
assertEquals(initAdminAccess.apply(statement), statement);
Statement statementList = Statements.newStatementList(statement); initAdminAccess.visit(Statements.exec("echo hello"));
assertEquals(initAdminAccess.apply(statementList), statementList);
initAdminAccess.visit(Statements.newStatementList(Statements.exec("echo hello")));
verify(configuration);
} }
@ -51,14 +58,14 @@ public class InitAdminAccessTest {
AdminAccess statement = createMock(AdminAccess.class); AdminAccess statement = createMock(AdminAccess.class);
AdminAccess newStatement = createMock(AdminAccess.class); AdminAccess newStatement = createMock(AdminAccess.class);
expect(statement.apply(configuration)).andReturn(newStatement); expect(statement.init(configuration)).andReturn(newStatement);
replay(configuration); replay(configuration);
replay(statement); replay(statement);
replay(newStatement); replay(newStatement);
InitAdminAccess initAdminAccess = new InitAdminAccess(configuration); InitAdminAccess initAdminAccess = new InitAdminAccess(configuration);
assertEquals(initAdminAccess.apply(statement), newStatement); initAdminAccess.visit(statement);
verify(configuration); verify(configuration);
verify(statement); verify(statement);
@ -70,15 +77,38 @@ public class InitAdminAccessTest {
AdminAccess statement = createMock(AdminAccess.class); AdminAccess statement = createMock(AdminAccess.class);
AdminAccess newStatement = createMock(AdminAccess.class); AdminAccess newStatement = createMock(AdminAccess.class);
expect(statement.apply(configuration)).andReturn(newStatement); expect(statement.init(configuration)).andReturn(newStatement);
replay(configuration); replay(configuration);
replay(statement); replay(statement);
replay(newStatement); replay(newStatement);
InitAdminAccess initAdminAccess = new InitAdminAccess(configuration); InitAdminAccess initAdminAccess = new InitAdminAccess(configuration);
assertEquals(initAdminAccess.apply(Statements.newStatementList(statement)), initAdminAccess.visit(Statements.newStatementList(statement));
Statements.newStatementList(newStatement));
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(configuration);
verify(statement); verify(statement);

View File

@ -38,7 +38,7 @@ public class AdminAccessTest {
public void testStandardUNIX() throws IOException { public void testStandardUNIX() throws IOException {
TestConfiguration.INSTANCE.reset(); TestConfiguration.INSTANCE.reset();
try { 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"), CharStreams.toString(Resources.newReaderSupplier(Resources.getResource("test_adminaccess_standard.sh"),
Charsets.UTF_8))); Charsets.UTF_8)));
} finally { } finally {
@ -51,7 +51,7 @@ public class AdminAccessTest {
try { try {
assertEquals( assertEquals(
AdminAccess.builder().adminPassword("bar").adminPrivateKey("fooPrivateKey") 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( .render(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(
Resources.getResource("test_adminaccess_params.sh"), Charsets.UTF_8))); Resources.getResource("test_adminaccess_params.sh"), Charsets.UTF_8)));
} finally { } finally {
@ -65,7 +65,7 @@ public class AdminAccessTest {
assertEquals( assertEquals(
AdminAccess.builder().grantSudoToAdminUser(false).authorizeAdminPublicKey(true) AdminAccess.builder().grantSudoToAdminUser(false).authorizeAdminPublicKey(true)
.installAdminPrivateKey(true).lockSsh(false).resetLoginPassword(false).build() .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))); .newReaderSupplier(Resources.getResource("test_adminaccess_plainuser.sh"), Charsets.UTF_8)));
} finally { } finally {
TestConfiguration.INSTANCE.reset(); TestConfiguration.INSTANCE.reset();
@ -74,6 +74,6 @@ public class AdminAccessTest {
@Test(expectedExceptions = UnsupportedOperationException.class) @Test(expectedExceptions = UnsupportedOperationException.class)
public void testCreateWheelWindowsNotSupported() { public void testCreateWheelWindowsNotSupported() {
AdminAccess.standard().apply(TestConfiguration.INSTANCE).render(OsFamily.WINDOWS); AdminAccess.standard().init(TestConfiguration.INSTANCE).render(OsFamily.WINDOWS);
} }
} }