mirror of https://github.com/fluxcd/cli-utils.git
163 lines
4.9 KiB
Go
163 lines
4.9 KiB
Go
// Copyright 2020 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package manifestreader
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/fluxcd/cli-utils/pkg/inventory"
|
|
"github.com/fluxcd/cli-utils/pkg/object"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"sigs.k8s.io/kustomize/kyaml/kio/filters"
|
|
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
)
|
|
|
|
// UnknownTypesError captures information about unknown types encountered.
|
|
type UnknownTypesError struct {
|
|
GroupVersionKinds []schema.GroupVersionKind
|
|
}
|
|
|
|
func (e *UnknownTypesError) Error() string {
|
|
var gvks []string
|
|
for _, gvk := range e.GroupVersionKinds {
|
|
gvks = append(gvks, fmt.Sprintf("%s/%s/%s",
|
|
gvk.Group, gvk.Version, gvk.Kind))
|
|
}
|
|
return fmt.Sprintf("unknown resource types: %s", strings.Join(gvks, ","))
|
|
}
|
|
|
|
// NamespaceMismatchError is returned if all resources must be in a specific
|
|
// namespace, and resources are found using other namespaces.
|
|
type NamespaceMismatchError struct {
|
|
RequiredNamespace string
|
|
Namespace string
|
|
}
|
|
|
|
func (e *NamespaceMismatchError) Error() string {
|
|
return fmt.Sprintf("found namespace %q, but all resources must be in namespace %q",
|
|
e.Namespace, e.RequiredNamespace)
|
|
}
|
|
|
|
// 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 object.IsCRD(obj) {
|
|
crdObjs = append(crdObjs, obj)
|
|
}
|
|
}
|
|
|
|
var unknownGVKs []schema.GroupVersionKind
|
|
for _, obj := range objs {
|
|
// Exclude any inventory objects here since we don't want to change
|
|
// their namespace.
|
|
if inventory.IsInventoryObject(obj) {
|
|
continue
|
|
}
|
|
|
|
// Look up the scope of the resource so we know if the resource
|
|
// should have a namespace set or not.
|
|
scope, err := object.LookupResourceScope(obj, crdObjs, mapper)
|
|
if err != nil {
|
|
var unknownTypeError *object.UnknownTypeError
|
|
if errors.As(err, &unknownTypeError) {
|
|
// If no scope was found, just add the resource type to the list
|
|
// of unknown types.
|
|
unknownGVKs = append(unknownGVKs, unknownTypeError.GroupVersionKind)
|
|
continue
|
|
}
|
|
// If something went wrong when looking up the scope, just
|
|
// give up.
|
|
return err
|
|
}
|
|
|
|
switch scope {
|
|
case meta.RESTScopeNamespace:
|
|
if obj.GetNamespace() == "" {
|
|
obj.SetNamespace(defaultNamespace)
|
|
} else {
|
|
ns := obj.GetNamespace()
|
|
if enforceNamespace && ns != defaultNamespace {
|
|
return &NamespaceMismatchError{
|
|
Namespace: ns,
|
|
RequiredNamespace: defaultNamespace,
|
|
}
|
|
}
|
|
}
|
|
case meta.RESTScopeRoot:
|
|
if ns := obj.GetNamespace(); ns != "" {
|
|
return fmt.Errorf("resource is cluster-scoped but has a non-empty namespace %q", ns)
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown RESTScope %q", scope.Name())
|
|
}
|
|
}
|
|
if len(unknownGVKs) > 0 {
|
|
return &UnknownTypesError{
|
|
GroupVersionKinds: unknownGVKs,
|
|
}
|
|
}
|
|
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
|
|
}
|