diff --git a/cli/pom.xml b/cli/pom.xml
index e65b174223..670e416a26 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -81,6 +81,10 @@
annotations
provided
+
+ commons-lang
+ commons-lang
+
diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java
index 5da726be0e..52c6785d58 100644
--- a/cli/src/main/java/hudson/cli/CLI.java
+++ b/cli/src/main/java/hudson/cli/CLI.java
@@ -77,6 +77,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.*;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
/**
* CLI entry point to Jenkins.
@@ -452,6 +453,10 @@ public class CLI implements AutoCloseable {
String user = null;
String auth = null;
+
+ String userIdEnv = System.getenv("JENKINS_USER_ID");
+ String tokenEnv = System.getenv("JENKINS_API_TOKEN");
+
boolean strictHostKey = false;
while(!args.isEmpty()) {
@@ -563,6 +568,17 @@ public class CLI implements AutoCloseable {
return -1;
}
+ if (auth == null) {
+ // -auth option not set
+ if (StringUtils.isNotBlank(userIdEnv) && StringUtils.isNotBlank(tokenEnv)) {
+ auth = StringUtils.defaultString(userIdEnv).concat(":").concat(StringUtils.defaultString(tokenEnv));
+ } else if (StringUtils.isNotBlank(userIdEnv) || StringUtils.isNotBlank(tokenEnv)) {
+ printUsage(Messages.CLI_BadAuth());
+ return -1;
+ } // Otherwise, none credentials were set
+
+ }
+
if (!url.endsWith("/")) {
url += '/';
}
diff --git a/cli/src/main/resources/hudson/cli/client/Messages.properties b/cli/src/main/resources/hudson/cli/client/Messages.properties
index 921fe67a21..44fa79c6fd 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages.properties
@@ -13,10 +13,12 @@ CLI.Usage=Jenkins CLI\n\
-strictHostKey : request strict host key checking (for use with -ssh)\n\
-logger FINE : enable detailed logging from the client\n\
-auth [ USER:SECRET | @FILE ] : specify username and either password or API token (or load from them both from a file);\n\
- for use with -http, or -remoting but only when the JNLP agent port is disabled\n\
+ for use with -http, or -remoting but only when the JNLP agent port is disabled.\n\
+ Not necessary if JENKINS_USER_ID and JENKINS_API_TOKEN env vars are set\n\
\n\
The available commands depend on the server. Run the 'help' command to\n\
see the list.
CLI.NoURL=Neither -s nor the JENKINS_URL env var is specified.
CLI.VersionMismatch=Version mismatch. This CLI cannot work with this Jenkins server.
CLI.NoSuchFileExists=No such file exists: {0}
+CLI.BadAuth=The JENKINS_USER_ID and JENKINS_API_TOKEN env vars should be both set or left empty.
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_es.properties b/cli/src/main/resources/hudson/cli/client/Messages_es.properties
index f9979a5619..b8ff42b3c2 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_es.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_es.properties
@@ -1,10 +1,24 @@
CLI.Usage=Jenkins CLI\n\
- Usar: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
- Options:\n\
- \ -s URL : dirección web (por defecto se usa la variable JENKINS_URL)\n\
+ Uso: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
+ Opciones:\n\
+ -s URL : direcci\u00f3n web (por defecto se usa la variable JENKINS_URL)\n\
+ -http : usa un protocolo plano sobre HTTP(S) (es el uso por defecto; excluyente con -ssh y -remoting)\n\
+ -ssh : usa el protocolo SSH (requiere -user; el puerto SSH debe estar abierto en el servidor y el usuario debe tener registrada su clave publica)\n\
+ -remoting : usa el protocolo deprecado de Remoting (siempre que est\u00e9 habilitado en el servidor; s\u00f3lo para compatibilidad con comandos heredados o legacy)\n\
+ -i KEY : clave privada SSH usada para autenticaci\u00f3n (usado con -ssh o -remoting)\n\
+ -p HOST:PORT : host y puerto para el uso de proxy HTTPS. Ver https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+ -noCertificateCheck : elude por completo la verificaci\u00f3n del certificado HTTPS. Usar con precauci\u00f3n\n\
+ -noKeyAuth : intenta no cargar la clave privada de autenticaci\u00f3n SSH. Presenta conflicto con -i\n\
+ -user : especifica el usuario (se usa con -ssh)\n\
+ -strictHostKey : solicita la comprobaci\u00f3n estricta de la clave del host (se usa con -ssh)\n\
+ -logger FINE : habilita logs detallados en el cliente\n\
+ -auth [ USER:SECRET | @FILE ] : especifica el usuario y contrase\u00f1a o API token (o los carga de un fichero);\n\
+ se usa con -http o -remoting pero s\u00f3lo si el puerto del agente JNLP est\u00e1 deshabilitado.\n\
+ No es necesario si las variables de entorno JENKINS_USER_ID y JENKINS_API_TOKEN se encuentran configuradas.\n\
\n\
- La lista completa de comandos disponibles depende del servidor. Ejecuta\n\
- el comando ''help'' para ver la lista.
-CLI.NoURL=No se ha especificado el parámetro -s ni la variable JENKINS_URL
-CLI.VersionMismatch=La versión no coincide. Esta consola "CLI" no se puede usar en este Jenkins
+ La lista de comandos disponibles depende del servidor. Ejecuta\n\
+ el comando ''help'' para ver la lista completa.
+CLI.NoURL=No se ha especificado el par\u00e1metro -s ni la variable JENKINS_URL
+CLI.VersionMismatch=La versi\u00f3n no coincide. Esta consola "CLI" no se puede usar en este Jenkins
CLI.NoSuchFileExists=El fichero {0} no existe.
+CLI.BadAuth=Ambas variables de entorno JENKINS_USER_ID y JENKINS_API_TOKEN deber\u00edan estar configuradas o mantenerse vac\u00edas.
diff --git a/core/pom.xml b/core/pom.xml
index f57e8453e8..a940e93423 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -279,7 +279,6 @@ THE SOFTWARE.
commons-lang
commons-lang
- 2.6
commons-digester
diff --git a/pom.xml b/pom.xml
index c809736be4..e5f3dc51df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -148,6 +148,12 @@ THE SOFTWARE.
2.4
+
+ commons-lang
+ commons-lang
+ 2.6
+
+
org.mockito
mockito-core
diff --git a/test/src/test/java/hudson/cli/CLIEnvVarTest.java b/test/src/test/java/hudson/cli/CLIEnvVarTest.java
new file mode 100644
index 0000000000..0a5b18a928
--- /dev/null
+++ b/test/src/test/java/hudson/cli/CLIEnvVarTest.java
@@ -0,0 +1,242 @@
+package hudson.cli;
+
+import hudson.EnvVars;
+import hudson.Launcher;
+import hudson.model.User;
+import hudson.util.StreamTaskListener;
+import jenkins.model.Jenkins;
+import jenkins.security.ApiTokenProperty;
+import jenkins.security.apitoken.ApiTokenTestHelper;
+import org.apache.commons.io.FileUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.MockAuthorizationStrategy;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+public class CLIEnvVarTest {
+
+ @Rule
+ public JenkinsRule r = new JenkinsRule();
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ private File home;
+ private File jar;
+
+ @Before
+ public void grabCliJar() throws IOException {
+ home = tmp.newFolder();
+ jar = tmp.newFile("jenkins-cli.jar");
+ FileUtils.copyURLToFile(r.jenkins.getJnlpJars("jenkins-cli.jar").getURL(), jar);
+ }
+
+ @Test
+ public void testSOptionWithoutJENKINS_URL() throws Exception {
+ assertEquals(0, launch("java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "-s", r.getURL().toString(),
+ "who-am-i")
+ );
+ }
+
+ @Test
+ public void testWithoutSOptionAndWithoutJENKINS_URL() throws Exception {
+ assertNotEquals(0, launch("java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "who-am-i")
+ );
+ }
+
+ @Test
+ public void testJENKINS_URLWithoutSOption() throws Exception {
+ // Valid URL
+ Map envars = new HashMap<>();
+ envars.put("JENKINS_URL", r.getURL().toString());
+ assertEquals(0, launch(envars,
+ "java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "who-am-i")
+ );
+
+ // Invalid URL
+ envars = new HashMap<>();
+ envars.put("JENKINS_URL", "http://invalid-url");
+ assertNotEquals(0, launch(envars,
+ "java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "who-am-i")
+ );
+
+ }
+
+ @Test
+ public void testSOptionOverridesJENKINS_URL() throws Exception {
+ Map envars = new HashMap<>();
+ envars.put("JENKINS_URL", "http://invalid-url");
+ assertEquals(0, launch(envars,
+ "java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "-s", r.getURL().toString(),
+ "who-am-i")
+ );
+ }
+
+ @Test
+ public void testAuthOptionWithoutEnvVars() throws Exception {
+ String token = getToken();
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ assertEquals(0, launch(Collections.emptyMap(), baos, null,
+ "java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "-s", r.getURL().toString(),
+ "-auth", String.format("%s:%s", "admin", token),
+ "who-am-i")
+ );
+ assertThat(baos.toString(), containsString("Authenticated as: admin"));
+ }
+ }
+
+ @Test
+ public void testWithoutEnvVarsAndWithoutAuthOption() throws Exception {
+ r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
+ r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("admin"));
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ assertEquals(0, launch(Collections.emptyMap(), baos, null,
+ "java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "-s", r.getURL().toString(),
+ "who-am-i")
+ );
+ assertThat(baos.toString(), containsString("Authenticated as: anonymous"));
+ }
+ }
+
+ @Test
+ public void testEnvVarsWithoutAuthOption() throws Exception {
+ String token = getToken();
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ Map envars = new HashMap<>();
+ envars.put("JENKINS_USER_ID", "admin");
+ envars.put("JENKINS_API_TOKEN", token);
+ assertEquals(0, launch(envars, baos, null,
+ "java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "-s", r.getURL().toString(),
+ "who-am-i")
+ );
+ assertThat(baos.toString(), containsString("Authenticated as: admin"));
+ }
+ }
+
+ @Test
+ public void testOnlyOneEnvVar() throws Exception {
+ String token = getToken();
+
+ // only JENKINS_USER_ID
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ Map envars = new HashMap<>();
+ envars.put("JENKINS_USER_ID", "admin");
+ assertNotEquals(0, launch(envars,
+ "java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "-s", r.getURL().toString(),
+ "who-am-i")
+ );
+ }
+
+ // only JENKINS_API_TOKEN
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ Map envars = new HashMap<>();
+ envars.put("JENKINS_API_TOKEN", token);
+ assertNotEquals(0, launch(envars,
+ "java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "-s", r.getURL().toString(),
+ "who-am-i")
+ );
+ }
+ }
+
+ @Test
+ public void testAuthOptionOverridesEnvVars() throws Exception {
+ String token = getToken();
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ Map envars = new HashMap<>();
+ envars.put("JENKINS_USER_ID", "other-user");
+ envars.put("JENKINS_API_TOKEN", "other-user");
+ assertEquals(0, launch(envars, baos, null,
+ "java",
+ "-Duser.home=" + home,
+ "-jar", jar.getAbsolutePath(),
+ "-s", r.getURL().toString(),
+ "-auth", String.format("%s:%s", "admin", token),
+ "who-am-i")
+ );
+ assertThat(baos.toString(), containsString("Authenticated as: admin"));
+ }
+ }
+
+ private String getToken() {
+ ApiTokenTestHelper.enableLegacyBehavior();
+
+ r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
+ r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("admin"));
+
+ return User.get("admin").getProperty(ApiTokenProperty.class).getApiToken();
+ }
+
+ private int launch(String... cmdArgs) throws Exception {
+ return launch(Collections.emptyMap(), cmdArgs);
+ }
+
+ private int launch(Map envars, String... cmdArgs) throws Exception {
+ return launch(envars, null, null, cmdArgs);
+ }
+
+ private int launch(Map envars, OutputStream out, OutputStream err, String... cmdArgs) throws Exception {
+ if (out == null) {
+ out = System.out;
+ }
+
+ if (err == null) {
+ err = System.err;
+ }
+
+ return new Launcher.LocalLauncher(StreamTaskListener.fromStderr())
+ .decorateByEnv(new EnvVars(envars))
+ .launch()
+ .cmds(cmdArgs)
+ .stdout(out)
+ .stderr(err)
+ .join();
+ }
+}