diff --git a/app-deployment-manager/VERSION b/app-deployment-manager/VERSION index f225a78a..aedc15bb 100644 --- a/app-deployment-manager/VERSION +++ b/app-deployment-manager/VERSION @@ -1 +1 @@ -2.5.2 +2.5.3 diff --git a/app-deployment-manager/deployment/charts/app-deployment-crd/Chart.yaml b/app-deployment-manager/deployment/charts/app-deployment-crd/Chart.yaml index 844cba22..aac7d9a0 100644 --- a/app-deployment-manager/deployment/charts/app-deployment-crd/Chart.yaml +++ b/app-deployment-manager/deployment/charts/app-deployment-crd/Chart.yaml @@ -6,8 +6,8 @@ apiVersion: v2 description: App Deployment CustomResourceDefinitions name: app-deployment-crd # Correct version will be added by "make helm-package" from VERSION -version: 2.5.2 +version: 2.5.3 annotations: - revision: "c0dad0d" - created: "2025-09-29T12:39:14Z" -appVersion: 2.5.2 + revision: "55e3426" + created: "2025-10-14T07:22:09Z" +appVersion: 2.5.3 diff --git a/app-deployment-manager/deployment/charts/app-deployment-manager/Chart.yaml b/app-deployment-manager/deployment/charts/app-deployment-manager/Chart.yaml index 71119b17..6a8f78da 100644 --- a/app-deployment-manager/deployment/charts/app-deployment-manager/Chart.yaml +++ b/app-deployment-manager/deployment/charts/app-deployment-manager/Chart.yaml @@ -6,8 +6,8 @@ apiVersion: v2 description: App Deployment Manager name: app-deployment-manager # Correct version will be added by "make helm-package" from VERSION -version: 2.5.2 +version: 2.5.3 annotations: - revision: "c0dad0d" - created: "2025-09-29T12:39:14Z" -appVersion: 2.5.2 + revision: "55e3426" + created: "2025-10-14T07:22:09Z" +appVersion: 2.5.3 diff --git a/app-deployment-manager/internal/northbound/northbound.go b/app-deployment-manager/internal/northbound/northbound.go index 25c838a3..e51e3fb5 100644 --- a/app-deployment-manager/internal/northbound/northbound.go +++ b/app-deployment-manager/internal/northbound/northbound.go @@ -392,18 +392,33 @@ func (c *DeploymentInstance) queryFilter(ctx context.Context, labelSet []string, // Return the deployment object. func (c *DeploymentInstance) createDeploymentObject(ctx context.Context, s *DeploymentSvc) (*deploymentpb.Deployment, bool) { + deploymentMatchesFilter := len(c.checkFilters) == 0 + + if len(c.checkFilters) > 0 { + // Check all targets across all applications to see if any match (OR logic) + for _, app := range c.deployment.Spec.Applications { + if deploymentMatchesFilter { + break + } + for _, target := range app.Targets { + if c.checkFilter(target) { + deploymentMatchesFilter = true + break + } + } + } + + if !deploymentMatchesFilter { + return &deploymentpb.Deployment{}, false + } + } + // Create the TargetClusters list targetClustersList := make([]*deploymentpb.TargetClusters, 0) + for _, app := range c.deployment.Spec.Applications { labelCheckList := app.Targets for _, l := range labelCheckList { - if len(c.checkFilters) > 0 { - foundFilter := c.checkFilter(l) - if !foundFilter { - return &deploymentpb.Deployment{}, false - } - } - if _, ok := l[string(deploymentv1beta1.ClusterName)]; ok { targetClustersList = append(targetClustersList, &deploymentpb.TargetClusters{ AppName: app.Name, @@ -1432,6 +1447,16 @@ func (s *DeploymentSvc) UpdateDeployment(ctx context.Context, in *deploymentpb.U return nil, errors.Status(k8serrors.K8sToTypedError(err)).Err() } + // Propagate targets to child deployments (dependencies) + // When the main deployment is updated with new clusters, ensure child deployments + // (dependencies) are also deployed to the same clusters + if len(updateInstance.Spec.ChildDeploymentList) > 0 { + err = s.propagateTargetsToChildDeployments(ctx, updateInstance) + if err != nil { + log.Warnf("failed to propagate targets to child deployments: %v", err) + } + } + // Need to update Owner Reference to clean up secrets. Cannot add required details during secret creation // since deployment UID is required. d, err = updateOwnerRefSecrets(ctx, s.k8sClient, d) @@ -1566,3 +1591,78 @@ func (s *DeploymentSvc) ListDeploymentClusters(ctx context.Context, in *deployme utils.LogActivity(ctx, "get", "Clusters for A Deployment", "deployment name "+name, "deploy id "+UID) return resp, nil } + +// propagateTargetsToChildDeployments updates child deployments (dependencies) to ensure +// they are deployed to the same clusters as the parent deployment +func (s *DeploymentSvc) propagateTargetsToChildDeployments(ctx context.Context, parentDeployment *deploymentv1beta1.Deployment) error { + // Collect all targets from parent deployment applications + allTargets := make([]map[string]string, 0) + for _, app := range parentDeployment.Spec.Applications { + allTargets = append(allTargets, app.Targets...) + } + + // Remove duplicate targets + uniqueTargets := make([]map[string]string, 0) + targetSet := make(map[string]bool) + for _, target := range allTargets { + clusterName := target[string(deploymentv1beta1.ClusterName)] + if !targetSet[clusterName] { + targetSet[clusterName] = true + uniqueTargets = append(uniqueTargets, target) + } + } + + log.Infof("Propagating targets to child deployments: %d unique clusters", len(uniqueTargets)) + + // Update each child deployment to include all targets + for childName := range parentDeployment.Spec.ChildDeploymentList { + log.Infof("Updating child deployment: %s", childName) + + // Get the child deployment + childDeployment, err := s.crClient.Deployments(parentDeployment.Namespace).Get(ctx, childName, metav1.GetOptions{}) + if err != nil { + log.Warnf("Failed to get child deployment %s: %v", childName, err) + continue + } + + // Update targets for all applications in the child deployment + updated := false + for i := range childDeployment.Spec.Applications { + app := &childDeployment.Spec.Applications[i] + + // Add missing targets to the child application + for _, newTarget := range uniqueTargets { + targetExists := false + newClusterName := newTarget[string(deploymentv1beta1.ClusterName)] + + // Check if this target already exists + for _, existingTarget := range app.Targets { + existingClusterName := existingTarget[string(deploymentv1beta1.ClusterName)] + if existingClusterName == newClusterName { + targetExists = true + break + } + } + + // Add the target if it doesn't exist + if !targetExists { + app.Targets = append(app.Targets, newTarget) + updated = true + log.Infof("Added cluster %s to child app %s", newClusterName, app.Name) + } + } + } + + // Update the child deployment if targets were modified + if updated { + _, err = s.crClient.Deployments(parentDeployment.Namespace).Update(ctx, childName, childDeployment, metav1.UpdateOptions{}) + if err != nil { + log.Warnf("Failed to update child deployment %s: %v", childName, err) + } else { + log.Infof("Successfully updated child deployment %s with new targets", childName) + } + } + } + + return nil +} diff --git a/app-deployment-manager/internal/northbound/northbound_test.go b/app-deployment-manager/internal/northbound/northbound_test.go index d3e6bbdb..278b4c68 100644 --- a/app-deployment-manager/internal/northbound/northbound_test.go +++ b/app-deployment-manager/internal/northbound/northbound_test.go @@ -3074,6 +3074,64 @@ var _ = Describe("Gateway gRPC Service", func() { Expect(ok).To(BeTrue()) Expect(s.Message()).Should(Equal("the server does not allow this method on the requested resource (delete secrets ProfileSecretName)")) }) + + When("updating deployment with child deployments propagates targets", func() { + It("should call child deployment update when parent has child deployments", func() { + // Use existing deployment setup but add child deployments + deployInstance.Spec.ChildDeploymentList = map[string]deploymentv1beta1.DependentDeploymentRef{ + "child-deployment": { + DeploymentName: "child-deployment", + DeploymentPackageRef: deploymentv1beta1.DeploymentPackageRef{ + Name: "child-package", + Version: "1.0.0", + }, + }, + } + + // Mock the successful parent update + s.k8sClient.On( + "Update", nbmocks.AnyContext, mock.AnythingOfType("string"), deployInstance, mock.AnythingOfType("v1.UpdateOptions"), + ).Return(deployInstance, nil) + + // Mock child deployment retrieval - this is what we want to test is called + childDeployment := &deploymentv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "child-deployment", + Namespace: "test-namespace", + }, + Spec: deploymentv1beta1.DeploymentSpec{ + Applications: []deploymentv1beta1.Application{ + {Name: "child-app", Version: "1.0.0", Targets: []map[string]string{{"cluster": "cluster1"}}}, + }, + }, + } + + s.k8sClient.On( + "Get", nbmocks.AnyContext, "child-deployment", mock.AnythingOfType("v1.GetOptions"), + ).Return(childDeployment, nil).Maybe() + + s.k8sClient.On( + "Update", nbmocks.AnyContext, mock.AnythingOfType("string"), mock.AnythingOfType("*v1beta1.Deployment"), mock.AnythingOfType("v1.UpdateOptions"), + ).Return(childDeployment, nil).Maybe() + + s.k8sClient.On( + "Delete", nbmocks.AnyContext, mock.AnythingOfType("string"), mock.AnythingOfType("v1.DeleteOptions"), + ).Return(nil).Maybe() + + s.catalogClient.On("GetDeploymentPackage", nbmocks.AnyContext, nbmocks.AnyGetDpReq).Return(&nbmocks.DpRespGood, nil) + s.catalogClient.On("GetApplication", nbmocks.AnyContext, nbmocks.AnyGetAppReq).Return(&nbmocks.AppHelmResp, nil) + s.catalogClient.On("GetRegistry", nbmocks.AnyContext, nbmocks.AnyGetRegReq).Return(&nbmocks.HelmRegResp, nil) + + // Execute the update - this should trigger child deployment propagation + res, err := s.deploymentServer.UpdateDeployment(s.ctx, &deploymentpb.UpdateDeploymentRequest{ + Deployment: deployInstanceResp, + DeplId: VALID_UID, + }) + + Expect(res).NotTo(BeNil()) + Expect(err).ShouldNot(HaveOccurred()) + }) + }) }) Describe("Gateway API List Deployments Per Cluster", func() {