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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [Unreleased]

### Fixed

- A deploy that changes a packaged object (a GCV object, policy or other driver resource that a package
installed) no longer rewrites its package stamps when the vault already has them. Before, it added the
`DirXML-PkgItemAux` class and wrote `DirXML-pkgGUID`, `DirXML-pkgAssociationId` and `DirXML-pkgChecksum`
again with the same values, so a one-value GCV change planned five steps. It now writes only the stamps that
differ, and adds the class only when the vault object has no stamps. Identity Applications objects already
worked this way.

## [0.62.0] - 2026-11-02

### Added
Expand Down
31 changes: 27 additions & 4 deletions src/main/java/com/pointblue/dirxml/dev/deploy/Plan.java
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,13 @@ public static Plan of(ModelDiff diff, DriverSet to, String dsDn, Secrets secrets
Map<String, List<byte[]>> attrs = "package-stamps".equals(c.what)
? new LinkedHashMap<>() : VaultMapping.attributes(a);
Map<String, List<byte[]>> stamps = VaultMapping.packageAttributes(tree, a, p.packageIndex);
Artifact was = c.kind == ModelDiff.Kind.ARTIFACT_CHANGED ? diff.from().resolve(c.path) : null;
if (!stamps.isEmpty()) {
attrs.putAll(stamps); // an installed object: Designer's stamps, the installed checksum included
// an installed object: Designer's stamps, the installed checksum included; on a changed
// object only those that differ from the vault's
attrs.putAll(changedStamps(stamps, was, p.packageIndex));
}
if (Packages.isPackaged(a) && Packages.isCustomized(a) && !attrs.containsKey(VaultMapping.PKG_CHECKSUM)) {
if (Packages.isPackaged(a) && Packages.isCustomized(a) && !stamps.containsKey(VaultMapping.PKG_CHECKSUM)) {
// customized, and no checksum recorded (a tree that named the package but never
// refreshed its stamp): a content-derived one, so the vault's pair differs from the baseline
byte[] content = VaultMapping.contentBytes(a);
Expand All @@ -276,15 +279,15 @@ public static Plan of(ModelDiff diff, DriverSet to, String dsDn, Secrets secrets
bucket.add(new Step(Op.ADD, dn, null, classes, attrs,
dn + " " + VaultMapping.objectClass(a) + " (" + size(attrs) + ")", c.path, a.driver));
} else {
if (!stamps.isEmpty()) {
if (!stamps.isEmpty() && (was == null || !VaultMapping.isStamped(was))) {
// a vault object with stamps already has the class that holds them
bucket.add(new Step(Op.AUX_CLASS, dn, null, List.of(VaultMapping.PKG_ITEM_AUX), null,
dn + " objectClass += " + VaultMapping.PKG_ITEM_AUX, c.path, a.driver));
}
for (Map.Entry<String, List<byte[]>> e : attrs.entrySet()) {
bucket.add(new Step(Op.MODIFY, dn, e.getKey(), null, Map.of(e.getKey(), e.getValue()),
dn + " " + e.getKey() + " (" + size(Map.of(e.getKey(), e.getValue())) + ")", c.path, a.driver));
}
Artifact was = diff.from().resolve(c.path);
if (stamps.isEmpty() && was != null && stripSteps(p, to, bucket, dn, a.meta, was.meta, c.path, a.driver)) {
p.notes.add(c.path + ": package stamps removed (the driver was stripped of its packages)");
}
Expand Down Expand Up @@ -851,6 +854,26 @@ private static boolean stripSteps(Plan p, DriverSet to, List<Step> bucket, Strin
return true;
}

/**
* The stamp attributes of a changed artifact that differ from the vault object's ({@code was}, null when
* unknown: all of them). A content change used to rewrite every stamp with the value already there.
* {@code DirXML-pkgInitialState} is not in the vault's model, so it is always kept.
*/
static Map<String, List<byte[]>> changedStamps(Map<String, List<byte[]>> stamps, Artifact was, com.pointblue.dirxml.dev.model.PackageStamps.Index index) {
if (was == null) {
return stamps;
}
Map<String, List<byte[]>> live = VaultMapping.packageAttributes(null, was, index);
Map<String, List<byte[]>> out = new LinkedHashMap<>();
for (Map.Entry<String, List<byte[]>> e : stamps.entrySet()) {
List<byte[]> v = live.get(e.getKey());
if (v == null || !sameBytes(v, e.getValue())) {
out.put(e.getKey(), e.getValue());
}
}
return out;
}

private static boolean sameBytes(List<byte[]> a, List<byte[]> b) {
if (a.size() != b.size()) {
return false;
Expand Down
39 changes: 39 additions & 0 deletions src/test/java/com/pointblue/dirxml/dev/deploy/PlanTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,45 @@ public void customizedStampedArtifactCarriesTheRecomputedChecksumNotTheOldOne()
assertNotEquals(staleChecksum, checksum);
}

private static void stamp(Policy p, String checksum) {
p.meta.put("dirxml-pkgguid", "PKGGUID_1;com.example.pkg;1.0.0;Example;PKG");
p.meta.put("dirxml-pkgassociationid", "ASSOC1");
p.meta.put("dirxml-pkgchecksum", checksum);
}

/** A changed packaged object whose stamps the vault already has: only its content is written (edir3, 2026-11-02). */
@Test
public void changedPackagedObjectDoesNotRewriteTheVaultsStamps() {
DriverSet from = VaultMappingTest.model();
DriverSet to = VaultMappingTest.model();
stamp((Policy) from.resolve("drivers/AD/subscriber/sub-ctp"), "4234957497");
Policy ctp = (Policy) to.resolve("drivers/AD/subscriber/sub-ctp");
stamp(ctp, "4234957497");
ctp.content = ValidatorTest.xml("<policy><rule><description>edited</description><conditions/><actions/></rule></policy>");

Plan p = plan(from, to, Secrets.none(), "none");
for (Plan.Step s : p.steps) {
assertNotEquals(p.text("stg", DS), Plan.Op.AUX_CLASS, s.op);
assertTrue(p.text("stg", DS), s.attr == null || !s.attr.startsWith("DirXML-pkg"));
}
assertTrue(p.text("stg", DS), p.steps.stream().anyMatch(s -> VaultMapping.XML_DATA.equals(s.attr)));
}

@Test
public void changedPackagedObjectWritesOnlyTheStampThatDiffers() {
DriverSet from = VaultMappingTest.model();
DriverSet to = VaultMappingTest.model();
stamp((Policy) from.resolve("drivers/AD/subscriber/sub-ctp"), "111111111");
Policy ctp = (Policy) to.resolve("drivers/AD/subscriber/sub-ctp");
stamp(ctp, "222222222");
ctp.content = ValidatorTest.xml("<policy><rule><description>edited</description><conditions/><actions/></rule></policy>");

Plan p = plan(from, to, Secrets.none(), "none");
List<String> pkg = p.steps.stream().filter(s -> s.attr != null && s.attr.startsWith("DirXML-pkg")).map(s -> s.attr).toList();
assertEquals(p.text("stg", DS), List.of(VaultMapping.PKG_CHECKSUM), pkg);
assertTrue(p.text("stg", DS), p.steps.stream().noneMatch(s -> s.op == Plan.Op.AUX_CLASS));
}

@Test
public void newDriverIsCreatedStoppedWithItsSecrets() throws IOException {
DriverSet from = VaultMappingTest.model();
Expand Down
Loading