diff --git a/src/main/java/com/cloudbees/plugins/credentials/SystemCredentialsProvider.java b/src/main/java/com/cloudbees/plugins/credentials/SystemCredentialsProvider.java index fee970cd4..19ef3e5f6 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/SystemCredentialsProvider.java +++ b/src/main/java/com/cloudbees/plugins/credentials/SystemCredentialsProvider.java @@ -186,6 +186,24 @@ public synchronized void setDomainCredentialsMap(Map> this.domainCredentialsMap = DomainCredentials.toCopyOnWriteMap(domainCredentialsMap); } + /** + * Merge the given credentials with the current set. Replace existing domain credentials or add new credentials. + * Existing credentials not in the given set will not be removed. + * + * @param domainCredentialsMap credentials to add or update + */ + public synchronized void mergeDomainCredentialsMap(Map> domainCredentialsMap) { + for (Map.Entry> entry : DomainCredentials.toCopyOnWriteMap(domainCredentialsMap).entrySet()) { + List target = this.domainCredentialsMap.get(entry.getKey()); + if (target == null) { + this.domainCredentialsMap.put(entry.getKey(), entry.getValue()); + } else { + target.removeAll(entry.getValue()); + target.addAll(entry.getValue()); + } + } + } + /** * Short-cut method for {@link Jenkins#checkPermission(hudson.security.Permission)} * diff --git a/src/main/java/com/cloudbees/plugins/credentials/casc/SystemCredentialsProviderConfigurator.java b/src/main/java/com/cloudbees/plugins/credentials/casc/SystemCredentialsProviderConfigurator.java index d262b87bf..299314ff8 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/casc/SystemCredentialsProviderConfigurator.java +++ b/src/main/java/com/cloudbees/plugins/credentials/casc/SystemCredentialsProviderConfigurator.java @@ -30,6 +30,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import hudson.Util; import io.jenkins.plugins.casc.Attribute; import io.jenkins.plugins.casc.BaseConfigurator; import io.jenkins.plugins.casc.ConfigurationContext; @@ -39,7 +40,9 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Set; /** @@ -62,10 +65,19 @@ protected SystemCredentialsProvider instance(Mapping mapping, ConfigurationConte @NonNull @Override public Set> describe() { - return Collections.singleton( - new MultivaluedAttribute("domainCredentials", DomainCredentials.class) - .setter( (target, value) -> target.setDomainCredentialsMap(DomainCredentials.asMap(value))) - ); + return new HashSet<>(Arrays.asList( + new MultivaluedAttribute("domainCredentials", DomainCredentials.class) + .setter((target, value) -> { + String strategy = getPropertyOrEnv("CASC_CREDENTIALS_MERGE_STRATEGY", "casc.credentials.merge.strategy"); + if ("merge".equalsIgnoreCase(strategy)) { + target.mergeDomainCredentialsMap(DomainCredentials.asMap(value)); + } else { + target.setDomainCredentialsMap(DomainCredentials.asMap(value)); + } + }), + new MultivaluedAttribute("mergeDomainCredentials", DomainCredentials.class) + .setter((target, value) -> target.mergeDomainCredentialsMap(DomainCredentials.asMap(value))) + )); } @CheckForNull @@ -77,4 +89,11 @@ public CNode describe(SystemCredentialsProvider instance, ConfigurationContext c } return mapping; } + + private static String getPropertyOrEnv(String envKey, String proKey) { + return Util.fixEmptyAndTrim(System.getProperty( + proKey, + System.getenv(envKey) + )); + } }