Skip to content
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
23 changes: 23 additions & 0 deletions demos/jobs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,26 @@ items:
defaultValue: "dev"
description: "Target environment"
```

## Item removal strategy

The `items` root element supports an optional `actionOnUndeclaredItems` property to
control how items that are not present in the configuration are handled.

The following strategies are supported:

| Strategy | Description |
|--------------| --- |
| `keep` | Do not remove items that are not present in the configuration. This is the default. |
| `delete-tracked` | Remove items that are not present in the configuration if they were previously managed by JCasC. |
| `delete-all` | Remove all items that are not present in the configuration. |

For example, to synchronize JCasC-managed items:

```yaml
items:
actionOnUndeclaredItems: delete-tracked
items:
- freestyle:
name: my-freestyle-job
```
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class FreestyleDemoTest {
public JenkinsConfiguredWithReadmeRule j = new JenkinsConfiguredWithReadmeRule();

@Test
@ConfiguredWithReadme("jobs/README.md")
@ConfiguredWithReadme("jobs/README.md#0")
public void shouldConfigureFreestyleJobFromReadme() {
FreeStyleProject job = (FreeStyleProject) Jenkins.get().getItem("my-freestyle-full-job");

Expand Down Expand Up @@ -77,4 +77,13 @@ public void shouldConfigureFreestyleJobFromReadme() {
assertEquals("DEPLOY_ENV", stringParam.getName());
assertEquals("dev", stringParam.getDefaultValue());
}

@Test
@ConfiguredWithReadme("jobs/README.md#1")
public void shouldConfigureFreestyleJobWithSyncStrategyFromReadme() {
FreeStyleProject job = (FreeStyleProject) Jenkins.get().getItem("my-freestyle-job");

assertNotNull("Freestyle job with sync strategy should have been created by JCasC from README", job);
assertEquals("my-freestyle-job", job.getName());
}
}
55 changes: 47 additions & 8 deletions plugin/src/main/java/io/jenkins/plugins/casc/SchemaGeneration.java
Original file line number Diff line number Diff line change
Expand Up @@ -264,16 +264,55 @@ private static void generateMultipleAttributeSchema(
}
itemsSchema.put("type", "string").put("enum", new JSONArray(values));
} else {
JSONObject properties = new JSONObject();
Configurator<Object> lookup = context.lookup(attribute.getType());
@SuppressWarnings("rawtypes")
Configurator lookup = context.lookup(attribute.getType());

if (lookup != null) {
lookup.getAttributes()
.forEach(attr -> properties.put(
attr.getName(),
generateNonEnumAttributeObject(attr, baseConfigurator, context, definitions)));
@SuppressWarnings({"rawtypes", "unchecked"})
List<Configurator> implementors = lookup.getConfigurators(context);

if (implementors.size() > 1) {
JSONArray oneOfJsonArray = new JSONArray();
JSONObject propertiesObject = new JSONObject();

for (Object obj : implementors) {
@SuppressWarnings("rawtypes")
Configurator impl = (Configurator) obj;
String name = impl.getName();
Class<?> targetClass = impl.getTarget();
propertiesObject.put(
name, new JSONObject().put("$ref", "#/definitions/" + targetClass.getName()));
oneOfJsonArray.put(new JSONObject().put("required", new JSONArray().put(name)));
ensureDefinitionExists(targetClass, context, definitions);
}
itemsSchema
.put("type", "object")
.put("additionalProperties", false)
.put("properties", propertiesObject)
.put("minProperties", 1)
.put("maxProperties", 1)
.put("oneOf", oneOfJsonArray);
} else if (lookup instanceof HeteroDescribableConfigurator) {
itemsSchema = generateHeteroDescribableConfigObject(
(HeteroDescribableConfigurator<?>) lookup, context, definitions);
} else {
JSONObject properties = new JSONObject();
for (Object attr : lookup.getAttributes()) {
Attribute<?, ?> a = (Attribute<?, ?>) attr;
properties.put(
a.getName(), generateNonEnumAttributeObject(a, baseConfigurator, context, definitions));
}
itemsSchema
.put("type", "object")
.put("properties", properties)
.put("additionalProperties", false);
}
} else {
itemsSchema
.put("type", "object")
.put("properties", new JSONObject())
.put("additionalProperties", false);
}

itemsSchema.put("type", "object").put("properties", properties).put("additionalProperties", false);
}

JSONObject attributeObject = new JSONObject().put("type", "array").put("items", itemsSchema);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.jenkins.plugins.casc.core;

import org.apache.commons.lang3.StringUtils;

public enum ItemRemoveStrategy {

/**
* Do not remove any items that are not present in the configuration.
*/
KEEP("keep"),

/**
* Remove items that are not present in the configuration if they were
* previously managed by Configuration as Code.
*/
DELETE_TRACKED("delete-tracked"),

/**
* Remove all items that are not present in the configuration,
* regardless of whether they were previously managed by Configuration as Code.
*/
DELETE_ALL("delete-all");

private final String value;

ItemRemoveStrategy(String value) {
this.value = value;
}

public String getValue() {
return value;
}

// In ItemRemoveStrategy.java
public static ItemRemoveStrategy fromString(String strategy) {
if (StringUtils.isBlank(strategy)) {
return KEEP;
}
String cleanStrategy = strategy.trim();
for (ItemRemoveStrategy s : values()) {
if (s.value.equalsIgnoreCase(cleanStrategy)) {
return s;
}
}
throw new IllegalArgumentException("Invalid actionOnUndeclaredItems: " + strategy);
}
}
Loading