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
17 changes: 17 additions & 0 deletions api/gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,17 @@ components:
apply; empty, every change on offer is left alone. A key that names no resource in the
diff is refused with `400` (`GTW-1025`), as is leaving out a resource whose key is longer
than 512 characters or whose id or name is longer than 255.
skip:
type: array
items:
type: string
description: |
The keys of changes to leave alone in this apply only: neither written nor removed, and
the gateway's standing choice for them kept as it is. A skipped change stays owed to the
gateway: the next diff offers it again, marked `skipped`, as `updated` or `deleted`, until an
apply writes or removes it or a selection leaves it alone for good. A selection decides the
other changes. A key that names no change in the diff, or one the selection also names, is
refused with `400` (`GTW-1025`); one naming a change that changes nothing skips nothing.
RevertRequest:
type: object
properties:
Expand All @@ -899,6 +910,12 @@ components:
The gateway is set to leave this resource alone. Such a change stays on offer, whatever the
versions say of it, until a selection includes it again: one whose removal was held back is
offered as `deleted` once no version names it.
skipped:
type: boolean
description: |
An earlier apply skipped this change, so it is offered again whatever the versions say of
it: as `updated` while the version carries the resource, since what the gateway holds of it
is not known, and as `deleted` once no version names it. Cleared by the apply that takes it.
resourceType:
type: string
id:
Expand Down
3 changes: 3 additions & 0 deletions backend/dbscripts/configdb/postgres.sql
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,9 @@ CREATE TABLE "GATEWAY_EXCLUDED_RESOURCE" (
RESOURCE_TYPE VARCHAR(255) NOT NULL,
RESOURCE_ID VARCHAR(255),
RESOURCE_NAME VARCHAR(255),
-- A resource skipped for one apply is owed to the gateway until an apply writes or removes it,
-- rather than left alone for good.
ONCE BOOLEAN NOT NULL DEFAULT FALSE,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
PRIMARY KEY (DEPLOYMENT_ID, GATEWAY_ID, RESOURCE_KEY)
);

Expand Down
3 changes: 3 additions & 0 deletions backend/dbscripts/configdb/sqlite.sql
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,9 @@ CREATE TABLE "GATEWAY_EXCLUDED_RESOURCE" (
RESOURCE_TYPE VARCHAR(255) NOT NULL,
RESOURCE_ID VARCHAR(255),
RESOURCE_NAME VARCHAR(255),
-- A resource skipped for one apply is owed to the gateway until an apply writes or removes it,
-- rather than left alone for good.
ONCE BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (DEPLOYMENT_ID, GATEWAY_ID, RESOURCE_KEY)
);

