mirror of https://github.com/fluxcd/cli-utils.git
166 lines
5.0 KiB
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
|
|
}
|