Draft
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
214 changes: 186 additions & 28 deletions internal/network/overlay.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,9 +149,24 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
if err != nil {
return err
}
if !captureERPCProvenanceEntries(prov, erpcConfig, ov) {
return nil
}
return writeERPCProvenance(cfg, prov)
}

// captureERPCProvenanceEntries is the in-memory half of
// captureERPCProvenance. It records the original value for each newly-owned
// key after retired overlay entries have been stripped, so replacing one
// overlay with another never mistakes the previous overlay value for chart
// base state.
func captureERPCProvenanceEntries(prov *erpcProvenance, erpcConfig map[string]any, ov *ERPCOverlay) bool {
if prov == nil || ov == nil {
return false
}
project := erpcConfigProject(erpcConfig)
if project == nil {
return nil
return false
}
changed := false

Expand DownExpand Up@@ -211,6 +226,49 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
}
}

return changed
}

// ensureERPCProvenanceTracksOverlay preserves ownership of an existing saved
// overlay before SetERPC replaces that document. Normally provenance already
// contains these keys. Missing entries can occur after upgrading an overlay
// created before provenance tracking; nil markers retain the legacy reset
// behavior (drop the owned key) when the original value is unknowable.
func ensureERPCProvenanceTracksOverlay(cfg *config.Config, ov *ERPCOverlay) error {
if ov == nil {
return nil
}
prov, err := readERPCProvenance(cfg)
if err != nil {
return err
}
changed := false
if len(ov.Networks) > 0 && prov.Networks == nil {
prov.Networks = make(map[string]*map[string]any)
}
for _, n := range ov.Networks {
key := networkMergeKey(n)
if key == "" {
continue
}
if _, tracked := prov.Networks[key]; !tracked {
prov.Networks[key] = nil
changed = true
}
}
if len(ov.Upstreams) > 0 && prov.Upstreams == nil {
prov.Upstreams = make(map[string]*map[string]any)
}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
if _, tracked := prov.Upstreams[id]; !tracked {
prov.Upstreams[id] = nil
changed = true
}
}
if !changed {
return nil
}
Expand DownExpand Up@@ -264,8 +322,10 @@ func ReconcileERPCOverlay(cfg *config.Config, u *ui.UI) {
len(ov.Networks), len(ov.Upstreams))
}

// ApplyERPCOverlayFile loads an overlay YAML from path, persists it under
// ConfigDir, and merges it into the live eRPC ConfigMap.
// SetERPC loads an overlay YAML from path, persists it under ConfigDir as the
// complete desired operator overlay, and reconciles the live eRPC ConfigMap to
// it. Entries owned by the previous overlay but omitted from the new document
// are removed (or restored from provenance) before the new entries are merged.
func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
data, err := os.ReadFile(path)
if err != nil {
Expand All@@ -275,6 +335,13 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
if err != nil {
return err
}
previous, err := readERPCOverlay(cfg)
if err != nil {
return fmt.Errorf("read existing eRPC overlay: %w", err)
}
if err := ensureERPCProvenanceTracksOverlay(cfg, previous); err != nil {
return fmt.Errorf("preserve existing eRPC overlay ownership: %w", err)
}
if err := writeERPCOverlay(cfg, ov); err != nil {
return err
}
Expand All@@ -287,8 +354,10 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
return nil
}

// ClearERPCOverlay removes overlay-owned fragments from the live ConfigMap
// (best-effort), then deletes the host-side overlay file.
// ResetERPC removes overlay-owned fragments from the live ConfigMap, then
// deletes the host-side overlay and provenance files. If cluster cleanup fails,
// the files are deliberately retained so reset or stack-up reconciliation can
// retry without losing ownership information.
func ResetERPC(cfg *config.Config, u *ui.UI) error {
ov, err := readERPCOverlay(cfg)
if err != nil {
Expand All@@ -300,10 +369,9 @@ func ResetERPC(cfg *config.Config, u *ui.UI) error {
}

if err := removeOverlayFromCluster(cfg, ov); err != nil {
u.Warnf("Could not strip eRPC config from live ConfigMap (will still reset host file): %v", err)
} else {
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")
return fmt.Errorf("strip eRPC config from live ConfigMap (durable overlay retained for retry): %w", err)
}
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")

path := erpcOverlayPath(cfg)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
Expand DownExpand Up@@ -487,18 +555,26 @@ func applyOverlayToCluster(cfg *config.Config, ov *ERPCOverlay, source string) e
if err != nil {
return err
}
// Snapshot whatever mergeERPCOverlay is about to replace, BEFORE it
// mutates erpcConfig, so ResetERPC can restore it later instead of
// deleting it outright.
if err := captureERPCProvenance(cfg, erpcConfig, ov); err != nil {
return fmt.Errorf("capture eRPC overlay provenance: %w", err)
prov, err := readERPCProvenance(cfg)
if err != nil {
return fmt.Errorf("read eRPC overlay provenance: %w", err)
}
if err := mergeERPCOverlay(erpcConfig, ov); err != nil {
if err := reconcileERPCOverlayConfig(erpcConfig, ov, prov); err != nil {
return err
}
// Persist the union provenance before mutating the cluster. If the cluster
// write fails, the saved desired overlay plus this record is sufficient for
// the next reconcile to remove both old and partially-applied ownership.
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("write eRPC overlay provenance: %w", err)
}
if err := writeERPCConfig(cfg, erpcConfig); err != nil {
return err
}
pruneERPCProvenance(prov, ov)
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("prune eRPC overlay provenance: %w", err)
}
hash := ""
if data, err := os.ReadFile(erpcOverlayPath(cfg)); err == nil {
sum := sha256.Sum256(data)
Expand All@@ -517,12 +593,75 @@ func removeOverlayFromCluster(cfg *config.Config, ov *ERPCOverlay) error {
if err != nil {
return err
}
if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
if provenanceHasEntries(prov) {
if err := stripERPCProvenanceKeys(erpcConfig, prov, nil, nil); err != nil {
return err
}
} else if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
return err
}
return writeERPCConfig(cfg, erpcConfig)
}

// reconcileERPCOverlayConfig treats ov as desired state for overlay-owned
// networks and upstreams. Provenance may temporarily contain a union of old
// and new keys to make replacement crash-safe; keys not present in ov are
// stripped before provenance for new keys is captured and ov is merged.
func reconcileERPCOverlayConfig(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
if ov == nil {
return nil
}
if prov == nil {
return fmt.Errorf("eRPC overlay provenance is nil")
}
desiredNetworks, desiredUpstreams := erpcOverlayKeySets(ov)
if err := stripERPCProvenanceKeys(erpcConfig, prov, desiredNetworks, desiredUpstreams); err != nil {
return err
}
captureERPCProvenanceEntries(prov, erpcConfig, ov)
return mergeERPCOverlay(erpcConfig, ov)
}

func erpcOverlayKeySets(ov *ERPCOverlay) (map[string]struct{}, map[string]struct{}) {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if ov == nil {
return networks, upstreams
}
for _, n := range ov.Networks {
if key := networkMergeKey(n); key != "" {
networks[key] = struct{}{}
}
}
for _, u := range ov.Upstreams {
if id, _ := u["id"].(string); id != "" {
upstreams[id] = struct{}{}
}
}
return networks, upstreams
}

func pruneERPCProvenance(prov *erpcProvenance, ov *ERPCOverlay) {
if prov == nil {
return
}
keepNetworks, keepUpstreams := erpcOverlayKeySets(ov)
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
delete(prov.Networks, key)
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
delete(prov.Upstreams, id)
}
}
}

func provenanceHasEntries(prov *erpcProvenance) bool {
return prov != nil && (len(prov.Networks) > 0 || len(prov.Upstreams) > 0)
}

// mergeERPCOverlay mutates erpcConfig in place. Exported for tests via
// the unexported name used from overlay_test.go (same package).
func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
Expand DownExpand Up@@ -565,6 +704,33 @@ func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
// provenance record at all (nil prov, or overlay applied before provenance
// tracking existed) falls back to the old drop-only behavior.
func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
networks, upstreams := erpcOverlayKeySets(ov)
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

// stripERPCProvenanceKeys strips every provenance-tracked key that is not in
// the corresponding keep set. A nil keep set means keep nothing. This is what
// lets a newly persisted overlay remove keys owned by its predecessor even
// though the predecessor document is no longer on disk.
func stripERPCProvenanceKeys(erpcConfig map[string]any, prov *erpcProvenance, keepNetworks, keepUpstreams map[string]struct{}) error {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if prov != nil {
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
networks[key] = struct{}{}
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
upstreams[id] = struct{}{}
}
}
}
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

