Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app-deployment-manager/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.5.2
2.5.3
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
114 changes: 107 additions & 7 deletions app-deployment-manager/internal/northbound/northbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
58 changes: 58 additions & 0 deletions app-deployment-manager/internal/northbound/northbound_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading