diff --git a/cmd/flux/tree_kustomization.go b/cmd/flux/tree_kustomization.go index c471295f..bf770cc6 100644 --- a/cmd/flux/tree_kustomization.go +++ b/cmd/flux/tree_kustomization.go @@ -272,5 +272,17 @@ func getHelmReleaseInventory(ctx context.Context, objectKey client.ObjectKey, ku return nil, fmt.Errorf("failed to read the Helm storage object for HelmRelease '%s': %w", objectKey.String(), err) } + for _, obj := range objects { + if obj.GetNamespace() == "" { + if isNamespaced, _ := utils.IsAPINamespaced(obj, kubeClient.Scheme(), kubeClient.RESTMapper()); isNamespaced { + if hr.Spec.TargetNamespace != "" { + obj.SetNamespace(hr.Spec.TargetNamespace) + } else { + obj.SetNamespace(hr.GetNamespace()) + } + } + } + } + return object.UnstructuredSetToObjMetadataSet(objects), nil } diff --git a/internal/utils/objectutil.go b/internal/utils/objectutil.go new file mode 100644 index 00000000..35b381bc --- /dev/null +++ b/internal/utils/objectutil.go @@ -0,0 +1,63 @@ +/* +Copyright 2023 The Flux authors +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// TODO: Remove this when +// https://github.com/kubernetes-sigs/controller-runtime/blob/c783d2527a7da76332a2d8d563a6ca0b80c12122/pkg/client/apiutil/apimachinery.go#L76-L104 +// is included in a semver release. + +package utils + +import ( + "errors" + "fmt" + + apimeta "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +// IsAPINamespaced returns true if the object is namespace scoped. +// For unstructured objects the gvk is found from the object itself. +func IsAPINamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) { + gvk, err := apiutil.GVKForObject(obj, scheme) + if err != nil { + return false, err + } + + return IsAPINamespacedWithGVK(gvk, scheme, restmapper) +} + +// IsAPINamespacedWithGVK returns true if the object having the provided +// GVK is namespace scoped. +func IsAPINamespacedWithGVK(gk schema.GroupVersionKind, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) { + restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gk.Group, Kind: gk.Kind}) + if err != nil { + return false, fmt.Errorf("failed to get restmapping: %w", err) + } + + scope := restmapping.Scope.Name() + + if scope == "" { + return false, errors.New("scope cannot be identified, empty scope returned") + } + + if scope != apimeta.RESTScopeNameRoot { + return true, nil + } + return false, nil +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index a1cb9a3d..ab9abe2e 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -54,9 +54,6 @@ import ( "github.com/fluxcd/flux2/pkg/manifestgen/install" ) -type Utils struct { -} - type ExecMode string const (