mirror of
https://github.com/k3s-io/k3s.git
synced 2024-06-07 19:41:36 +00:00
621 lines
15 KiB
Go
621 lines
15 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package resmap
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"github.com/pkg/errors"
|
|
"sigs.k8s.io/kustomize/api/resid"
|
|
"sigs.k8s.io/kustomize/api/resource"
|
|
"sigs.k8s.io/kustomize/api/types"
|
|
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
|
)
|
|
|
|
// resWrangler implements ResMap.
|
|
type resWrangler struct {
|
|
// Resource list maintained in load (append) order.
|
|
// This is important for transformers, which must
|
|
// be performed in a specific order, and for users
|
|
// who for whatever reasons wish the order they
|
|
// specify in kustomizations to be maintained and
|
|
// available as an option for final YAML rendering.
|
|
rList []*resource.Resource
|
|
}
|
|
|
|
func newOne() *resWrangler {
|
|
result := &resWrangler{}
|
|
result.Clear()
|
|
return result
|
|
}
|
|
|
|
// Clear implements ResMap.
|
|
func (m *resWrangler) Clear() {
|
|
m.rList = nil
|
|
}
|
|
|
|
// DropEmpties quickly drops empty resources.
|
|
// It doesn't use Append, which checks for Id collisions.
|
|
func (m *resWrangler) DropEmpties() {
|
|
var rList []*resource.Resource
|
|
for _, r := range m.rList {
|
|
if !r.IsEmpty() {
|
|
rList = append(rList, r)
|
|
}
|
|
}
|
|
m.rList = rList
|
|
}
|
|
|
|
// Size implements ResMap.
|
|
func (m *resWrangler) Size() int {
|
|
return len(m.rList)
|
|
}
|
|
|
|
func (m *resWrangler) indexOfResource(other *resource.Resource) int {
|
|
for i, r := range m.rList {
|
|
if r == other {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Resources implements ResMap.
|
|
func (m *resWrangler) Resources() []*resource.Resource {
|
|
tmp := make([]*resource.Resource, len(m.rList))
|
|
copy(tmp, m.rList)
|
|
return tmp
|
|
}
|
|
|
|
// Append implements ResMap.
|
|
func (m *resWrangler) Append(res *resource.Resource) error {
|
|
id := res.CurId()
|
|
if r := m.GetMatchingResourcesByCurrentId(id.Equals); len(r) > 0 {
|
|
return fmt.Errorf(
|
|
"may not add resource with an already registered id: %s", id)
|
|
}
|
|
m.append(res)
|
|
return nil
|
|
}
|
|
|
|
// append appends without performing an Id check
|
|
func (m *resWrangler) append(res *resource.Resource) {
|
|
m.rList = append(m.rList, res)
|
|
}
|
|
|
|
// Remove implements ResMap.
|
|
func (m *resWrangler) Remove(adios resid.ResId) error {
|
|
var rList []*resource.Resource
|
|
for _, r := range m.rList {
|
|
if r.CurId() != adios {
|
|
rList = append(rList, r)
|
|
}
|
|
}
|
|
if len(rList) != m.Size()-1 {
|
|
return fmt.Errorf("id %s not found in removal", adios)
|
|
}
|
|
m.rList = rList
|
|
return nil
|
|
}
|
|
|
|
// Replace implements ResMap.
|
|
func (m *resWrangler) Replace(res *resource.Resource) (int, error) {
|
|
id := res.CurId()
|
|
i, err := m.GetIndexOfCurrentId(id)
|
|
if err != nil {
|
|
return -1, errors.Wrap(err, "in Replace")
|
|
}
|
|
if i < 0 {
|
|
return -1, fmt.Errorf("cannot find resource with id %s to replace", id)
|
|
}
|
|
m.rList[i] = res
|
|
return i, nil
|
|
}
|
|
|
|
// AllIds implements ResMap.
|
|
func (m *resWrangler) AllIds() (ids []resid.ResId) {
|
|
ids = make([]resid.ResId, m.Size())
|
|
for i, r := range m.rList {
|
|
ids[i] = r.CurId()
|
|
}
|
|
return
|
|
}
|
|
|
|
// Debug implements ResMap.
|
|
func (m *resWrangler) Debug(title string) {
|
|
fmt.Println("--------------------------- " + title)
|
|
firstObj := true
|
|
for i, r := range m.rList {
|
|
if firstObj {
|
|
firstObj = false
|
|
} else {
|
|
fmt.Println("---")
|
|
}
|
|
fmt.Printf("# %d %s\n%s\n", i, r.OrgId(), r.String())
|
|
}
|
|
}
|
|
|
|
type IdMatcher func(resid.ResId) bool
|
|
|
|
// GetByIndex implements ResMap.
|
|
func (m *resWrangler) GetByIndex(i int) *resource.Resource {
|
|
if i < 0 || i >= m.Size() {
|
|
return nil
|
|
}
|
|
return m.rList[i]
|
|
}
|
|
|
|
// GetIndexOfCurrentId implements ResMap.
|
|
func (m *resWrangler) GetIndexOfCurrentId(id resid.ResId) (int, error) {
|
|
count := 0
|
|
result := -1
|
|
for i, r := range m.rList {
|
|
if id.Equals(r.CurId()) {
|
|
count++
|
|
result = i
|
|
}
|
|
}
|
|
if count > 1 {
|
|
return -1, fmt.Errorf("id matched %d resources", count)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
type IdFromResource func(r *resource.Resource) resid.ResId
|
|
|
|
func GetCurrentId(r *resource.Resource) resid.ResId { return r.CurId() }
|
|
|
|
// GetMatchingResourcesByCurrentId implements ResMap.
|
|
func (m *resWrangler) GetMatchingResourcesByCurrentId(
|
|
matches IdMatcher) []*resource.Resource {
|
|
return m.filteredById(matches, GetCurrentId)
|
|
}
|
|
|
|
// GetMatchingResourcesByAnyId implements ResMap.
|
|
func (m *resWrangler) GetMatchingResourcesByAnyId(
|
|
matches IdMatcher) []*resource.Resource {
|
|
var result []*resource.Resource
|
|
for _, r := range m.rList {
|
|
for _, id := range append(r.PrevIds(), r.CurId()) {
|
|
if matches(id) {
|
|
result = append(result, r)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (m *resWrangler) filteredById(
|
|
matches IdMatcher, idGetter IdFromResource) []*resource.Resource {
|
|
var result []*resource.Resource
|
|
for _, r := range m.rList {
|
|
if matches(idGetter(r)) {
|
|
result = append(result, r)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetByCurrentId implements ResMap.
|
|
func (m *resWrangler) GetByCurrentId(
|
|
id resid.ResId) (*resource.Resource, error) {
|
|
return demandOneMatch(m.GetMatchingResourcesByCurrentId, id, "Current")
|
|
}
|
|
|
|
// GetById implements ResMap.
|
|
func (m *resWrangler) GetById(
|
|
id resid.ResId) (*resource.Resource, error) {
|
|
r, err := demandOneMatch(m.GetMatchingResourcesByAnyId, id, "Id")
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"%s; failed to find unique target for patch %s",
|
|
err.Error(), id.GvknString())
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
type resFinder func(IdMatcher) []*resource.Resource
|
|
|
|
func demandOneMatch(
|
|
f resFinder, id resid.ResId, s string) (*resource.Resource, error) {
|
|
r := f(id.Equals)
|
|
if len(r) == 1 {
|
|
return r[0], nil
|
|
}
|
|
if len(r) > 1 {
|
|
return nil, fmt.Errorf("multiple matches for %s %s", s, id)
|
|
}
|
|
return nil, fmt.Errorf("no matches for %sId %s", s, id)
|
|
}
|
|
|
|
// GroupedByCurrentNamespace implements ResMap.GroupByCurrentNamespace
|
|
func (m *resWrangler) GroupedByCurrentNamespace() map[string][]*resource.Resource {
|
|
items := m.groupedByCurrentNamespace()
|
|
delete(items, resid.TotallyNotANamespace)
|
|
return items
|
|
}
|
|
|
|
// NonNamespaceable implements ResMap.NonNamespaceable
|
|
func (m *resWrangler) NonNamespaceable() []*resource.Resource {
|
|
return m.groupedByCurrentNamespace()[resid.TotallyNotANamespace]
|
|
}
|
|
|
|
func (m *resWrangler) groupedByCurrentNamespace() map[string][]*resource.Resource {
|
|
byNamespace := make(map[string][]*resource.Resource)
|
|
for _, res := range m.rList {
|
|
namespace := res.CurId().EffectiveNamespace()
|
|
if _, found := byNamespace[namespace]; !found {
|
|
byNamespace[namespace] = []*resource.Resource{}
|
|
}
|
|
byNamespace[namespace] = append(byNamespace[namespace], res)
|
|
}
|
|
return byNamespace
|
|
}
|
|
|
|
// GroupedByNamespace implements ResMap.GroupByOrginalNamespace
|
|
func (m *resWrangler) GroupedByOriginalNamespace() map[string][]*resource.Resource {
|
|
items := m.groupedByOriginalNamespace()
|
|
delete(items, resid.TotallyNotANamespace)
|
|
return items
|
|
}
|
|
|
|
func (m *resWrangler) groupedByOriginalNamespace() map[string][]*resource.Resource {
|
|
byNamespace := make(map[string][]*resource.Resource)
|
|
for _, res := range m.rList {
|
|
namespace := res.OrgId().EffectiveNamespace()
|
|
if _, found := byNamespace[namespace]; !found {
|
|
byNamespace[namespace] = []*resource.Resource{}
|
|
}
|
|
byNamespace[namespace] = append(byNamespace[namespace], res)
|
|
}
|
|
return byNamespace
|
|
}
|
|
|
|
// AsYaml implements ResMap.
|
|
func (m *resWrangler) AsYaml() ([]byte, error) {
|
|
firstObj := true
|
|
var b []byte
|
|
buf := bytes.NewBuffer(b)
|
|
for _, res := range m.rList {
|
|
out, err := res.AsYAML()
|
|
if err != nil {
|
|
m, _ := res.Map()
|
|
return nil, errors.Wrapf(err, "%#v", m)
|
|
}
|
|
if firstObj {
|
|
firstObj = false
|
|
} else {
|
|
if _, err = buf.WriteString("---\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if _, err = buf.Write(out); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// ErrorIfNotEqualSets implements ResMap.
|
|
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
|
m2, ok := other.(*resWrangler)
|
|
if !ok {
|
|
return fmt.Errorf("bad cast to resWrangler 1")
|
|
}
|
|
if m.Size() != m2.Size() {
|
|
return fmt.Errorf(
|
|
"lists have different number of entries: %#v doesn't equal %#v",
|
|
m.rList, m2.rList)
|
|
}
|
|
seen := make(map[int]bool)
|
|
for _, r1 := range m.rList {
|
|
id := r1.CurId()
|
|
others := m2.GetMatchingResourcesByCurrentId(id.Equals)
|
|
if len(others) == 0 {
|
|
return fmt.Errorf(
|
|
"id in self missing from other; id: %s", id)
|
|
}
|
|
if len(others) > 1 {
|
|
return fmt.Errorf(
|
|
"id in self matches %d in other; id: %s", len(others), id)
|
|
}
|
|
r2 := others[0]
|
|
if !r1.NodeEqual(r2) {
|
|
return fmt.Errorf(
|
|
"nodes unequal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
|
r1, r2, r1, r2)
|
|
}
|
|
seen[m2.indexOfResource(r2)] = true
|
|
}
|
|
if len(seen) != m.Size() {
|
|
return fmt.Errorf("counting problem %d != %d", len(seen), m.Size())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ErrorIfNotEqualList implements ResMap.
|
|
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
|
m2, ok := other.(*resWrangler)
|
|
if !ok {
|
|
return fmt.Errorf("bad cast to resWrangler 2")
|
|
}
|
|
if m.Size() != m2.Size() {
|
|
return fmt.Errorf(
|
|
"lists have different number of entries: %#v doesn't equal %#v",
|
|
m.rList, m2.rList)
|
|
}
|
|
for i, r1 := range m.rList {
|
|
r2 := m2.rList[i]
|
|
if err := r1.ErrIfNotEquals(r2); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type resCopier func(r *resource.Resource) *resource.Resource
|
|
|
|
// ShallowCopy implements ResMap.
|
|
func (m *resWrangler) ShallowCopy() ResMap {
|
|
return m.makeCopy(
|
|
func(r *resource.Resource) *resource.Resource {
|
|
return r
|
|
})
|
|
}
|
|
|
|
// DeepCopy implements ResMap.
|
|
func (m *resWrangler) DeepCopy() ResMap {
|
|
return m.makeCopy(
|
|
func(r *resource.Resource) *resource.Resource {
|
|
return r.DeepCopy()
|
|
})
|
|
}
|
|
|
|
// makeCopy copies the ResMap.
|
|
func (m *resWrangler) makeCopy(copier resCopier) ResMap {
|
|
result := &resWrangler{}
|
|
result.rList = make([]*resource.Resource, m.Size())
|
|
for i, r := range m.rList {
|
|
result.rList[i] = copier(r)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// SubsetThatCouldBeReferencedByResource implements ResMap.
|
|
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
|
referrer *resource.Resource) ResMap {
|
|
referrerId := referrer.CurId()
|
|
if !referrerId.IsNamespaceableKind() {
|
|
// A cluster scoped resource can refer to anything.
|
|
return m
|
|
}
|
|
result := newOne()
|
|
roleBindingNamespaces := getNamespacesForRoleBinding(referrer)
|
|
for _, possibleTarget := range m.rList {
|
|
id := possibleTarget.CurId()
|
|
if !id.IsNamespaceableKind() {
|
|
// A cluster-scoped resource can be referred to by anything.
|
|
result.append(possibleTarget)
|
|
continue
|
|
}
|
|
if id.IsNsEquals(referrerId) {
|
|
// The two objects are in the same namespace.
|
|
result.append(possibleTarget)
|
|
continue
|
|
}
|
|
// The two objects are namespaced (not cluster-scoped), AND
|
|
// are in different namespaces.
|
|
// There's still a chance they can refer to each other.
|
|
ns := possibleTarget.GetNamespace()
|
|
if roleBindingNamespaces[ns] {
|
|
result.append(possibleTarget)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// getNamespacesForRoleBinding returns referenced ServiceAccount namespaces
|
|
// if the resource is a RoleBinding
|
|
func getNamespacesForRoleBinding(r *resource.Resource) map[string]bool {
|
|
result := make(map[string]bool)
|
|
if r.GetKind() != "RoleBinding" {
|
|
return result
|
|
}
|
|
subjects, err := r.GetSlice("subjects")
|
|
if err != nil || subjects == nil {
|
|
return result
|
|
}
|
|
for _, s := range subjects {
|
|
subject := s.(map[string]interface{})
|
|
if ns, ok1 := subject["namespace"]; ok1 {
|
|
if kind, ok2 := subject["kind"]; ok2 {
|
|
if kind.(string) == "ServiceAccount" {
|
|
result[ns.(string)] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// AppendAll implements ResMap.
|
|
func (m *resWrangler) AppendAll(other ResMap) error {
|
|
if other == nil {
|
|
return nil
|
|
}
|
|
m2, ok := other.(*resWrangler)
|
|
if !ok {
|
|
return fmt.Errorf("bad cast to resWrangler 3")
|
|
}
|
|
return m.appendAll(m2.rList)
|
|
}
|
|
|
|
// appendAll appends all the resources, error on Id collision.
|
|
func (m *resWrangler) appendAll(list []*resource.Resource) error {
|
|
for _, res := range list {
|
|
if err := m.Append(res); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AbsorbAll implements ResMap.
|
|
func (m *resWrangler) AbsorbAll(other ResMap) error {
|
|
if other == nil {
|
|
return nil
|
|
}
|
|
m2, ok := other.(*resWrangler)
|
|
if !ok {
|
|
return fmt.Errorf("bad cast to resWrangler 4")
|
|
}
|
|
for _, r := range m2.rList {
|
|
err := m.appendReplaceOrMerge(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *resWrangler) appendReplaceOrMerge(res *resource.Resource) error {
|
|
id := res.CurId()
|
|
matches := m.GetMatchingResourcesByAnyId(id.Equals)
|
|
switch len(matches) {
|
|
case 0:
|
|
switch res.Behavior() {
|
|
case types.BehaviorMerge, types.BehaviorReplace:
|
|
return fmt.Errorf(
|
|
"id %#v does not exist; cannot merge or replace", id)
|
|
default:
|
|
// presumably types.BehaviorCreate
|
|
return m.Append(res)
|
|
}
|
|
case 1:
|
|
old := matches[0]
|
|
if old == nil {
|
|
return fmt.Errorf("id lookup failure")
|
|
}
|
|
index := m.indexOfResource(old)
|
|
if index < 0 {
|
|
return fmt.Errorf("indexing problem")
|
|
}
|
|
switch res.Behavior() {
|
|
case types.BehaviorReplace:
|
|
res.CopyMergeMetaDataFieldsFrom(old)
|
|
case types.BehaviorMerge:
|
|
res.CopyMergeMetaDataFieldsFrom(old)
|
|
res.MergeDataMapFrom(old)
|
|
res.MergeBinaryDataMapFrom(old)
|
|
default:
|
|
return fmt.Errorf(
|
|
"id %#v exists; behavior must be merge or replace", id)
|
|
}
|
|
i, err := m.Replace(res)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if i != index {
|
|
return fmt.Errorf("unexpected target index in replacement")
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf(
|
|
"found multiple objects %v that could accept merge of %v",
|
|
matches, id)
|
|
}
|
|
}
|
|
|
|
// Select returns a list of resources that
|
|
// are selected by a Selector
|
|
func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
|
var result []*resource.Resource
|
|
sr, err := types.NewSelectorRegex(&s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, r := range m.rList {
|
|
curId := r.CurId()
|
|
orgId := r.OrgId()
|
|
|
|
// It first tries to match with the original namespace
|
|
// then matches with the current namespace
|
|
if !sr.MatchNamespace(orgId.EffectiveNamespace()) &&
|
|
!sr.MatchNamespace(curId.EffectiveNamespace()) {
|
|
continue
|
|
}
|
|
|
|
// It first tries to match with the original name
|
|
// then matches with the current name
|
|
if !sr.MatchName(orgId.Name) &&
|
|
!sr.MatchName(curId.Name) {
|
|
continue
|
|
}
|
|
|
|
// matches the GVK
|
|
if !sr.MatchGvk(r.GetGvk()) {
|
|
continue
|
|
}
|
|
|
|
// matches the label selector
|
|
matched, err := r.MatchesLabelSelector(s.LabelSelector)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !matched {
|
|
continue
|
|
}
|
|
|
|
// matches the annotation selector
|
|
matched, err = r.MatchesAnnotationSelector(s.AnnotationSelector)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !matched {
|
|
continue
|
|
}
|
|
result = append(result, r)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// ToRNodeSlice returns a copy of the resources as RNodes.
|
|
func (m *resWrangler) ToRNodeSlice() []*kyaml.RNode {
|
|
result := make([]*kyaml.RNode, len(m.rList))
|
|
for i := range m.rList {
|
|
result[i] = m.rList[i].AsRNode()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ApplySmPatch applies the patch, and errors on Id collisions.
|
|
func (m *resWrangler) ApplySmPatch(
|
|
selectedSet *resource.IdSet, patch *resource.Resource) error {
|
|
var list []*resource.Resource
|
|
for _, res := range m.rList {
|
|
if selectedSet.Contains(res.CurId()) {
|
|
patchCopy := patch.DeepCopy()
|
|
patchCopy.CopyMergeMetaDataFieldsFrom(patch)
|
|
patchCopy.SetGvk(res.GetGvk())
|
|
patchCopy.SetKind(patch.GetKind())
|
|
if err := res.ApplySmPatch(patchCopy); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if !res.IsEmpty() {
|
|
list = append(list, res)
|
|
}
|
|
}
|
|
m.Clear()
|
|
return m.appendAll(list)
|
|
}
|
|
|
|
func (m *resWrangler) RemoveBuildAnnotations() {
|
|
for _, r := range m.rList {
|
|
r.RemoveBuildAnnotations()
|
|
}
|
|
}
|