Skip to content

Commit

Permalink
Add ability to override a library defined at folder level
Browse files Browse the repository at this point in the history
  • Loading branch information
c3p0-maif committed Sep 24, 2024
1 parent f4e0e4a commit 069b5dc
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.jenkinsci.plugins.workflow.libs.FolderLibraries;
import org.jenkinsci.plugins.workflow.libs.GlobalLibraries;
import org.jenkinsci.plugins.workflow.libs.LibraryConfiguration;
import org.jenkinsci.plugins.workflow.libs.LibraryResolver;
Expand All @@ -31,17 +33,17 @@ public class FolderConfigurations extends AbstractFolderProperty<AbstractFolder<
private List<LibraryCustomConfiguration> overrides = Collections.emptyList();

@DataBoundConstructor
public FolderConfigurations() {
LOGGER.log(Level.FINER, "Instantiating new FolderConfigurations\n");
}
public FolderConfigurations() {}

/**
* Returns the overrides configurations added to the folder
*
* @return The list of overrides configurations added to the folder
*/
public LibraryCustomConfiguration[] getOverrides() {
return overrides.toArray(new LibraryCustomConfiguration[0]);
LibraryCustomConfiguration[] lcc = overrides.toArray(new LibraryCustomConfiguration[0]);
LOGGER.log(Level.FINER, "get overrides : ({0})\n", lcc);
return lcc;
}

/**
Expand All @@ -51,7 +53,7 @@ public LibraryCustomConfiguration[] getOverrides() {
*/
@DataBoundSetter
public void setOverrides(List<LibraryCustomConfiguration> items) {
LOGGER.log(Level.FINER, "FolderConfigurations.setOverrides({0})\n", items);
LOGGER.log(Level.FINER, "Add new overrides : ({0})\n", items);
this.overrides = items;
}

Expand All @@ -77,37 +79,62 @@ public boolean isTrusted() {
return true;
}

