cli-utils/pkg/manifestreader/common.go

166 lines
5.0 KiB
Go

// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package manifestreader
import (
"encoding/json"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/cli-utils/pkg/apply/solver"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type UnknownTypesError struct {
GroupKinds []schema.GroupKind
}
func (e *UnknownTypesError) Error() string {
var gks []string
for _, gk := range e.GroupKinds {
gks = append(gks, gk.String())
}
return fmt.Sprintf("unknown resource types: %s", strings.Join(gks, ","))
}
// SetNamespaces verifies that every namespaced resource has the namespace
// set, and if one does not, it will set the namespace to the provided
// defaultNamespace.
// This implementation will check each resource (that doesn't already have
// the namespace set) on whether it is namespace or cluster scoped. It does
// this by first checking the RESTMapper, and it there is not match there,
// it will look for CRDs in the provided Unstructureds.
func SetNamespaces(mapper meta.RESTMapper, objs []*unstructured.Unstructured,
defaultNamespace string, enforceNamespace bool) error {
var crdObjs []*unstructured.Unstructured
// find any crds in the set of resources.
for _, obj := range objs {
if solver.IsCRD(obj) {
crdObjs = append(crdObjs, obj)
}
}
var unknownGKs []schema.GroupKind
for _, obj := range objs {
// Exclude any inventory objects here since we don't want to change
// their namespace.
if inventory.IsInventoryObject(obj) {
continue
}
// if the resource already has the namespace set, we don't
// need to do anything
if ns := obj.GetNamespace(); ns != "" {
if enforceNamespace && ns != defaultNamespace {
return fmt.Errorf("the namespace from the provided object %q "+
"does not match the namespace %q. You must pass '--namespace=%s' to perform this operation",
ns, defaultNamespace, ns)
}
continue
}
gk := obj.GetObjectKind().GroupVersionKind().GroupKind()
mapping, err := mapper.RESTMapping(gk)
if err != nil && !meta.IsNoMatchError(err) {
return err
}
if err == nil {
// If we find a mapping for the resource type in the RESTMapper,
// we just use it.
if mapping.Scope == meta.RESTScopeNamespace {
// This means the resource does not have the namespace set,
// but it is a namespaced resource. So we set the namespace
// to the provided default value.
obj.SetNamespace(defaultNamespace)
}
continue
}
// If we get here, it means the resource does not have the namespace
// set and we didn't find the resource type in the RESTMapper. As
// a last try, we look at all the CRDS that are part of the set and
// see if we get a match on the resource type. If so, we can determine
// from the CRD whether the resource type is cluster-scoped or
// namespace-scoped. If it is the latter, we set the namespace
// to the provided default.
var scope string
for _, crdObj := range crdObjs {
group, _, _ := unstructured.NestedString(crdObj.Object, "spec", "group")
kind, _, _ := unstructured.NestedString(crdObj.Object, "spec", "names", "kind")
if gk.Kind == kind && gk.Group == group {
scope, _, _ = unstructured.NestedString(crdObj.Object, "spec", "scope")
}
}
switch scope {
case "":
unknownGKs = append(unknownGKs, gk)
case "Cluster":
continue
case "Namespaced":
obj.SetNamespace(defaultNamespace)
}
}
if len(unknownGKs) > 0 {
return &UnknownTypesError{
GroupKinds: unknownGKs,
}
}
return nil
}
// FilterLocalConfig returns a new slice of Unstructured where all resources
// with the LocalConfig annotation is filtered out.
func FilterLocalConfig(objs []*unstructured.Unstructured) []*unstructured.Unstructured {
var filteredObjs []*unstructured.Unstructured
for _, obj := range objs {
// Ignoring the value of the LocalConfigAnnotation here. This is to be
// consistent with the behavior in the kyaml library:
// https://github.com/kubernetes-sigs/kustomize/blob/30b58e90a39485bc5724b2278651c5d26b815cb2/kyaml/kio/filters/local.go#L29
if _, found := obj.GetAnnotations()[filters.LocalConfigAnnotation]; !found {
filteredObjs = append(filteredObjs, obj)
}
}
return filteredObjs
}
// RemoveAnnotations removes the specified kioutil annotations from the resource.
func RemoveAnnotations(n *yaml.RNode, annotations ...kioutil.AnnotationKey) error {
for _, a := range annotations {
err := n.PipeE(yaml.ClearAnnotation(a))
if err != nil {
return err
}
}
return nil
}
// KyamlNodeToUnstructured take a resource represented as a kyaml RNode and
// turns it into an Unstructured object.
func KyamlNodeToUnstructured(n *yaml.RNode) (*unstructured.Unstructured, error) {
b, err := n.MarshalJSON()
if err != nil {
return nil, err
}
var m map[string]interface{}
err = json.Unmarshal(b, &m)
if err != nil {
return nil, err
}
return &unstructured.Unstructured{
Object: m,
}, nil
}