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

Allow configuring credentials for custom Gradle plugin repository #361

Merged
merged 3 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class BuildScansInjectionSettings extends JenkinsConfig {
private static final String GE_PLUGIN_VERSION_FIELD = "gradlePluginVersion";
private static final String GIT_REPOSITORY_FILTERS_FIELD = "vcsRepositoryFilter";
private static final String GE_ACCESS_KEY_FIELD = "accessKey";
private static final String GE_GRADLE_PLUGIN_REPOSITORY_PASSWORD_FIELD = "gradlePluginRepositoryPassword";

public BuildScansInjectionSettings(Jenkins jenkins) {
super(jenkins);
Expand Down Expand Up @@ -55,6 +56,10 @@ public void setGradleEnterpriseAccessKey(String accessKey) {
setBuildScansInjectionFormValue(GE_ACCESS_KEY_FIELD, accessKey);
}

public void setGradleEnterpriseGradlePluginRepoPassword(String password) {
setBuildScansInjectionFormValue(GE_GRADLE_PLUGIN_REPOSITORY_PASSWORD_FIELD, password);
}

public void setGradleEnterprisePluginVersion(String version) {
setBuildScansInjectionFormValue(GE_PLUGIN_VERSION_FIELD, version);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ protected final void enableBuildScansForMaven() {
});
}

protected final void setGradlePluginRepositoryPassword(String password) {
updateBuildScansInjectionSettings(settings -> {
settings.setGradleEnterpriseGradlePluginRepoPassword(password);
});
}

private void updateBuildScansInjectionSettings(Consumer<BuildScansInjectionSettings> spec) {
BuildScansInjectionSettings settings = new BuildScansInjectionSettings(jenkins);
settings.configure();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,29 @@ public void accessKeyIsMasked() {
build.action(EnvInjectAction.class).shouldContain("GRADLE_ENTERPRISE_ACCESS_KEY", "[*******]");
}

@Test
@WithPlugins("envinject")
public void gradlePluginRepoPasswordIsMasked() {
// given
setGradlePluginRepositoryPassword("foo");

FreeStyleJob job = jenkins.jobs.create(FreeStyleJob.class);
job.copyDir(resource("/simple_gradle_project"));
GradleStep gradle = job.addBuildStep(GradleStep.class);
gradle.setVersion(GRADLE_VERSION);
gradle.setSwitches("--no-daemon");
gradle.setTasks("helloWorld");
job.save();

// when
Build build = job.startBuild();

// then
build.shouldSucceed();
assertBuildScanPublished(build);
build.action(EnvInjectAction.class).shouldContain("JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_PASSWORD", "[*******]");
}

@Test
public void logsErrorIfBuildScanUploadFailed() {
// given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import org.gradle.api.internal.artifacts.transform.UnzipTransform
import org.gradle.internal.os.OperatingSystem

// Latest version: https://chromedriver.storage.googleapis.com/LATEST_RELEASE
val chromeDriverVersion = "116.0.5845.96"
val chromeDriverVersion = "117.0.5938.88"
val ciTeamCityBuild: Boolean by (gradle as ExtensionAware).extra

val os: OperatingSystem = OperatingSystem.current()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,28 @@
import hudson.util.Secret;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static hudson.plugins.gradle.injection.GradleInjectionAware.JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_PASSWORD;

@Extension
public class BuildScanEnvironmentContributor extends EnvironmentContributor {

@Override
public void buildEnvironmentFor(@Nonnull Run run, @Nonnull EnvVars envs, @Nonnull TaskListener listener) {
Secret secret = InjectionConfig.get().getAccessKey();
if (secret == null || alreadyExecuted(run)) {
return;
}

String accessKey = secret.getPlainText();
if (!GradleEnterpriseAccessKeyValidator.getInstance().isValid(accessKey)) {
GradleEnterpriseLogger logger = new GradleEnterpriseLogger(listener);
logger.error("Gradle Enterprise access key format is not valid");
run.addAction(GradleEnterpriseParametersAction.empty());
Secret secretKey = InjectionConfig.get().getAccessKey();
Secret secretPassword = InjectionConfig.get().getGradlePluginRepositoryPassword();
if ((secretKey == null && secretPassword == null) || alreadyExecuted(run)) {
return;
}

run.addAction(GradleEnterpriseParametersAction.of(accessKey));
run.addAction(GradleEnterpriseParametersAction.of(new GradleEnterpriseLogger(listener), secretKey, secretPassword));
}

private static boolean alreadyExecuted(@Nonnull Run run) {
Expand All @@ -45,7 +43,7 @@ private static boolean alreadyExecuted(@Nonnull Run run) {
public static class GradleEnterpriseParametersAction extends ParametersAction {

private static final String GRADLE_ENTERPRISE_ACCESS_KEY = "GRADLE_ENTERPRISE_ACCESS_KEY";
private static final Set<String> ADDITIONAL_SAFE_PARAMETERS = Collections.singleton(GRADLE_ENTERPRISE_ACCESS_KEY);
private static final String GRADLE_ENTERPRISE_REPO_PASSWORD = JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_PASSWORD;

private static final GradleEnterpriseParametersAction EMPTY = new GradleEnterpriseParametersAction();

Expand All @@ -61,10 +59,24 @@ static GradleEnterpriseParametersAction empty() {
return EMPTY;
}

static GradleEnterpriseParametersAction of(String accessKey) {
static GradleEnterpriseParametersAction of(GradleEnterpriseLogger logger, @Nullable Secret accessKey, @Nullable Secret repoPassword) {
List<ParameterValue> values = new ArrayList<>();
if (accessKey != null) {
if (!GradleEnterpriseAccessKeyValidator.getInstance().isValid(accessKey.getPlainText())) {
logger.error("Gradle Enterprise access key format is not valid");
} else {
values.add(new PasswordParameterValue(GRADLE_ENTERPRISE_ACCESS_KEY, accessKey.getPlainText()));
}
}
if (repoPassword != null) {
values.add(new PasswordParameterValue(GRADLE_ENTERPRISE_REPO_PASSWORD, repoPassword.getPlainText()));
}
if (values.isEmpty()) {
return GradleEnterpriseParametersAction.empty();
}
return new GradleEnterpriseParametersAction(
Collections.singletonList(new PasswordParameterValue(GRADLE_ENTERPRISE_ACCESS_KEY, accessKey, null)),
ADDITIONAL_SAFE_PARAMETERS
values,
Stream.of(GRADLE_ENTERPRISE_ACCESS_KEY, GRADLE_ENTERPRISE_REPO_PASSWORD).collect(Collectors.toSet())
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ private void injectEnvironmentVariables(InjectionConfig config, Node node) {
if (pluginRepositoryUrl != null && InjectionUtil.isValid(InjectionConfig.checkUrl(pluginRepositoryUrl))) {
EnvUtil.setEnvVar(node, JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_URL, pluginRepositoryUrl);
}

if (config.getGradlePluginRepositoryUsername() != null) {
EnvUtil.setEnvVar(node, JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_USERNAME, config.getGradlePluginRepositoryUsername());
} else {
EnvUtil.removeEnvVar(node, JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_USERNAME);
}

String ccudPluginVersion = config.getCcudPluginVersion();
if (ccudPluginVersion != null && InjectionUtil.isValid(InjectionConfig.checkVersion(ccudPluginVersion))) {
EnvUtil.setEnvVar(node, JENKINSGRADLEPLUGIN_CCUD_PLUGIN_VERSION, ccudPluginVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public interface GradleInjectionAware {
String JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_ENFORCE_URL = "JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_ENFORCE_URL";
String JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_ALLOW_UNTRUSTED_SERVER = "JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_ALLOW_UNTRUSTED_SERVER";
String JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_URL = "JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_URL";
String JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_USERNAME = "JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_USERNAME";
String JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_PASSWORD = "JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_PASSWORD";
String JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_PLUGIN_VERSION = "JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_PLUGIN_VERSION";
String JENKINSGRADLEPLUGIN_CCUD_PLUGIN_VERSION = "JENKINSGRADLEPLUGIN_CCUD_PLUGIN_VERSION";

Expand Down
26 changes: 26 additions & 0 deletions src/main/java/hudson/plugins/gradle/injection/InjectionConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public class InjectionConfig extends GlobalConfiguration {
private String gradlePluginVersion;
private String ccudPluginVersion;
private String gradlePluginRepositoryUrl;
private String gradlePluginRepositoryUsername;
private Secret gradlePluginRepositoryPassword;
private ImmutableList<NodeLabelItem> gradleInjectionEnabledNodes;
private ImmutableList<NodeLabelItem> gradleInjectionDisabledNodes;

Expand Down Expand Up @@ -148,6 +150,30 @@ public void setAccessKey(Secret accessKey) {
}
}

@CheckForNull
public String getGradlePluginRepositoryUsername() {
return gradlePluginRepositoryUsername;
}

@DataBoundSetter
public void setGradlePluginRepositoryUsername(String gradlePluginRepositoryUsername) {
this.gradlePluginRepositoryUsername = Util.fixEmptyAndTrim(gradlePluginRepositoryUsername);
}

@CheckForNull
public Secret getGradlePluginRepositoryPassword() {
return gradlePluginRepositoryPassword;
}

@DataBoundSetter
public void setGradlePluginRepositoryPassword(Secret gradlePluginRepositoryPassword) {
if (Util.fixEmptyAndTrim(gradlePluginRepositoryPassword.getPlainText()) == null) {
this.gradlePluginRepositoryPassword = null;
} else {
this.gradlePluginRepositoryPassword = gradlePluginRepositoryPassword;
}
}

@CheckForNull
public String getGradlePluginVersion() {
return gradlePluginVersion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@
<f:entry title="${%Gradle plugin repository url}" field="gradlePluginRepositoryUrl">
<f:textbox checkMethod="post"/>
</f:entry>
<f:entry title="${%Gradle plugin repository username}" field="gradlePluginRepositoryUsername">
<f:textbox checkMethod="post"/>
</f:entry>
<f:entry title="${%Gradle plugin repository password}" field="gradlePluginRepositoryPassword">
<f:password/>
</f:entry>
<f:entry title="${%Gradle auto-injection enabled nodes}"
help="/plugin/gradle/help-gradleInjectionEnabledNodes.html">
<f:repeatableProperty field="gradleInjectionEnabledNodes">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,28 @@ initscript {
def pluginRepositoryUrl = getInputParam('jenkinsGradlePlugin.gradle.plugin-repository.url')
def gePluginVersion = getInputParam('jenkinsGradlePlugin.gradle-enterprise.plugin.version')
def ccudPluginVersion = getInputParam('jenkinsGradlePlugin.ccud.plugin.version')
def gradlePluginRepoUsername = getInputParam('jenkinsGradlePlugin.gradle.plugin-repository.username')
def gradlePluginRepoPassword = getInputParam('jenkinsGradlePlugin.gradle.plugin-repository.password')

def atLeastGradle5 = GradleVersion.current() >= GradleVersion.version('5.0')
def atLeastGradle4 = GradleVersion.current() >= GradleVersion.version('4.0')

if (gePluginVersion || ccudPluginVersion && atLeastGradle4) {
pluginRepositoryUrl = pluginRepositoryUrl ?: 'https://plugins.gradle.org/m2'
logger.quiet("Gradle Enterprise plugins resolution: $pluginRepositoryUrl")

repositories {
maven { url pluginRepositoryUrl }
maven {
url pluginRepositoryUrl
if (gradlePluginRepoUsername && gradlePluginRepoPassword) {
credentials {
username(gradlePluginRepoUsername)
password(gradlePluginRepoPassword)
}
authentication {
basic(BasicAuthentication)
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package hudson.plugins.gradle.injection

import hudson.EnvVars
import hudson.model.PasswordParameterValue
import hudson.model.ParameterValue
import hudson.model.Run
import hudson.model.TaskListener
import hudson.plugins.gradle.BaseJenkinsIntegrationTest
Expand Down Expand Up @@ -29,6 +29,19 @@ class BuildScanEnvironmentContributorTest extends BaseJenkinsIntegrationTest {
0 * run.addAction(_)
}

def 'does nothing if no password'() {
given:
def config = InjectionConfig.get()
config.setGradlePluginRepositoryPassword(Secret.fromString(""))
config.save()

when:
buildScanEnvironmentContributor.buildEnvironmentFor(run, new EnvVars(), TaskListener.NULL)

then:
0 * run.addAction(_)
}

def 'adds empty action if access key is invalid'() {
given:
def config = InjectionConfig.get()
Expand All @@ -44,6 +57,24 @@ class BuildScanEnvironmentContributorTest extends BaseJenkinsIntegrationTest {
}
}

def 'adds action if access key is invalid but password is there'() {
given:
def config = InjectionConfig.get()
config.setAccessKey(Secret.fromString("secret"))
config.setGradlePluginRepositoryPassword(Secret.fromString("foo"))
config.save()

when:
buildScanEnvironmentContributor.buildEnvironmentFor(run, new EnvVars(), TaskListener.NULL)

then:
1 * run.addAction { GradleEnterpriseParametersAction action ->
def parameters = action.getAllParameters()
parameters.size() == 1
paramEquals(parameters.first(), 'JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_PASSWORD', 'foo')
}
}

def 'adds an action with the access key'() {
given:
def accessKey = "server=secret"
Expand All @@ -58,7 +89,48 @@ class BuildScanEnvironmentContributorTest extends BaseJenkinsIntegrationTest {
1 * run.addAction { GradleEnterpriseParametersAction action ->
def parameters = action.getAllParameters()
parameters.size() == 1
parameters.first() == new PasswordParameterValue('GRADLE_ENTERPRISE_ACCESS_KEY', accessKey, null)
paramEquals(parameters.first(), 'GRADLE_ENTERPRISE_ACCESS_KEY', accessKey)
}
}

def 'adds an action with the password'() {
given:
def config = InjectionConfig.get()
config.setGradlePluginRepositoryPassword(Secret.fromString("foo"))
config.save()

when:
buildScanEnvironmentContributor.buildEnvironmentFor(run, new EnvVars(), TaskListener.NULL)

then:
1 * run.addAction { GradleEnterpriseParametersAction action ->
def parameters = action.getAllParameters()
parameters.size() == 1
paramEquals(parameters.first(), 'JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_PASSWORD', 'foo')
}
}

def 'adds an action with access key and password'() {
given:
def config = InjectionConfig.get()
config.setAccessKey(Secret.fromString("server=secret"))
config.setGradlePluginRepositoryPassword(Secret.fromString("foo"))
config.save()

when:
buildScanEnvironmentContributor.buildEnvironmentFor(run, new EnvVars(), TaskListener.NULL)

then:
1 * run.addAction { GradleEnterpriseParametersAction action ->
def parameters = action.getAllParameters()
parameters.size() == 2
paramEquals(parameters[0], 'GRADLE_ENTERPRISE_ACCESS_KEY', 'server=secret')
paramEquals(parameters[1], 'JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_PASSWORD', 'foo')
}
}

private boolean paramEquals(ParameterValue param, String name, String value) {
param.name == name && param.value?.plainText == value
}

}