diff --git a/pkg/operator/render/options/config.go b/pkg/operator/render/options/config.go index 0d840a2b8f..2f016f5b5e 100644 --- a/pkg/operator/render/options/config.go +++ b/pkg/operator/render/options/config.go @@ -1,7 +1,16 @@ package options import ( + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + "k8s.io/apimachinery/pkg/api/meta" + "github.com/openshift/library-go/pkg/operator/resource/resourceread" + "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/klog/v2" @@ -57,29 +66,148 @@ type TemplateData struct { FileConfig } -func (c RenderedManifests) ListManifestOfType(gvk schema.GroupVersionKind) []RenderedManifest { +func (renderedManifests RenderedManifests) ListManifestOfType(gvk schema.GroupVersionKind) []RenderedManifest { ret := []RenderedManifest{} - for i := range c { - obj, err := c[i].GetDecodedObj() + for i := range renderedManifests { + obj, err := renderedManifests[i].GetDecodedObj() if err != nil { - klog.Warningf("failure to read %q: %v", c[i].OriginalFilename, err) + klog.Warningf("failure to read %q: %v", renderedManifests[i].OriginalFilename, err) continue } if obj.GetObjectKind().GroupVersionKind() == gvk { - ret = append(ret, c[i]) + ret = append(ret, renderedManifests[i]) } } return ret } +func (renderedManifests RenderedManifests) GetManifest(gvk schema.GroupVersionKind, namespace, name string) (RenderedManifest, error) { + for i := range renderedManifests { + obj, err := renderedManifests[i].GetDecodedObj() + if err != nil { + klog.Warningf("failure to read %q: %v", renderedManifests[i].OriginalFilename, err) + continue + } + if obj.GetObjectKind().GroupVersionKind() != gvk { + continue + } + objMetadata, err := meta.Accessor(obj) + if err != nil { + klog.Warningf("failure to read metadata %q: %v", renderedManifests[i].OriginalFilename, err) + continue + } + + // since validation requires that all of these are the same, it doesn't matterwhich one we return + if objMetadata.GetName() == name && objMetadata.GetNamespace() == namespace { + return renderedManifests[i], nil + } + } + + return RenderedManifest{}, apierrors.NewNotFound( + schema.GroupResource{ + Group: gvk.Group, + Resource: gvk.Kind, + }, + name) +} + +func (renderedManifests RenderedManifests) GetObject(gvk schema.GroupVersionKind, namespace, name string) (runtime.Object, error) { + manifest, err := renderedManifests.GetManifest(gvk, namespace, name) + if err != nil { + return nil, err + } + return manifest.decodedObj, nil +} + +func (renderedManifests RenderedManifests) ValidateManifestPredictability() error { + errs := []error{} + decodeErrorsObserved := map[int]bool{} + metadataErrorsObserved := map[int]bool{} + + type compareTuple struct { + i, j int + } + compareTuplesErrorsObserved := map[compareTuple]bool{} + + for i := range renderedManifests { + lhs := renderedManifests[i] + lhsObj, err := lhs.GetDecodedObj() + if err != nil { + if !decodeErrorsObserved[i] { + errs = append(errs, err) + decodeErrorsObserved[i] = true + } + continue + } + lhsMetadata, err := meta.Accessor(lhsObj) + if err != nil { + if !metadataErrorsObserved[i] { + errs = append(errs, fmt.Errorf("unable to read metadata for %q: %w", lhs.OriginalFilename, err)) + metadataErrorsObserved[i] = true + } + continue + } + + for j := range renderedManifests { + if i == j { + continue + } + rhs := renderedManifests[j] + rhsObj, err := rhs.GetDecodedObj() + if err != nil { + if !decodeErrorsObserved[j] { + errs = append(errs, err) + decodeErrorsObserved[j] = true + } + continue + } + rhsMetadata, err := meta.Accessor(rhsObj) + if err != nil { + if !metadataErrorsObserved[j] { + errs = append(errs, fmt.Errorf("unable to read metadata for %q: %w", rhs.OriginalFilename, err)) + metadataErrorsObserved[j] = true + } + continue + } + if lhsObj.GetObjectKind().GroupVersionKind().GroupKind() != rhsObj.GetObjectKind().GroupVersionKind().GroupKind() { + continue + } + if lhsMetadata.GetName() != rhsMetadata.GetName() { + continue + } + if lhsMetadata.GetNamespace() != rhsMetadata.GetNamespace() { + continue + } + + if !equality.Semantic.DeepEqual(lhsObj, rhsObj) { + if !compareTuplesErrorsObserved[compareTuple{i, j}] { + errs = append(errs, + fmt.Errorf("%q and %q both set %v.%v/%v in ns/%v, but have different values", + lhs.OriginalFilename, + rhs.OriginalFilename, + lhsObj.GetObjectKind().GroupVersionKind().Kind, + lhsObj.GetObjectKind().GroupVersionKind().Group, + lhsMetadata.GetName(), + lhsMetadata.GetNamespace(), + )) + compareTuplesErrorsObserved[compareTuple{i, j}] = true + compareTuplesErrorsObserved[compareTuple{j, i}] = true + } + } + } + } + + return utilerrors.NewAggregate(errs) +} + func (c *RenderedManifest) GetDecodedObj() (runtime.Object, error) { if c.decodedObj != nil { return c.decodedObj, nil } obj, err := resourceread.ReadGenericWithUnstructured(c.Content) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to decode %q: %w", c.OriginalFilename, err) } c.decodedObj = obj diff --git a/pkg/operator/render/options/generic.go b/pkg/operator/render/options/generic.go index d9e16538ce..c0efaea8c6 100644 --- a/pkg/operator/render/options/generic.go +++ b/pkg/operator/render/options/generic.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io/ioutil" - "os" "text/template" "github.com/ghodss/yaml" @@ -90,10 +89,11 @@ func (o *GenericOptions) Validate() error { return errors.New("missing required flag: --config-output-file") } - for _, filename := range o.RenderedManifestInputFilenames { - _, err := os.Stat(filename) - if err != nil { - return fmt.Errorf("--rendered-manifest-files, value %q could not be read: %v", filename, err) + if renderedManifests, err := o.ReadInputManifests(); err != nil { + return fmt.Errorf("--rendered-manifest-files, could not be read: %v", err) + } else { + if err := renderedManifests.ValidateManifestPredictability(); err != nil { + return fmt.Errorf("--rendered-manifest-files, are not consistent so results would be unpredictable depending on apply order: %v", err) } } @@ -102,6 +102,7 @@ func (o *GenericOptions) Validate() error { default: return fmt.Errorf("invalid feature-set specified: %q", o.FeatureSet) } + return nil }