Repository: argoproj-labs/argocd-image-updater (Go)

Issue:
ArgoCD does not consider images for arch "linux/arm64/v8"
**Describe the bug**
linux/arm64/v8 != linux/arm64

**To Reproduce**
Create a manifest for a multi-arch image with one of the arch being "linux/arm64/v8"
Run ArgoCD image updater in k8s using the "linux/arm64" (note missing v8) image
Create application using digest update against multi-arch image

**Expected behavior**
ArgoCD pattern matches linux/arm64 with linux/arm64/v8

**Version**
0.13.0




Discussion:
Work around.

Explicitly add annotation:

`argocd-image-updater.argoproj.io/app.platforms: 'linux/arm64/v8'`
we had the same issue 
the problem is that the log indicates
```ignoring OCI manifest sha256:1b86e767c10ce0c0e0e64e183a0065d114adbd38b13638811229a19a2d23c598. Manifest platform: linux/arm64, requested: linux/arm64"```

and it does not tell that actually they are different
@arontsang solution worked for us as well
Same problem in #665 
Please review the linked PR https://github.com/argoproj-labs/argocd-image-updater/pull/858 , and check if it fixes the problem, and if the change of behavior is acceptable.



Patch:
diff --git a/pkg/options/options.go b/pkg/options/options.go
index 071b63d0..67614eda 100644
--- a/pkg/options/options.go
+++ b/pkg/options/options.go
@@ -32,7 +32,7 @@ func PlatformKey(os string, arch string, variant string) string {
 	return key
 }
 
-// MatchesPlatform returns true if given OS name matches the OS set in options
+// WantsPlatform returns true if given platform matches the platform set in options
 func (o *ManifestOptions) WantsPlatform(os string, arch string, variant string) bool {
 	o.mutex.RLock()
 	defer o.mutex.RUnlock()
@@ -40,7 +40,17 @@ func (o *ManifestOptions) WantsPlatform(os string, arch string, variant string)
 		return true
 	}
 	_, ok := o.platforms[PlatformKey(os, arch, variant)]
-	return ok
+	if ok {
+		return true
+	}
+
+	// if no exact match, and the passed platform has variant, it may be a more
+	// specific variant of the platform specified in options. So compare os/arch only
+	if variant != "" {
+		_, ok = o.platforms[PlatformKey(os, arch, "")]
+		return ok
+	}
+	return false
 }
 
 // WithPlatform sets a platform filter for options o
diff --git a/pkg/registry/client.go b/pkg/registry/client.go
index e4f95ede..ba75fd85 100644
--- a/pkg/registry/client.go
+++ b/pkg/registry/client.go
@@ -64,7 +64,7 @@ type registryClient struct {
 }
 
 // credentials is an implementation of distribution/V3/session struct
-// to mangage registry credentials and token
+// to manage registry credentials and token
 type credentials struct {
 	username      string
 	password      string
@@ -306,8 +306,8 @@ func (client *registryClient) TagMetadata(manifest distribution.Manifest, opts *
 		}
 
 		if !opts.WantsPlatform(info.OS, info.Arch, info.Variant) {
-			logCtx.Debugf("ignoring v2 manifest %v. Manifest platform: %s/%s, requested: %s",
-				ti.EncodedDigest(), info.OS, info.Arch, strings.Join(opts.Platforms(), ","))
+			logCtx.Debugf("ignoring v2 manifest %v. Manifest platform: %s, requested: %s",
+				ti.EncodedDigest(), options.PlatformKey(info.OS, info.Arch, info.Variant), strings.Join(opts.Platforms(), ","))
 			return nil, nil
 		}
 
@@ -338,8 +338,8 @@ func (client *registryClient) TagMetadata(manifest distribution.Manifest, opts *
 		}
 
 		if !opts.WantsPlatform(info.OS, info.Arch, info.Variant) {
-			logCtx.Debugf("ignoring OCI manifest %v. Manifest platform: %s/%s, requested: %s",
-				ti.EncodedDigest(), info.OS, info.Arch, strings.Join(opts.Platforms(), ","))
+			logCtx.Debugf("ignoring OCI manifest %v. Manifest platform: %s, requested: %s",
+				ti.EncodedDigest(), options.PlatformKey(info.OS, info.Arch, info.Variant), strings.Join(opts.Platforms(), ","))
 			return nil, nil
 		}
 
@@ -367,19 +367,20 @@ func TagInfoFromReferences(client *registryClient, opts *options.ManifestOptions
 			refArch = ref.Platform.Architecture
 			refVariant = ref.Platform.Variant
 		}
-		platforms = append(platforms, refOS+"/"+refArch)
-		logCtx.Tracef("Found %s", options.PlatformKey(refOS, refArch, refVariant))
+		platform1 := options.PlatformKey(refOS, refArch, refVariant)
+		platforms = append(platforms, platform1)
+		logCtx.Tracef("Found %s", platform1)
 		if !opts.WantsPlatform(refOS, refArch, refVariant) {
 			logCtx.Tracef("Ignoring referenced manifest %v because platform %s does not match any of: %s",
 				ref.Digest,
-				options.PlatformKey(refOS, refArch, refVariant),
+				platform1,
 				strings.Join(opts.Platforms(), ","))
 			continue
 		}
 		ml = append(ml, ref)
 	}
 
