diff options
author | Håkon Hallingstad <hakon@oath.com> | 2018-08-28 11:40:21 +0200 |
---|---|---|
committer | Håkon Hallingstad <hakon@oath.com> | 2018-08-28 11:40:21 +0200 |
commit | 6591a9f52683829f296c9b0eb2610b9278b5f5ba (patch) | |
tree | ea7b9e4cddac1851cb4681ea7188af5098e68e37 /node-admin/src | |
parent | d8beea0be39ed0e3a9686a9f555d7d279e08dde1 (diff) |
Extract YumPackageName
Diffstat (limited to 'node-admin/src')
4 files changed, 218 insertions, 146 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 5a95c90d216..226135a1617 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 @@ -94,10 +94,10 @@ public class Yum { * * <p>WARNING: In order to simplify the user interface of {@link #lockVersion()}, * the package name specified in the command, e.g. {@link #install(String...)}, MUST be of - * a simple format, see {@link PackageName#fromString(String)}. + * a simple format, see {@link YumPackageName#fromString(String)}. */ public GenericYumCommand lockVersion() { - packages.forEach(PackageName::fromString); // to throw any parse error here instead of later + packages.forEach(YumPackageName::fromString); // to throw any parse error here instead of later lockVersion = true; return this; } @@ -110,19 +110,17 @@ public class Yum { // Remove all locks for other version packages.stream() - .map(PackageName::fromString) - .map(PackageName.Builder::new) - .map(builder -> builder.setArchitecture("*").build()) + .map(YumPackageName::fromString) .forEach(packageName -> { packageNamesToLock.add(packageName.getName()); - fullPackageNamesToLock.add(packageName.toFullName()); + fullPackageNamesToLock.add(packageName.toVersionLock()); }); terminal.newCommandLine(context) .add("yum", "--quiet", "versionlock", "list") .executeSilently() .getOutputLinesStream() - .map(PackageName::parseString) + .map(YumPackageName::parseString) .filter(Optional::isPresent) .map(Optional::get) .forEach(packageName -> { @@ -130,10 +128,10 @@ public class Yum { 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 fullName = packageName.toFullName(); - if (!fullPackageNamesToLock.remove(fullName)) { + String versionLockName = packageName.toVersionLock(); + if (!fullPackageNamesToLock.remove(versionLockName)) { terminal.newCommandLine(context) - .add("yum", "versionlock", "delete", fullName) + .add("yum", "versionlock", "delete", versionLockName) .execute(); } } @@ -174,126 +172,4 @@ public class Yum { } } - /** YUM package name. */ - static class PackageName { - private static final Pattern ARCHITECTURE_PATTERN = Pattern.compile("\\.(noarch|x86_64|i686|i386|\\*)$"); - private static final Pattern NAME_VER_REL_PATTERN = Pattern.compile("^(.+)-([^-]*[0-9][^-]*)-([^-]*[0-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; - - private PackageName(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>According to yum(8) a package can be specified using a variety of different - * and ambiguous formats. We'll use a subset: - * - * <ul> - * <li>spec MUST be of the form name-ver-rel, name-ver-rel.arch, or epoch:name-ver-rel.arch. - * <li>If specified, arch MUST be one of "noarch", "i686", "x86_64", or "*". The wildcard - * is equivalent to not specifying arch. - * <li>rel cannot end in something that would be mistaken for the '.arch' suffix. - * <li>ver and rel are assumed to not contain any '-' to uniquely identify name, - * and must contain a digit. - * </ul> - * - * @param spec A package name of the form epoch:name-ver-rel.arch, name-ver-rel.arch, or name-ver-rel. - * @return The package with that name. - * @throws IllegalArgumentException if spec does not specify a package name. - */ - public static PackageName fromString(String spec) { - return parseString(spec).orElseThrow(() -> new IllegalArgumentException("Failed to decode the YUM package spec '" + spec + "'")); - } - - /** See {@link #fromString(String)}. */ - public static Optional<PackageName> parseString(String spec) { - Optional<String> epoch = Optional.empty(); - int epochColon = spec.indexOf(':'); - if (epochColon >= 0) { - epoch = Optional.of(spec.substring(0, epochColon)); - spec = spec.substring(epochColon + 1); - } - - 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()); - } - - - Matcher matcher = NAME_VER_REL_PATTERN.matcher(spec); - if (matcher.find()) { - return Optional.of(new PackageName( - epoch, - matcher.group(1), - Optional.of(matcher.group(2)), - Optional.of(matcher.group(3)), - architecture)); - } - - 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 the full name of the package in the format epoch:name-ver-rel.arch, which can - * be used with e.g. the YUM install and versionlock commands. - * - * <p>The package MUST have both version and release. Absent epoch defaults to "0". - * Absent arch defaults to "*". - */ - public String toFullName() { - return String.format("%s:%s-%s-%s.%s", - epoch.orElse("0"), - name, - version.orElseThrow(() -> new IllegalStateException("Version is missing for YUM package " + name)), - release.orElseThrow(() -> new IllegalStateException("Release is missing for YUM package " + name)), - architecture.orElse("*")); - } - - public static class Builder { - private Optional<String> epoch; - private String name; - private Optional<String> version; - private Optional<String> release; - private Optional<String> architecture; - - public Builder(PackageName aPackage) { - epoch = aPackage.epoch; - name = aPackage.name; - version = aPackage.version; - release = aPackage.release; - architecture = aPackage.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 PackageName build() { return new PackageName(epoch, name, version, release, architecture); } - } - } } 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..d6565a67979 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java @@ -0,0 +1,148 @@ +// 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.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * YUM package name. + * + * @author hakonhall + */ +public class YumPackageName { + private static final Pattern ARCHITECTURE_PATTERN = Pattern.compile("\\.(noarch|x86_64|i686|i386|\\*)$"); + private static final Pattern NAME_VER_REL_PATTERN = Pattern.compile("^(.+)-([^-]*[0-9][^-]*)-([^-]*[0-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; + + /** @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>According to yum(8) a package can be specified using a variety of different + * and ambiguous formats. We'll use a subset: + * + * <ul> + * <li>spec MUST be of the form name-ver-rel, name-ver-rel.arch, or epoch:name-ver-rel.arch. + * <li>If specified, arch should be one of "noarch", "i686", "x86_64", or "*". The wildcard + * is equivalent to not specifying arch. + * <li>rel cannot end in something that would be mistaken for the '.arch' suffix. + * <li>ver and rel are assumed to not contain any '-' to uniquely identify name, + * and must contain a digit. + * </ul> + * + * @param spec A package name of the form epoch:name-ver-rel.arch, name-ver-rel.arch, or name-ver-rel. + * @return The package with that name. + * @throws IllegalArgumentException if spec does not specify a package name. + */ + public static YumPackageName fromString(String spec) { + return parseString(spec).orElseThrow(() -> new IllegalArgumentException("Failed to decode the YUM package spec '" + spec + "'")); + } + + /** See {@link #fromString(String)}. */ + public static Optional<YumPackageName> parseString(String spec) { + Optional<String> epoch = Optional.empty(); + int epochColon = spec.indexOf(':'); + if (epochColon >= 0) { + epoch = Optional.of(spec.substring(0, epochColon)); + if (!epoch.get().chars().allMatch(Character::isDigit)) { + throw new IllegalArgumentException("Epoch is not a number: " + epoch.get()); + } + + spec = spec.substring(epochColon + 1); + } + + 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()); + } + + + Matcher matcher = NAME_VER_REL_PATTERN.matcher(spec); + if (matcher.find()) { + return Optional.of(new YumPackageName( + epoch, + matcher.group(1), + Optional.of(matcher.group(2)), + Optional.of(matcher.group(3)), + architecture)); + } + + 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 the full name of the package in the format epoch:name-ver-rel.arch, which can + * be used with e.g. the YUM install and versionlock commands. + * + * <p>The package MUST have both version and release. Absent epoch defaults to "0". + * Absent arch defaults to "*". + */ + public String toFullName() { + return String.format("%s:%s-%s-%s.%s", + epoch.orElse("0"), + name, + version.orElseThrow(() -> new IllegalStateException("Version is missing for YUM package " + name)), + release.orElseThrow(() -> new IllegalStateException("Release is missing for YUM package " + name)), + architecture.orElse("*")); + } + + /** The package name output by 'yum versionlock list'. Can also be used with 'add' and 'delete'. */ + public String toVersionLock() { + return String.format("%s:%s-%s-%s.%s", + epoch.orElse("0"), + name, + version.orElseThrow(() -> new IllegalStateException("Version is missing for YUM package " + name)), + release.orElseThrow(() -> new IllegalStateException("Release is missing for YUM package " + name)), + "*"); + } + + public static class Builder { + private Optional<String> epoch; + private String name; + private Optional<String> version; + private Optional<String> release; + private Optional<String> architecture; + + public Builder(YumPackageName aPackage) { + epoch = aPackage.epoch; + name = aPackage.name; + version = aPackage.version; + release = aPackage.release; + architecture = aPackage.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); } + } +} 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..911a5b64b33 --- /dev/null +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java @@ -0,0 +1,62 @@ +// 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class YumPackageNameTest { + + @Test + public void parsePackageName() { + YumPackageName packageName = YumPackageName.fromString("docker-engine-selinux-1.12.6-1.el7"); + assertFalse(packageName.getEpoch().isPresent()); + assertEquals("docker-engine-selinux", packageName.getName()); + assertEquals("1.12.6", packageName.getVersion().get()); + assertEquals("1.el7", packageName.getRelease().get()); + assertFalse(packageName.getArchitecture().isPresent()); + assertEquals("0:docker-engine-selinux-1.12.6-1.el7.*", packageName.toFullName()); + } + + @Test + public void parsePackageNameWithArchitecture() { + YumPackageName packageName = YumPackageName.fromString("docker-engine-selinux-1.12.6-1.el7.x86_64"); + assertFalse(packageName.getEpoch().isPresent()); + assertEquals("docker-engine-selinux", packageName.getName()); + assertEquals("1.12.6", packageName.getVersion().get()); + assertEquals("1.el7", packageName.getRelease().get()); + assertEquals("x86_64", packageName.getArchitecture().get()); + assertEquals("0:docker-engine-selinux-1.12.6-1.el7.x86_64", packageName.toFullName()); + assertEquals("0:docker-engine-selinux-1.12.6-1.el7.*", packageName.toVersionLock()); + } + + @Test + public void parsePackageNameWithEpochAndArchitecture() { + YumPackageName packageName = YumPackageName.fromString("1:docker-engine-selinux-1.12.6-1.el7.x86_64"); + assertEquals("1", packageName.getEpoch().get()); + assertEquals("docker-engine-selinux", packageName.getName()); + assertEquals("1.12.6", packageName.getVersion().get()); + assertEquals("1.el7", packageName.getRelease().get()); + assertEquals("x86_64", packageName.getArchitecture().get()); + assertEquals("1:docker-engine-selinux-1.12.6-1.el7.x86_64", packageName.toFullName()); + assertEquals("1:docker-engine-selinux-1.12.6-1.el7.*", packageName.toVersionLock()); + } + + @Test(expected = IllegalArgumentException.class) + public void failParsingOfPackageName() { + YumPackageName.fromString("docker-engine-selinux"); + } + + @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(expected = IllegalArgumentException.class) + public void failParsingOfPackageNameWithEpochAndArchitecture() { + YumPackageName.fromString("epoch:docker-engine-selinux-1.12.6-1.el7.x86_64"); + } +}
\ 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 896914b7967..f2a2306263a 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 @@ -191,18 +191,4 @@ public class YumTest { yum.upgrade().converge(taskContext); } - - @Test - public void parsePackageName() { - Yum.PackageName packageName = Yum.PackageName.fromString("docker-engine-selinux-1.12.6-1.el7"); - assertEquals("docker-engine-selinux", packageName.getName()); - assertEquals("1.12.6", packageName.getVersion().get()); - assertEquals("1.el7", packageName.getRelease().get()); - assertEquals("0:docker-engine-selinux-1.12.6-1.el7.*", packageName.toFullName()); - } - - @Test(expected = IllegalArgumentException.class) - public void failParsingOfPackageName() { - Yum.PackageName.fromString("docker-engine-selinux"); - } }
\ No newline at end of file |