Skip to content

Commit

Permalink
Fix: Don't destroy credentials when re-creating folders
Browse files Browse the repository at this point in the history
As noted in JENKINS-44681, any credentials on folders are lost when
re-creating folders, as we don't merge the existing credentials with the
newly created folder.

To do this, we need to find any instances of
`FolderCredentialsProvider.FolderCredentialsProperty` in the `Folder`'s
and if present, merge the credentials.

We need to promote our dependency on `cloudbees-folder` to a runtime
dependency, as we now need to reference the `Folder` and any
`FolderCredentialsProperty`s.

Note that in `JenkinsJobManagementSpec`, we need to use
`getNodeTemplate` to ensure that the real implementation of `getXml` is
called, which will execute the `configuredBlocks`.

Because we need to call to the `configure` method, we need to call out
to a new Groovy file, `DslItemConfigurer`, which processes the new
`AbstractFolderProperty` and converts it to the correct XML
representation. When parsing it, we need to make sure we add a valid XML
header, otherwise `XmlParser` will reject it.

Closes JENKINS-44681.
  • Loading branch information
jamietanna committed Oct 4, 2020
1 parent b3bd348 commit 8d8ea63
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 2 deletions.
2 changes: 1 addition & 1 deletion job-dsl-plugin/build.gradle
Expand Up @@ -89,6 +89,7 @@ dependencies {
compile(project(':job-dsl-core')) {
exclude group: 'org.jvnet.hudson', module:'xstream'
}
jenkinsPlugins 'org.jenkins-ci.plugins:cloudbees-folder:5.14'
jenkinsPlugins 'org.jenkins-ci.plugins:structs:1.19'
jenkinsPlugins 'org.jenkins-ci.plugins:script-security:1.54'
optionalJenkinsPlugins('org.jenkins-ci.plugins:vsphere-cloud:1.1.11') {
Expand All @@ -99,7 +100,6 @@ dependencies {
optionalJenkinsPlugins 'io.jenkins:configuration-as-code:1.15'
jenkinsTest 'io.jenkins:configuration-as-code:1.15'
jenkinsTest 'io.jenkins:configuration-as-code:1.15:tests'
jenkinsTest 'org.jenkins-ci.plugins:cloudbees-folder:5.14'
jenkinsTest 'org.jenkins-ci.plugins:matrix-auth:1.3'
jenkinsTest 'org.jenkins-ci.plugins:nested-view:1.14'
jenkinsTest 'org.jenkins-ci.plugins:credentials:2.1.10'
Expand Down
@@ -0,0 +1,21 @@
package javaposse.jobdsl.plugin

import com.cloudbees.hudson.plugins.folder.AbstractFolderProperty
import hudson.model.Items
import javaposse.jobdsl.dsl.Item

class DslItemConfigurer {
private static final String XML_HEADER = "<?xml version='1.1' encoding='UTF-8'?>"

/**
* Merge an {@link AbstractFolderProperty} into a new {@link Item}'s properties.
*
* @param item the property to merge
* @param dslItem the DSL item to merge the properties into
*/
static void mergeCredentials(AbstractFolderProperty<?> property, Item dslItem) {
String xml = Items.XSTREAM2.toXML(property)
Node node = new XmlParser().parseText(XML_HEADER + xml)
dslItem.configure { p -> p / 'properties' << node }
}
}
@@ -1,5 +1,13 @@
package javaposse.jobdsl.plugin;

import static hudson.model.Result.UNSTABLE;
import static hudson.model.View.createViewFromXML;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.cloudbees.hudson.plugins.folder.AbstractFolderProperty;
import com.cloudbees.hudson.plugins.folder.Folder;
import com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider.FolderCredentialsProperty;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.thoughtworks.xstream.io.xml.XppDriver;
Expand Down Expand Up @@ -52,6 +60,7 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -444,6 +453,7 @@ private String lookupJob(String path) throws IOException {
}

private boolean updateExistingItem(AbstractItem item, javaposse.jobdsl.dsl.Item dslItem) {
mergeCredentials(item, dslItem);
String config = dslItem.getXml();

item.checkPermission(Item.EXTENDED_READ);
Expand Down Expand Up @@ -576,6 +586,22 @@ private void renameJob(Job from, String to) throws IOException {
from.renameTo(FilenameUtils.getName(to));
}

private void mergeCredentials(AbstractItem item, javaposse.jobdsl.dsl.Item dslItem) {
if (item instanceof Folder) {
Folder folder = (Folder) item;
Optional<AbstractFolderProperty<?>> maybeProperty =
folder.getProperties().stream()
.filter(p -> p instanceof FolderCredentialsProperty)
.findFirst();

if (maybeProperty.isPresent()) {
LOGGER.log(Level.FINE, format("Merging credentials for %s", item.getName()));
DslItemConfigurer.mergeCredentials(maybeProperty.get(), dslItem);
}
}
}


@SuppressWarnings("unchecked")
private static <I extends AbstractItem & TopLevelItem> void move(Item item, DirectlyModifiableTopLevelItemGroup destination) throws IOException {
Items.move((I) item, destination);
Expand Down
@@ -1,7 +1,16 @@
package javaposse.jobdsl.plugin

import com.cloudbees.hudson.plugins.folder.AbstractFolder
import com.cloudbees.hudson.plugins.folder.AbstractFolderProperty
import com.cloudbees.hudson.plugins.folder.AbstractFolderPropertyDescriptor
import com.cloudbees.hudson.plugins.folder.Folder
import com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider
import com.cloudbees.plugins.credentials.CredentialsScope
import com.cloudbees.plugins.credentials.domains.Domain
import com.cloudbees.plugins.credentials.domains.DomainCredentials
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
import com.google.common.io.Resources
import hudson.Extension
import hudson.FilePath
import hudson.model.AbstractBuild
import hudson.model.AllView
Expand Down Expand Up @@ -632,6 +641,32 @@ class JenkinsJobManagementSpec extends Specification {
e.message == 'Type of item "my-job" does not match existing type, item type can not be changed'
}
def 'createOrUpdateConfig should preserve credentials if they exist on a folder'() {
setup:
Folder folder = jenkinsRule.createProject(Folder, 'folder')
folder.addProperty(createCredentialProperty())
when:
jobManagement.createOrUpdateConfig(createItem('folder', '/folder.xml'), false)
then:
def actual = jenkinsRule.jenkins.getItem('folder')
actual.properties.size() == 1
}
def 'createOrUpdateConfig should ignore other properties on the folder'() {
setup:
Folder folder = jenkinsRule.createProject(Folder, 'folder')
folder.addProperty(new FakeProperty())
when:
jobManagement.createOrUpdateConfig(createItem('folder', '/folder.xml'), false)
then:
def actual = jenkinsRule.jenkins.getItem('folder')
actual.properties.size() == 0
}
def 'createOrUpdateView should work if view type changes'() {
setup:
jenkinsRule.jenkins.addView(new AllView('foo'))
Expand Down Expand Up @@ -973,10 +1008,27 @@ class JenkinsJobManagementSpec extends Specification {
Resources.toString(Resources.getResource(resourceName), UTF_8)
}
private static FolderCredentialsProvider.FolderCredentialsProperty createCredentialProperty() {
UsernamePasswordCredentialsImpl credentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, '',
'', '', '')
DomainCredentials[] domainCredentials = new DomainCredentials[1]
domainCredentials[0] = new DomainCredentials(Domain.global(), Collections.singletonList(credentials))
new FolderCredentialsProvider.FolderCredentialsProperty(domainCredentials)
}
private static class FakeProperty extends AbstractFolderProperty<AbstractFolder<?>> {
@Extension(optional = true)
static class DescriptorImpl extends AbstractFolderPropertyDescriptor {
final String displayName = ''
}
}
private Item createItem(String name, String config) {
new Item(jobManagement, name) {
@Override
Node getNode() {
Node getNodeTemplate() {
new XmlParser().parse(JenkinsJobManagementSpec.getResourceAsStream(config))
}
}
Expand Down
2 changes: 2 additions & 0 deletions job-dsl-plugin/src/test/resources/folder.xml
@@ -0,0 +1,2 @@
<com.cloudbees.hudson.plugins.folder.Folder>
</com.cloudbees.hudson.plugins.folder.Folder>

0 comments on commit 8d8ea63

Please sign in to comment.