From 0e4f194e816fdee7531852c548acf3c1c87d8e41 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 3 Apr 2017 17:02:30 +0200 Subject: [PATCH] #44 - Added infrastructure to prevent passwords from being printed to the console. Introduced value objects to properly abstract command line arguments and introduced an interface Masked that allows masking a String value when printed to the console. --- .../data/release/build/CommandLine.java | 254 ++++++++++++++++++ .../data/release/build/MavenBuildSystem.java | 120 +++++---- .../data/release/build/MavenRuntime.java | 17 +- .../deployment/DeploymentProperties.java | 6 +- .../data/release/git/GitProperties.java | 7 +- .../data/release/model/Masked.java | 33 +++ .../data/release/model/Password.java | 56 ++++ .../release/utils/HttpBasicCredentials.java | 7 +- .../data/release/build/ArgumentUnitTest.java | 59 ++++ 9 files changed, 489 insertions(+), 70 deletions(-) create mode 100644 src/main/java/org/springframework/data/release/build/CommandLine.java create mode 100644 src/main/java/org/springframework/data/release/model/Masked.java create mode 100644 src/main/java/org/springframework/data/release/model/Password.java create mode 100644 src/test/java/org/springframework/data/release/build/ArgumentUnitTest.java diff --git a/src/main/java/org/springframework/data/release/build/CommandLine.java b/src/main/java/org/springframework/data/release/build/CommandLine.java new file mode 100644 index 0000000..3060e35 --- /dev/null +++ b/src/main/java/org/springframework/data/release/build/CommandLine.java @@ -0,0 +1,254 @@ +/* + * Copyright 2017 the original author or authors. + * + * 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.springframework.data.release.build; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Value; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.data.release.model.Masked; +import org.springframework.util.Assert; + +/** + * Value object to represent a Maven command line. + * + * @author Oliver Gierke + */ +@Value +class CommandLine { + + @NonNull List goals; + @NonNull List arguments; + + /** + * Creates a new {@link CommandLine} for the given {@link Goal} and {@link Argument}s. + * + * @param goal must not be {@literal null}. + * @param argument must not be {@literal null}. + * @return + */ + public static CommandLine of(Goal goal, Argument... argument) { + return new CommandLine(Collections.singletonList(goal), Arrays.asList(argument)); + } + + /** + * Creates a new {@link CommandLine} for the given {@link Goal}s and {@link Argument}s. + * + * @param goal must not be {@literal null}. + * @param argument must not be {@literal null}. + * @return + */ + public static CommandLine of(Goal first, Goal second, Argument... argument) { + return new CommandLine(Arrays.asList(first, second), Arrays.asList(argument)); + } + + /** + * Returns a new {@link CommandLine} with the given {@link Argument} added in case the given {@link BooleanSupplier} + * evaluates to {@literal true}. + * + * @param argument must not be {@literal null}. + * @param condition must not be {@literal null}. + * @return + */ + public CommandLine conditionalAnd(Argument argument, BooleanSupplier condition) { + return condition.getAsBoolean() ? and(argument) : this; + } + + /** + * Returns a new {@link CommandLine} with the given {@link Argument} added. + * + * @param argument must not be {@literal null}. + * @return + */ + public CommandLine and(Argument argument) { + + Assert.notNull(argument, "Argument must not be null!"); + + List newArguments = new ArrayList(arguments.size() + 1); + newArguments.addAll(arguments); + newArguments.add(argument); + + return new CommandLine(goals, newArguments); + } + + /** + * Renders the current {@link CommandLine} as a plain {@link List} of {@link String}s using the given {@link Function} + * to expand the {@link Goal}s. + * + * @param goalExpansion must not be {@literal null}. + * @return + */ + public List toCommandLine(Function goalExpansion) { + + Stream goalStream = goals.stream().map(goalExpansion); + Stream argumentStream = arguments.stream().map(it -> it.toCommandLineArgument()); + + return Stream.concat(goalStream, argumentStream).collect(Collectors.toList()); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + Stream goalStream = goals.stream().map(it -> it.getGoal()); + Stream argumentStream = arguments.stream().map(Object::toString); + + return Stream.concat(goalStream, argumentStream).collect(Collectors.joining(" ")); + } + + /** + * Represents a Maven goal to invoke. Can be a custom one but also one of the predefined instances. + * + * @author Oliver Gierke + */ + @Value(staticConstructor = "goal") + public static class Goal { + + public static final Goal CLEAN = Goal.goal("clean"); + public static final Goal INSTALL = Goal.goal("install"); + public static final Goal DEPLOY = Goal.goal("deploy"); + public static final Goal VALIDATE = Goal.goal("validate"); + + String goal; + } + + @Value + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public static class Argument { + + public static Argument SKIP_TESTS = Argument.arg("skipTests"); + + @NonNull String name; + @NonNull Optional> value; + + private Argument(String name, ArgumentValue value) { + this(name, Optional.of(value)); + } + + static Argument of(String name) { + return new Argument(name, Optional.empty()); + } + + /** + * Enables the given comma-separated profiles for the {@link CommandLine}. + * + * @param name must not be {@literal null} or empty. + * @return + */ + public static Argument profile(String name, String... others) { + + Assert.hasText(name, "Profiles must not be null or empty!"); + Assert.notNull(others, "Other profiles must not be null!"); + + String profiles = Stream.concat(Stream.of(name), Arrays.stream(others)).collect(Collectors.joining(",")); + + return Argument.of("-P".concat(profiles)); + } + + public static Argument arg(String name) { + return Argument.of("-D".concat(name)); + } + + public Argument withValue(Object value) { + return new Argument(name, ArgumentValue.of(value)); + } + + public Argument withQuotedValue(Object value) { + return new Argument(name, ArgumentValue.of(value, it -> String.format("\"%s\"", it.toString()))); + } + + public Argument withValue(Masked masked) { + return new Argument(name, ArgumentValue.of(masked)); + } + + public String toCommandLineArgument() { + return toNameValuePair(value.map(ArgumentValue::toCommandLine)); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return toNameValuePair(value.map(Object::toString)); + } + + private String toNameValuePair(Optional source) { + + return source// + .map(it -> String.format("%s=%s", name, it))// + .orElse(name); + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + private static class ArgumentValue { + + private final @NonNull T value; + private final @NonNull Optional> preparer; + private final @NonNull Optional> toString; + + public static ArgumentValue of(T value) { + return new ArgumentValue<>(value, Optional.empty(), Optional.empty()); + } + + public static ArgumentValue of(T value, Function preparer) { + return new ArgumentValue<>(value, Optional.of(preparer), Optional.empty()); + } + + /** + * Returns an {@link ArgumentValue} for the given {@link Masked} value. + * + * @param masked must not be {@literal null}. + * @return + */ + public static ArgumentValue of(T masked) { + return new ArgumentValue<>(masked, Optional.empty(), Optional.of(it -> it.masked())); + } + + /** + * Returns the {@link String} variant of the argument value. + * + * @return + */ + public String toCommandLine() { + return preparer.map(it -> it.apply(value)).orElseGet(() -> value.toString()); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + return toString.map(it -> it.apply(value)).orElseGet(() -> toCommandLine()); + } + } + } +} diff --git a/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java b/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java index 499f2f2..42d3687 100644 --- a/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java +++ b/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java @@ -15,6 +15,8 @@ */ package org.springframework.data.release.build; +import static org.springframework.data.release.build.CommandLine.Argument.*; +import static org.springframework.data.release.build.CommandLine.Goal.*; import static org.springframework.data.release.model.Projects.*; import lombok.AccessLevel; @@ -22,11 +24,11 @@ import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import java.io.File; -import java.util.ArrayList; -import java.util.List; import java.util.function.Consumer; import org.springframework.core.annotation.Order; +import org.springframework.data.release.build.CommandLine.Argument; +import org.springframework.data.release.build.CommandLine.Goal; import org.springframework.data.release.deployment.DefaultDeploymentInformation; import org.springframework.data.release.deployment.DeploymentInformation; import org.springframework.data.release.deployment.DeploymentProperties; @@ -109,7 +111,8 @@ class MavenBuildSystem implements BuildSystem { logger.log(project, "Triggering distribution build…"); - mvn.execute(project, "clean", "deploy", "-DskipTests", "-Pdistribute", "-B"); + mvn.execute(project, CommandLine.of(Goal.CLEAN, Goal.DEPLOY, // + SKIP_TESTS, profile("distribute"), Argument.of("-B"))); logger.log(project, "Successfully finished distribution build!"); @@ -186,17 +189,8 @@ class MavenBuildSystem implements BuildSystem { logger.log(project, "Triggering distribution build…"); - ArtifactVersion version = ArtifactVersion.of(module); - - String profile = "-Pdistribute"; - - if (version.isMilestoneVersion()) { - profile = profile.concat(",milestone"); - } else if (version.isReleaseVersion()) { - profile = profile.concat(",release"); - } - - mvn.execute(project, "clean", "deploy", "-DskipTests", profile); + mvn.execute(project, + CommandLine.of(Goal.CLEAN, Goal.DEPLOY, ReleaseVersion.of(module).getDistributionProfiles(), SKIP_TESTS)); logger.log(project, "Successfully finished distribution build!"); }); @@ -212,17 +206,18 @@ class MavenBuildSystem implements BuildSystem { Project project = module.getProject(); UpdateInformation information = UpdateInformation.of(module.getTrainIteration(), phase); - mvn.execute(project, "versions:set", "versions:commit", - "-DnewVersion=".concat(information.getProjectVersionToSet(project).toString())); + CommandLine goals = CommandLine.of(goal("versions:set"), goal("versions:commit")); + + mvn.execute(project, goals.and(arg("newVersion").withValue(information.getProjectVersionToSet(project)))); if (BUILD.equals(project)) { - mvn.execute(project, "versions:set", // - "-DnewVersion=".concat(information.getReleaseTrainVersion()), // - "-DgroupId=org.springframework.data", // - "-DartifactId=spring-data-releasetrain"); + mvn.execute(project, + goals.and(arg("newVersion").withValue(information.getReleaseTrainVersion()))// + .and(arg("groupId").withValue("org.springframework.data"))// + .and(arg("artifactId").withValue("spring-data-releasetrain"))); - mvn.execute(project, "install"); + mvn.execute(project, CommandLine.of(Goal.INSTALL)); } return module; @@ -252,13 +247,8 @@ class MavenBuildSystem implements BuildSystem { @Override public ModuleIteration triggerBuild(ModuleIteration module) { - List arguments = new ArrayList<>(); - arguments.add("clean"); - arguments.add("install"); - - if (module.getProject().skipTests()) { - arguments.add("-DskipTests"); - } + CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.INSTALL)// + .conditionalAnd(SKIP_TESTS, () -> module.getProject().skipTests()); mvn.execute(module.getProject(), arguments); @@ -271,7 +261,7 @@ class MavenBuildSystem implements BuildSystem { */ public ModuleIteration triggerPreReleaseCheck(ModuleIteration module) { - mvn.execute(module.getProject(), "clean", "validate", "-Ppre-release"); + mvn.execute(module.getProject(), CommandLine.of(Goal.CLEAN, Goal.VALIDATE, profile("pre-release"))); return module; } @@ -298,18 +288,15 @@ class MavenBuildSystem implements BuildSystem { logger.log(module, "Deploying artifacts to Spring Artifactory…"); - List arguments = new ArrayList<>(); - arguments.add("clean"); - arguments.add("deploy"); - - arguments.add("-Pci,release"); - arguments.add("-DskipTests"); - arguments.add("-Dartifactory.server=".concat(properties.getServer().getUri())); - arguments.add("-Dartifactory.staging-repository=".concat(properties.getStagingRepository())); - arguments.add("-Dartifactory.username=".concat(properties.getUsername())); - arguments.add("-Dartifactory.password=".concat(properties.getPassword())); - arguments.add("-Dartifactory.build-name=\"".concat(information.getBuildName()).concat("\"")); - arguments.add("-Dartifactory.build-number=".concat(information.getBuildNumber())); + CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.DEPLOY, // + profile("ci,release"), // + SKIP_TESTS, // + arg("artifactory.server").withValue(properties.getServer().getUri()), + arg("artifactory.staging-repository").withValue(properties.getStagingRepository()), + arg("artifactory.username").withValue(properties.getUsername()), + arg("artifactory.password").withValue(properties.getPassword()), + arg("artifactory.build-name").withQuotedValue(information.getBuildName()), + arg("artifactory.build-number").withValue(information.getBuildNumber())); mvn.execute(module.getProject(), arguments); } @@ -332,16 +319,13 @@ class MavenBuildSystem implements BuildSystem { logger.log(module, "Deploying artifacts to Sonatype OSS Nexus…"); - List arguments = new ArrayList<>(); - arguments.add("deploy"); - arguments.add("-Pci,central"); - arguments.add("-DskipTests"); - Gpg gpg = properties.getGpg(); - arguments.add("-Dgpg.executable=".concat(gpg.getExecutable())); - arguments.add("-Dgpg.keyname=".concat(gpg.getKeyname())); - arguments.add("-Dgpg.password=".concat(gpg.getPassword())); + CommandLine arguments = CommandLine.of(Goal.DEPLOY, // + profile("ci,central"), // + SKIP_TESTS, // + arg("gpg.executable").withValue(gpg.getExecutable()), arg("gpg.keyname").withValue(gpg.getKeyname()), + arg("gpg.password").withValue(gpg.getPassword())); mvn.execute(module.getProject(), arguments); } @@ -371,4 +355,42 @@ class MavenBuildSystem implements BuildSystem { throw new RuntimeException(o_O); } } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + private static class ReleaseVersion { + + private final ArtifactVersion version; + + /** + * Creates a new {@link ReleaseVersion} for the given {@link ModuleIteration}. + * + * @param module must not be {@literal null}. + * @return + */ + public static ReleaseVersion of(ModuleIteration module) { + + ArtifactVersion artifactVersion = ArtifactVersion.of(module); + + Assert.isTrue(artifactVersion.isMilestoneVersion() || artifactVersion.isReleaseVersion(), + String.format("Given module is not in a fixed version, detected %s!", artifactVersion)); + + return new ReleaseVersion(ArtifactVersion.of(module)); + } + + /** + * Returns the Maven profiles to be used during the distribution build. + * + * @return + */ + public Argument getDistributionProfiles() { + + if (version.isMilestoneVersion()) { + return profile("distribute", "milestone"); + } else if (version.isReleaseVersion()) { + return profile("distribute", "release"); + } + + throw new IllegalStateException("Should not occur!"); + } + } } diff --git a/src/main/java/org/springframework/data/release/build/MavenRuntime.java b/src/main/java/org/springframework/data/release/build/MavenRuntime.java index 30678a2..73e0492 100644 --- a/src/main/java/org/springframework/data/release/build/MavenRuntime.java +++ b/src/main/java/org/springframework/data/release/build/MavenRuntime.java @@ -18,9 +18,6 @@ package org.springframework.data.release.build; import lombok.extern.slf4j.Slf4j; import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; import org.apache.maven.shared.invoker.DefaultInvocationRequest; import org.apache.maven.shared.invoker.DefaultInvoker; @@ -75,24 +72,16 @@ class MavenRuntime { } } - public void execute(Project project, String... arguments) { - execute(project, Arrays.asList(arguments)); - } - - public void execute(Project project, List arguments) { + public void execute(Project project, CommandLine arguments) { DefaultInvocationRequest request = new DefaultInvocationRequest(); request.setJavaHome(os.getJavaHome()); request.setShellEnvironmentInherited(true); request.setBaseDirectory(workspace.getProjectDirectory(project)); - List goals = arguments.stream()// - .map(argument -> properties.getFullyQualifiedPlugin(argument))// - .collect(Collectors.toList()); + logger.log(project, "Executing mvn %s", arguments.toString()); - request.setGoals(goals); - - logger.log(project, "Executing mvn %s", goals.stream().collect(Collectors.joining(" "))); + request.setGoals(arguments.toCommandLine(it -> properties.getFullyQualifiedPlugin(it.getGoal()))); try { diff --git a/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java b/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java index e4e00c8..f29fa66 100644 --- a/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java +++ b/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java @@ -20,6 +20,7 @@ import lombok.Data; import java.net.URI; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.data.release.model.Password; import org.springframework.data.release.utils.HttpBasicCredentials; import org.springframework.stereotype.Component; import org.springframework.util.Assert; @@ -48,7 +49,7 @@ public class DeploymentProperties { /** * The deployer's password. */ - private String password; + private Password password; /** * The repository to deploy the artifacts to. @@ -110,6 +111,7 @@ public class DeploymentProperties { @Data public static class Gpg { - private String keyname, password, executable; + private String keyname, executable; + private Password password; } } diff --git a/src/main/java/org/springframework/data/release/git/GitProperties.java b/src/main/java/org/springframework/data/release/git/GitProperties.java index 576472a..2c58103 100644 --- a/src/main/java/org/springframework/data/release/git/GitProperties.java +++ b/src/main/java/org/springframework/data/release/git/GitProperties.java @@ -24,6 +24,7 @@ import javax.annotation.PostConstruct; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.data.release.model.Password; import org.springframework.data.release.utils.HttpBasicCredentials; import org.springframework.stereotype.Component; import org.springframework.util.Assert; @@ -39,14 +40,14 @@ import org.springframework.util.Assert; @ConfigurationProperties(prefix = "git") public class GitProperties { - private @Getter(AccessLevel.PRIVATE) String password; + private @Getter(AccessLevel.PRIVATE) Password password; private String username, author, email; @PostConstruct public void init() { Assert.hasText(username, "No GitHub username (git.username) configured!"); - Assert.hasText(password, "No GitHub password (git.password) configured!"); + Assert.notNull(password, "No GitHub password (git.password) configured!"); Assert.hasText(author, "No Git author (git.author) configured!"); Assert.hasText(email, "No Git email (git.email) configured!"); } @@ -57,7 +58,7 @@ public class GitProperties { * @return */ public CredentialsProvider getCredentials() { - return new UsernamePasswordCredentialsProvider(username, password); + return new UsernamePasswordCredentialsProvider(username, password.toString()); } public HttpBasicCredentials getHttpCredentials() { diff --git a/src/main/java/org/springframework/data/release/model/Masked.java b/src/main/java/org/springframework/data/release/model/Masked.java new file mode 100644 index 0000000..4499123 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Masked.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 the original author or authors. + * + * 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.springframework.data.release.model; + +/** + * Interface to exposes a method {@link #masked()} to hide an actual value (usually for security reasons). + * + * @author Oliver Gierke + */ +public interface Masked { + + /** + * Returns a masked {@link String} as well as information about the type whose String representation was masked. + * + * @return + */ + default String masked() { + return String.format("******** (%s)", getClass().getSimpleName()); + } +} diff --git a/src/main/java/org/springframework/data/release/model/Password.java b/src/main/java/org/springframework/data/release/model/Password.java new file mode 100644 index 0000000..e06b736 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Password.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017 the original author or authors. + * + * 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.springframework.data.release.model; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Value; + +import org.springframework.util.Assert; + +/** + * Value object to represent a password. + * + * @author Oliver Gierke + */ +@Value +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class Password implements Masked { + + @Getter(AccessLevel.NONE) String value; + + /** + * Create a new {@link Password} for the given value. + * + * @param password + * @return + */ + public static Password of(String password) { + + Assert.hasText(password, "Password must not be null or empty!"); + return new Password(password); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/org/springframework/data/release/utils/HttpBasicCredentials.java b/src/main/java/org/springframework/data/release/utils/HttpBasicCredentials.java index 1c3c702..68d86ff 100644 --- a/src/main/java/org/springframework/data/release/utils/HttpBasicCredentials.java +++ b/src/main/java/org/springframework/data/release/utils/HttpBasicCredentials.java @@ -20,13 +20,16 @@ import lombok.Value; import java.nio.charset.StandardCharsets; import java.util.Base64; +import org.springframework.data.release.model.Password; + /** * @author Oliver Gierke */ @Value public class HttpBasicCredentials { - private final String username, password; + private final String username; + private final Password password; /* * (non-Javadoc) @@ -34,7 +37,7 @@ public class HttpBasicCredentials { */ public String toString() { - String header = username.concat(":").concat(password); + String header = username.concat(":").concat(password.toString()); byte[] encodedAuth = Base64.getEncoder().encode(header.getBytes(StandardCharsets.US_ASCII)); return "Basic ".concat(new String(encodedAuth)); diff --git a/src/test/java/org/springframework/data/release/build/ArgumentUnitTest.java b/src/test/java/org/springframework/data/release/build/ArgumentUnitTest.java new file mode 100644 index 0000000..98c72f5 --- /dev/null +++ b/src/test/java/org/springframework/data/release/build/ArgumentUnitTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2017 the original author or authors. + * + * 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.springframework.data.release.build; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.release.build.CommandLine.Argument; +import org.springframework.data.release.model.Password; + +/** + * Unit tests for {@link Argument}. + * + * @author Oliver Gierke + */ +public class ArgumentUnitTest { + + @Test + public void masksMaskedForToString() { + + Argument argument = Argument.of("password").withValue(Password.of("password")); + + assertThat(argument.toCommandLineArgument(), is("password=password")); + assertThat(argument.toString(), startsWith("password=********")); + } + + @Test + public void quotesValue() { + + Argument argument = Argument.of("quoted").withQuotedValue("value"); + + assertThat(argument.toCommandLineArgument(), is("quoted=\"value\"")); + } + + @Test + public void argCreatesJvmArgument() { + assertThat(Argument.arg("foo").toCommandLineArgument(), is("-Dfoo")); + } + + @Test + public void profileCreatesMavenProfile() { + assertThat(Argument.profile("foo").toCommandLineArgument(), is("-Pfoo")); + assertThat(Argument.profile("foo", "bar").toCommandLineArgument(), is("-Pfoo,bar")); + } +}