mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
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/api/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
|
|
}
|