Skip to content

Commit

Permalink
Support output format for list option
Browse files Browse the repository at this point in the history
Direct cli user messages to standard error rather than standard out. Standard out
is reserved for primary output, which in cases like `--list --output yaml` would
be the yaml formatted plugin list. This separation of output is consistent with
general cli guidelines, https://clig.dev/#the-basics.

Fixes: #377 #284 #227
  • Loading branch information
cronik committed Jun 21, 2022
1 parent 1a3ab36 commit ddd7b54
Show file tree
Hide file tree
Showing 21 changed files with 743 additions and 332 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class CliOptions {
private boolean showAvailableUpdates;

@Option(name = "--output", usage = "Output format for available updates", aliases = "-o")
private OutputFormat outputFormat;
private OutputFormat outputFormat = OutputFormat.STDOUT;

@Option(name = "--view-security-warnings",
usage = "Show if any security warnings exist for the requested plugins",
Expand Down Expand Up @@ -177,6 +177,7 @@ Config setup() {
.build();
}

@NonNull
public OutputFormat getOutputFormat() {
return outputFormat;
}
Expand All @@ -189,14 +190,10 @@ public OutputFormat getOutputFormat() {
*/
private File getPluginFile() {
if (pluginFile == null) {
if (verbose) {
System.out.println("No .txt or .yaml file containing list of plugins to be downloaded entered.");
}
logVerbose("No .txt or .yaml file containing list of plugins to be downloaded entered.");
} else {
if (Files.exists(pluginFile.toPath())) {
if (verbose) {
System.out.println("File containing list of plugins to be downloaded: " + pluginFile);
}
logVerbose("File containing list of plugins to be downloaded: " + pluginFile);
} else {
throw new PluginInputException("File containing list of plugins does not exist " + pluginFile.toPath());
}
Expand All @@ -210,21 +207,14 @@ private File getPluginFile() {
@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "we want the user to be able to specify a path")
private File getPluginDir() {
if (pluginDir != null) {
if (verbose) {
System.out.println("Plugin download location: " + pluginDir);
}
logVerbose("Plugin download location: " + pluginDir);
return pluginDir;
} else if (!StringUtils.isEmpty(System.getenv("PLUGIN_DIR"))) {
if (verbose) {
System.out.println("No directory to download plugins entered. " +
"Will use location specified in PLUGIN_DIR environment variable: " + System.getenv("PLUGIN_DIR"));
}
logVerbose("No directory to download plugins entered. " +
"Will use location specified in PLUGIN_DIR environment variable: " + System.getenv("PLUGIN_DIR"));
return new File(System.getenv("PLUGIN_DIR"));
}
if (verbose) {
System.out.println("No directory to download plugins entered. " +
"Will use default of " + Settings.DEFAULT_PLUGIN_DIR_LOCATION);
}
logVerbose("No directory to download plugins entered. Will use default of " + Settings.DEFAULT_PLUGIN_DIR_LOCATION);
return new File(Settings.DEFAULT_PLUGIN_DIR_LOCATION);
}

Expand Down Expand Up @@ -255,14 +245,10 @@ private VersionNumber getJenkinsVersion() {
*/
private String getJenkinsWar() {
if (jenkinsWarFile == null) {
if (verbose) {
System.out.println("No war entered. Will use default of " + Settings.DEFAULT_WAR);
}
logVerbose("No war entered. Will use default of " + Settings.DEFAULT_WAR);
return Settings.DEFAULT_WAR;
} else {
if (verbose) {
System.out.println("Will use war file: " + jenkinsWarFile);
}
logVerbose("Will use war file: " + jenkinsWarFile);
return jenkinsWarFile;
}
}
Expand Down Expand Up @@ -350,22 +336,15 @@ private URL getUpdateCenter() {
try {
if (jenkinsUc != null) {
jenkinsUpdateCenter = new URL(appendFilePathIfNotPresent(jenkinsUc.toString()));
if (verbose) {
System.out.println("Using update center " + jenkinsUpdateCenter + " specified with CLI option");
}
logVerbose("Using update center " + jenkinsUpdateCenter + " specified with CLI option");
} else {
String jenkinsUcFromEnv = System.getenv("JENKINS_UC");
if (!StringUtils.isEmpty(jenkinsUcFromEnv)) {
jenkinsUpdateCenter = new URL(appendFilePathIfNotPresent(jenkinsUcFromEnv));
if (verbose) {
System.out.println("Using update center " + jenkinsUpdateCenter + " from JENKINS_UC environment variable");
}
logVerbose("Using update center " + jenkinsUpdateCenter + " from JENKINS_UC environment variable");
} else {
jenkinsUpdateCenter = Settings.DEFAULT_UPDATE_CENTER;
if (verbose) {
System.out.println("No CLI option or environment variable set for update center, using default of " +
jenkinsUpdateCenter);
}
logVerbose("No CLI option or environment variable set for update center, using default of " + jenkinsUpdateCenter);
}
}
} catch (MalformedURLException e) {
Expand Down Expand Up @@ -397,23 +376,14 @@ private URL getExperimentalUpdateCenter() {
try {
if (jenkinsUcExperimental != null) {
experimentalUpdateCenter = new URL(appendFilePathIfNotPresent(jenkinsUcExperimental.toString()));
if (verbose) {
System.out.println(
"Using experimental update center " + experimentalUpdateCenter + " specified with CLI option");
}
logVerbose("Using experimental update center " + experimentalUpdateCenter + " specified with CLI option");
} else if (!StringUtils.isEmpty(System.getenv("JENKINS_UC_EXPERIMENTAL"))) {
experimentalUpdateCenter = new URL(appendFilePathIfNotPresent(System.getenv("JENKINS_UC_EXPERIMENTAL")));
if (verbose) {
System.out.println("Using experimental update center " + experimentalUpdateCenter +
" from JENKINS_UC_EXPERIMENTAL environment variable");
}
logVerbose("Using experimental update center " + experimentalUpdateCenter + " from JENKINS_UC_EXPERIMENTAL environment variable");
} else {
experimentalUpdateCenter = Settings.DEFAULT_EXPERIMENTAL_UPDATE_CENTER;
if (verbose) {
System.out.println(
"No CLI option or environment variable set for experimental update center, using default of " +
experimentalUpdateCenter);
}
logVerbose("No CLI option or environment variable set for experimental update center, using default of " +
experimentalUpdateCenter);
}
} catch (MalformedURLException e) {
/* Spotbugs 4.7.0 warns when throwing a runtime exception,
Expand All @@ -436,9 +406,7 @@ private URL getIncrementalsMirror() {
URL jenkinsIncrementalsRepo;
if (jenkinsIncrementalsRepoMirror != null) {
jenkinsIncrementalsRepo = jenkinsIncrementalsRepoMirror;
if (verbose) {
System.out.println("Using incrementals mirror " + jenkinsIncrementalsRepo + " specified with CLI option");
}
logVerbose("Using incrementals mirror " + jenkinsIncrementalsRepo + " specified with CLI option");
} else if (!StringUtils.isEmpty(System.getenv("JENKINS_INCREMENTALS_REPO_MIRROR"))) {
try {
jenkinsIncrementalsRepo = new URL(System.getenv("JENKINS_INCREMENTALS_REPO_MIRROR"));
Expand All @@ -449,16 +417,13 @@ private URL getIncrementalsMirror() {
*/
throw new RuntimeException(e);
}
if (verbose) {
System.out.println("Using incrementals mirror " + jenkinsIncrementalsRepo +
" from JENKINS_INCREMENTALS_REPO_MIRROR environment variable");
}

logVerbose("Using incrementals mirror " + jenkinsIncrementalsRepo +
" from JENKINS_INCREMENTALS_REPO_MIRROR environment variable");
} else {
jenkinsIncrementalsRepo = Settings.DEFAULT_INCREMENTALS_REPO_MIRROR;
if (verbose) {
System.out.println("No CLI option or environment variable set for incrementals mirror, using default of " +
jenkinsIncrementalsRepo);
}
logVerbose("No CLI option or environment variable set for incrementals mirror, using default of " +
jenkinsIncrementalsRepo);
}
return jenkinsIncrementalsRepo;
}
Expand All @@ -474,9 +439,7 @@ private URL getPluginInfo() {
URL pluginInfo;
if (jenkinsPluginInfo != null) {
pluginInfo = jenkinsPluginInfo;
if (verbose) {
System.out.println("Using plugin info " + jenkinsPluginInfo + " specified with CLI option");
}
logVerbose("Using plugin info " + jenkinsPluginInfo + " specified with CLI option");
} else if (!StringUtils.isEmpty(System.getenv("JENKINS_PLUGIN_INFO"))) {
try {
pluginInfo = new URL(System.getenv("JENKINS_PLUGIN_INFO"));
Expand All @@ -487,16 +450,11 @@ private URL getPluginInfo() {
*/
throw new RuntimeException(e);
}
if (verbose) {
System.out.println("Using plugin info " + pluginInfo +
" from JENKINS_PLUGIN_INFO environment variable");
}

logVerbose("Using plugin info " + pluginInfo + " from JENKINS_PLUGIN_INFO environment variable");
} else {
pluginInfo = Settings.DEFAULT_PLUGIN_INFO;
if (verbose) {
System.out.println("No CLI option or environment variable set for plugin info, using default of " +
pluginInfo);
}
logVerbose("No CLI option or environment variable set for plugin info, using default of " + pluginInfo);
}
return pluginInfo;
}
Expand Down Expand Up @@ -600,4 +558,16 @@ private HashFunction getHashFunction() {
}
}

/**
* Outputs information to the console (std err) if verbose option was set to true
*
* @param message informational string to output
*/
private void logVerbose(String message) {
if (verbose) {
// log to stderr to not interfere with primary cli output sent to stdout
System.err.println(message);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.jenkins.tools.pluginmanager.config.Config;
import io.jenkins.tools.pluginmanager.config.OutputFormat;
import io.jenkins.tools.pluginmanager.impl.Plugin;
import io.jenkins.tools.pluginmanager.impl.PluginManager;
import io.jenkins.tools.pluginmanager.parsers.StdOutPluginOutputConverter;
import io.jenkins.tools.pluginmanager.parsers.TxtOutputConverter;
import io.jenkins.tools.pluginmanager.parsers.YamlPluginOutputConverter;
import io.jenkins.tools.pluginmanager.parsers.AvailableUpdatesStdOutPluginOutputConverter;
import java.io.IOException;
import java.util.List;
import org.kohsuke.args4j.CmdLineException;
Expand Down Expand Up @@ -47,27 +44,13 @@ public static void main(String[] args) throws IOException {

Config cfg = options.setup();
try (PluginManager pm = new PluginManager(cfg)) {

if (options.isShowAvailableUpdates()) {
pm.getUCJson(pm.getJenkinsVersion());
List<Plugin> latestVersionsOfPlugins = pm.getLatestVersionsOfPlugins(cfg.getPlugins());
OutputFormat outputFormat = options.getOutputFormat() == null ? OutputFormat.STDOUT : options.getOutputFormat();
String output;
switch (outputFormat) {
case YAML:
output = new YamlPluginOutputConverter().convert(latestVersionsOfPlugins);
break;
case TXT:
output = new TxtOutputConverter().convert(latestVersionsOfPlugins);
break;
default:
output = new StdOutPluginOutputConverter(cfg.getPlugins()).convert(latestVersionsOfPlugins);
}
System.out.println(output);
return;
pm.outputPluginList(latestVersionsOfPlugins, () -> new AvailableUpdatesStdOutPluginOutputConverter(cfg.getPlugins()));
} else {
pm.start();
}

pm.start();
}
} catch (Exception e) {
if (options.isVerbose()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protected VersionNumber parse(String argument) throws NumberFormatException, Cmd
try {
return new VersionNumber(argument);
} catch (Exception ex) {
throw new CmdLineException("Failed to parse the version number", ex);
throw new CmdLineException(owner, "Failed to parse the version number", ex);
}
}

Expand Down
11 changes: 11 additions & 0 deletions plugin-management-cli/src/test/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# From 'plugin-management-cli' dir:
# build:
# docker build -t jnks-plugin-tool -f src/test/docker/Dockerfile .
# run:
# docker run --rm -it jnks-plugin-tool
# docker run --rm -it --entrypoint bash jnks-plugin-tool
#
FROM jenkins/jenkins:lts
COPY target/jenkins-plugin-manager-*.jar /opt/jenkins-plugin-manager.jar
WORKDIR $JENKINS_HOME
ENTRYPOINT ["java", "-jar", "/opt/jenkins-plugin-manager.jar"]
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import hudson.util.VersionNumber;
import io.jenkins.tools.pluginmanager.config.Config;
import io.jenkins.tools.pluginmanager.config.Credentials;
import io.jenkins.tools.pluginmanager.config.OutputFormat;
import io.jenkins.tools.pluginmanager.config.PluginInputException;
import io.jenkins.tools.pluginmanager.config.Settings;
import io.jenkins.tools.pluginmanager.impl.Plugin;
Expand All @@ -20,6 +21,7 @@
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemErrNormalized;
import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOutNormalized;
import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable;
import static java.nio.charset.StandardCharsets.UTF_8;
Expand Down Expand Up @@ -111,7 +113,7 @@ public void setupAliasTest() throws CmdLineException, IOException, URISyntaxExce
}

@Test
public void setupPluginsTest() throws CmdLineException, IOException, URISyntaxException {
public void setupPluginsTest() throws Exception {
File pluginTxtFile = new File(this.getClass().getResource("/plugins.txt").toURI());

File pluginFile = temporaryFolder.newFile("plugins.txt");
Expand All @@ -125,9 +127,13 @@ public void setupPluginsTest() throws CmdLineException, IOException, URISyntaxEx
requestedPlugins.add(new Plugin("mailer", "latest", null, null));
requestedPlugins.add(new Plugin("cobertura", "experimental", null, null));

Config cfg = options.setup();
String stdOut = tapSystemOutNormalized(() -> {
Config cfg = options.setup();

assertConfigHasPlugins(cfg, requestedPlugins);
assertConfigHasPlugins(cfg, requestedPlugins);
});

assertThat(stdOut).isEmpty();
}


Expand Down Expand Up @@ -347,6 +353,54 @@ public void invalidCredentialsTest() {
.isInstanceOf(CmdLineException.class).hasMessageContaining("Require at least a value containing 2 colons but found \"myhost:myuser\". The value must adhere to the grammar \"<host>[:port]:<username>:<password>\"");
}

@Test
public void outputFormatDefaultTest() throws CmdLineException {
parser.parseArgument("--plugins", "foo");

assertThat(options.getOutputFormat()).isEqualTo(OutputFormat.STDOUT);
Config cfg = options.setup();
assertThat(cfg.getOutputFormat()).isEqualTo(OutputFormat.STDOUT);
}

@Test
public void outputFormatTest() throws CmdLineException {
parser.parseArgument("--plugins", "foo", "--output", "yaml");

assertThat(options.getOutputFormat()).isEqualTo(OutputFormat.YAML);
Config cfg = options.setup();
assertThat(cfg.getOutputFormat()).isEqualTo(OutputFormat.YAML);
}

@Test
public void verboseTest() throws Exception {
parser.parseArgument("--plugins", "foo", "--verbose");

String stdOut = tapSystemOutNormalized(() -> {
options.setup();
});
assertThat(stdOut).isEmpty();

String stdErr = tapSystemErrNormalized(() -> {
options.setup();
});
assertThat(stdErr).isNotEmpty();
}

@Test
public void verboseDisabledTest() throws Exception {
parser.parseArgument("--plugins", "foo");

String stdOut = tapSystemOutNormalized(() -> {
options.setup();
});
assertThat(stdOut).isEmpty();

String stdErr = tapSystemErrNormalized(() -> {
options.setup();
});
assertThat(stdErr).isEmpty();
}

private void assertConfigHasPlugins(Config cfg, List<Plugin> expectedPlugins) {
Plugin[] expectedPluginsAsArray = expectedPlugins.toArray(new Plugin[0]);
assertThat(cfg.getPlugins()).containsExactlyInAnyOrder(expectedPluginsAsArray);
Expand Down
Loading

0 comments on commit ddd7b54

Please sign in to comment.