From 0c3a0ac7ee17f421a71e4f66ee63fd6226b5eb2d Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sun, 1 May 2011 12:52:50 -0400 Subject: [PATCH] Issue 543: added ability to reset default login user's password --- .../jclouds/scriptbuilder/ScriptBuilder.java | 4 +- .../scriptbuilder/domain/AppendFile.java | 2 +- .../jclouds/scriptbuilder/domain/Call.java | 2 +- .../scriptbuilder/domain/CreateRunScript.java | 6 +- .../domain/InterpretableStatement.java | 2 +- .../jclouds/scriptbuilder/domain/Kill.java | 2 +- .../scriptbuilder/domain/Statement.java | 2 +- .../scriptbuilder/domain/StatementList.java | 4 +- .../scriptbuilder/domain/Statements.java | 2 +- .../scriptbuilder/domain/SwitchArg.java | 4 +- .../login/ReplaceShadowPasswordEntry.java | 98 +++++++++++++++++++ ...ReplaceShadowPasswordEntryOfLoginUser.java | 32 ++++++ .../statements/login/ShadowStatements.java | 37 +++++++ .../statements/login/Sudoers.java | 2 +- .../statements/login/UserAdd.java | 2 +- .../ssh/AuthorizeRSAPublicKeys.java | 2 +- .../statements/ssh/InstallRSAPrivateKey.java | 2 +- .../statements/ssh/SshdConfig.java | 2 +- ...aceShadowPasswordEntryOfLoginUserTest.java | 41 ++++++++ .../login/ReplaceShadowPasswordEntryTest.java | 41 ++++++++ 20 files changed, 269 insertions(+), 20 deletions(-) create mode 100644 scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntry.java create mode 100644 scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryOfLoginUser.java create mode 100644 scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ShadowStatements.java create mode 100644 scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryOfLoginUserTest.java create mode 100644 scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryTest.java diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/ScriptBuilder.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/ScriptBuilder.java index 573425679b..2d46badfbc 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/ScriptBuilder.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/ScriptBuilder.java @@ -133,7 +133,7 @@ public class ScriptBuilder implements Statement { new Function>() { @Override public Iterable apply(Statement from) { - return from.functionDependecies(osFamily); + return from.functionDependencies(osFamily); } })); List unresolvedFunctions = Lists.newArrayList(dependentFunctions); @@ -144,7 +144,7 @@ public class ScriptBuilder implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return ImmutableSet. of(); } } \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AppendFile.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AppendFile.java index 06deea7eda..bb73a17b06 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AppendFile.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/AppendFile.java @@ -64,7 +64,7 @@ public class AppendFile implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return Collections.emptyList(); } diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Call.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Call.java index ba26f96538..876212ecd6 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Call.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Call.java @@ -87,7 +87,7 @@ public class Call implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return ImmutableList.of(function); } } \ 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 3860650483..f27268c03a 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/CreateRunScript.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/CreateRunScript.java @@ -69,7 +69,7 @@ public class CreateRunScript implements Statement { "echo title {title}>>{file}\r\n"); @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return Collections.emptyList(); } @@ -96,7 +96,7 @@ public class CreateRunScript implements Statement { "echo set {export}={value}>>{file}\r\n"); @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return Collections.emptyList(); } @@ -122,7 +122,7 @@ public class CreateRunScript implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return Collections.emptyList(); } diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/InterpretableStatement.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/InterpretableStatement.java index 111079c23b..0ff4272134 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/InterpretableStatement.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/InterpretableStatement.java @@ -46,7 +46,7 @@ public class InterpretableStatement implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return ImmutableList.of(); } diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Kill.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Kill.java index 1ceb462711..cafe95a02e 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Kill.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Kill.java @@ -45,7 +45,7 @@ public class Kill implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return ImmutableList.of(); } } \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statement.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statement.java index 63ad0c970f..507b408943 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statement.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statement.java @@ -24,7 +24,7 @@ package org.jclouds.scriptbuilder.domain; * @author Adrian Cole */ public interface Statement { - Iterable functionDependecies(OsFamily family); + Iterable functionDependencies(OsFamily family); String render(OsFamily family); } \ No newline at end of file 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 36509379f0..c763239e85 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/StatementList.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/StatementList.java @@ -51,10 +51,10 @@ public class StatementList implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { List functions = Lists.newArrayList(); for (Statement statement : statements) { - Iterables.addAll(functions, statement.functionDependecies(family)); + Iterables.addAll(functions, statement.functionDependencies(family)); } return functions; } 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 79fe8cbd2e..792b27c3d5 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statements.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/Statements.java @@ -44,7 +44,7 @@ public class Statements { return new Statement() { @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return ImmutableList.of(); } 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 eaec91d13a..665ba63106 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/SwitchArg.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/domain/SwitchArg.java @@ -126,10 +126,10 @@ public class SwitchArg implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { List functions = Lists.newArrayList(); for (Statement statement : valueToActions.values()) { - Iterables.addAll(functions, statement.functionDependecies(family)); + Iterables.addAll(functions, statement.functionDependencies(family)); } return functions; } diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntry.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntry.java new file mode 100644 index 0000000000..4595c27227 --- /dev/null +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntry.java @@ -0,0 +1,98 @@ +/** + * + * 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.statements.login; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.encryption.internal.JCECrypto; +import org.jclouds.scriptbuilder.domain.OsFamily; +import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.scriptbuilder.domain.StatementList; +import org.jclouds.scriptbuilder.domain.Statements; +import org.jclouds.scriptbuilder.util.Sha512Crypt; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; + +/** + * + * @author Adrian Cole + */ +public class ReplaceShadowPasswordEntry implements Statement { + + private final String login; + private final String password; + + public ReplaceShadowPasswordEntry(String login, String password) { + this.login = checkNotNull(login, "login"); + this.password = checkNotNull(password, "password"); + } + + @Override + public Iterable functionDependencies(OsFamily family) { + return ImmutableList.of(); + } + + @Override + public String render(OsFamily family) { + checkNotNull(family, "family"); + if (family == OsFamily.WINDOWS) + throw new UnsupportedOperationException("windows not yet implemented"); + try { + String shadowPasswordEntry = Sha512Crypt.makeShadowLine(password, null, new JCECrypto()); + String shadowFile = "/etc/shadow"; + Statement replaceEntryInTempFile = Statements + .exec(String + .format( + "awk -v user=^%1$s: -v password='%2$s' 'BEGIN { FS=OFS=\":\" } $0 ~ user { $2 = password } 1' %3$s >%3$s.%1$s", + login, shadowPasswordEntry, shadowFile)); + Statement replaceShadowFile = Statements.exec(String.format("test -f %2$s.%1$s && mv %2$s.%1$s %2$s", login, + shadowFile)); + return new StatementList(ImmutableList.of(replaceEntryInTempFile, replaceShadowFile)).render(family); + } catch (Exception e) { + Throwables.propagate(e); + return null; + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((login == null) ? 0 : login.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ReplaceShadowPasswordEntry other = (ReplaceShadowPasswordEntry) obj; + if (login == null) { + if (other.login != null) + return false; + } else if (!login.equals(other.login)) + return false; + return true; + } +} \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryOfLoginUser.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryOfLoginUser.java new file mode 100644 index 0000000000..c36d62429f --- /dev/null +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryOfLoginUser.java @@ -0,0 +1,32 @@ +/** + * + * 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.statements.login; + + +/** + * + * @author Adrian Cole + */ +public class ReplaceShadowPasswordEntryOfLoginUser extends ReplaceShadowPasswordEntry { + + public ReplaceShadowPasswordEntryOfLoginUser(String password) { + super("${SUDO_USER:=${USER}}", password); + } + +} \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ShadowStatements.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ShadowStatements.java new file mode 100644 index 0000000000..c19f832330 --- /dev/null +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/ShadowStatements.java @@ -0,0 +1,37 @@ +/** + * + * 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.statements.login; + +import org.jclouds.scriptbuilder.domain.Statement; + +/** + * Statements used to manipulate the shadow file + * + * @author Adrian Cole + */ +public class ShadowStatements { + + /** + * note must be run as root, and will either reset the root password, or + * whoever sudoed to root. + */ + public static Statement resetLoginUserPasswordTo(String password) { + return new ReplaceShadowPasswordEntryOfLoginUser(password); + } +} \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/Sudoers.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/Sudoers.java index 91fd3e718c..423790e5fb 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/Sudoers.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/Sudoers.java @@ -52,7 +52,7 @@ public class Sudoers implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return ImmutableList.of(); } } \ No newline at end of file diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/UserAdd.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/UserAdd.java index 2d7a10cecb..1648ae13db 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/UserAdd.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/UserAdd.java @@ -128,7 +128,7 @@ public class UserAdd implements Statement { private final String shell; @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return ImmutableList.of(); } diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/AuthorizeRSAPublicKeys.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/AuthorizeRSAPublicKeys.java index 50b25bf453..fa76d5568b 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/AuthorizeRSAPublicKeys.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/AuthorizeRSAPublicKeys.java @@ -52,7 +52,7 @@ public class AuthorizeRSAPublicKeys implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return Collections.emptyList(); } diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/InstallRSAPrivateKey.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/InstallRSAPrivateKey.java index 061f014519..ae618b54e9 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/InstallRSAPrivateKey.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/InstallRSAPrivateKey.java @@ -50,7 +50,7 @@ public class InstallRSAPrivateKey implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return Collections.emptyList(); } diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/SshdConfig.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/SshdConfig.java index f7a809c7ea..8f7cac1f7f 100644 --- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/SshdConfig.java +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/ssh/SshdConfig.java @@ -54,7 +54,7 @@ public class SshdConfig implements Statement { } @Override - public Iterable functionDependecies(OsFamily family) { + public Iterable functionDependencies(OsFamily family) { return ImmutableList.of(); } } \ No newline at end of file diff --git a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryOfLoginUserTest.java b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryOfLoginUserTest.java new file mode 100644 index 0000000000..deb6171a4c --- /dev/null +++ b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryOfLoginUserTest.java @@ -0,0 +1,41 @@ +/** + * + * 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.statements.login; + +import org.jclouds.scriptbuilder.domain.OsFamily; +import org.testng.annotations.Test; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit") +public class ReplaceShadowPasswordEntryOfLoginUserTest { + + public void testWithPasswordUNIX() { + String cmd = new ReplaceShadowPasswordEntryOfLoginUser("bar").render(OsFamily.UNIX); + assert cmd.startsWith("awk -v user=^${SUDO_USER:=${USER}}: -v password='$6$") : cmd; + assert cmd + .endsWith("' 'BEGIN { FS=OFS=\":\" } $0 ~ user { $2 = password } 1' /etc/shadow >/etc/shadow.${SUDO_USER:=${USER}}\ntest -f /etc/shadow.${SUDO_USER:=${USER}} && mv /etc/shadow.${SUDO_USER:=${USER}} /etc/shadow\n") : cmd; + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testAddUserWindowsNotSupported() { + new ReplaceShadowPasswordEntryOfLoginUser("password").render(OsFamily.WINDOWS); + } +} diff --git a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryTest.java b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryTest.java new file mode 100644 index 0000000000..dde7ca7cdb --- /dev/null +++ b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/ReplaceShadowPasswordEntryTest.java @@ -0,0 +1,41 @@ +/** + * + * 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.statements.login; + +import org.jclouds.scriptbuilder.domain.OsFamily; +import org.testng.annotations.Test; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit") +public class ReplaceShadowPasswordEntryTest { + + public void testWithPasswordUNIX() { + String userAdd = new ReplaceShadowPasswordEntry("foo", "bar").render(OsFamily.UNIX); + assert userAdd.startsWith("awk -v user=^foo: -v password='$6$") : userAdd; + assert userAdd + .endsWith("' 'BEGIN { FS=OFS=\":\" } $0 ~ user { $2 = password } 1' /etc/shadow >/etc/shadow.foo\ntest -f /etc/shadow.foo && mv /etc/shadow.foo /etc/shadow\n") : userAdd; + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testAddUserWindowsNotSupported() { + new ReplaceShadowPasswordEntry("user", "password").render(OsFamily.WINDOWS); + } +}