Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to set Basic credentials #292

Merged
merged 10 commits into from
Mar 2, 2021
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ jenkins-plugin-cli --plugin-file /your/path/to/plugins.txt --plugins delivery-pi
* `--no-download`: (optional) Set to true to not download plugins. By default it is set to false and plugins will be downloaded.
* `--skip-failed-plugins`: (optional) Adds the option to skip plugins that fail to download - CAUTION should be used when passing this flag as it could leave
Jenkins in a broken state.
* `--credentials`: (optional) Comma-separated list of credentials to use for Basic Authentication for specific hosts (and optionally ports). Each value must adhere to format `<host>[:port]:<username>:<password>`. The password must not contain a `,`! The credentials are not used preemptively.

#### Advanced configuration

Expand Down
3 changes: 3 additions & 0 deletions plugin-management-cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@
<directory>src/main/resources-filtered</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor Author

@kwin kwin Mar 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Messages.properties doesn't need to be filtered. Can move it to src/main/resources-filtered but this has the overhead of unnecessary filtering...

</resource>
</resources>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
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;
Expand Down Expand Up @@ -140,6 +141,10 @@ class CliOptions {
handler = BooleanOptionHandler.class)
private boolean skipFailedPlugins;

@Option(name = "--credentials", usage = "Comma-separated list of credentials in format '<host>[:port]:<username>:<password>'. The password must not contain space or ','",
handler = MultiCredentialsOptionHandler.class)
private List<Credentials> credentials;

