@@ -2491,7 +2491,8 @@ enum changeType
24912491 DELETE , // delete complete RRset
24922492 REPLACE , // replace complete RRset
24932493 PRUNE , // remove single record from RRset if found
2494- EXTEND // add single record to RRset if not found
2494+ EXTEND , // add single record to RRset if not found
2495+ ALWAYSEXTEND , // add single record to RRset if not found, and always rewrite the RRset
24952496};
24962497
24972498// Validate the "changetype" field of a Json patch record.
@@ -2511,6 +2512,9 @@ static changeType validateChangeType(const std::string& changetype)
25112512 if (changetype == " EXTEND" ) {
25122513 return EXTEND ;
25132514 }
2515+ if (changetype == " ALWAYSEXTEND" ) {
2516+ return ALWAYSEXTEND ;
2517+ }
25142518 throw ApiException (" Changetype '" + changetype + " ' is not a valid value" );
25152519}
25162520
@@ -2667,19 +2671,19 @@ static applyResult applyReplace(const DomainInfo& domainInfo, const ZoneName& zo
26672671 return SUCCESS ;
26682672}
26692673
2670- // Apply a PRUNE or EXTEND changetype.
2674+ // Apply a PRUNE, EXTEND or ALWAYSEXTEND changetype.
26712675static applyResult applyPruneOrExtend (const DomainInfo& domainInfo, const ZoneName& zonename, const Json& container, DNSName& qname, QType& qtype, bool allowUnderscores, soaEditSettings& soa, HttpResponse* resp, changeType operationType, std::vector<DNSResourceRecord>& rrset)
26722676{
26732677 if (!container[" records" ].is_array ()) {
2674- throw ApiException (" No record provided for PRUNE or EXTEND operation" );
2678+ throw ApiException (" No record provided for PRUNE, EXTEND or ALWAYSEXTEND operation" );
26752679 }
26762680
26772681 try {
26782682 vector<DNSResourceRecord> new_records;
26792683 uint32_t ttl = uintFromJson (container, " ttl" );
26802684 gatherRecords (container, qname, qtype, ttl, new_records);
26812685 if (new_records.size () != 1 ) {
2682- throw ApiException (" Exactly one record should be provided for PRUNE or EXTEND operation" );
2686+ throw ApiException (" Exactly one record should be provided for PRUNE, EXTEND or ALWAYSEXTEND operation" );
26832687 }
26842688
26852689 auto & new_record = new_records.front ();
@@ -2703,10 +2707,15 @@ static applyResult applyPruneOrExtend(const DomainInfo& domainInfo, const ZoneNa
27032707 }
27042708 }
27052709 // Add new record to RRset if not found.
2706- if (operationType == EXTEND && !seenRecord) {
2710+ if (operationType != PRUNE && !seenRecord) {
27072711 rrset.emplace_back (new_record);
27082712 }
2709- bool submitChanges = (operationType == EXTEND && !seenRecord) || (operationType == PRUNE && seenRecord);
2713+ // clang-format off
2714+ bool submitChanges =
2715+ operationType == ALWAYSEXTEND ||
2716+ (operationType == EXTEND && !seenRecord) ||
2717+ (operationType == PRUNE && seenRecord);
2718+ // clang-format on
27102719 if (!submitChanges) {
27112720 return NOP ;
27122721 }
@@ -2741,7 +2750,7 @@ static void patchZone(UeberBackend& backend, const ZoneName& zonename, DomainInf
27412750 domainInfo.backend ->getDomainMetadataOne (zonename, " SOA-EDIT" , soa.edit_kind );
27422751 bool allowUnderscores = areUnderscoresAllowed (zonename, *domainInfo.backend );
27432752
2744- // For PRUNE and EXTEND operations, we are not being passed the complete
2753+ // For PRUNE and * EXTEND operations, we are not being passed the complete
27452754 // RRset, and will need to fetch it from the backend. But we may have
27462755 // processed a DELETE or REPLACE operation for the same RRset first, in
27472756 // which case we can't assume querying the backend will be consistent with
@@ -2750,7 +2759,7 @@ static void patchZone(UeberBackend& backend, const ZoneName& zonename, DomainInf
27502759 // To be sure to work on consistent contents, without having to rely upon
27512760 // specific backend behaviour, we will need to cache the RRset values
27522761 // in this routine, but we only need to do that for RRset which are
2753- // subject to both PRUNE/EXTEND and DELETE/REPLACE operation.
2762+ // subject to both PRUNE/* EXTEND and DELETE/REPLACE operation.
27542763 // That first pass over the change requests computes this (and also
27552764 // performs basic validation).
27562765 using key = std::pair<DNSName, QType>;
@@ -2782,7 +2791,7 @@ static void patchZone(UeberBackend& backend, const ZoneName& zonename, DomainInf
27822791 if (auto iter = changes.find (currentKey); iter != changes.end ()) {
27832792 auto operations = iter->second ;
27842793 // Only allow one DELETE or REPLACE operation per RRset. On the other
2785- // hand, it makes sense to allow multiple PRUNE or EXTEND, since the
2794+ // hand, it makes sense to allow multiple PRUNE or * EXTEND, since the
27862795 // individual records they'll concern might differ.
27872796 if (operationType == DELETE || operationType == REPLACE ) {
27882797 if ((operations & newOperation) != 0 ) {
@@ -2797,7 +2806,7 @@ static void patchZone(UeberBackend& backend, const ZoneName& zonename, DomainInf
27972806 }
27982807
27992808 // In this second pass, we will process the changes and maintain a cache
2800- // of the RRset subject to PRUNE/EXTEND operations.
2809+ // of the RRset subject to PRUNE/* EXTEND operations.
28012810 std::map<key, std::vector<DNSResourceRecord>> cache;
28022811 for (const auto & container : rrsets) {
28032812 string changetype = toUpper (stringFromJson (container, " changetype" ));
@@ -2810,7 +2819,7 @@ static void patchZone(UeberBackend& backend, const ZoneName& zonename, DomainInf
28102819 bool cacheNeeded{false };
28112820 if (auto iter = changes.find (currentKey); iter != changes.end ()) {
28122821 auto operations = iter->second ;
2813- cacheNeeded = (operations & ((1U << PRUNE ) | (1U << EXTEND ))) != 0 ;
2822+ cacheNeeded = (operations & ((1U << PRUNE ) | (1U << EXTEND ) | ( 1U << ALWAYSEXTEND ) )) != 0 ;
28142823 }
28152824
28162825 applyResult result{ABORT };
@@ -2824,6 +2833,7 @@ static void patchZone(UeberBackend& backend, const ZoneName& zonename, DomainInf
28242833 break ;
28252834 case PRUNE :
28262835 case EXTEND :
2836+ case ALWAYSEXTEND :
28272837 // First, obtain the current RRset, either from the backend or from
28282838 // our local cache if we already did some operations.
28292839 if (const auto iter = cache.find (currentKey); iter != cache.end ()) {
0 commit comments