@@ -1236,6 +1236,114 @@ func Test_UpdateApplication(t *testing.T) {
12361236 assert .Equal (t , 0 , res .NumImagesUpdated )
12371237 })
12381238
1239+ t .Run ("Test Kubernetes Job with forceUpdate and digest strategy (issue #1344)" , func (t * testing.T ) {
1240+ // This test reproduces the scenario from issue #1344:
1241+ // - Application uses a Kubernetes Job (not in app.Status.Summary.Images)
1242+ // - forceUpdate: true is set
1243+ // - updateStrategy: "digest" is used
1244+ // - A version constraint (tag) is provided
1245+
1246+ mockClientFn := func (endpoint * registry.RegistryEndpoint , username , password string ) (registry.RegistryClient , error ) {
1247+ regMock := regmock.RegistryClient {}
1248+ regMock .On ("NewRepository" , mock .MatchedBy (func (s string ) bool {
1249+ return s == "org/job-image"
1250+ })).Return (nil )
1251+ // Return the tag that matches our version constraint
1252+ regMock .On ("Tags" , mock .Anything ).Return ([]string {"latest" }, nil )
1253+
1254+ // For digest strategy, we need to mock ManifestForTag and TagMetadata
1255+ meta1 := & schema1.SignedManifest {} //nolint:staticcheck
1256+ meta1 .Name = "org/job-image"
1257+ meta1 .Tag = "latest"
1258+ regMock .On ("ManifestForTag" , mock .Anything , "latest" ).Return (meta1 , nil )
1259+ // Create a digest as [32]byte array
1260+ var digest [32 ]byte
1261+ copy (digest [:], []byte ("abcdef1234567890" ))
1262+ regMock .On ("TagMetadata" , mock .Anything , mock .Anything , mock .Anything ).Return (& tag.TagInfo {
1263+ CreatedAt : time .Unix (1234567890 , 0 ),
1264+ Digest : digest ,
1265+ }, nil )
1266+
1267+ return & regMock , nil
1268+ }
1269+
1270+ argoClient := argomock.ArgoCD {}
1271+ argoClient .On ("UpdateSpec" , mock .Anything , mock .Anything ).Return (nil , nil )
1272+
1273+ kubeClient := kube.ImageUpdaterKubernetesClient {
1274+ KubeClient : & registryKube.KubernetesClient {
1275+ Clientset : fake .NewFakeKubeClient (),
1276+ },
1277+ }
1278+
1279+ // Image configuration with forceUpdate and digest strategy
1280+ // The tag "latest" serves as the version constraint for digest strategy
1281+ containerImg := image .NewFromIdentifier ("job-image=gcr.io/org/job-image:latest" )
1282+ iuImg := NewImage (containerImg )
1283+ iuImg .KustomizeImageName = "org/job-image"
1284+ iuImg .ForceUpdate = true
1285+ iuImg .UpdateStrategy = image .StrategyDigest
1286+
1287+ imageList := ImageList {iuImg }
1288+
1289+ appImages := & ApplicationImages {
1290+ Application : v1alpha1.Application {
1291+ ObjectMeta : v1.ObjectMeta {
1292+ Name : "job-app" ,
1293+ Namespace : "default" ,
1294+ },
1295+ Spec : v1alpha1.ApplicationSpec {
1296+ Source : & v1alpha1.ApplicationSource {
1297+ Kustomize : & v1alpha1.ApplicationSourceKustomize {
1298+ Images : v1alpha1.KustomizeImages {},
1299+ },
1300+ },
1301+ },
1302+ Status : v1alpha1.ApplicationStatus {
1303+ SourceType : v1alpha1 .ApplicationSourceTypeKustomize ,
1304+ Summary : v1alpha1.ApplicationSummary {
1305+ // Empty images list - simulating a Kubernetes Job that doesn't
1306+ // appear in the application status
1307+ Images : []string {},
1308+ },
1309+ },
1310+ },
1311+ Images : imageList ,
1312+ WriteBackConfig : & WriteBackConfig {
1313+ Method : WriteBackApplication ,
1314+ },
1315+ }
1316+
1317+ // Before the fix for issue #1344, this would fail with:
1318+ // "cannot use update strategy 'digest' for image... without a version constraint"
1319+ // because the constraint was lost when setting ImageTag to nil
1320+ res := UpdateApplication (context .Background (), & UpdateConfiguration {
1321+ NewRegFN : mockClientFn ,
1322+ ArgoClient : & argoClient ,
1323+ KubeClient : & kubeClient ,
1324+ UpdateApp : appImages ,
1325+ DryRun : false ,
1326+ }, NewSyncIterationState ())
1327+
1328+ // Verify the update succeeded
1329+ assert .Equal (t , 0 , res .NumErrors , "Should not have errors with forceUpdate + digest strategy" )
1330+ assert .Equal (t , 0 , res .NumSkipped )
1331+ assert .Equal (t , 1 , res .NumApplicationsProcessed )
1332+ assert .Equal (t , 1 , res .NumImagesConsidered )
1333+ assert .Equal (t , 1 , res .NumImagesUpdated , "Image should be updated with digest" )
1334+
1335+ // Verify the kustomize image was updated with the digest
1336+ require .Len (t , appImages .Application .Spec .Source .Kustomize .Images , 1 )
1337+ updatedImage := string (appImages .Application .Spec .Source .Kustomize .Images [0 ])
1338+ assert .Contains (t , updatedImage , "gcr.io/org/job-image" )
1339+ assert .Contains (t , updatedImage , "latest" )
1340+ assert .Contains (t , updatedImage , "sha256:" , "Image should include digest" )
1341+
1342+ // The constraint on the original image must still be present.
1343+ require .NotNil (t , iuImg .ContainerImage .ImageTag )
1344+ assert .Equal (t , "latest" , iuImg .ContainerImage .ImageTag .TagName )
1345+ })
1346+
12391347}
12401348
12411349func Test_MarshalParamsOverride (t * testing.T ) {
0 commit comments