/**
* Creates a configuration class with configurations specified from the CLI and/or environment variables.
*
Expand All @@ -166,6 +171,7 @@ Config setup() {
.withUseLatestSpecified(isUseLatestSpecified())
.withUseLatestAll(isUseLatestAll())
.withSkipFailedPlugins(isSkipFailedPlugins())
.withCredentials(credentials)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.jenkins.tools.pluginmanager.cli;

import io.jenkins.tools.pluginmanager.config.Credentials;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.OptionDef;
import org.kohsuke.args4j.spi.OneArgumentOptionHandler;
import org.kohsuke.args4j.spi.Setter;

public class CredentialsOptionHandler extends OneArgumentOptionHandler<Credentials> {

public CredentialsOptionHandler(CmdLineParser parser, OptionDef option, Setter<? super Credentials> setter) {
super(parser, option, setter);
}

@Override
protected Credentials parse(String argument) throws CmdLineException {
final Credentials option;
// extract the 4 pieces: username, password, host, port
final String[] parts = argument.split(":", 4);
if (parts.length < 3) {
throw new CmdLineException(owner, Messages.INVALID_CREDENTIALS_VALUE, argument);
}
if (parts.length == 3) {
option = new Credentials(parts[1], parts[2], parts[0]);
} else {
int port = Integer.parseInt(parts[1]);
option = new Credentials(parts[2], parts[3], parts[0], port);
}
return option;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.jenkins.tools.pluginmanager.cli;

import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;
import org.kohsuke.args4j.Localizable;

enum Messages implements Localizable {
INVALID_CREDENTIALS_VALUE;

public String formatWithLocale(Locale locale, Object... args) {
ResourceBundle localized = ResourceBundle.getBundle(Messages.class.getName(), locale);
return MessageFormat.format(localized.getString(name()),args);
}

public String format(Object... args) {
return formatWithLocale(Locale.getDefault(),args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.jenkins.tools.pluginmanager.cli;

import io.jenkins.tools.pluginmanager.config.Credentials;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.OptionDef;
import org.kohsuke.args4j.spi.DelimitedOptionHandler;
import org.kohsuke.args4j.spi.Setter;

public class MultiCredentialsOptionHandler extends DelimitedOptionHandler<Credentials> {

public MultiCredentialsOptionHandler(CmdLineParser parser, OptionDef option, Setter<? super Credentials> setter) {
super(parser, option, setter, ",", new CredentialsOptionHandler(parser, option, setter));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INVALID_CREDENTIALS_VALUE = \
Require at least a value containing 2 colons but found "{0}". The value must adhere to the grammar "<host>[:port]:<username>:<password>"
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,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.PluginInputException;
import io.jenkins.tools.pluginmanager.config.Settings;
import io.jenkins.tools.pluginmanager.impl.Plugin;
Expand Down Expand Up @@ -328,6 +329,22 @@ public void useLatestSpecifiedAndLatestAllTest() throws CmdLineException {
.isFalse();
}

@Test
public void credentialsTest() throws CmdLineException {
parser.parseArgument("--credentials", "myhost:myuser:mypass,myhost2:1234:myuser2:mypass2");

Config cfg = options.setup();
assertThat(cfg.getCredentials()).containsExactly(
new Credentials("myuser", "mypass", "myhost"),
new Credentials("myuser2", "mypass2", "myhost2", 1234));
}

@Test
public void invalidCredentialsTest() throws CmdLineException {
assertThatThrownBy(() -> parser.parseArgument("--credentials", "myhost:myuser,myhost2:1234:myuser2:mypass2"))
.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>\"");
}

private void assertConfigHasPlugins(Config cfg, List<Plugin> expectedPlugins) {
Plugin[] expectedPluginsAsArray = expectedPlugins.toArray(new Plugin[0]);
assertThat(cfg.getPlugins()).containsExactlyInAnyOrder(expectedPluginsAsArray);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.CheckForNull;

Expand Down Expand Up @@ -47,6 +48,7 @@ public class Config {
private boolean useLatestAll;
private boolean skipFailedPlugins;
private final OutputFormat outputFormat;
private final List<Credentials> credentials;

private Config(
File pluginDir,
Expand All @@ -67,7 +69,8 @@ private Config(
boolean useLatestSpecified,
boolean useLatestAll,
boolean skipFailedPlugins,
OutputFormat outputFormat) {
OutputFormat outputFormat,
List<Credentials> credentials) {
this.pluginDir = pluginDir;
this.cleanPluginDir = cleanPluginDir;
this.showWarnings = showWarnings;
Expand All @@ -87,6 +90,7 @@ private Config(
this.useLatestAll = useLatestAll;
this.skipFailedPlugins = skipFailedPlugins;
this.outputFormat = outputFormat;
this.credentials = credentials;
}

public File getPluginDir() {
Expand Down Expand Up @@ -166,6 +170,10 @@ public boolean isSkipFailedPlugins() {
return skipFailedPlugins;
}

public List<Credentials> getCredentials() {
return credentials;
}

public static Builder builder() {
return new Builder();
}
Expand All @@ -190,6 +198,7 @@ public static class Builder {
private boolean useLatestAll;
private boolean skipFailedPlugins;
private OutputFormat outputFormat = OutputFormat.STDOUT;
private List<Credentials> credentials = Collections.emptyList();

private Builder() {
}
Expand Down Expand Up @@ -294,6 +303,12 @@ public Builder withOutputFormat(OutputFormat outputFormat) {
return this;
}


public Builder withCredentials(List<Credentials> credentials) {
this.credentials = credentials;
return this;
}

public Config build() {
return new Config(
pluginDir,
Expand All @@ -314,8 +329,10 @@ public Config build() {
useLatestSpecified,
useLatestAll,
skipFailedPlugins,
outputFormat
outputFormat,
credentials
);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package io.jenkins.tools.pluginmanager.config;

public class Credentials {

private final String username;
private final String password;
private final String host;
private final int port;

public Credentials(String username, String password, String host, int port) {
this.username = username;
this.password = password;
this.host = host;
this.port = port;
}

public Credentials(String username, String password, String host) {
this(username, password, host, -1);
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public String getHost() {
return host;
}

public int getPort() {
return port;
}

@Override
public String toString() {
return "Credentials [username=" + username + ", password=***, host=" + host + ", port=" + port + "]";
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + ((password == null) ? 0 : password.hashCode());
result = prime * result + port;
result = prime * result + ((username == null) ? 0 : username.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;
Credentials other = (Credentials) obj;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (port != other.port)
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
}
Loading