diff --git a/pkg/sync/reconcile.go b/pkg/sync/reconcile.go index dc18eb7d7..550697691 100644 --- a/pkg/sync/reconcile.go +++ b/pkg/sync/reconcile.go @@ -76,18 +76,36 @@ func Reconcile(targetObjs []*unstructured.Unstructured, liveObjByKey map[kube.Re managedLiveObj := make([]*unstructured.Unstructured, len(targetObjs)) for i, obj := range targetObjs { gvk := obj.GroupVersionKind() + ns := text.FirstNonEmpty(obj.GetNamespace(), namespace) - if namespaced := kubeutil.IsNamespacedOrUnknown(resInfo, obj.GroupVersionKind().GroupKind()); !namespaced { - ns = "" + + namespaced, err := resInfo.IsNamespaced(gvk.GroupKind()) + unknownScope := err != nil + + var keysToCheck []kubeutil.ResourceKey + // If we get an error, we don't know whether the resource is namespaced. So we need to check for both in the + // live objects. If we don't check for both, then we risk missing the object and deleting it. + if namespaced || unknownScope { + keysToCheck = append(keysToCheck, kubeutil.NewResourceKey(gvk.Group, gvk.Kind, ns, obj.GetName())) } - key := kubeutil.NewResourceKey(gvk.Group, gvk.Kind, ns, obj.GetName()) - if liveObj, ok := liveObjByKey[key]; ok { - managedLiveObj[i] = liveObj - delete(liveObjByKey, key) - } else { + if !namespaced || unknownScope { + keysToCheck = append(keysToCheck, kubeutil.NewResourceKey(gvk.Group, gvk.Kind, "", obj.GetName())) + } + + found := false + for _, key := range keysToCheck { + if liveObj, ok := liveObjByKey[key]; ok { + managedLiveObj[i] = liveObj + delete(liveObjByKey, key) + found = true + break + } + } + if !found { managedLiveObj[i] = nil } } + for _, obj := range liveObjByKey { targetObjs = append(targetObjs, nil) managedLiveObj = append(managedLiveObj, obj) diff --git a/pkg/sync/reconcile_test.go b/pkg/sync/reconcile_test.go new file mode 100644 index 000000000..d73e79001 --- /dev/null +++ b/pkg/sync/reconcile_test.go @@ -0,0 +1,52 @@ +package sync + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/argoproj/gitops-engine/pkg/utils/kube" +) + +type unknownResourceInfoProvider struct{} + +func (e *unknownResourceInfoProvider) IsNamespaced(gk schema.GroupKind) (bool, error) { + return false, errors.New("unknown") +} + +func TestReconcileWithUnknownDiscoveryDataForClusterScopedResources(t *testing.T) { + targetObjs := []*unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "my-namespace", + }, + }, + }, + } + + liveNS := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "my-namespace", + "uid": "c99ff56d-1921-495d-8512-d66cdfcb5740", + }, + }, + } + liveObjByKey := map[kube.ResourceKey]*unstructured.Unstructured{ + kube.NewResourceKey("", "Namespace", "", "my-namespace"): liveNS, + } + + result := Reconcile(targetObjs, liveObjByKey, "some-namespace", &unknownResourceInfoProvider{}) + require.Len(t, result.Target, 1) + require.Equal(t, result.Target[0], targetObjs[0]) + require.Len(t, result.Live, 1) + require.Equal(t, result.Live[0], liveNS) +}