diff --git a/e2e/hub_app/guestbook-app-set.yaml b/e2e/hub_app/guestbook-app-set.yaml index aaf6e0c..adce241 100644 --- a/e2e/hub_app/guestbook-app-set.yaml +++ b/e2e/hub_app/guestbook-app-set.yaml @@ -11,11 +11,21 @@ spec: matchLabels: cluster.open-cluster-management.io/placement: guestbook-app-placement requeueAfterSeconds: 10 + strategy: + type: RollingSync + rollingSync: + steps: + - matchExpressions: + - key: envLabel + operator: In + values: + - env-dev template: metadata: name: '{{name}}-guestbook-app' labels: apps.open-cluster-management.io/pull-to-ocm-managed-cluster: 'true' + envLabel: 'env-dev' annotations: argocd.argoproj.io/skip-reconcile: 'true' apps.open-cluster-management.io/ocm-managed-cluster: '{{name}}' @@ -30,7 +40,5 @@ spec: server: https://kubernetes.default.svc namespace: guestbook syncPolicy: - automated: - prune: true syncOptions: - CreateNamespace=true diff --git a/e2e/run_e2e.sh b/e2e/run_e2e.sh index 8c1c7db..6bd85e3 100755 --- a/e2e/run_e2e.sh +++ b/e2e/run_e2e.sh @@ -41,6 +41,10 @@ kubectl -n argocd scale deployment/argocd-redis --replicas 0 kubectl -n argocd scale deployment/argocd-notifications-controller --replicas 0 kubectl -n argocd scale statefulset/argocd-application-controller --replicas 0 +# enable progressive sync +kubectl -n argocd patch configmap argocd-cmd-params-cm --type merge -p '{"data":{"applicationsetcontroller.enable.progressive.syncs":"true"}}' +kubectl -n argocd rollout restart deployment argocd-applicationset-controller + sleep 60s echo "TEST Propgation controller startup" @@ -131,6 +135,12 @@ else echo "Propagation FAILED: manifestwork does not contain appSet hash" exit 1 fi +if kubectl -n cluster1 get manifestwork -o yaml | grep RollingSync; then + echo "Propagation: manifestwork contains operation RollingSync" +else + echo "Propagation FAILED: manifestwork does not contain operation RollingSync" + exit 1 +fi kubectl config use-context kind-cluster1 if kubectl -n argocd get app cluster1-guestbook-app | grep Synced | grep Healthy; then echo "Propagation: managed cluster application cluster1-guestbook-app created, synced and healthy" diff --git a/propagation-controller/application/application_status_controller.go b/propagation-controller/application/application_status_controller.go index eb1ae74..e8b0d80 100644 --- a/propagation-controller/application/application_status_controller.go +++ b/propagation-controller/application/application_status_controller.go @@ -17,9 +17,10 @@ limitations under the License. package application import ( + "bytes" "context" + "encoding/json" "fmt" - "reflect" "strings" "k8s.io/apimachinery/pkg/api/errors" @@ -102,40 +103,29 @@ func (r *ApplicationStatusReconciler) Reconcile(ctx context.Context, req ctrl.Re } newStatus := make(map[string]interface{}) - for k, v := range oldStatus { - newStatus[k] = v - } - _, ok, _ := unstructured.NestedMap(newStatus, "sync") - if !ok { - err := unstructured.SetNestedMap(newStatus, map[string]interface{}{}, "sync") - if err != nil { - log.Error(err, "unable to set sync") - return ctrl.Result{}, err - } + oldJSON, err := json.Marshal(oldStatus) + if err != nil { + log.Error(err, "unable to marshal oldStatus") + return ctrl.Result{}, err } - if cc.SyncStatus != "" { - err := unstructured.SetNestedField(newStatus, cc.SyncStatus, "sync", "status") - if err != nil { - log.Error(err, "unable to set sync status") - return ctrl.Result{}, err - } + err = json.Unmarshal(oldJSON, &newStatus) + if err != nil { + log.Error(err, "unable to unmarshal oldStatus into newStatus") + return ctrl.Result{}, err } - _, ok, _ = unstructured.NestedMap(newStatus, "health") - if !ok { - err := unstructured.SetNestedMap(newStatus, map[string]interface{}{}, "health") - if err != nil { - log.Error(err, "unable to set health") + if cc.SyncStatus != "" { + if err := unstructured.SetNestedField(newStatus, cc.SyncStatus, "sync", "status"); err != nil { + log.Error(err, "unable to set sync") return ctrl.Result{}, err } } if cc.HealthStatus != "" { - err := unstructured.SetNestedField(newStatus, cc.HealthStatus, "health", "status") - if err != nil { - log.Error(err, "unable to set health status") + if err := unstructured.SetNestedField(newStatus, cc.HealthStatus, "health", "status"); err != nil { + log.Error(err, "unable to set health") return ctrl.Result{}, err } } @@ -169,7 +159,13 @@ func (r *ApplicationStatusReconciler) Reconcile(ctx context.Context, req ctrl.Re } } - if !reflect.DeepEqual(oldStatus, newStatus) { + newJSON, err := json.Marshal(newStatus) + if err != nil { + log.Error(err, "unable to marshal newStatus") + return ctrl.Result{}, err + } + + if !bytes.Equal(oldJSON, newJSON) { err := unstructured.SetNestedField(application.Object, newStatus, "status") if err != nil { log.Error(err, "unable to set application status") diff --git a/propagation-controller/application/helper.go b/propagation-controller/application/helper.go index 6e7833f..f7feff5 100644 --- a/propagation-controller/application/helper.go +++ b/propagation-controller/application/helper.go @@ -130,6 +130,7 @@ func getAppSetOwnerName(ownerRefs []metav1.OwnerReference) string { // - reset the meta // - set the namespace value // - ensures the Application Destination is set to in-cluster resource deployment +// - ensures the operation field is also set if present func prepareApplicationForWorkPayload(application *unstructured.Unstructured) unstructured.Unstructured { newApp := &unstructured.Unstructured{} newApp.SetGroupVersionKind(schema.GroupVersionKind{ @@ -141,6 +142,12 @@ func prepareApplicationForWorkPayload(application *unstructured.Unstructured) un newApp.SetName(application.GetName()) newApp.SetFinalizers(application.GetFinalizers()) + // set the operation field + if operation, ok := application.Object["operation"].(map[string]interface{}); ok { + newApp.Object["operation"] = operation + } + + // set the spec field if newSpec, ok := application.Object["spec"].(map[string]interface{}); ok { if destination, ok := newSpec["destination"].(map[string]interface{}); ok { // empty the name diff --git a/propagation-controller/application/helper_test.go b/propagation-controller/application/helper_test.go index 433ac2e..158f096 100644 --- a/propagation-controller/application/helper_test.go +++ b/propagation-controller/application/helper_test.go @@ -314,6 +314,24 @@ func Test_prepareApplicationForWorkPayload(t *testing.T) { "server": "originalServer", }, } + app.Object["operation"] = map[string]interface{}{ + "info": []interface{}{ + map[string]interface{}{ + "name": "Reason", + "value": "ApplicationSet RollingSync triggered a sync of this Application resource.", + }, + }, + "initiatedBy": map[string]interface{}{ + "automated": true, + "username": "applicationset-controller", + }, + "retry": map[string]interface{}{}, + "sync": map[string]interface{}{ + "syncOptions": []interface{}{ + "CreateNamespace=true", + }, + }, + } type args struct { application *unstructured.Unstructured @@ -344,6 +362,24 @@ func Test_prepareApplicationForWorkPayload(t *testing.T) { "server": KubernetesInternalAPIServerAddr, }, } + expectedApp.Object["operation"] = map[string]interface{}{ + "info": []interface{}{ + map[string]interface{}{ + "name": "Reason", + "value": "ApplicationSet RollingSync triggered a sync of this Application resource.", + }, + }, + "initiatedBy": map[string]interface{}{ + "automated": true, + "username": "applicationset-controller", + }, + "retry": map[string]interface{}{}, + "sync": map[string]interface{}{ + "syncOptions": []interface{}{ + "CreateNamespace=true", + }, + }, + } return expectedApp }(), }, @@ -371,6 +407,13 @@ func Test_prepareApplicationForWorkPayload(t *testing.T) { t.Errorf("prepareApplicationForWorkPayload() Spec = %v, want %v", gotSpec, wantSpec) } + gotOperation, _, _ := unstructured.NestedMap(got.Object, "operation") + wantOperation, _, _ := unstructured.NestedMap(tt.want.Object, "operation") + + if !reflect.DeepEqual(gotOperation, wantOperation) { + t.Errorf("prepareApplicationForWorkPayload() Operation = %v, want %v", gotOperation, wantOperation) + } + if !reflect.DeepEqual(got.GetLabels(), tt.want.GetLabels()) { t.Errorf("prepareApplicationForWorkPayload() Labels = %v, want %v", got.GetLabels(), tt.want.GetLabels()) }