-	// We need at least one reference that matches requested plaforms
+	// We need at least one reference that matches requested platforms
 	if len(ml) == 0 {
 		logCtx.Debugf("Manifest list did not contain any usable reference. Platforms requested: (%s), platforms included: (%s)",
 			strings.Join(opts.Platforms(), ","), strings.Join(platforms, ","))


Test patch:
diff --git a/pkg/image/options_test.go b/pkg/image/options_test.go
index 25716860..1acaebb8 100644
--- a/pkg/image/options_test.go
+++ b/pkg/image/options_test.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"regexp"
 	"runtime"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -380,8 +381,17 @@ func Test_GetPlatformOptions(t *testing.T) {
 		opts := img.GetPlatformOptions(annotations, false)
 		os := runtime.GOOS
 		arch := runtime.GOARCH
-		assert.True(t, opts.WantsPlatform(os, arch, ""))
-		assert.False(t, opts.WantsPlatform(os, arch, "invalid"))
+		platform := opts.Platforms()[0]
+		slashCount := strings.Count(platform, "/")
+		if slashCount == 1 {
+			assert.True(t, opts.WantsPlatform(os, arch, ""))
+			assert.True(t, opts.WantsPlatform(os, arch, "invalid"))
+		} else if slashCount == 2 {
+			assert.False(t, opts.WantsPlatform(os, arch, ""))
+			assert.False(t, opts.WantsPlatform(os, arch, "invalid"))
+		} else {
+			t.Fatal("invalid platform options ", platform)
+		}
 	})
 	t.Run("Empty platform options without restriction", func(t *testing.T) {
 		annotations := map[string]string{}
@@ -396,7 +406,7 @@ func Test_GetPlatformOptions(t *testing.T) {
 	t.Run("Single platform without variant requested", func(t *testing.T) {
 		os := "linux"
 		arch := "arm64"
-		variant := ""
+		variant := "v8"
 		annotations := map[string]string{
 			fmt.Sprintf(common.PlatformsAnnotation, "dummy"): options.PlatformKey(os, arch, variant),
 		}
@@ -431,7 +441,7 @@ func Test_GetPlatformOptions(t *testing.T) {
 		assert.True(t, opts.WantsPlatform(os, arch, variant))
 		assert.True(t, opts.WantsPlatform(runtime.GOOS, runtime.GOARCH, ""))
 		assert.False(t, opts.WantsPlatform(os, arch, ""))
-		assert.False(t, opts.WantsPlatform(runtime.GOOS, runtime.GOARCH, variant))
+		assert.True(t, opts.WantsPlatform(runtime.GOOS, runtime.GOARCH, variant))
 	})
 	t.Run("Invalid platform requested", func(t *testing.T) {
 		os := "linux"
diff --git a/pkg/options/options_test.go b/pkg/options/options_test.go
index 8f1efddc..99dd8bb6 100644
--- a/pkg/options/options_test.go
+++ b/pkg/options/options_test.go
@@ -26,10 +26,28 @@ func Test_WantsPlatform(t *testing.T) {
 		opts = opts.WithPlatform("linux", "arm", "v7")
 		assert.True(t, opts.WantsPlatform("linux", "arm", "v7"))
 	})
-	t.Run("Platform appended", func(t *testing.T) {
+	t.Run("Platform appended and non-match", func(t *testing.T) {
 		opts = opts.WithPlatform("linux", "arm", "v8")
 		assert.True(t, opts.WantsPlatform("linux", "arm", "v7"))
 		assert.True(t, opts.WantsPlatform("linux", "arm", "v8"))
+
+		assert.False(t, opts.WantsPlatform("linux", "arm", "v6"))
+		assert.False(t, opts.WantsPlatform("linux", "arm", ""))
+		assert.False(t, opts.WantsPlatform("linux", "", ""))
+
+		assert.False(t, opts.WantsPlatform("linux", "amd64", "v7"))
+		assert.False(t, opts.WantsPlatform("linux", "amd64", ""))
+
+		assert.False(t, opts.WantsPlatform("darwin", "arm", "v7"))
+		assert.False(t, opts.WantsPlatform("darwin", "arm", ""))
+	})
+	t.Run("Platform lenient match", func(t *testing.T) {
+		opts := &ManifestOptions{}
+		opts = opts.WithPlatform("linux", "arm", "")
+		opts = opts.WithPlatform("linux", "arm", "v7")
+		assert.True(t, opts.WantsPlatform("linux", "arm", "v8"))
+		assert.True(t, opts.WantsPlatform("linux", "arm", "v7"))
+		assert.True(t, opts.WantsPlatform("linux", "arm", ""))
 	})
 	t.Run("Uninitialized options", func(t *testing.T) {
 		opts := &ManifestOptions{}