private Collection<LibraryConfiguration> forGroup(@CheckForNull ItemGroup<?> group, boolean checkPermission) {
// Lookup GlobalLibraries
public static Collection<LibraryConfiguration> getDefinedLibrariesForGroup(@CheckForNull ItemGroup<?> group) {
// Get all global librairies
GlobalLibraries libs = ExtensionList.lookupSingleton(GlobalLibraries.class);

List<LibraryConfiguration> libraries = new ArrayList<>(libs.getLibraries());
// Get all folder local libraries
for (ItemGroup<?> g = group; g instanceof AbstractFolder; g = ((AbstractFolder<?>) g).getParent()) {
AbstractFolder<?> f = (AbstractFolder<?>) g;
FolderLibraries prop = f.getProperties().get(FolderLibraries.class);
if (prop != null) {
libraries.addAll(prop.getLibraries());
}
}
LOGGER.log(
Level.FINE,
"CustomLibraryResolver.getDefinedLibrariesForGroup {0}\n",
libraries.stream().map(LibraryConfiguration::getName).collect(Collectors.toList()));
return libraries;
}

private Collection<LibraryConfiguration> forGroup(@CheckForNull ItemGroup<?> group, boolean checkPermission) {
// Get all available libraries
Collection<LibraryConfiguration> allLibs = getDefinedLibrariesForGroup(group);
List<LibraryConfiguration> libraries = new ArrayList<>();
for (ItemGroup<?> g = group; g instanceof AbstractFolder; g = ((AbstractFolder<?>) g).getParent()) {
AbstractFolder<?> f = (AbstractFolder<?>) g;
if (!checkPermission || f.hasPermission(Item.CONFIGURE)) {
FolderConfigurations prop = f.getProperties().get(FolderConfigurations.class);
if (prop != null) {
for (LibraryCustomConfiguration item : prop.getOverrides()) {
if (item.isValid()) {
LibraryConfiguration libConfig = getLibraryConfiguration(item, libs);
if (libConfig != null) {
libraries.add(libConfig);
}
LibraryConfiguration libConfig = getLibraryConfiguration(item, allLibs);
if (libConfig != null) {
libraries.add(libConfig);
}
}
}
}
}
LOGGER.log(
Level.FINE,
"CustomLibraryResolver.forGroup {0}\n",
libraries.stream().map(LibraryConfiguration::getName).collect(Collectors.toList()));
return libraries;
}

private static LibraryConfiguration getLibraryConfiguration(
LibraryCustomConfiguration item, GlobalLibraries libs) {
LibraryCustomConfiguration item, Collection<LibraryConfiguration> libs) {
LibraryConfiguration libConfig = null;
for (LibraryConfiguration lib : libs.getLibraries()) {
for (LibraryConfiguration lib : libs) {
if (lib.getName().equals(item.getName())) {
// if original library don't allow version override, so don't take it
if (!lib.isAllowVersionOverride()) {
LOGGER.log(
Level.FINE,
"CustomLibraryResolver.getLibraryConfiguration {0} don't allow version override, don't take it.\n",
lib.getName());
continue;
}
libConfig = new LibraryConfiguration(lib.getName(), lib.getRetriever());
Expand All @@ -125,18 +152,21 @@ private static LibraryConfiguration getLibraryConfiguration(
@Override
public Collection<LibraryConfiguration> forJob(
@NonNull Job<?, ?> job, @NonNull Map<String, String> libraryVersions) {
LOGGER.log(Level.FINER, "CustomLibraryResolver.forJob({0})\n", job);
return forGroup(job.getParent(), false);
}

@NonNull
@Override
public Collection<LibraryConfiguration> fromConfiguration(@NonNull StaplerRequest request) {
LOGGER.log(Level.FINER, "CustomLibraryResolver.fromConfiguration({0})\n", request);
return forGroup(request.findAncestorObject(AbstractFolder.class), true);
}

@NonNull
@Override
public Collection<LibraryConfiguration> suggestedConfigurations(@NonNull ItemGroup<?> group) {
LOGGER.log(Level.FINER, "CustomLibraryResolver.suggestedConfigurations({0})\n", group);
return forGroup(group, false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.workflow.libs.GlobalLibraries;
import org.jenkinsci.plugins.workflow.libs.LibraryConfiguration;
import org.jenkinsci.plugins.workflow.libs.LibraryResolver;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.verb.POST;

/**
Expand All @@ -26,18 +27,15 @@
* @author Cyril Pottiers
*/
public class LibraryCustomConfiguration extends AbstractDescribableImpl<LibraryCustomConfiguration> {
private static final Logger LOGGER = Logger.getLogger(LibraryCustomConfiguration.class.getName());

public String name;
public String version;
public boolean valid;

@DataBoundConstructor
public LibraryCustomConfiguration(String name, String version) {
this.name = Util.fixEmptyAndTrim(name);
this.version = Util.fixEmptyAndTrim(version);
List<FormValidation> validations = new ArrayList<>();
validations.add(isNameValid(this.name));
validations.add(isVersionValid(this.version));
this.valid = (FormValidation.aggregate(validations).kind.equals(FormValidation.Kind.OK));
}

public String getName() {
Expand All @@ -48,97 +46,105 @@ public String getVersion() {
return version;
}

public boolean isValid() {
return valid;
}
@Extension
public static class DescriptorImpl extends Descriptor<LibraryCustomConfiguration> {

public static FormValidation isNameValid(String name) {
List<FormValidation> validations = new ArrayList<>();
validations.add(FormValidation.validateRequired(name));
// Lookup GlobalLibraries
if (name != null && !name.isBlank()) {
GlobalLibraries libs = ExtensionList.lookupSingleton(GlobalLibraries.class);
boolean isKnown = false;
for (LibraryConfiguration lib : libs.getLibraries()) {
if (lib.getName().equals(name)) {
if (!lib.isAllowVersionOverride()) {
validations.add(FormValidation.error(Messages.LibraryCustomConfiguration_ImmutableVersion()));
}
isKnown = true;
break;
private ItemGroup<?> getItemGroupFromItem(Item item) {
ItemGroup<?> group = null;
if (item != null) {
if (ItemGroup.class.isAssignableFrom(item.getClass())) {
group = (ItemGroup<?>) item;
} else {
group = item.getParent();
}
}
if (!isKnown) {
validations.add(FormValidation.error(Messages.LibraryCustomConfiguration_NameUnknown()));
}
return group;
}
return FormValidation.aggregate(validations);
}

public static FormValidation isVersionValid(String version) {
return FormValidation.validateRequired(version);
}

@Extension
public static class DescriptorImpl extends Descriptor<LibraryCustomConfiguration> {

@POST
public ListBoxModel doFillNameItems(@AncestorInPath Item item) {
if (item == null) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
public FormValidation doCheckVersion(
@AncestorInPath Item item, @QueryParameter String version, @QueryParameter String name) {
if (version.isEmpty()) {
return FormValidation.ok();
} else {
item.checkPermission(Item.CONFIGURE);
}
ListBoxModel items = new ListBoxModel();
GlobalLibraries libs = ExtensionList.lookupSingleton(GlobalLibraries.class);
for (LibraryConfiguration lib : libs.getLibraries()) {
items.add(new ListBoxModel.Option(lib.getName(), lib.getName()));
for (LibraryResolver resolver : ExtensionList.lookup(LibraryResolver.class)) {
for (LibraryConfiguration config : resolver.fromConfiguration(Stapler.getCurrentRequest())) {
if (config.getName().equals(name)) {
return config.getRetriever().validateVersion(name, version, item);
}
}
}
return FormValidation.ok("Cannot validate default version until after saving and reconfiguring.");
}
return items;
}

@POST
public FormValidation doCheckName(@QueryParameter("value") String name, @AncestorInPath Item item)
throws IOException, ServletException {
public ListBoxModel doFillNameItems(@AncestorInPath Item item) {
if (item == null) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
LOGGER.log(Level.FINE, "DescriptorImpl.doFillNameItems for item null\n");
} else {
item.checkPermission(Item.CONFIGURE);
LOGGER.log(Level.FINE, "DescriptorImpl.doFillNameItems for item {0}\n", item.getName());
}
return isNameValid(name);
}

@POST
public FormValidation doCheckVersion(@QueryParameter("value") String version, @AncestorInPath Item item)
throws IOException, ServletException {
if (item == null) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
} else {
item.checkPermission(Item.CONFIGURE);
Set<String> libNames = new TreeSet<>();
ItemGroup<?> group = getItemGroupFromItem(item);
Collection<LibraryConfiguration> libs =
FolderConfigurations.CustomLibraryResolver.getDefinedLibrariesForGroup(group);
for (LibraryConfiguration lib : libs) {
libNames.add(lib.getName());
}
return isVersionValid(version);

ListBoxModel items = new ListBoxModel();
for (String libName : libNames) {
items.add(new ListBoxModel.Option(libName));
}
return items;
}

@POST
public FormValidation doValidate(
@QueryParameter("name") final String name,
@QueryParameter("version") final String version,
@AncestorInPath Item item)
throws ServletException, IOException {
@AncestorInPath Item item) {
if (item == null) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
} else {
item.checkPermission(Item.CONFIGURE);
}

List<FormValidation> validations = new ArrayList<>();
validations.add(isNameValid(name));
validations.add(isVersionValid(version));
FormValidation result = FormValidation.aggregate(validations);
if (result.kind.equals(FormValidation.Kind.OK)) {
return FormValidation.ok("Success");
// Check name existence and version override allowance
ItemGroup<?> group = getItemGroupFromItem(item);
Collection<LibraryConfiguration> libs =
FolderConfigurations.CustomLibraryResolver.getDefinedLibrariesForGroup(group);
LibraryConfiguration lib = libs.stream()
.filter(l -> l.getName().equals(name))
.findFirst()
.orElse(null);
if (lib == null) {
validations.add(FormValidation.error(Messages.LibraryCustomConfiguration_Validation_NameUnknown()));
} else if (!lib.isAllowVersionOverride()) {
validations.add(
FormValidation.error(Messages.LibraryCustomConfiguration_Validation_ImmutableVersion()));
}
if (version.isEmpty()) {
validations.add(FormValidation.error(Messages.LibraryCustomConfiguration_Validation_EmptyVersion()));
}
// check version existence
if (lib != null) {
FormValidation versionValidation = lib.getRetriever().validateVersion(name, version, item);
if (versionValidation.kind != FormValidation.Kind.OK) {
validations.add(
FormValidation.error(Messages.LibraryCustomConfiguration_Validation_UnknownVersion()));
}
}

if (validations.isEmpty()) {
return FormValidation.ok(Messages.LibraryCustomConfiguration_Validation_Success());
}
return FormValidation.error("Validation failed");
return FormValidation.aggregate(validations);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:section title="${%Title}">
<f:section title="${%Title}" >
<f:block>
<j:out value="${%Description}"/>
<j:out value="${%Description}" />
</f:block>
<f:entry>
<f:repeatableProperty field="overrides"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<f:select field="name" />
</f:entry>
<f:entry title="${%Version}" field="version">
<f:textbox />
<f:textbox checkMethod="post"/>
</f:entry>
<f:entry>
<f:validateButton title="${%Validate}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
LibraryCustomConfiguration.NameUnknown=Unknown library
LibraryCustomConfiguration.ImmutableVersion=Version override not permitted
LibraryCustomConfiguration.Validation.NameUnknown=Unknown library
LibraryCustomConfiguration.Validation.ImmutableVersion=Version override not permitted
LibraryCustomConfiguration.Validation.EmptyVersion=Version required
LibraryCustomConfiguration.Validation.UnknownVersion=Unknown version
LibraryCustomConfiguration.Validation.Success=Success
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
LibraryCustomConfiguration.NameUnknown=Librairie inconnue
LibraryCustomConfiguration.ImmutableVersion=Surcharge de version non permise
LibraryCustomConfiguration.Validation.NameUnknown=Librairie inconnue
LibraryCustomConfiguration.Validation.ImmutableVersion=Surcharge de version non permise
LibraryCustomConfiguration.Validation.EmptyVersion=Version requise
LibraryCustomConfiguration.Validation.UnknownVersion=Version inconnue
LibraryCustomConfiguration.Validation.Success=Succès
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,5 @@ public void validNameAndVersion() throws Exception {
LibraryCustomConfiguration item = new LibraryCustomConfiguration(libraryName, defaultVersion);
assertEquals("greet", item.getName());
assertEquals("master", item.getVersion());
assertTrue(item.isValid());
}

@Test
public void invalidName() throws Exception {
String libraryName = "groot";
String defaultVersion = "master";

LibraryCustomConfiguration item = new LibraryCustomConfiguration(libraryName, defaultVersion);
assertFalse(item.isValid());
}
}

0 comments on commit 069b5dc

Please sign in to comment.