summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java107
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java255
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java142
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTest.java95
4 files changed, 580 insertions, 19 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java
index 5d60823d1c5..cb23f053086 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java
@@ -6,10 +6,13 @@ import com.yahoo.vespa.hosted.node.admin.task.util.process.CommandLine;
import com.yahoo.vespa.hosted.node.admin.task.util.process.Terminal;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/**
* @author hakonhall
@@ -29,27 +32,41 @@ public class Yum {
this.terminal = terminal;
}
- /**
- * @param packages A list of packages, each package being of the form name-1.2.3-1.el7.noarch
- */
- public GenericYumCommand install(String... packages) {
+ public GenericYumCommand install(YumPackageName... packages) {
return newYumCommand("install", packages, INSTALL_NOOP_PATTERN);
}
- /**
- * @param packages A list of packages, each package being of the form name-1.2.3-1.el7.noarch,
- * if no packages are given, will upgrade all installed packages
- */
- public GenericYumCommand upgrade(String... packages) {
+ public GenericYumCommand install(String package1, String... packages) {
+ return install(toYumPackageNameArray(package1, packages));
+ }
+
+ public GenericYumCommand upgrade(YumPackageName... packages) {
return newYumCommand("upgrade", packages, UPGRADE_NOOP_PATTERN);
}
- public GenericYumCommand remove(String... packages) {
+ public GenericYumCommand upgrade(String package1, String... packages) {
+ return upgrade(toYumPackageNameArray(package1, packages));
+ }
+
+ public GenericYumCommand remove(YumPackageName... packages) {
return newYumCommand("remove", packages, REMOVE_NOOP_PATTERN);
}
+ public GenericYumCommand remove(String package1, String... packages) {
+ return remove(toYumPackageNameArray(package1, packages));
+ }
+
+ static YumPackageName[] toYumPackageNameArray(String package1, String... packages) {
+ YumPackageName[] array = new YumPackageName[1 + packages.length];
+ array[0] = YumPackageName.fromString(package1);
+ for (int i = 0; i < packages.length; ++i) {
+ array[1 + i] = YumPackageName.fromString(packages[i]);
+ }
+ return array;
+ }
+
private GenericYumCommand newYumCommand(String yumCommand,
- String[] packages,
+ YumPackageName[] packages,
Pattern noopPattern) {
return new GenericYumCommand(
terminal,
@@ -61,13 +78,15 @@ public class Yum {
public static class GenericYumCommand {
private final Terminal terminal;
private final String yumCommand;
- private final List<String> packages;
+ private final List<YumPackageName> packages;
private final Pattern commandOutputNoopPattern;
+
private Optional<String> enabledRepo = Optional.empty();
+ private boolean lockVersion = false;
private GenericYumCommand(Terminal terminal,
String yumCommand,
- List<String> packages,
+ List<YumPackageName> packages,
Pattern commandOutputNoopPattern) {
this.terminal = terminal;
this.yumCommand = yumCommand;
@@ -85,11 +104,58 @@ public class Yum {
return this;
}
- public boolean converge(TaskContext taskContext) {
- CommandLine commandLine = terminal.newCommandLine(taskContext);
+ /**
+ * Ensure the version of the installs are locked.
+ *
+ * <p>WARNING: In order to simplify the user interface of {@link #lockVersion()},
+ * the package name specified in the command, e.g. {@link #install(String, String...)}, MUST be of
+ * a simple format, see {@link YumPackageName#fromString(String)}.
+ */
+ public GenericYumCommand lockVersion() {
+ // Verify each package has sufficient info to form a proper version lock name.
+ packages.forEach(YumPackageName::toVersionLockName);
+ lockVersion = true;
+ return this;
+ }
+
+ public boolean converge(TaskContext context) {
+ Set<String> packageNamesToLock = new HashSet<>();
+ Set<String> fullPackageNamesToLock = new HashSet<>();
+
+ if (lockVersion) {
+ // Remove all locks for other version
+
+ packages.forEach(packageName -> {
+ packageNamesToLock.add(packageName.getName());
+ fullPackageNamesToLock.add(packageName.toVersionLockName());
+ });
+
+ terminal.newCommandLine(context)
+ .add("yum", "--quiet", "versionlock", "list")
+ .executeSilently()
+ .getOutputLinesStream()
+ .map(YumPackageName::parseString)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .forEach(packageName -> {
+ // Ignore lines for other packages
+ if (packageNamesToLock.contains(packageName.getName())) {
+ // If existing lock doesn't exactly match the full package name,
+ // it means it's locked to another version and we must remove that lock.
+ String versionLockName = packageName.toVersionLockName();
+ if (!fullPackageNamesToLock.remove(versionLockName)) {
+ terminal.newCommandLine(context)
+ .add("yum", "versionlock", "delete", versionLockName)
+ .execute();
+ }
+ }
+ });
+ }
+
+ CommandLine commandLine = terminal.newCommandLine(context);
commandLine.add("yum", yumCommand, "--assumeyes");
enabledRepo.ifPresent(repo -> commandLine.add("--enablerepo=" + repo));
- commandLine.add(packages);
+ commandLine.add(packages.stream().map(YumPackageName::toName).collect(Collectors.toList()));
// There's no way to figure out whether a yum command would have been a no-op.
// Therefore, run the command and parse the output to decide.
@@ -101,10 +167,16 @@ public class Yum {
commandLine.recordSilentExecutionAsSystemModification();
}
+ fullPackageNamesToLock.forEach(fullPackageName ->
+ terminal.newCommandLine(context)
+ .add("yum", "versionlock", "add", fullPackageName)
+ .execute());
+ modifiedSystem |= !fullPackageNamesToLock.isEmpty();
+
return modifiedSystem;
}
- public boolean mapOutput(String output) {
+ private boolean mapOutput(String output) {
Matcher unknownPackageMatcher = UNKNOWN_PACKAGE_PATTERN.matcher(output);
if (unknownPackageMatcher.find()) {
throw new IllegalArgumentException("Unknown package: " + unknownPackageMatcher.group(1));
@@ -113,4 +185,5 @@ public class Yum {
return !commandOutputNoopPattern.matcher(output).find();
}
}
+
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java
new file mode 100644
index 00000000000..d894af9d378
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java
@@ -0,0 +1,255 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.task.util.yum;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * YUM package name.
+ *
+ * <p>From yum(8): YUM package names are used with install, update, remove, list, info etc
+ * with any of the following as well as globs of any of the following, with any of the
+ * following as well as globs of any of the following:
+ *
+ * <ol>
+ * <li>name
+ * <li>name.arch
+ * <li>name-ver
+ * <li>name-ver-rel
+ * <li>name-ver-rel.arch
+ * <li>name-epoch:ver-rel.arch
+ * <li>epoch:name-ver-rel.arch
+ * </ol>
+ *
+ * <p>However this specification is terribly ambiguous. This class allows constructing
+ * a package name from its components, which is beneficial because with certain YUM
+ * commands that needs to canonicalize names (e.g. versionlock).
+ *
+ * @author hakonhall
+ */
+public class YumPackageName {
+ private enum Architecture { noarch, x86_64, i386, i586, i686 }
+
+ private static final String ARCHITECTURES_OR =
+ Arrays.stream(Architecture.values()).map(Architecture::name).collect(Collectors.joining("|"));
+ private static final Pattern ARCHITECTURE_PATTERN = Pattern.compile("\\.(" + ARCHITECTURES_OR + "|\\*)$");
+ private static final Pattern EPOCH_PATTERN = Pattern.compile("^((.+)-)?([0-9]+)$");
+ private static final Pattern NAME_VER_REL_PATTERN = Pattern.compile("^((.+)-)?" +
+ "([a-z0-9._]*[0-9][a-z0-9._]*)-" + // ver contains at least one digit
+ "([a-z0-9._]*[0-9][a-z0-9._]*)$"); // rel contains at least one digit
+ private static final Pattern NAME_PATTERN = Pattern.compile("^[a-z0-9._-]+$");
+
+ public final Optional<String> epoch;
+ public final String name;
+ public final Optional<String> version;
+ public final Optional<String> release;
+ public final Optional<String> architecture;
+
+ public static class Builder {
+ private Optional<String> epoch = Optional.empty();
+ private String name;
+ private Optional<String> version = Optional.empty();
+ private Optional<String> release = Optional.empty();
+ private Optional<String> architecture = Optional.empty();
+
+ public Builder(String name) {
+ this.name = name;
+ }
+
+ public Builder(YumPackageName packageName) {
+ epoch = packageName.epoch;
+ name = packageName.name;
+ version = packageName.version;
+ release = packageName.release;
+ architecture = packageName.architecture;
+ }
+
+ public Builder setEpoch(String epoch) { this.epoch = Optional.of(epoch); return this; }
+ public Builder setName(String name) { this.name = name; return this; }
+ public Builder setRelease(String version) { this.version = Optional.of(version); return this; }
+ public Builder setVersion(String release) { this.release = Optional.of(release); return this; }
+ public Builder setArchitecture(String architecture) { this.architecture = Optional.of(architecture); return this; }
+
+ public YumPackageName build() { return new YumPackageName(epoch, name, version, release, architecture); }
+ }
+
+ /** @see Builder */
+ private YumPackageName(Optional<String> epoch,
+ String name,
+ Optional<String> version,
+ Optional<String> release,
+ Optional<String> architecture) {
+ this.epoch = epoch;
+ this.name = name;
+ this.version = version;
+ this.release = release;
+ this.architecture = architecture;
+ }
+
+ /**
+ * Parse the string specification of a YUM package.
+ *
+ * <p>The following formats are supported:
+ *
+ * <ol>
+ * <li>name
+ * <li>name.arch
+ * <li>name-ver-rel
+ * <li>name-ver-rel.arch
+ * <li>name-epoch:ver-rel.arch
+ * <li>epoch:name-ver-rel.arch
+ * </ol>
+ *
+ * @throws IllegalArgumentException if spec does not specify a package name.
+ * @see #parseString(String)
+ */
+ public static YumPackageName fromString(final String packageSpec) {
+ String spec = packageSpec;
+ Optional<String> epoch = Optional.empty();
+ String name = null;
+
+ // packageSpec spec
+ // name name
+ // name.arch name.arch
+ // name-ver-rel name-ver-rel
+ // name-ver-rel.arch name-ver-rel.arch
+ // name-epoch:ver-rel.arch name-epoch:ver-rel.arch
+ // epoch:name-ver-rel.arch epoch:name-ver-rel.arch
+
+ int epochColon = spec.indexOf(':');
+ if (epochColon >= 0) {
+ Matcher epochMatcher = EPOCH_PATTERN.matcher(spec.substring(0, epochColon));
+ if (!epochMatcher.find()) {
+ throw new IllegalArgumentException("Unexpected epoch format: " + packageSpec);
+ }
+
+ name = epochMatcher.group(2);
+ epoch = Optional.of(epochMatcher.group(3));
+
+ spec = spec.substring(epochColon + 1);
+ }
+
+ // packageSpec spec
+ // name name
+ // name.arch name.arch
+ // name-ver-rel name-ver-rel
+ // name-ver-rel.arch name-ver-rel.arch
+ // name-epoch:ver-rel.arch ver-rel.arch (non-null name)
+ // epoch:name-ver-rel.arch name-ver-rel.arch
+
+ Optional<String> architecture = Optional.empty();
+ Matcher architectureMatcher = ARCHITECTURE_PATTERN.matcher(spec);
+ if (architectureMatcher.find()) {
+ architecture = Optional.of(architectureMatcher.group(1));
+ spec = spec.substring(0, architectureMatcher.start());
+ }
+
+ // packageSpec spec
+ // name name
+ // name.arch name
+ // name-ver-rel name-ver-rel
+ // name-ver-rel.arch name-ver-rel
+ // name-epoch:ver-rel.arch ver-rel (non-null name)
+ // epoch:name-ver-rel.arch name-ver-rel
+
+ Optional<String> version = Optional.empty();
+ Optional<String> release = Optional.empty();
+ Matcher matcher = NAME_VER_REL_PATTERN.matcher(spec);
+ if (matcher.find()) {
+ // spec format one of:
+ // 1. name-ver-rel
+ // 2. ver-rel
+
+ spec = matcher.group(2);
+ if (spec == null) {
+ if (name == null) {
+ throw new IllegalArgumentException("No package name was found: " + packageSpec);
+ }
+ spec = name; // makes spec hold the package name in all cases below.
+ } else if (name != null) {
+ throw new IllegalArgumentException("Ambiguous package names were found for " +
+ packageSpec + ": '" + name + "' and '" + spec + "'");
+ }
+
+ version = Optional.of(matcher.group(3));
+ release = Optional.of(matcher.group(4));
+ }
+
+ // packageSpec spec
+ // name name
+ // name.arch name
+ // name-ver-rel name
+ // name-ver-rel.arch name
+ // name-epoch:ver-rel.arch name
+ // epoch:name-ver-rel.arch name
+
+ if (!NAME_PATTERN.matcher(spec).find()) {
+ throw new IllegalArgumentException("Bad package name in " + packageSpec + ": '" + spec + "'");
+ }
+ name = spec;
+
+ return new YumPackageName(epoch, name, version, release, architecture);
+ }
+
+ /** See {@link #fromString(String)}. */
+ public static Optional<YumPackageName> parseString(final String packageSpec) {
+ try {
+ return Optional.of(fromString(packageSpec));
+ } catch (IllegalArgumentException e) {
+ return Optional.empty();
+ }
+ }
+
+ public Optional<String> getEpoch() { return epoch; }
+ public String getName() { return name; }
+ public Optional<String> getVersion() { return version; }
+ public Optional<String> getRelease() { return release; }
+ public Optional<String> getArchitecture() { return architecture; }
+
+ /** Return package name, omitting components that are not specified. */
+ public String toName() {
+ StringBuilder builder = new StringBuilder();
+ epoch.ifPresent(ep -> builder.append(ep).append(':'));
+ builder.append(name);
+ version.ifPresent(ver -> builder.append('-').append(ver));
+ release.ifPresent(rel -> builder.append('-').append(rel));
+ architecture.ifPresent(arch -> builder.append('.').append(arch));
+ return builder.toString();
+ }
+
+ /**
+ * The package name output by 'yum versionlock list'. Can also be used with 'add' and 'delete'.
+ *
+ * @throws IllegalStateException if any field required for the version lock spec is missing
+ */
+ public String toVersionLockName() {
+ return String.format("%s:%s-%s-%s.%s",
+ epoch.orElseThrow(() -> new IllegalStateException("Epoch is missing for YUM package " + name)),
+ name,
+ version.orElseThrow(() -> new IllegalStateException("Version is missing for YUM package " + name)),
+ release.orElseThrow(() -> new IllegalStateException("Release is missing for YUM package " + name)),
+ "*");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ YumPackageName that = (YumPackageName) o;
+ return Objects.equals(epoch, that.epoch) &&
+ Objects.equals(name, that.name) &&
+ Objects.equals(version, that.version) &&
+ Objects.equals(release, that.release) &&
+ Objects.equals(architecture, that.architecture);
+ }
+
+ @Override
+ public int hashCode() {
+
+ return Objects.hash(epoch, name, version, release, architecture);
+ }
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java
new file mode 100644
index 00000000000..2e1ef4c0a61
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java
@@ -0,0 +1,142 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.task.util.yum;
+
+import org.junit.Test;
+
+import java.util.Optional;
+
+import static org.hamcrest.CoreMatchers.containsStringIgnoringCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+public class YumPackageNameTest {
+
+ @Test
+ public void testAllValidFormats() {
+ // name
+ verifyPackageName(
+ "docker-engine-selinux",
+ null,
+ "docker-engine-selinux",
+ null,
+ null,
+ null,
+ "docker-engine-selinux",
+ null);
+
+ // name.arch
+ verifyPackageName(
+ "docker-engine-selinux.x86_64",
+ null,
+ "docker-engine-selinux",
+ null,
+ null,
+ "x86_64",
+ "docker-engine-selinux.x86_64",
+ null);
+
+ // name-ver-rel
+ verifyPackageName("docker-engine-selinux-1.12.6-1.el7",
+ null,
+ "docker-engine-selinux",
+ "1.12.6",
+ "1.el7",
+ null,
+ "docker-engine-selinux-1.12.6-1.el7",
+ null);
+
+ // name-ver-rel.arch
+ verifyPackageName("docker-engine-selinux-1.12.6-1.el7.x86_64",
+ null,
+ "docker-engine-selinux",
+ "1.12.6",
+ "1.el7",
+ "x86_64",
+ "docker-engine-selinux-1.12.6-1.el7.x86_64",
+ null);
+
+ // name-epoch:ver-rel.arch
+ verifyPackageName(
+ "docker-2:1.12.6-71.git3e8e77d.el7.centos.1.x86_64",
+ "2",
+ "docker",
+ "1.12.6",
+ "71.git3e8e77d.el7.centos.1",
+ "x86_64",
+ "2:docker-1.12.6-71.git3e8e77d.el7.centos.1.x86_64",
+ "2:docker-1.12.6-71.git3e8e77d.el7.centos.1.*");
+
+ // epoch:name-ver-rel.arch
+ verifyPackageName(
+ "2:docker-1.12.6-71.git3e8e77d.el7.centos.1.x86_64",
+ "2",
+ "docker",
+ "1.12.6",
+ "71.git3e8e77d.el7.centos.1",
+ "x86_64",
+ "2:docker-1.12.6-71.git3e8e77d.el7.centos.1.x86_64",
+ "2:docker-1.12.6-71.git3e8e77d.el7.centos.1.*");
+ }
+
+ private void verifyPackageName(String packageName,
+ String epoch,
+ String name,
+ String version,
+ String release,
+ String architecture,
+ String toName,
+ String toVersionName) {
+ YumPackageName yumPackageName = YumPackageName.fromString(packageName);
+ verifyValue(epoch, yumPackageName.getEpoch());
+ verifyValue(name, Optional.of(yumPackageName.getName()));
+ verifyValue(version, yumPackageName.getVersion());
+ verifyValue(release, yumPackageName.getRelease());
+ verifyValue(architecture, yumPackageName.getArchitecture());
+ verifyValue(toName, Optional.of(yumPackageName.toName()));
+
+ if (toVersionName == null) {
+ try {
+ yumPackageName.toVersionLockName();
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage(), containsStringIgnoringCase("epoch is missing"));
+ }
+ } else {
+ assertEquals(toVersionName, yumPackageName.toVersionLockName());
+ }
+ }
+
+ private void verifyValue(String value, Optional<String> actual) {
+ if (value == null) {
+ assertFalse(actual.isPresent());
+ } else {
+ assertEquals(value, actual.get());
+ }
+ }
+
+ @Test
+ public void testArchitectures() {
+ assertEquals("x86_64", YumPackageName.fromString("docker.x86_64").getArchitecture().get());
+ assertEquals("i686", YumPackageName.fromString("docker.i686").getArchitecture().get());
+ assertEquals("noarch", YumPackageName.fromString("docker.noarch").getArchitecture().get());
+ }
+
+ @Test
+ public void unrecognizedArchitectureGetsGobbledUp() {
+ YumPackageName packageName = YumPackageName.fromString("docker-engine-selinux-1.12.6-1.el7.i486");
+ // This is not a great feature - please use YumPackageName.Builder instead.
+ assertEquals("1.el7.i486", packageName.getRelease().get());
+ }
+
+ @Test
+ public void failParsingOfPackageNameWithEpochAndArchitecture() {
+ try {
+ YumPackageName.fromString("epoch:docker-engine-selinux-1.12.6-1.el7.x86_64");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsStringIgnoringCase("epoch"));
+ }
+ }
+} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTest.java
index 7f37336db70..a635dd6a44d 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTest.java
@@ -4,12 +4,15 @@ package com.yahoo.vespa.hosted.node.admin.task.util.yum;
import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
import com.yahoo.vespa.hosted.node.admin.task.util.process.ChildProcessFailureException;
import com.yahoo.vespa.hosted.node.admin.task.util.process.TestTerminal;
-import org.junit.Before;
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@@ -19,12 +22,31 @@ public class YumTest {
private final TestTerminal terminal = new TestTerminal();
private final Yum yum = new Yum(terminal);
- @Before
+ @After
public void tearDown() {
terminal.verifyAllCommandsExecuted();
}
@Test
+ public void testArrayConversion() {
+ YumPackageName[] expected = new YumPackageName[] { new YumPackageName.Builder("1").build() };
+ assertArrayEquals(expected, Yum.toYumPackageNameArray("1"));
+
+ YumPackageName[] expected2 = new YumPackageName[] {
+ new YumPackageName.Builder("1").build(),
+ new YumPackageName.Builder("2").build()
+ };
+ assertArrayEquals(expected2, Yum.toYumPackageNameArray("1", "2"));
+
+ YumPackageName[] expected3 = new YumPackageName[] {
+ new YumPackageName.Builder("1").build(),
+ new YumPackageName.Builder("2").build(),
+ new YumPackageName.Builder("3").build()
+ };
+ assertArrayEquals(expected3, Yum.toYumPackageNameArray("1", "2", "3"));
+ }
+
+ @Test
public void testAlreadyInstalled() {
terminal.expectCommand(
"yum install --assumeyes --enablerepo=repo-name package-1 package-2 2>&1",
@@ -86,6 +108,75 @@ public class YumTest {
.converge(taskContext));
}
+ @Test
+ public void testWithVersionLock() {
+ terminal.expectCommand("yum --quiet versionlock list 2>&1",
+ 0,
+ "Repository chef_rpms-release is listed more than once in the configuration\n" +
+ "0:chef-12.21.1-1.el7.*\n");
+ terminal.expectCommand(
+ "yum install --assumeyes \"0:package-1-0.10-654.el7.*\" 2>&1",
+ 0,
+ "installing");
+ terminal.expectCommand("yum versionlock add \"0:package-1-0.10-654.el7.*\" 2>&1");
+
+ assertTrue(yum
+ .install("0:package-1-0.10-654.el7.*")
+ .lockVersion()
+ .converge(taskContext));
+ }
+
+ @Test
+ public void testWithDifferentVersionLock() {
+ terminal.expectCommand("yum --quiet versionlock list 2>&1",
+ 0,
+ "Repository chef_rpms-release is listed more than once in the configuration\n" +
+ "0:chef-12.21.1-1.el7.*\n" +
+ "0:package-1-0.1-8.el7.*\n");
+
+ terminal.expectCommand("yum versionlock delete \"0:package-1-0.1-8.el7.*\" 2>&1");
+
+ terminal.expectCommand(
+ "yum install --assumeyes \"0:package-1-0.10-654.el7.*\" 2>&1",
+ 0,
+ "Nothing to do\n");
+
+ terminal.expectCommand("yum versionlock add \"0:package-1-0.10-654.el7.*\" 2>&1");
+
+ assertTrue(yum
+ .install("0:package-1-0.10-654.el7.*")
+ .lockVersion()
+ .converge(taskContext));
+ }
+
+ @Test
+ public void testWithExistingVersionLock() {
+ terminal.expectCommand("yum --quiet versionlock list 2>&1",
+ 0,
+ "Repository chef_rpms-release is listed more than once in the configuration\n" +
+ "0:chef-12.21.1-1.el7.*\n" +
+ "0:package-1-0.10-654.el7.*\n");
+ terminal.expectCommand(
+ "yum install --assumeyes \"0:package-1-0.10-654.el7.*\" 2>&1",
+ 0,
+ "Nothing to do\n");
+
+ assertFalse(yum
+ .install("0:package-1-0.10-654.el7.*")
+ .lockVersion()
+ .converge(taskContext));
+ }
+
+ @Test
+ public void testBadPackageNameWithLock() {
+ try {
+ yum.install("package-1-0.10-654.el7").lockVersion();
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage(), CoreMatchers.containsStringIgnoringCase("epoch is missing"));
+ }
+ }
+
@Test(expected = ChildProcessFailureException.class)
public void testFailedInstall() {
terminal.expectCommand(