Expand Down
8 changes: 4 additions & 4 deletions backend/internal/gateway/version_error_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,13 @@ var (
Code: "GTW-1025",
Error: tidcommon.I18nMessage{
Key: "error.gatewayservice.invalid_selection",
DefaultValue: "The selection names a resource this apply does not report",
DefaultValue: "The selection or skip names a resource this apply does not report",
},
ErrorDescription: tidcommon.I18nMessage{
Key: "error.gatewayservice.invalid_selection.description",
DefaultValue: "Select changes by the keys a diff of this version reports for this gateway. " +
"A resource whose key is longer than 512 characters, or whose id or name is longer than 255, " +
"cannot be left out.",
DefaultValue: "Select or skip changes by the keys a diff of this version reports for this gateway. " +
"A change cannot be both selected and skipped. A resource whose key is longer than 512 characters, " +
"or whose id or name is longer than 255, cannot be left out.",
},
}
// ErrorGatewayUnreachable is returned when a gateway could not be called or refused the import.
Expand Down
156 changes: 155 additions & 1 deletion backend/internal/gateway/version_exclusions.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,26 @@ import (

// excludedResource is a resource a gateway is set to leave alone. Its type, id and name are kept
// with its key, so it can be offered once no version names it any more: a removal held back is
// still owed to the gateway then.
// still owed to the gateway then. One with Once set was skipped for one apply rather than left alone
// for good: it is owed to the gateway, and offered again until an apply writes or removes it.
type excludedResource struct {
Key string
Type string
ID string
Name string
Once bool
}

// splitExclusions separates a gateway's standing choice from what a skipped apply left owed to it.
func splitExclusions(all []excludedResource) (standing, owed []excludedResource) {
for _, resource := range all {
if resource.Once {
owed = append(owed, resource)
} else {
standing = append(standing, resource)
}
}
return standing, owed
}

// offered reports whether a change is one a selection decides: one that changes something, or one
Expand Down Expand Up @@ -100,6 +114,146 @@ func nextExclusions(existing []excludedResource, changes []Change,
return next, exclude, include
}

// applyChoice is what an apply leaves alone, and how the gateway's standing choice moves for it.
type applyChoice struct {
// leftAlone is everything this apply neither writes nor removes: the standing choice, as the
// selection leaves it, and what is skipped this once.
leftAlone []excludedResource
// exclude and include are what the gateway is newly set to leave alone, and to take again.
exclude []excludedResource
include []string
// skipped is what this apply leaves alone this once, owed to the gateway from now on.
skipped []excludedResource
}

// choose works out an apply's choice from the gateway's standing one, a selection and a skip, and
// reports false for a selection or a skip that names what the diff does not offer.
func choose(changes []Change, excluded []excludedResource, selection *[]string, skip []string) (applyChoice, bool) {
skipped, choosable, ok := skippedOf(changes, skip, selection)
if !ok {
return applyChoice{}, false
}
choice := applyChoice{}
if selection != nil {
if !validSelection(choosable, excluded, *selection) {
return applyChoice{}, false
}
excluded, choice.exclude, choice.include = nextExclusions(excluded, choosable, *selection)
}
choice.leftAlone = withSkipped(excluded, skipped)
choice.skipped = skipped
return choice, true
}

// skippedOf reads the changes an apply skips: each key must name a change the diff reports and not
// one the selection also names, since a change cannot be both applied and left alone, and the
// resource must fit the columns that keep what is owed. A key naming a change that changes nothing is
// taken and skips nothing: there is nothing to leave out. It returns the skipped resources, owed to
// the gateway from now on, the changes a selection decides, which are the rest, and whether the skip
// is valid.
func skippedOf(changes []Change, skip []string, selection *[]string) ([]excludedResource, []Change, bool) {
if len(skip) == 0 {
return nil, changes, true
}
skipping := toSet(skip)
if selection != nil {
for _, key := range *selection {
if skipping[key] {
return nil, nil, false
}
}
}
named := 0
skipped := make([]excludedResource, 0, len(skipping))
choosable := make([]Change, 0, len(changes))
for _, change := range changes {
if skipping[change.Key] {
named++
if offered(change) {
if !keepable(change) {
return nil, nil, false
}
skipped = append(skipped, excludedResource{Key: change.Key, Type: change.ResourceType,
ID: change.ID, Name: change.Name, Once: true})
continue
}
}
choosable = append(choosable, change)
}
return skipped, choosable, named == len(skipping)
}

// markOwed offers again what an earlier apply skipped. A skipped resource is owed to the gateway until
// an apply writes or removes it: one the version still carries is offered as updated, even when the
// versions do not differ on it, since what the gateway holds of it is not known, and one no version
// names any more is offered as deleted.
func markOwed(changes []Change, owed []excludedResource) []Change {
if len(owed) == 0 {
return changes
}
keys := keysOf(owed)
named := make(map[string]bool, len(changes))
for i := range changes {
named[changes[i].Key] = true
if !keys[changes[i].Key] {
continue
}
changes[i].Skipped = true
if changes[i].Change == ChangeUnchanged {
changes[i].Change = ChangeUpdated
}
}
for _, resource := range owed {
if !named[resource.Key] {
changes = append(changes, Change{Key: resource.Key, ResourceType: resource.Type, ID: resource.ID,
Name: resource.Name, Change: ChangeDeleted, Skipped: true})
}
}
sortChanges(changes)
return changes
}

// newlyOwed is what a skip leaves owed to the gateway that it did not owe, or leave alone, before.
func newlyOwed(skipped, before []excludedResource) []excludedResource {
held := keysOf(before)
owed := make([]excludedResource, 0, len(skipped))
for _, resource := range skipped {
if !held[resource.Key] {
owed = append(owed, resource)
}
}
return owed
}

// owedTaken is the keys of the resources owed to a gateway that an apply takes: those it does not
// leave alone, and so writes or removes.
func owedTaken(owed, leftAlone []excludedResource) []string {
alone := keysOf(leftAlone)
taken := make([]string, 0, len(owed))
for _, resource := range owed {
if !alone[resource.Key] {
taken = append(taken, resource.Key)
}
}
return taken
}

// withSkipped is a gateway's standing choice with the resources skipped this once added.
func withSkipped(excluded, skipped []excludedResource) []excludedResource {
if len(skipped) == 0 {
return excluded
}
held := keysOf(excluded)
all := append([]excludedResource{}, excluded...)
for _, resource := range skipped {
if !held[resource.Key] {
all = append(all, resource)
}
}
sort.Slice(all, func(i, j int) bool { return all[i].Key < all[j].Key })
return all
}

// markExcluded flags the changes a gateway is set to leave alone, and offers as a removal each one
// left alone that neither version names: one whose removal was held back is still owed then.
func markExcluded(changes []Change, excluded []excludedResource) []Change {
Expand Down
Loading
Loading