func stripERPCKeys(erpcConfig map[string]any, networkKeys, upstreamIDs map[string]struct{}, prov *erpcProvenance) error {
project := erpcConfigProject(erpcConfig)
if project == nil {
return fmt.Errorf("eRPC config has no projects")
Expand All@@ -573,14 +739,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
prov = &erpcProvenance{}
}

if len(ov.Upstreams) > 0 {
if len(upstreamIDs) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
for id := range upstreamIDs {
if orig, tracked := prov.Upstreams[id]; tracked && orig != nil {
restore[id] = *orig
} else {
Expand DownExpand Up@@ -608,14 +770,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
project["upstreams"] = kept
}

if len(ov.Networks) > 0 {
if len(networkKeys) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, n := range ov.Networks {
k := networkMergeKey(n)
if k == "" {
continue
}
for k := range networkKeys {
if orig, tracked := prov.Networks[k]; tracked && orig != nil {
restore[k] = *orig
} else {
Expand DownExpand Up@@ -648,7 +806,7 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
}

func mergeNetworksByKey(existing []any, overlay []map[string]any) []any {
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
index := map[string]int{} // mergeKey → index in out
for _, n := range existing {
nm, ok := n.(map[string]any)
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Draft
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
214 changes: 186 additions & 28 deletions internal/network/overlay.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,9 +149,24 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
if err != nil {
return err
}
if !captureERPCProvenanceEntries(prov, erpcConfig, ov) {
return nil
}
return writeERPCProvenance(cfg, prov)
}

// captureERPCProvenanceEntries is the in-memory half of
// captureERPCProvenance. It records the original value for each newly-owned
// key after retired overlay entries have been stripped, so replacing one
// overlay with another never mistakes the previous overlay value for chart
// base state.
func captureERPCProvenanceEntries(prov *erpcProvenance, erpcConfig map[string]any, ov *ERPCOverlay) bool {
if prov == nil || ov == nil {
return false
}
project := erpcConfigProject(erpcConfig)
if project == nil {
return nil
return false
}
changed := false

Expand DownExpand Up@@ -211,6 +226,49 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
}
}

return changed
}

// ensureERPCProvenanceTracksOverlay preserves ownership of an existing saved
// overlay before SetERPC replaces that document. Normally provenance already
// contains these keys. Missing entries can occur after upgrading an overlay
// created before provenance tracking; nil markers retain the legacy reset
// behavior (drop the owned key) when the original value is unknowable.
func ensureERPCProvenanceTracksOverlay(cfg *config.Config, ov *ERPCOverlay) error {
if ov == nil {
return nil
}
prov, err := readERPCProvenance(cfg)
if err != nil {
return err
}
changed := false
if len(ov.Networks) > 0 && prov.Networks == nil {
prov.Networks = make(map[string]*map[string]any)
}
for _, n := range ov.Networks {
key := networkMergeKey(n)
if key == "" {
continue
}
if _, tracked := prov.Networks[key]; !tracked {
prov.Networks[key] = nil
changed = true
}
}
if len(ov.Upstreams) > 0 && prov.Upstreams == nil {
prov.Upstreams = make(map[string]*map[string]any)
}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
if _, tracked := prov.Upstreams[id]; !tracked {
prov.Upstreams[id] = nil
changed = true
}
}
if !changed {
return nil
}
Expand DownExpand Up@@ -264,8 +322,10 @@ func ReconcileERPCOverlay(cfg *config.Config, u *ui.UI) {
len(ov.Networks), len(ov.Upstreams))
}

// ApplyERPCOverlayFile loads an overlay YAML from path, persists it under
// ConfigDir, and merges it into the live eRPC ConfigMap.
// SetERPC loads an overlay YAML from path, persists it under ConfigDir as the
// complete desired operator overlay, and reconciles the live eRPC ConfigMap to
// it. Entries owned by the previous overlay but omitted from the new document
// are removed (or restored from provenance) before the new entries are merged.
func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
data, err := os.ReadFile(path)
if err != nil {
Expand All@@ -275,6 +335,13 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
if err != nil {
return err
}
previous, err := readERPCOverlay(cfg)
if err != nil {
return fmt.Errorf("read existing eRPC overlay: %w", err)
}
if err := ensureERPCProvenanceTracksOverlay(cfg, previous); err != nil {
return fmt.Errorf("preserve existing eRPC overlay ownership: %w", err)
}
if err := writeERPCOverlay(cfg, ov); err != nil {
return err
}
Expand All@@ -287,8 +354,10 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
return nil
}

// ClearERPCOverlay removes overlay-owned fragments from the live ConfigMap
// (best-effort), then deletes the host-side overlay file.
// ResetERPC removes overlay-owned fragments from the live ConfigMap, then
// deletes the host-side overlay and provenance files. If cluster cleanup fails,
// the files are deliberately retained so reset or stack-up reconciliation can
// retry without losing ownership information.
func ResetERPC(cfg *config.Config, u *ui.UI) error {
ov, err := readERPCOverlay(cfg)
if err != nil {
Expand All@@ -300,10 +369,9 @@ func ResetERPC(cfg *config.Config, u *ui.UI) error {
}

if err := removeOverlayFromCluster(cfg, ov); err != nil {
u.Warnf("Could not strip eRPC config from live ConfigMap (will still reset host file): %v", err)
} else {
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")
return fmt.Errorf("strip eRPC config from live ConfigMap (durable overlay retained for retry): %w", err)
}
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")

path := erpcOverlayPath(cfg)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
Expand DownExpand Up@@ -487,18 +555,26 @@ func applyOverlayToCluster(cfg *config.Config, ov *ERPCOverlay, source string) e
if err != nil {
return err
}
// Snapshot whatever mergeERPCOverlay is about to replace, BEFORE it
// mutates erpcConfig, so ResetERPC can restore it later instead of
// deleting it outright.
if err := captureERPCProvenance(cfg, erpcConfig, ov); err != nil {
return fmt.Errorf("capture eRPC overlay provenance: %w", err)
prov, err := readERPCProvenance(cfg)
if err != nil {
return fmt.Errorf("read eRPC overlay provenance: %w", err)
}
if err := mergeERPCOverlay(erpcConfig, ov); err != nil {
if err := reconcileERPCOverlayConfig(erpcConfig, ov, prov); err != nil {
return err
}
// Persist the union provenance before mutating the cluster. If the cluster
// write fails, the saved desired overlay plus this record is sufficient for
// the next reconcile to remove both old and partially-applied ownership.
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("write eRPC overlay provenance: %w", err)
}
if err := writeERPCConfig(cfg, erpcConfig); err != nil {
return err
}
pruneERPCProvenance(prov, ov)
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("prune eRPC overlay provenance: %w", err)
}
hash := ""
if data, err := os.ReadFile(erpcOverlayPath(cfg)); err == nil {
sum := sha256.Sum256(data)
Expand All@@ -517,12 +593,75 @@ func removeOverlayFromCluster(cfg *config.Config, ov *ERPCOverlay) error {
if err != nil {
return err
}
if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
if provenanceHasEntries(prov) {
if err := stripERPCProvenanceKeys(erpcConfig, prov, nil, nil); err != nil {
return err
}
} else if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
return err
}
return writeERPCConfig(cfg, erpcConfig)
}

// reconcileERPCOverlayConfig treats ov as desired state for overlay-owned
// networks and upstreams. Provenance may temporarily contain a union of old
// and new keys to make replacement crash-safe; keys not present in ov are
// stripped before provenance for new keys is captured and ov is merged.
func reconcileERPCOverlayConfig(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
if ov == nil {
return nil
}
if prov == nil {
return fmt.Errorf("eRPC overlay provenance is nil")
}
desiredNetworks, desiredUpstreams := erpcOverlayKeySets(ov)
if err := stripERPCProvenanceKeys(erpcConfig, prov, desiredNetworks, desiredUpstreams); err != nil {
return err
}
captureERPCProvenanceEntries(prov, erpcConfig, ov)
return mergeERPCOverlay(erpcConfig, ov)
}

func erpcOverlayKeySets(ov *ERPCOverlay) (map[string]struct{}, map[string]struct{}) {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if ov == nil {
return networks, upstreams
}
for _, n := range ov.Networks {
if key := networkMergeKey(n); key != "" {
networks[key] = struct{}{}
}
}
for _, u := range ov.Upstreams {
if id, _ := u["id"].(string); id != "" {
upstreams[id] = struct{}{}
}
}
return networks, upstreams
}

func pruneERPCProvenance(prov *erpcProvenance, ov *ERPCOverlay) {
if prov == nil {
return
}
keepNetworks, keepUpstreams := erpcOverlayKeySets(ov)
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
delete(prov.Networks, key)
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
delete(prov.Upstreams, id)
}
}
}

func provenanceHasEntries(prov *erpcProvenance) bool {
return prov != nil && (len(prov.Networks) > 0 || len(prov.Upstreams) > 0)
}

// mergeERPCOverlay mutates erpcConfig in place. Exported for tests via
// the unexported name used from overlay_test.go (same package).
func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
Expand DownExpand Up@@ -565,6 +704,33 @@ func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
// provenance record at all (nil prov, or overlay applied before provenance
// tracking existed) falls back to the old drop-only behavior.
func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
networks, upstreams := erpcOverlayKeySets(ov)
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

// stripERPCProvenanceKeys strips every provenance-tracked key that is not in
// the corresponding keep set. A nil keep set means keep nothing. This is what
// lets a newly persisted overlay remove keys owned by its predecessor even
// though the predecessor document is no longer on disk.
func stripERPCProvenanceKeys(erpcConfig map[string]any, prov *erpcProvenance, keepNetworks, keepUpstreams map[string]struct{}) error {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if prov != nil {
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
networks[key] = struct{}{}
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
upstreams[id] = struct{}{}
}
}
}
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

func stripERPCKeys(erpcConfig map[string]any, networkKeys, upstreamIDs map[string]struct{}, prov *erpcProvenance) error {
project := erpcConfigProject(erpcConfig)
if project == nil {
return fmt.Errorf("eRPC config has no projects")
Expand All@@ -573,14 +739,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
prov = &erpcProvenance{}
}

if len(ov.Upstreams) > 0 {
if len(upstreamIDs) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
for id := range upstreamIDs {
if orig, tracked := prov.Upstreams[id]; tracked && orig != nil {
restore[id] = *orig
} else {
Expand DownExpand Up@@ -608,14 +770,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
project["upstreams"] = kept
}

if len(ov.Networks) > 0 {
if len(networkKeys) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, n := range ov.Networks {
k := networkMergeKey(n)
if k == "" {
continue
}
for k := range networkKeys {
if orig, tracked := prov.Networks[k]; tracked && orig != nil {
restore[k] = *orig
} else {
Expand DownExpand Up@@ -648,7 +806,7 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
}

func mergeNetworksByKey(existing []any, overlay []map[string]any) []any {
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
index := map[string]int{} // mergeKey → index in out
for _, n := range existing {
nm, ok := n.(map[string]any)
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Draft
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
214 changes: 186 additions & 28 deletions internal/network/overlay.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,9 +149,24 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
if err != nil {
return err
}
if !captureERPCProvenanceEntries(prov, erpcConfig, ov) {
return nil
}
return writeERPCProvenance(cfg, prov)
}

// captureERPCProvenanceEntries is the in-memory half of
// captureERPCProvenance. It records the original value for each newly-owned
// key after retired overlay entries have been stripped, so replacing one
// overlay with another never mistakes the previous overlay value for chart
// base state.
func captureERPCProvenanceEntries(prov *erpcProvenance, erpcConfig map[string]any, ov *ERPCOverlay) bool {
if prov == nil || ov == nil {
return false
}
project := erpcConfigProject(erpcConfig)
if project == nil {
return nil
return false
}
changed := false

Expand DownExpand Up@@ -211,6 +226,49 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
}
}

return changed
}

// ensureERPCProvenanceTracksOverlay preserves ownership of an existing saved
// overlay before SetERPC replaces that document. Normally provenance already
// contains these keys. Missing entries can occur after upgrading an overlay
// created before provenance tracking; nil markers retain the legacy reset
// behavior (drop the owned key) when the original value is unknowable.
func ensureERPCProvenanceTracksOverlay(cfg *config.Config, ov *ERPCOverlay) error {
if ov == nil {
return nil
}
prov, err := readERPCProvenance(cfg)
if err != nil {
return err
}
changed := false
if len(ov.Networks) > 0 && prov.Networks == nil {
prov.Networks = make(map[string]*map[string]any)
}
for _, n := range ov.Networks {
key := networkMergeKey(n)
if key == "" {
continue
}
if _, tracked := prov.Networks[key]; !tracked {
prov.Networks[key] = nil
changed = true
}
}
if len(ov.Upstreams) > 0 && prov.Upstreams == nil {
prov.Upstreams = make(map[string]*map[string]any)
}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
if _, tracked := prov.Upstreams[id]; !tracked {
prov.Upstreams[id] = nil
changed = true
}
}
if !changed {
return nil
}
Expand DownExpand Up@@ -264,8 +322,10 @@ func ReconcileERPCOverlay(cfg *config.Config, u *ui.UI) {
len(ov.Networks), len(ov.Upstreams))
}

// ApplyERPCOverlayFile loads an overlay YAML from path, persists it under
// ConfigDir, and merges it into the live eRPC ConfigMap.
// SetERPC loads an overlay YAML from path, persists it under ConfigDir as the
// complete desired operator overlay, and reconciles the live eRPC ConfigMap to
// it. Entries owned by the previous overlay but omitted from the new document
// are removed (or restored from provenance) before the new entries are merged.
func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
data, err := os.ReadFile(path)
if err != nil {
Expand All@@ -275,6 +335,13 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
if err != nil {
return err
}
previous, err := readERPCOverlay(cfg)
if err != nil {
return fmt.Errorf("read existing eRPC overlay: %w", err)
}
if err := ensureERPCProvenanceTracksOverlay(cfg, previous); err != nil {
return fmt.Errorf("preserve existing eRPC overlay ownership: %w", err)
}
if err := writeERPCOverlay(cfg, ov); err != nil {
return err
}
Expand All@@ -287,8 +354,10 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
return nil
}

// ClearERPCOverlay removes overlay-owned fragments from the live ConfigMap
// (best-effort), then deletes the host-side overlay file.
// ResetERPC removes overlay-owned fragments from the live ConfigMap, then
// deletes the host-side overlay and provenance files. If cluster cleanup fails,
// the files are deliberately retained so reset or stack-up reconciliation can
// retry without losing ownership information.
func ResetERPC(cfg *config.Config, u *ui.UI) error {
ov, err := readERPCOverlay(cfg)
if err != nil {
Expand All@@ -300,10 +369,9 @@ func ResetERPC(cfg *config.Config, u *ui.UI) error {
}

if err := removeOverlayFromCluster(cfg, ov); err != nil {
u.Warnf("Could not strip eRPC config from live ConfigMap (will still reset host file): %v", err)
} else {
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")
return fmt.Errorf("strip eRPC config from live ConfigMap (durable overlay retained for retry): %w", err)
}
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")

path := erpcOverlayPath(cfg)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
Expand DownExpand Up@@ -487,18 +555,26 @@ func applyOverlayToCluster(cfg *config.Config, ov *ERPCOverlay, source string) e
if err != nil {
return err
}
// Snapshot whatever mergeERPCOverlay is about to replace, BEFORE it
// mutates erpcConfig, so ResetERPC can restore it later instead of
// deleting it outright.
if err := captureERPCProvenance(cfg, erpcConfig, ov); err != nil {
return fmt.Errorf("capture eRPC overlay provenance: %w", err)
prov, err := readERPCProvenance(cfg)
if err != nil {
return fmt.Errorf("read eRPC overlay provenance: %w", err)
}
if err := mergeERPCOverlay(erpcConfig, ov); err != nil {
if err := reconcileERPCOverlayConfig(erpcConfig, ov, prov); err != nil {
return err
}
// Persist the union provenance before mutating the cluster. If the cluster
// write fails, the saved desired overlay plus this record is sufficient for
// the next reconcile to remove both old and partially-applied ownership.
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("write eRPC overlay provenance: %w", err)
}
if err := writeERPCConfig(cfg, erpcConfig); err != nil {
return err
}
pruneERPCProvenance(prov, ov)
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("prune eRPC overlay provenance: %w", err)
}
hash := ""
if data, err := os.ReadFile(erpcOverlayPath(cfg)); err == nil {
sum := sha256.Sum256(data)
Expand All@@ -517,12 +593,75 @@ func removeOverlayFromCluster(cfg *config.Config, ov *ERPCOverlay) error {
if err != nil {
return err
}
if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
if provenanceHasEntries(prov) {
if err := stripERPCProvenanceKeys(erpcConfig, prov, nil, nil); err != nil {
return err
}
} else if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
return err
}
return writeERPCConfig(cfg, erpcConfig)
}

// reconcileERPCOverlayConfig treats ov as desired state for overlay-owned
// networks and upstreams. Provenance may temporarily contain a union of old
// and new keys to make replacement crash-safe; keys not present in ov are
// stripped before provenance for new keys is captured and ov is merged.
func reconcileERPCOverlayConfig(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
if ov == nil {
return nil
}
if prov == nil {
return fmt.Errorf("eRPC overlay provenance is nil")
}
desiredNetworks, desiredUpstreams := erpcOverlayKeySets(ov)
if err := stripERPCProvenanceKeys(erpcConfig, prov, desiredNetworks, desiredUpstreams); err != nil {
return err
}
captureERPCProvenanceEntries(prov, erpcConfig, ov)
return mergeERPCOverlay(erpcConfig, ov)
}

func erpcOverlayKeySets(ov *ERPCOverlay) (map[string]struct{}, map[string]struct{}) {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if ov == nil {
return networks, upstreams
}
for _, n := range ov.Networks {
if key := networkMergeKey(n); key != "" {
networks[key] = struct{}{}
}
}
for _, u := range ov.Upstreams {
if id, _ := u["id"].(string); id != "" {
upstreams[id] = struct{}{}
}
}
return networks, upstreams
}

func pruneERPCProvenance(prov *erpcProvenance, ov *ERPCOverlay) {
if prov == nil {
return
}
keepNetworks, keepUpstreams := erpcOverlayKeySets(ov)
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
delete(prov.Networks, key)
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
delete(prov.Upstreams, id)
}
}
}

func provenanceHasEntries(prov *erpcProvenance) bool {
return prov != nil && (len(prov.Networks) > 0 || len(prov.Upstreams) > 0)
}

// mergeERPCOverlay mutates erpcConfig in place. Exported for tests via
// the unexported name used from overlay_test.go (same package).
func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
Expand DownExpand Up@@ -565,6 +704,33 @@ func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
// provenance record at all (nil prov, or overlay applied before provenance
// tracking existed) falls back to the old drop-only behavior.
func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
networks, upstreams := erpcOverlayKeySets(ov)
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

// stripERPCProvenanceKeys strips every provenance-tracked key that is not in
// the corresponding keep set. A nil keep set means keep nothing. This is what
// lets a newly persisted overlay remove keys owned by its predecessor even
// though the predecessor document is no longer on disk.
func stripERPCProvenanceKeys(erpcConfig map[string]any, prov *erpcProvenance, keepNetworks, keepUpstreams map[string]struct{}) error {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if prov != nil {
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
networks[key] = struct{}{}
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
upstreams[id] = struct{}{}
}
}
}
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

func stripERPCKeys(erpcConfig map[string]any, networkKeys, upstreamIDs map[string]struct{}, prov *erpcProvenance) error {
project := erpcConfigProject(erpcConfig)
if project == nil {
return fmt.Errorf("eRPC config has no projects")
Expand All@@ -573,14 +739,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
prov = &erpcProvenance{}
}

if len(ov.Upstreams) > 0 {
if len(upstreamIDs) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
for id := range upstreamIDs {
if orig, tracked := prov.Upstreams[id]; tracked && orig != nil {
restore[id] = *orig
} else {
Expand DownExpand Up@@ -608,14 +770,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
project["upstreams"] = kept
}

if len(ov.Networks) > 0 {
if len(networkKeys) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, n := range ov.Networks {
k := networkMergeKey(n)
if k == "" {
continue
}
for k := range networkKeys {
if orig, tracked := prov.Networks[k]; tracked && orig != nil {
restore[k] = *orig
} else {
Expand DownExpand Up@@ -648,7 +806,7 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
}

func mergeNetworksByKey(existing []any, overlay []map[string]any) []any {
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
index := map[string]int{} // mergeKey → index in out
for _, n := range existing {
nm, ok := n.(map[string]any)
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Draft
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
214 changes: 186 additions & 28 deletions internal/network/overlay.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,9 +149,24 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
if err != nil {
return err
}
if !captureERPCProvenanceEntries(prov, erpcConfig, ov) {
return nil
}
return writeERPCProvenance(cfg, prov)
}

// captureERPCProvenanceEntries is the in-memory half of
// captureERPCProvenance. It records the original value for each newly-owned
// key after retired overlay entries have been stripped, so replacing one
// overlay with another never mistakes the previous overlay value for chart
// base state.
func captureERPCProvenanceEntries(prov *erpcProvenance, erpcConfig map[string]any, ov *ERPCOverlay) bool {
if prov == nil || ov == nil {
return false
}
project := erpcConfigProject(erpcConfig)
if project == nil {
return nil
return false
}
changed := false

Expand DownExpand Up@@ -211,6 +226,49 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
}
}

return changed
}

// ensureERPCProvenanceTracksOverlay preserves ownership of an existing saved
// overlay before SetERPC replaces that document. Normally provenance already
// contains these keys. Missing entries can occur after upgrading an overlay
// created before provenance tracking; nil markers retain the legacy reset
// behavior (drop the owned key) when the original value is unknowable.
func ensureERPCProvenanceTracksOverlay(cfg *config.Config, ov *ERPCOverlay) error {
if ov == nil {
return nil
}
prov, err := readERPCProvenance(cfg)
if err != nil {
return err
}
changed := false
if len(ov.Networks) > 0 && prov.Networks == nil {
prov.Networks = make(map[string]*map[string]any)
}
for _, n := range ov.Networks {
key := networkMergeKey(n)
if key == "" {
continue
}
if _, tracked := prov.Networks[key]; !tracked {
prov.Networks[key] = nil
changed = true
}
}
if len(ov.Upstreams) > 0 && prov.Upstreams == nil {
prov.Upstreams = make(map[string]*map[string]any)
}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
if _, tracked := prov.Upstreams[id]; !tracked {
prov.Upstreams[id] = nil
changed = true
}
}
if !changed {
return nil
}
Expand DownExpand Up@@ -264,8 +322,10 @@ func ReconcileERPCOverlay(cfg *config.Config, u *ui.UI) {
len(ov.Networks), len(ov.Upstreams))
}

// ApplyERPCOverlayFile loads an overlay YAML from path, persists it under
// ConfigDir, and merges it into the live eRPC ConfigMap.
// SetERPC loads an overlay YAML from path, persists it under ConfigDir as the
// complete desired operator overlay, and reconciles the live eRPC ConfigMap to
// it. Entries owned by the previous overlay but omitted from the new document
// are removed (or restored from provenance) before the new entries are merged.
func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
data, err := os.ReadFile(path)
if err != nil {
Expand All@@ -275,6 +335,13 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
if err != nil {
return err
}
previous, err := readERPCOverlay(cfg)
if err != nil {
return fmt.Errorf("read existing eRPC overlay: %w", err)
}
if err := ensureERPCProvenanceTracksOverlay(cfg, previous); err != nil {
return fmt.Errorf("preserve existing eRPC overlay ownership: %w", err)
}
if err := writeERPCOverlay(cfg, ov); err != nil {
return err
}
Expand All@@ -287,8 +354,10 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
return nil
}

// ClearERPCOverlay removes overlay-owned fragments from the live ConfigMap
// (best-effort), then deletes the host-side overlay file.
// ResetERPC removes overlay-owned fragments from the live ConfigMap, then
// deletes the host-side overlay and provenance files. If cluster cleanup fails,
// the files are deliberately retained so reset or stack-up reconciliation can
// retry without losing ownership information.
func ResetERPC(cfg *config.Config, u *ui.UI) error {
ov, err := readERPCOverlay(cfg)
if err != nil {
Expand All@@ -300,10 +369,9 @@ func ResetERPC(cfg *config.Config, u *ui.UI) error {
}

if err := removeOverlayFromCluster(cfg, ov); err != nil {
u.Warnf("Could not strip eRPC config from live ConfigMap (will still reset host file): %v", err)
} else {
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")
return fmt.Errorf("strip eRPC config from live ConfigMap (durable overlay retained for retry): %w", err)
}
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")

path := erpcOverlayPath(cfg)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
Expand DownExpand Up@@ -487,18 +555,26 @@ func applyOverlayToCluster(cfg *config.Config, ov *ERPCOverlay, source string) e
if err != nil {
return err
}
// Snapshot whatever mergeERPCOverlay is about to replace, BEFORE it
// mutates erpcConfig, so ResetERPC can restore it later instead of
// deleting it outright.
if err := captureERPCProvenance(cfg, erpcConfig, ov); err != nil {
return fmt.Errorf("capture eRPC overlay provenance: %w", err)
prov, err := readERPCProvenance(cfg)
if err != nil {
return fmt.Errorf("read eRPC overlay provenance: %w", err)
}
if err := mergeERPCOverlay(erpcConfig, ov); err != nil {
if err := reconcileERPCOverlayConfig(erpcConfig, ov, prov); err != nil {
return err
}
// Persist the union provenance before mutating the cluster. If the cluster
// write fails, the saved desired overlay plus this record is sufficient for
// the next reconcile to remove both old and partially-applied ownership.
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("write eRPC overlay provenance: %w", err)
}
if err := writeERPCConfig(cfg, erpcConfig); err != nil {
return err
}
pruneERPCProvenance(prov, ov)
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("prune eRPC overlay provenance: %w", err)
}
hash := ""
if data, err := os.ReadFile(erpcOverlayPath(cfg)); err == nil {
sum := sha256.Sum256(data)
Expand All@@ -517,12 +593,75 @@ func removeOverlayFromCluster(cfg *config.Config, ov *ERPCOverlay) error {
if err != nil {
return err
}
if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
if provenanceHasEntries(prov) {
if err := stripERPCProvenanceKeys(erpcConfig, prov, nil, nil); err != nil {
return err
}
} else if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
return err
}
return writeERPCConfig(cfg, erpcConfig)
}

// reconcileERPCOverlayConfig treats ov as desired state for overlay-owned
// networks and upstreams. Provenance may temporarily contain a union of old
// and new keys to make replacement crash-safe; keys not present in ov are
// stripped before provenance for new keys is captured and ov is merged.
func reconcileERPCOverlayConfig(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
if ov == nil {
return nil
}
if prov == nil {
return fmt.Errorf("eRPC overlay provenance is nil")
}
desiredNetworks, desiredUpstreams := erpcOverlayKeySets(ov)
if err := stripERPCProvenanceKeys(erpcConfig, prov, desiredNetworks, desiredUpstreams); err != nil {
return err
}
captureERPCProvenanceEntries(prov, erpcConfig, ov)
return mergeERPCOverlay(erpcConfig, ov)
}

func erpcOverlayKeySets(ov *ERPCOverlay) (map[string]struct{}, map[string]struct{}) {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if ov == nil {
return networks, upstreams
}
for _, n := range ov.Networks {
if key := networkMergeKey(n); key != "" {
networks[key] = struct{}{}
}
}
for _, u := range ov.Upstreams {
if id, _ := u["id"].(string); id != "" {
upstreams[id] = struct{}{}
}
}
return networks, upstreams
}

func pruneERPCProvenance(prov *erpcProvenance, ov *ERPCOverlay) {
if prov == nil {
return
}
keepNetworks, keepUpstreams := erpcOverlayKeySets(ov)
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
delete(prov.Networks, key)
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
delete(prov.Upstreams, id)
}
}
}

func provenanceHasEntries(prov *erpcProvenance) bool {
return prov != nil && (len(prov.Networks) > 0 || len(prov.Upstreams) > 0)
}

// mergeERPCOverlay mutates erpcConfig in place. Exported for tests via
// the unexported name used from overlay_test.go (same package).
func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
Expand DownExpand Up@@ -565,6 +704,33 @@ func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
// provenance record at all (nil prov, or overlay applied before provenance
// tracking existed) falls back to the old drop-only behavior.
func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
networks, upstreams := erpcOverlayKeySets(ov)
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

// stripERPCProvenanceKeys strips every provenance-tracked key that is not in
// the corresponding keep set. A nil keep set means keep nothing. This is what
// lets a newly persisted overlay remove keys owned by its predecessor even
// though the predecessor document is no longer on disk.
func stripERPCProvenanceKeys(erpcConfig map[string]any, prov *erpcProvenance, keepNetworks, keepUpstreams map[string]struct{}) error {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if prov != nil {
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
networks[key] = struct{}{}
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
upstreams[id] = struct{}{}
}
}
}
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

func stripERPCKeys(erpcConfig map[string]any, networkKeys, upstreamIDs map[string]struct{}, prov *erpcProvenance) error {
project := erpcConfigProject(erpcConfig)
if project == nil {
return fmt.Errorf("eRPC config has no projects")
Expand All@@ -573,14 +739,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
prov = &erpcProvenance{}
}

if len(ov.Upstreams) > 0 {
if len(upstreamIDs) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
for id := range upstreamIDs {
if orig, tracked := prov.Upstreams[id]; tracked && orig != nil {
restore[id] = *orig
} else {
Expand DownExpand Up@@ -608,14 +770,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
project["upstreams"] = kept
}

if len(ov.Networks) > 0 {
if len(networkKeys) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, n := range ov.Networks {
k := networkMergeKey(n)
if k == "" {
continue
}
for k := range networkKeys {
if orig, tracked := prov.Networks[k]; tracked && orig != nil {
restore[k] = *orig
} else {
Expand DownExpand Up@@ -648,7 +806,7 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
}

func mergeNetworksByKey(existing []any, overlay []map[string]any) []any {
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
index := map[string]int{} // mergeKey → index in out
for _, n := range existing {
nm, ok := n.(map[string]any)
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Draft
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
214 changes: 186 additions & 28 deletions internal/network/overlay.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,9 +149,24 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
if err != nil {
return err
}
if !captureERPCProvenanceEntries(prov, erpcConfig, ov) {
return nil
}
return writeERPCProvenance(cfg, prov)
}

// captureERPCProvenanceEntries is the in-memory half of
// captureERPCProvenance. It records the original value for each newly-owned
// key after retired overlay entries have been stripped, so replacing one
// overlay with another never mistakes the previous overlay value for chart
// base state.
func captureERPCProvenanceEntries(prov *erpcProvenance, erpcConfig map[string]any, ov *ERPCOverlay) bool {
if prov == nil || ov == nil {
return false
}
project := erpcConfigProject(erpcConfig)
if project == nil {
return nil
return false
}
changed := false

Expand DownExpand Up@@ -211,6 +226,49 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
}
}

return changed
}

// ensureERPCProvenanceTracksOverlay preserves ownership of an existing saved
// overlay before SetERPC replaces that document. Normally provenance already
// contains these keys. Missing entries can occur after upgrading an overlay
// created before provenance tracking; nil markers retain the legacy reset
// behavior (drop the owned key) when the original value is unknowable.
func ensureERPCProvenanceTracksOverlay(cfg *config.Config, ov *ERPCOverlay) error {
if ov == nil {
return nil
}
prov, err := readERPCProvenance(cfg)
if err != nil {
return err
}
changed := false
if len(ov.Networks) > 0 && prov.Networks == nil {
prov.Networks = make(map[string]*map[string]any)
}
for _, n := range ov.Networks {
key := networkMergeKey(n)
if key == "" {
continue
}
if _, tracked := prov.Networks[key]; !tracked {
prov.Networks[key] = nil
changed = true
}
}
if len(ov.Upstreams) > 0 && prov.Upstreams == nil {
prov.Upstreams = make(map[string]*map[string]any)
}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
if _, tracked := prov.Upstreams[id]; !tracked {
prov.Upstreams[id] = nil
changed = true
}
}
if !changed {
return nil
}
Expand DownExpand Up@@ -264,8 +322,10 @@ func ReconcileERPCOverlay(cfg *config.Config, u *ui.UI) {
len(ov.Networks), len(ov.Upstreams))
}

// ApplyERPCOverlayFile loads an overlay YAML from path, persists it under
// ConfigDir, and merges it into the live eRPC ConfigMap.
// SetERPC loads an overlay YAML from path, persists it under ConfigDir as the
// complete desired operator overlay, and reconciles the live eRPC ConfigMap to
// it. Entries owned by the previous overlay but omitted from the new document
// are removed (or restored from provenance) before the new entries are merged.
func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
data, err := os.ReadFile(path)
if err != nil {
Expand All@@ -275,6 +335,13 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
if err != nil {
return err
}
previous, err := readERPCOverlay(cfg)
if err != nil {
return fmt.Errorf("read existing eRPC overlay: %w", err)
}
if err := ensureERPCProvenanceTracksOverlay(cfg, previous); err != nil {
return fmt.Errorf("preserve existing eRPC overlay ownership: %w", err)
}
if err := writeERPCOverlay(cfg, ov); err != nil {
return err
}
Expand All@@ -287,8 +354,10 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
return nil
}

// ClearERPCOverlay removes overlay-owned fragments from the live ConfigMap
// (best-effort), then deletes the host-side overlay file.
// ResetERPC removes overlay-owned fragments from the live ConfigMap, then
// deletes the host-side overlay and provenance files. If cluster cleanup fails,
// the files are deliberately retained so reset or stack-up reconciliation can
// retry without losing ownership information.
func ResetERPC(cfg *config.Config, u *ui.UI) error {
ov, err := readERPCOverlay(cfg)
if err != nil {
Expand All@@ -300,10 +369,9 @@ func ResetERPC(cfg *config.Config, u *ui.UI) error {
}

if err := removeOverlayFromCluster(cfg, ov); err != nil {
u.Warnf("Could not strip eRPC config from live ConfigMap (will still reset host file): %v", err)
} else {
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")
return fmt.Errorf("strip eRPC config from live ConfigMap (durable overlay retained for retry): %w", err)
}
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")

path := erpcOverlayPath(cfg)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
Expand DownExpand Up@@ -487,18 +555,26 @@ func applyOverlayToCluster(cfg *config.Config, ov *ERPCOverlay, source string) e
if err != nil {
return err
}
// Snapshot whatever mergeERPCOverlay is about to replace, BEFORE it
// mutates erpcConfig, so ResetERPC can restore it later instead of
// deleting it outright.
if err := captureERPCProvenance(cfg, erpcConfig, ov); err != nil {
return fmt.Errorf("capture eRPC overlay provenance: %w", err)
prov, err := readERPCProvenance(cfg)
if err != nil {
return fmt.Errorf("read eRPC overlay provenance: %w", err)
}
if err := mergeERPCOverlay(erpcConfig, ov); err != nil {
if err := reconcileERPCOverlayConfig(erpcConfig, ov, prov); err != nil {
return err
}
// Persist the union provenance before mutating the cluster. If the cluster
// write fails, the saved desired overlay plus this record is sufficient for
// the next reconcile to remove both old and partially-applied ownership.
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("write eRPC overlay provenance: %w", err)
}
if err := writeERPCConfig(cfg, erpcConfig); err != nil {
return err
}
pruneERPCProvenance(prov, ov)
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("prune eRPC overlay provenance: %w", err)
}
hash := ""
if data, err := os.ReadFile(erpcOverlayPath(cfg)); err == nil {
sum := sha256.Sum256(data)
Expand All@@ -517,12 +593,75 @@ func removeOverlayFromCluster(cfg *config.Config, ov *ERPCOverlay) error {
if err != nil {
return err
}
if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
if provenanceHasEntries(prov) {
if err := stripERPCProvenanceKeys(erpcConfig, prov, nil, nil); err != nil {
return err
}
} else if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
return err
}
return writeERPCConfig(cfg, erpcConfig)
}

// reconcileERPCOverlayConfig treats ov as desired state for overlay-owned
// networks and upstreams. Provenance may temporarily contain a union of old
// and new keys to make replacement crash-safe; keys not present in ov are
// stripped before provenance for new keys is captured and ov is merged.
func reconcileERPCOverlayConfig(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
if ov == nil {
return nil
}
if prov == nil {
return fmt.Errorf("eRPC overlay provenance is nil")
}
desiredNetworks, desiredUpstreams := erpcOverlayKeySets(ov)
if err := stripERPCProvenanceKeys(erpcConfig, prov, desiredNetworks, desiredUpstreams); err != nil {
return err
}
captureERPCProvenanceEntries(prov, erpcConfig, ov)
return mergeERPCOverlay(erpcConfig, ov)
}

func erpcOverlayKeySets(ov *ERPCOverlay) (map[string]struct{}, map[string]struct{}) {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if ov == nil {
return networks, upstreams
}
for _, n := range ov.Networks {
if key := networkMergeKey(n); key != "" {
networks[key] = struct{}{}
}
}
for _, u := range ov.Upstreams {
if id, _ := u["id"].(string); id != "" {
upstreams[id] = struct{}{}
}
}
return networks, upstreams
}

func pruneERPCProvenance(prov *erpcProvenance, ov *ERPCOverlay) {
if prov == nil {
return
}
keepNetworks, keepUpstreams := erpcOverlayKeySets(ov)
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
delete(prov.Networks, key)
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
delete(prov.Upstreams, id)
}
}
}

func provenanceHasEntries(prov *erpcProvenance) bool {
return prov != nil && (len(prov.Networks) > 0 || len(prov.Upstreams) > 0)
}

// mergeERPCOverlay mutates erpcConfig in place. Exported for tests via
// the unexported name used from overlay_test.go (same package).
func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
Expand DownExpand Up@@ -565,6 +704,33 @@ func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
// provenance record at all (nil prov, or overlay applied before provenance
// tracking existed) falls back to the old drop-only behavior.
func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
networks, upstreams := erpcOverlayKeySets(ov)
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

// stripERPCProvenanceKeys strips every provenance-tracked key that is not in
// the corresponding keep set. A nil keep set means keep nothing. This is what
// lets a newly persisted overlay remove keys owned by its predecessor even
// though the predecessor document is no longer on disk.
func stripERPCProvenanceKeys(erpcConfig map[string]any, prov *erpcProvenance, keepNetworks, keepUpstreams map[string]struct{}) error {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if prov != nil {
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
networks[key] = struct{}{}
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
upstreams[id] = struct{}{}
}
}
}
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

func stripERPCKeys(erpcConfig map[string]any, networkKeys, upstreamIDs map[string]struct{}, prov *erpcProvenance) error {
project := erpcConfigProject(erpcConfig)
if project == nil {
return fmt.Errorf("eRPC config has no projects")
Expand All@@ -573,14 +739,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
prov = &erpcProvenance{}
}

if len(ov.Upstreams) > 0 {
if len(upstreamIDs) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
for id := range upstreamIDs {
if orig, tracked := prov.Upstreams[id]; tracked && orig != nil {
restore[id] = *orig
} else {
Expand DownExpand Up@@ -608,14 +770,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
project["upstreams"] = kept
}

if len(ov.Networks) > 0 {
if len(networkKeys) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, n := range ov.Networks {
k := networkMergeKey(n)
if k == "" {
continue
}
for k := range networkKeys {
if orig, tracked := prov.Networks[k]; tracked && orig != nil {
restore[k] = *orig
} else {
Expand DownExpand Up@@ -648,7 +806,7 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
}

func mergeNetworksByKey(existing []any, overlay []map[string]any) []any {
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
index := map[string]int{} // mergeKey → index in out
for _, n := range existing {
nm, ok := n.(map[string]any)
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Draft
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
214 changes: 186 additions & 28 deletions internal/network/overlay.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,9 +149,24 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
if err != nil {
return err
}
if !captureERPCProvenanceEntries(prov, erpcConfig, ov) {
return nil
}
return writeERPCProvenance(cfg, prov)
}

// captureERPCProvenanceEntries is the in-memory half of
// captureERPCProvenance. It records the original value for each newly-owned
// key after retired overlay entries have been stripped, so replacing one
// overlay with another never mistakes the previous overlay value for chart
// base state.
func captureERPCProvenanceEntries(prov *erpcProvenance, erpcConfig map[string]any, ov *ERPCOverlay) bool {
if prov == nil || ov == nil {
return false
}
project := erpcConfigProject(erpcConfig)
if project == nil {
return nil
return false
}
changed := false

Expand DownExpand Up@@ -211,6 +226,49 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
}
}

return changed
}

// ensureERPCProvenanceTracksOverlay preserves ownership of an existing saved
// overlay before SetERPC replaces that document. Normally provenance already
// contains these keys. Missing entries can occur after upgrading an overlay
// created before provenance tracking; nil markers retain the legacy reset
// behavior (drop the owned key) when the original value is unknowable.
func ensureERPCProvenanceTracksOverlay(cfg *config.Config, ov *ERPCOverlay) error {
if ov == nil {
return nil
}
prov, err := readERPCProvenance(cfg)
if err != nil {
return err
}
changed := false
if len(ov.Networks) > 0 && prov.Networks == nil {
prov.Networks = make(map[string]*map[string]any)
}
for _, n := range ov.Networks {
key := networkMergeKey(n)
if key == "" {
continue
}
if _, tracked := prov.Networks[key]; !tracked {
prov.Networks[key] = nil
changed = true
}
}
if len(ov.Upstreams) > 0 && prov.Upstreams == nil {
prov.Upstreams = make(map[string]*map[string]any)
}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
if _, tracked := prov.Upstreams[id]; !tracked {
prov.Upstreams[id] = nil
changed = true
}
}
if !changed {
return nil
}
Expand DownExpand Up@@ -264,8 +322,10 @@ func ReconcileERPCOverlay(cfg *config.Config, u *ui.UI) {
len(ov.Networks), len(ov.Upstreams))
}

// ApplyERPCOverlayFile loads an overlay YAML from path, persists it under
// ConfigDir, and merges it into the live eRPC ConfigMap.
// SetERPC loads an overlay YAML from path, persists it under ConfigDir as the
// complete desired operator overlay, and reconciles the live eRPC ConfigMap to
// it. Entries owned by the previous overlay but omitted from the new document
// are removed (or restored from provenance) before the new entries are merged.
func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
data, err := os.ReadFile(path)
if err != nil {
Expand All@@ -275,6 +335,13 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
if err != nil {
return err
}
previous, err := readERPCOverlay(cfg)
if err != nil {
return fmt.Errorf("read existing eRPC overlay: %w", err)
}
if err := ensureERPCProvenanceTracksOverlay(cfg, previous); err != nil {
return fmt.Errorf("preserve existing eRPC overlay ownership: %w", err)
}
if err := writeERPCOverlay(cfg, ov); err != nil {
return err
}
Expand All@@ -287,8 +354,10 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
return nil
}

// ClearERPCOverlay removes overlay-owned fragments from the live ConfigMap
// (best-effort), then deletes the host-side overlay file.
// ResetERPC removes overlay-owned fragments from the live ConfigMap, then
// deletes the host-side overlay and provenance files. If cluster cleanup fails,
// the files are deliberately retained so reset or stack-up reconciliation can
// retry without losing ownership information.
func ResetERPC(cfg *config.Config, u *ui.UI) error {
ov, err := readERPCOverlay(cfg)
if err != nil {
Expand All@@ -300,10 +369,9 @@ func ResetERPC(cfg *config.Config, u *ui.UI) error {
}

if err := removeOverlayFromCluster(cfg, ov); err != nil {
u.Warnf("Could not strip eRPC config from live ConfigMap (will still reset host file): %v", err)
} else {
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")
return fmt.Errorf("strip eRPC config from live ConfigMap (durable overlay retained for retry): %w", err)
}
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")

path := erpcOverlayPath(cfg)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
Expand DownExpand Up@@ -487,18 +555,26 @@ func applyOverlayToCluster(cfg *config.Config, ov *ERPCOverlay, source string) e
if err != nil {
return err
}
// Snapshot whatever mergeERPCOverlay is about to replace, BEFORE it
// mutates erpcConfig, so ResetERPC can restore it later instead of
// deleting it outright.
if err := captureERPCProvenance(cfg, erpcConfig, ov); err != nil {
return fmt.Errorf("capture eRPC overlay provenance: %w", err)
prov, err := readERPCProvenance(cfg)
if err != nil {
return fmt.Errorf("read eRPC overlay provenance: %w", err)
}
if err := mergeERPCOverlay(erpcConfig, ov); err != nil {
if err := reconcileERPCOverlayConfig(erpcConfig, ov, prov); err != nil {
return err
}
// Persist the union provenance before mutating the cluster. If the cluster
// write fails, the saved desired overlay plus this record is sufficient for
// the next reconcile to remove both old and partially-applied ownership.
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("write eRPC overlay provenance: %w", err)
}
if err := writeERPCConfig(cfg, erpcConfig); err != nil {
return err
}
pruneERPCProvenance(prov, ov)
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("prune eRPC overlay provenance: %w", err)
}
hash := ""
if data, err := os.ReadFile(erpcOverlayPath(cfg)); err == nil {
sum := sha256.Sum256(data)
Expand All@@ -517,12 +593,75 @@ func removeOverlayFromCluster(cfg *config.Config, ov *ERPCOverlay) error {
if err != nil {
return err
}
if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
if provenanceHasEntries(prov) {
if err := stripERPCProvenanceKeys(erpcConfig, prov, nil, nil); err != nil {
return err
}
} else if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
return err
}
return writeERPCConfig(cfg, erpcConfig)
}

// reconcileERPCOverlayConfig treats ov as desired state for overlay-owned
// networks and upstreams. Provenance may temporarily contain a union of old
// and new keys to make replacement crash-safe; keys not present in ov are
// stripped before provenance for new keys is captured and ov is merged.
func reconcileERPCOverlayConfig(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
if ov == nil {
return nil
}
if prov == nil {
return fmt.Errorf("eRPC overlay provenance is nil")
}
desiredNetworks, desiredUpstreams := erpcOverlayKeySets(ov)
if err := stripERPCProvenanceKeys(erpcConfig, prov, desiredNetworks, desiredUpstreams); err != nil {
return err
}
captureERPCProvenanceEntries(prov, erpcConfig, ov)
return mergeERPCOverlay(erpcConfig, ov)
}

func erpcOverlayKeySets(ov *ERPCOverlay) (map[string]struct{}, map[string]struct{}) {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if ov == nil {
return networks, upstreams
}
for _, n := range ov.Networks {
if key := networkMergeKey(n); key != "" {
networks[key] = struct{}{}
}
}
for _, u := range ov.Upstreams {
if id, _ := u["id"].(string); id != "" {
upstreams[id] = struct{}{}
}
}
return networks, upstreams
}

func pruneERPCProvenance(prov *erpcProvenance, ov *ERPCOverlay) {
if prov == nil {
return
}
keepNetworks, keepUpstreams := erpcOverlayKeySets(ov)
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
delete(prov.Networks, key)
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
delete(prov.Upstreams, id)
}
}
}

func provenanceHasEntries(prov *erpcProvenance) bool {
return prov != nil && (len(prov.Networks) > 0 || len(prov.Upstreams) > 0)
}

// mergeERPCOverlay mutates erpcConfig in place. Exported for tests via
// the unexported name used from overlay_test.go (same package).
func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
Expand DownExpand Up@@ -565,6 +704,33 @@ func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
// provenance record at all (nil prov, or overlay applied before provenance
// tracking existed) falls back to the old drop-only behavior.
func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
networks, upstreams := erpcOverlayKeySets(ov)
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

// stripERPCProvenanceKeys strips every provenance-tracked key that is not in
// the corresponding keep set. A nil keep set means keep nothing. This is what
// lets a newly persisted overlay remove keys owned by its predecessor even
// though the predecessor document is no longer on disk.
func stripERPCProvenanceKeys(erpcConfig map[string]any, prov *erpcProvenance, keepNetworks, keepUpstreams map[string]struct{}) error {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if prov != nil {
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
networks[key] = struct{}{}
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
upstreams[id] = struct{}{}
}
}
}
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

func stripERPCKeys(erpcConfig map[string]any, networkKeys, upstreamIDs map[string]struct{}, prov *erpcProvenance) error {
project := erpcConfigProject(erpcConfig)
if project == nil {
return fmt.Errorf("eRPC config has no projects")
Expand All@@ -573,14 +739,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
prov = &erpcProvenance{}
}

if len(ov.Upstreams) > 0 {
if len(upstreamIDs) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
for id := range upstreamIDs {
if orig, tracked := prov.Upstreams[id]; tracked && orig != nil {
restore[id] = *orig
} else {
Expand DownExpand Up@@ -608,14 +770,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
project["upstreams"] = kept
}

if len(ov.Networks) > 0 {
if len(networkKeys) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, n := range ov.Networks {
k := networkMergeKey(n)
if k == "" {
continue
}
for k := range networkKeys {
if orig, tracked := prov.Networks[k]; tracked && orig != nil {
restore[k] = *orig
} else {
Expand DownExpand Up@@ -648,7 +806,7 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
}

func mergeNetworksByKey(existing []any, overlay []map[string]any) []any {
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
index := map[string]int{} // mergeKey → index in out
for _, n := range existing {
nm, ok := n.(map[string]any)
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Draft
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
214 changes: 186 additions & 28 deletions internal/network/overlay.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,9 +149,24 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
if err != nil {
return err
}
if !captureERPCProvenanceEntries(prov, erpcConfig, ov) {
return nil
}
return writeERPCProvenance(cfg, prov)
}

// captureERPCProvenanceEntries is the in-memory half of
// captureERPCProvenance. It records the original value for each newly-owned
// key after retired overlay entries have been stripped, so replacing one
// overlay with another never mistakes the previous overlay value for chart
// base state.
func captureERPCProvenanceEntries(prov *erpcProvenance, erpcConfig map[string]any, ov *ERPCOverlay) bool {
if prov == nil || ov == nil {
return false
}
project := erpcConfigProject(erpcConfig)
if project == nil {
return nil
return false
}
changed := false

Expand DownExpand Up@@ -211,6 +226,49 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
}
}

return changed
}

// ensureERPCProvenanceTracksOverlay preserves ownership of an existing saved
// overlay before SetERPC replaces that document. Normally provenance already
// contains these keys. Missing entries can occur after upgrading an overlay
// created before provenance tracking; nil markers retain the legacy reset
// behavior (drop the owned key) when the original value is unknowable.
func ensureERPCProvenanceTracksOverlay(cfg *config.Config, ov *ERPCOverlay) error {
if ov == nil {
return nil
}
prov, err := readERPCProvenance(cfg)
if err != nil {
return err
}
changed := false
if len(ov.Networks) > 0 && prov.Networks == nil {
prov.Networks = make(map[string]*map[string]any)
}
for _, n := range ov.Networks {
key := networkMergeKey(n)
if key == "" {
continue
}
if _, tracked := prov.Networks[key]; !tracked {
prov.Networks[key] = nil
changed = true
}
}
if len(ov.Upstreams) > 0 && prov.Upstreams == nil {
prov.Upstreams = make(map[string]*map[string]any)
}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
if _, tracked := prov.Upstreams[id]; !tracked {
prov.Upstreams[id] = nil
changed = true
}
}
if !changed {
return nil
}
Expand DownExpand Up@@ -264,8 +322,10 @@ func ReconcileERPCOverlay(cfg *config.Config, u *ui.UI) {
len(ov.Networks), len(ov.Upstreams))
}

// ApplyERPCOverlayFile loads an overlay YAML from path, persists it under
// ConfigDir, and merges it into the live eRPC ConfigMap.
// SetERPC loads an overlay YAML from path, persists it under ConfigDir as the
// complete desired operator overlay, and reconciles the live eRPC ConfigMap to
// it. Entries owned by the previous overlay but omitted from the new document
// are removed (or restored from provenance) before the new entries are merged.
func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
data, err := os.ReadFile(path)
if err != nil {
Expand All@@ -275,6 +335,13 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
if err != nil {
return err
}
previous, err := readERPCOverlay(cfg)
if err != nil {
return fmt.Errorf("read existing eRPC overlay: %w", err)
}
if err := ensureERPCProvenanceTracksOverlay(cfg, previous); err != nil {
return fmt.Errorf("preserve existing eRPC overlay ownership: %w", err)
}
if err := writeERPCOverlay(cfg, ov); err != nil {
return err
}
Expand All@@ -287,8 +354,10 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
return nil
}

// ClearERPCOverlay removes overlay-owned fragments from the live ConfigMap
// (best-effort), then deletes the host-side overlay file.
// ResetERPC removes overlay-owned fragments from the live ConfigMap, then
// deletes the host-side overlay and provenance files. If cluster cleanup fails,
// the files are deliberately retained so reset or stack-up reconciliation can
// retry without losing ownership information.
func ResetERPC(cfg *config.Config, u *ui.UI) error {
ov, err := readERPCOverlay(cfg)
if err != nil {
Expand All@@ -300,10 +369,9 @@ func ResetERPC(cfg *config.Config, u *ui.UI) error {
}

if err := removeOverlayFromCluster(cfg, ov); err != nil {
u.Warnf("Could not strip eRPC config from live ConfigMap (will still reset host file): %v", err)
} else {
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")
return fmt.Errorf("strip eRPC config from live ConfigMap (durable overlay retained for retry): %w", err)
}
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")

path := erpcOverlayPath(cfg)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
Expand DownExpand Up@@ -487,18 +555,26 @@ func applyOverlayToCluster(cfg *config.Config, ov *ERPCOverlay, source string) e
if err != nil {
return err
}
// Snapshot whatever mergeERPCOverlay is about to replace, BEFORE it
// mutates erpcConfig, so ResetERPC can restore it later instead of
// deleting it outright.
if err := captureERPCProvenance(cfg, erpcConfig, ov); err != nil {
return fmt.Errorf("capture eRPC overlay provenance: %w", err)
prov, err := readERPCProvenance(cfg)
if err != nil {
return fmt.Errorf("read eRPC overlay provenance: %w", err)
}
if err := mergeERPCOverlay(erpcConfig, ov); err != nil {
if err := reconcileERPCOverlayConfig(erpcConfig, ov, prov); err != nil {
return err
}
// Persist the union provenance before mutating the cluster. If the cluster
// write fails, the saved desired overlay plus this record is sufficient for
// the next reconcile to remove both old and partially-applied ownership.
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("write eRPC overlay provenance: %w", err)
}
if err := writeERPCConfig(cfg, erpcConfig); err != nil {
return err
}
pruneERPCProvenance(prov, ov)
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("prune eRPC overlay provenance: %w", err)
}
hash := ""
if data, err := os.ReadFile(erpcOverlayPath(cfg)); err == nil {
sum := sha256.Sum256(data)
Expand All@@ -517,12 +593,75 @@ func removeOverlayFromCluster(cfg *config.Config, ov *ERPCOverlay) error {
if err != nil {
return err
}
if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
if provenanceHasEntries(prov) {
if err := stripERPCProvenanceKeys(erpcConfig, prov, nil, nil); err != nil {
return err
}
} else if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
return err
}
return writeERPCConfig(cfg, erpcConfig)
}

// reconcileERPCOverlayConfig treats ov as desired state for overlay-owned
// networks and upstreams. Provenance may temporarily contain a union of old
// and new keys to make replacement crash-safe; keys not present in ov are
// stripped before provenance for new keys is captured and ov is merged.
func reconcileERPCOverlayConfig(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
if ov == nil {
return nil
}
if prov == nil {
return fmt.Errorf("eRPC overlay provenance is nil")
}
desiredNetworks, desiredUpstreams := erpcOverlayKeySets(ov)
if err := stripERPCProvenanceKeys(erpcConfig, prov, desiredNetworks, desiredUpstreams); err != nil {
return err
}
captureERPCProvenanceEntries(prov, erpcConfig, ov)
return mergeERPCOverlay(erpcConfig, ov)
}

func erpcOverlayKeySets(ov *ERPCOverlay) (map[string]struct{}, map[string]struct{}) {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if ov == nil {
return networks, upstreams
}
for _, n := range ov.Networks {
if key := networkMergeKey(n); key != "" {
networks[key] = struct{}{}
}
}
for _, u := range ov.Upstreams {
if id, _ := u["id"].(string); id != "" {
upstreams[id] = struct{}{}
}
}
return networks, upstreams
}

func pruneERPCProvenance(prov *erpcProvenance, ov *ERPCOverlay) {
if prov == nil {
return
}
keepNetworks, keepUpstreams := erpcOverlayKeySets(ov)
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
delete(prov.Networks, key)
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
delete(prov.Upstreams, id)
}
}
}

func provenanceHasEntries(prov *erpcProvenance) bool {
return prov != nil && (len(prov.Networks) > 0 || len(prov.Upstreams) > 0)
}

// mergeERPCOverlay mutates erpcConfig in place. Exported for tests via
// the unexported name used from overlay_test.go (same package).
func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
Expand DownExpand Up@@ -565,6 +704,33 @@ func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
// provenance record at all (nil prov, or overlay applied before provenance
// tracking existed) falls back to the old drop-only behavior.
func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
networks, upstreams := erpcOverlayKeySets(ov)
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

// stripERPCProvenanceKeys strips every provenance-tracked key that is not in
// the corresponding keep set. A nil keep set means keep nothing. This is what
// lets a newly persisted overlay remove keys owned by its predecessor even
// though the predecessor document is no longer on disk.
func stripERPCProvenanceKeys(erpcConfig map[string]any, prov *erpcProvenance, keepNetworks, keepUpstreams map[string]struct{}) error {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if prov != nil {
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
networks[key] = struct{}{}
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
upstreams[id] = struct{}{}
}
}
}
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

func stripERPCKeys(erpcConfig map[string]any, networkKeys, upstreamIDs map[string]struct{}, prov *erpcProvenance) error {
project := erpcConfigProject(erpcConfig)
if project == nil {
return fmt.Errorf("eRPC config has no projects")
Expand All@@ -573,14 +739,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
prov = &erpcProvenance{}
}

if len(ov.Upstreams) > 0 {
if len(upstreamIDs) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
for id := range upstreamIDs {
if orig, tracked := prov.Upstreams[id]; tracked && orig != nil {
restore[id] = *orig
} else {
Expand DownExpand Up@@ -608,14 +770,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
project["upstreams"] = kept
}

if len(ov.Networks) > 0 {
if len(networkKeys) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, n := range ov.Networks {
k := networkMergeKey(n)
if k == "" {
continue
}
for k := range networkKeys {
if orig, tracked := prov.Networks[k]; tracked && orig != nil {
restore[k] = *orig
} else {
Expand DownExpand Up@@ -648,7 +806,7 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
}

func mergeNetworksByKey(existing []any, overlay []map[string]any) []any {
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
index := map[string]int{} // mergeKey → index in out
for _, n := range existing {
nm, ok := n.(map[string]any)
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Draft
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
214 changes: 186 additions & 28 deletions internal/network/overlay.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,9 +149,24 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
if err != nil {
return err
}
if !captureERPCProvenanceEntries(prov, erpcConfig, ov) {
return nil
}
return writeERPCProvenance(cfg, prov)
}

// captureERPCProvenanceEntries is the in-memory half of
// captureERPCProvenance. It records the original value for each newly-owned
// key after retired overlay entries have been stripped, so replacing one
// overlay with another never mistakes the previous overlay value for chart
// base state.
func captureERPCProvenanceEntries(prov *erpcProvenance, erpcConfig map[string]any, ov *ERPCOverlay) bool {
if prov == nil || ov == nil {
return false
}
project := erpcConfigProject(erpcConfig)
if project == nil {
return nil
return false
}
changed := false

Expand DownExpand Up@@ -211,6 +226,49 @@ func captureERPCProvenance(cfg *config.Config, erpcConfig map[string]any, ov *ER
}
}

return changed
}

// ensureERPCProvenanceTracksOverlay preserves ownership of an existing saved
// overlay before SetERPC replaces that document. Normally provenance already
// contains these keys. Missing entries can occur after upgrading an overlay
// created before provenance tracking; nil markers retain the legacy reset
// behavior (drop the owned key) when the original value is unknowable.
func ensureERPCProvenanceTracksOverlay(cfg *config.Config, ov *ERPCOverlay) error {
if ov == nil {
return nil
}
prov, err := readERPCProvenance(cfg)
if err != nil {
return err
}
changed := false
if len(ov.Networks) > 0 && prov.Networks == nil {
prov.Networks = make(map[string]*map[string]any)
}
for _, n := range ov.Networks {
key := networkMergeKey(n)
if key == "" {
continue
}
if _, tracked := prov.Networks[key]; !tracked {
prov.Networks[key] = nil
changed = true
}
}
if len(ov.Upstreams) > 0 && prov.Upstreams == nil {
prov.Upstreams = make(map[string]*map[string]any)
}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
if _, tracked := prov.Upstreams[id]; !tracked {
prov.Upstreams[id] = nil
changed = true
}
}
if !changed {
return nil
}
Expand DownExpand Up@@ -264,8 +322,10 @@ func ReconcileERPCOverlay(cfg *config.Config, u *ui.UI) {
len(ov.Networks), len(ov.Upstreams))
}

// ApplyERPCOverlayFile loads an overlay YAML from path, persists it under
// ConfigDir, and merges it into the live eRPC ConfigMap.
// SetERPC loads an overlay YAML from path, persists it under ConfigDir as the
// complete desired operator overlay, and reconciles the live eRPC ConfigMap to
// it. Entries owned by the previous overlay but omitted from the new document
// are removed (or restored from provenance) before the new entries are merged.
func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
data, err := os.ReadFile(path)
if err != nil {
Expand All@@ -275,6 +335,13 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
if err != nil {
return err
}
previous, err := readERPCOverlay(cfg)
if err != nil {
return fmt.Errorf("read existing eRPC overlay: %w", err)
}
if err := ensureERPCProvenanceTracksOverlay(cfg, previous); err != nil {
return fmt.Errorf("preserve existing eRPC overlay ownership: %w", err)
}
if err := writeERPCOverlay(cfg, ov); err != nil {
return err
}
Expand All@@ -287,8 +354,10 @@ func SetERPC(cfg *config.Config, u *ui.UI, path string) error {
return nil
}

// ClearERPCOverlay removes overlay-owned fragments from the live ConfigMap
// (best-effort), then deletes the host-side overlay file.
// ResetERPC removes overlay-owned fragments from the live ConfigMap, then
// deletes the host-side overlay and provenance files. If cluster cleanup fails,
// the files are deliberately retained so reset or stack-up reconciliation can
// retry without losing ownership information.
func ResetERPC(cfg *config.Config, u *ui.UI) error {
ov, err := readERPCOverlay(cfg)
if err != nil {
Expand All@@ -300,10 +369,9 @@ func ResetERPC(cfg *config.Config, u *ui.UI) error {
}

if err := removeOverlayFromCluster(cfg, ov); err != nil {
u.Warnf("Could not strip eRPC config from live ConfigMap (will still reset host file): %v", err)
} else {
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")
return fmt.Errorf("strip eRPC config from live ConfigMap (durable overlay retained for retry): %w", err)
}
u.Success("Removed overlay-added networks/upstreams and restored any chart-base/recorded entries they replaced")

path := erpcOverlayPath(cfg)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
Expand DownExpand Up@@ -487,18 +555,26 @@ func applyOverlayToCluster(cfg *config.Config, ov *ERPCOverlay, source string) e
if err != nil {
return err
}
// Snapshot whatever mergeERPCOverlay is about to replace, BEFORE it
// mutates erpcConfig, so ResetERPC can restore it later instead of
// deleting it outright.
if err := captureERPCProvenance(cfg, erpcConfig, ov); err != nil {
return fmt.Errorf("capture eRPC overlay provenance: %w", err)
prov, err := readERPCProvenance(cfg)
if err != nil {
return fmt.Errorf("read eRPC overlay provenance: %w", err)
}
if err := mergeERPCOverlay(erpcConfig, ov); err != nil {
if err := reconcileERPCOverlayConfig(erpcConfig, ov, prov); err != nil {
return err
}
// Persist the union provenance before mutating the cluster. If the cluster
// write fails, the saved desired overlay plus this record is sufficient for
// the next reconcile to remove both old and partially-applied ownership.
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("write eRPC overlay provenance: %w", err)
}
if err := writeERPCConfig(cfg, erpcConfig); err != nil {
return err
}
pruneERPCProvenance(prov, ov)
if err := writeERPCProvenance(cfg, prov); err != nil {
return fmt.Errorf("prune eRPC overlay provenance: %w", err)
}
hash := ""
if data, err := os.ReadFile(erpcOverlayPath(cfg)); err == nil {
sum := sha256.Sum256(data)
Expand All@@ -517,12 +593,75 @@ func removeOverlayFromCluster(cfg *config.Config, ov *ERPCOverlay) error {
if err != nil {
return err
}
if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
if provenanceHasEntries(prov) {
if err := stripERPCProvenanceKeys(erpcConfig, prov, nil, nil); err != nil {
return err
}
} else if err := stripERPCOverlay(erpcConfig, ov, prov); err != nil {
return err
}
return writeERPCConfig(cfg, erpcConfig)
}

// reconcileERPCOverlayConfig treats ov as desired state for overlay-owned
// networks and upstreams. Provenance may temporarily contain a union of old
// and new keys to make replacement crash-safe; keys not present in ov are
// stripped before provenance for new keys is captured and ov is merged.
func reconcileERPCOverlayConfig(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
if ov == nil {
return nil
}
if prov == nil {
return fmt.Errorf("eRPC overlay provenance is nil")
}
desiredNetworks, desiredUpstreams := erpcOverlayKeySets(ov)
if err := stripERPCProvenanceKeys(erpcConfig, prov, desiredNetworks, desiredUpstreams); err != nil {
return err
}
captureERPCProvenanceEntries(prov, erpcConfig, ov)
return mergeERPCOverlay(erpcConfig, ov)
}

func erpcOverlayKeySets(ov *ERPCOverlay) (map[string]struct{}, map[string]struct{}) {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if ov == nil {
return networks, upstreams
}
for _, n := range ov.Networks {
if key := networkMergeKey(n); key != "" {
networks[key] = struct{}{}
}
}
for _, u := range ov.Upstreams {
if id, _ := u["id"].(string); id != "" {
upstreams[id] = struct{}{}
}
}
return networks, upstreams
}

func pruneERPCProvenance(prov *erpcProvenance, ov *ERPCOverlay) {
if prov == nil {
return
}
keepNetworks, keepUpstreams := erpcOverlayKeySets(ov)
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
delete(prov.Networks, key)
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
delete(prov.Upstreams, id)
}
}
}

func provenanceHasEntries(prov *erpcProvenance) bool {
return prov != nil && (len(prov.Networks) > 0 || len(prov.Upstreams) > 0)
}

// mergeERPCOverlay mutates erpcConfig in place. Exported for tests via
// the unexported name used from overlay_test.go (same package).
func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
Expand DownExpand Up@@ -565,6 +704,33 @@ func mergeERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay) error {
// provenance record at all (nil prov, or overlay applied before provenance
// tracking existed) falls back to the old drop-only behavior.
func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProvenance) error {
networks, upstreams := erpcOverlayKeySets(ov)
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

// stripERPCProvenanceKeys strips every provenance-tracked key that is not in
// the corresponding keep set. A nil keep set means keep nothing. This is what
// lets a newly persisted overlay remove keys owned by its predecessor even
// though the predecessor document is no longer on disk.
func stripERPCProvenanceKeys(erpcConfig map[string]any, prov *erpcProvenance, keepNetworks, keepUpstreams map[string]struct{}) error {
networks := make(map[string]struct{})
upstreams := make(map[string]struct{})
if prov != nil {
for key := range prov.Networks {
if _, keep := keepNetworks[key]; !keep {
networks[key] = struct{}{}
}
}
for id := range prov.Upstreams {
if _, keep := keepUpstreams[id]; !keep {
upstreams[id] = struct{}{}
}
}
}
return stripERPCKeys(erpcConfig, networks, upstreams, prov)
}

func stripERPCKeys(erpcConfig map[string]any, networkKeys, upstreamIDs map[string]struct{}, prov *erpcProvenance) error {
project := erpcConfigProject(erpcConfig)
if project == nil {
return fmt.Errorf("eRPC config has no projects")
Expand All@@ -573,14 +739,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
prov = &erpcProvenance{}
}

if len(ov.Upstreams) > 0 {
if len(upstreamIDs) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, u := range ov.Upstreams {
id, _ := u["id"].(string)
if id == "" {
continue
}
for id := range upstreamIDs {
if orig, tracked := prov.Upstreams[id]; tracked && orig != nil {
restore[id] = *orig
} else {
Expand DownExpand Up@@ -608,14 +770,10 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
project["upstreams"] = kept
}

if len(ov.Networks) > 0 {
if len(networkKeys) > 0 {
drop := map[string]struct{}{}
restore := map[string]map[string]any{}
for _, n := range ov.Networks {
k := networkMergeKey(n)
if k == "" {
continue
}
for k := range networkKeys {
if orig, tracked := prov.Networks[k]; tracked && orig != nil {
restore[k] = *orig
} else {
Expand DownExpand Up@@ -648,7 +806,7 @@ func stripERPCOverlay(erpcConfig map[string]any, ov *ERPCOverlay, prov *erpcProv
}

func mergeNetworksByKey(existing []any, overlay []map[string]any) []any {
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
out := make([]any, 0) // no capacity hint: len(a)+len(b) trips CodeQL allocation-size-overflow
index := map[string]int{} // mergeKey → index in out
for _, n := range existing {
nm, ok := n.(map[string]any)
Expand Down
Loading
Loading