mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-10 08:20:59 +00:00
- In ResMap, drop concept of internal Id to Resource map. The ResMap is now (just) a list, allowing only very particular edits. - Resources should now be maintained in the order loaded. A later PR can adjust tests to remove the internal legacy sorting, and confirm order-out is predictable from order-in. The PR would suppress the sort in tests, and reorder the output to make all tests pass again, and confirm that the new order matched depth-first input traversal. The FromMap fixture function was removed from all test inputs to establish a predictable input order. - Resources now have two 'Ids', OriginalId and CurrentId. The former is fixed as GVK-name-namespace at load time, the latter changes during transformations. The latter can be used to narrow name references when the former maps to multiple resources. We allow bases to be loaded more than once in a build (a diamond pattern), so the OriginalId is not unique across the resources set. The CurrentId is (and must be) unique, but is constantly mutating. Failing to make this distinction clear, and attempting to maintain a mapping from a single mutating Id to a resource was making the code too complex. - Drop prefix/suffix from ResId - the ResId is now immutable. A later PR can remove the distinction with ItemId. - This PR increases coverage of ResMap is since this is a large refactor. Higher level tests didn't need much change outside reordering of results at the resource level.
236 lines
5.7 KiB
Go
236 lines
5.7 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package inventory
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"sigs.k8s.io/kustomize/pkg/resid"
|
|
)
|
|
|
|
//Refs is a reference map. Each key is the id
|
|
//of a k8s resource, and each value is a list of
|
|
//object ids that refer back to the object in the
|
|
//key.
|
|
|
|
//For example, the key could correspond to a
|
|
//ConfigMap, and the list of values might include
|
|
//several different Deployments that get data from
|
|
//that ConfigMap (and thus refer to it).
|
|
|
|
//References are important in inventory management
|
|
//because one may not delete an object before all
|
|
//objects referencing it have been removed.
|
|
type Refs map[resid.ResId][]resid.ResId
|
|
|
|
func NewRefs() Refs {
|
|
return Refs{}
|
|
}
|
|
|
|
// Merge merges a Refs into an existing Refs
|
|
func (rf Refs) Merge(b Refs) Refs {
|
|
for key, value := range b {
|
|
_, ok := rf[key]
|
|
if ok {
|
|
rf[key] = append(rf[key], value...)
|
|
} else {
|
|
rf[key] = value
|
|
}
|
|
}
|
|
return rf
|
|
}
|
|
|
|
// removeIfContains removes the reference relationship
|
|
// a --> b
|
|
// from the Refs if it exists
|
|
func (rf Refs) RemoveIfContains(a, b resid.ResId) {
|
|
refs, ok := rf[a]
|
|
if !ok {
|
|
return
|
|
}
|
|
for i, ref := range refs {
|
|
if ref.Equals(b) {
|
|
rf[a] = append(refs[:i], refs[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
//Inventory is a an object intended for
|
|
//serialization into the annotations of a so-called
|
|
//apply-root object (a ConfigMap, an Application,
|
|
//etc.) living in the cluster. This apply-root
|
|
//object is written as part of an apply operation as
|
|
//a means to record overall cluster state changes.
|
|
|
|
//At the end of a successful apply, the "current"
|
|
//field in Inventory will be a map whose keys all
|
|
//correspond to an object in the cluster, and
|
|
//"previous" will be the previous such set (an empty
|
|
//set on the first apply).
|
|
|
|
//An Inventory allows the Prune method to work.
|
|
type Inventory struct {
|
|
Current Refs `json:"current,omitempty"`
|
|
Previous Refs `json:"previous,omitempty"`
|
|
}
|
|
|
|
// NewInventory returns an Inventory object
|
|
func NewInventory() *Inventory {
|
|
return &Inventory{
|
|
Current: NewRefs(),
|
|
Previous: NewRefs(),
|
|
}
|
|
}
|
|
|
|
// UpdateCurrent updates the Inventory given a
|
|
// new current Refs
|
|
// The existing Current refs is merged into
|
|
// the Previous refs
|
|
func (a *Inventory) UpdateCurrent(curref Refs) *Inventory {
|
|
if len(a.Previous) > 0 {
|
|
a.Previous.Merge(a.Current)
|
|
} else {
|
|
a.Previous = a.Current
|
|
}
|
|
a.Current = curref
|
|
return a
|
|
}
|
|
|
|
func (a *Inventory) removeNewlyOrphanedItemsFromPrevious() []resid.ResId {
|
|
var results []resid.ResId
|
|
for item, refs := range a.Previous {
|
|
if _, ok := a.Current[item]; ok {
|
|
delete(a.Previous, item)
|
|
continue
|
|
}
|
|
|
|
var newRefs []resid.ResId
|
|
toDelete := true
|
|
for _, ref := range refs {
|
|
if _, ok := a.Current[ref]; ok {
|
|
toDelete = false
|
|
newRefs = append(newRefs, ref)
|
|
}
|
|
}
|
|
if toDelete {
|
|
results = append(results, item)
|
|
delete(a.Previous, item)
|
|
} else {
|
|
a.Previous[item] = newRefs
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func (a *Inventory) removeOrphanedItemsFromPreviousThatAreNotInCurrent() []resid.ResId {
|
|
var results []resid.ResId
|
|
for item, refs := range a.Previous {
|
|
if _, ok := a.Current[item]; ok {
|
|
continue
|
|
}
|
|
if len(refs) == 0 {
|
|
results = append(results, item)
|
|
delete(a.Previous, item)
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func (a *Inventory) removeOrphanedItemsFromPreviousThatAreInCurrent() {
|
|
//Remove references from Previous that are already in Current refs
|
|
for item, refs := range a.Current {
|
|
for _, ref := range refs {
|
|
a.Previous.RemoveIfContains(item, ref)
|
|
}
|
|
}
|
|
//Remove items from Previous that are already in Current refs
|
|
for item, refs := range a.Previous {
|
|
if len(refs) == 0 {
|
|
if _, ok := a.Current[item]; ok {
|
|
delete(a.Previous, item)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prune computes the diff of Current refs and Previous refs
|
|
// and returns a list of Items that can be pruned.
|
|
// An item that can be pruned shows up only in Previous refs.
|
|
// Prune also updates the Previous refs with those items removed
|
|
func (a *Inventory) Prune() []resid.ResId {
|
|
a.removeOrphanedItemsFromPreviousThatAreInCurrent()
|
|
|
|
// These are candidates for deletion from the cluster.
|
|
removable1 := a.removeOrphanedItemsFromPreviousThatAreNotInCurrent()
|
|
removable2 := a.removeNewlyOrphanedItemsFromPrevious()
|
|
return append(removable1, removable2...)
|
|
}
|
|
|
|
// inventory is the internal type used for serialization
|
|
type inventory struct {
|
|
Current map[string][]resid.ResId `json:"current,omitempty"`
|
|
Previous map[string][]resid.ResId `json:"previous,omitempty"`
|
|
}
|
|
|
|
func (a *Inventory) toInternalType() inventory {
|
|
prev := map[string][]resid.ResId{}
|
|
curr := map[string][]resid.ResId{}
|
|
for id, refs := range a.Current {
|
|
curr[id.String()] = refs
|
|
}
|
|
for id, refs := range a.Previous {
|
|
prev[id.String()] = refs
|
|
}
|
|
return inventory{
|
|
Current: curr,
|
|
Previous: prev,
|
|
}
|
|
}
|
|
|
|
func (a *Inventory) fromInternalType(i *inventory) {
|
|
for s, refs := range i.Previous {
|
|
a.Previous[resid.FromString(s)] = refs
|
|
}
|
|
for s, refs := range i.Current {
|
|
a.Current[resid.FromString(s)] = refs
|
|
}
|
|
}
|
|
|
|
func (a *Inventory) marshal() ([]byte, error) {
|
|
return json.Marshal(a.toInternalType())
|
|
}
|
|
|
|
func (a *Inventory) unMarshal(data []byte) error {
|
|
inv := &inventory{
|
|
Current: map[string][]resid.ResId{},
|
|
Previous: map[string][]resid.ResId{},
|
|
}
|
|
err := json.Unmarshal(data, inv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.fromInternalType(inv)
|
|
return nil
|
|
}
|
|
|
|
// UpdateAnnotations update the annotation map
|
|
func (a *Inventory) UpdateAnnotations(annot map[string]string) error {
|
|
data, err := a.marshal()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
annot[ContentAnnotation] = string(data)
|
|
return nil
|
|
}
|
|
|
|
// LoadFromAnnotation loads the Inventory date from the annotation map
|
|
func (a *Inventory) LoadFromAnnotation(annot map[string]string) error {
|
|
value, ok := annot[ContentAnnotation]
|
|
if ok {
|
|
return a.unMarshal([]byte(value))
|
|
}
|
|
return nil
|
|
}
|