Skip to content
Open
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
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/deckarep/golang-set/v2 v2.8.0
github.com/evanphx/json-patch v4.12.0+incompatible
github.com/go-logr/logr v1.4.3
github.com/google/go-containerregistry v0.20.3
github.com/onsi/ginkgo/v2 v2.22.2
github.com/onsi/gomega v1.36.2
github.com/prometheus/client_golang v1.21.1
Expand Down Expand Up @@ -70,6 +71,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
Expand Down Expand Up @@ -106,7 +108,7 @@ require (
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/tools v0.34.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYu
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down Expand Up @@ -119,6 +121,8 @@ github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -246,8 +250,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
45 changes: 35 additions & 10 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package utils
import (
"encoding/hex"
"fmt"
"regexp"
"strconv"
"strings"

Expand All @@ -14,10 +13,12 @@ import (

asdbv1 "github.com/aerospike/aerospike-kubernetes-operator/v4/api/v1"
"github.com/aerospike/aerospike-kubernetes-operator/v4/api/v1beta1"
registryname "github.com/google/go-containerregistry/pkg/name"
)

const (
DockerHubImagePrefix = "docker.io/"
DockerHubImagePrefix = "docker.io/"
DockerHubParsedRegistry = "index.docker.io"

// ReasonImagePullBackOff when pod status is Pending as container image pull failed.
ReasonImagePullBackOff = "ImagePullBackOff"
Expand Down Expand Up @@ -63,24 +64,48 @@ func IsImageEqual(image1, image2 string) bool {
desiredRegistry, desiredName, desiredVersion := ParseDockerImageTag(desiredImageWithVersion)
actualRegistry, actualName, actualVersion := ParseDockerImageTag(actualImageWithVersion)

// registry name, image name and version should match.
return desiredRegistry == actualRegistry && desiredName == actualName && (desiredVersion == actualVersion ||
(desiredVersion == ":latest" && actualVersion == "") ||
(actualVersion == ":latest" && desiredVersion == ""))
// image version should match first
if desiredVersion == actualVersion ||
(desiredVersion == "latest" && actualVersion == "") ||
(actualVersion == "latest" && desiredVersion == "") {
// if either desired or actual registry is docker hub, but the registries don't match,
// then we allow the names to match if one is a suffix of the other.
// This is to allow for pull through cache registries that prepend their path to the
// image name.
// e.g. aerospike/aerospike-server-enterprise:8.1 should match
// 000000000000.dkr.ecr.some-region.amazonaws.com/docker-hub/aerospike/aerospike-server-enterprise:8.1
if (desiredRegistry == DockerHubParsedRegistry || actualRegistry == DockerHubParsedRegistry) &&
desiredRegistry != actualRegistry {
return strings.HasSuffix(desiredName, actualName) || strings.HasSuffix(actualName, desiredName)
}

return desiredRegistry == actualRegistry && desiredName == actualName
}

return false
}

// ParseDockerImageTag parses input tag into registry, name and version.
func ParseDockerImageTag(tag string) (
registry string, name string, version string,
) {
if tag == "" {
// remove @sha256: digest if exists
digest := ""
if idx := strings.Index(tag, "@sha256:"); idx != -1 {
digest = tag[idx:]
tag = tag[:idx]
}

ref, err := registryname.ParseReference(tag)
if err != nil {
return "", "", ""
}

r := regexp.MustCompile(`(?P<registry>[^/]+/)?(?P<image>[^:]+)(?P<version>:.+)?`)
matches := r.FindStringSubmatch(tag)
registry = ref.Context().RegistryStr()
name = ref.Context().RepositoryStr()
version = ref.Identifier() + digest // version can be tag or digest

return matches[1], matches[2], strings.TrimPrefix(matches[3], ":")
return registry, name, version
}

// IsPVCTerminating returns true if pvc's DeletionTimestamp has been set
Expand Down
209 changes: 209 additions & 0 deletions pkg/utils/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package utils

import "testing"

func TestIsImageEqual(t *testing.T) {
type args struct {
image1 string
image2 string
}

tests := []struct {
name string
args args
want bool
}{
{
name: "same image with version",
args: args{
image1: "aerospike/aerospike-server-enterprise:8.1",
image2: "aerospike/aerospike-server-enterprise:8.1",
},
want: true,
},
{
name: "same image without latest version",
args: args{
image1: "aerospike/aerospike-server-enterprise:latest",
image2: "aerospike/aerospike-server-enterprise",
},
want: true,
},
{
name: "same image without latest version",
args: args{
image1: "aerospike/aerospike-server-enterprise",
image2: "aerospike/aerospike-server-enterprise:latest",
},
want: true,
},
{
name: "same image without docker.io prefix",
args: args{
image1: "docker.io/aerospike/aerospike-server-enterprise:8.1",
image2: "aerospike/aerospike-server-enterprise:8.1",
},
want: true,
},
{
name: "different image with version",
args: args{
image1: "aerospike/aerospike-server-enterprise:8.1",
image2: "aerospike/aerospike-server-enterprise:8.0",
},
want: false,
},
{
name: "different image name",
args: args{
image1: "aerospike/aerospike-server-enterprise:8.1",
image2: "aerospike/aerospike-server:8.1",
},
want: false,
},
{
name: "same image with pull through cache registry",
args: args{
image1: "aerospike/aerospike-server-enterprise:8.1",
image2: "000000000000.dkr.ecr.some-region.amazonaws.com/docker-hub/aerospike/aerospike-server-enterprise:8.1",
},
want: true,
},
{
name: "same image with pull through cache registry",
args: args{
image1: "000000000000.dkr.ecr.some-region.amazonaws.com/docker-hub/aerospike/aerospike-server-enterprise:8.1",
image2: "aerospike/aerospike-server-enterprise:8.1",
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsImageEqual(tt.args.image1, tt.args.image2); got != tt.want {
t.Errorf("IsImageEqual() = %v, want %v", got, tt.want)
}
})
}
}

func TestParseDockerImageTag(t *testing.T) {
type args struct {
tag string
}

tests := []struct {
name string
args args
wantRegistry string
wantName string
wantVersion string
}{
{
name: "image without registry",
args: args{
tag: "aerospike/aerospike-server-enterprise:8.1",
},
wantRegistry: "index.docker.io",
wantName: "aerospike/aerospike-server-enterprise",
wantVersion: "8.1",
},
{
name: "image with registry",
args: args{
tag: "000000000000.dkr.ecr.some-region.amazonaws.com/docker-hub/aerospike/aerospike-server-enterprise:8.1",
},
wantRegistry: "000000000000.dkr.ecr.some-region.amazonaws.com",
wantName: "docker-hub/aerospike/aerospike-server-enterprise",
wantVersion: "8.1",
},
{
name: "image without version",
args: args{
tag: "aerospike/aerospike-server-enterprise",
},
wantRegistry: "index.docker.io",
wantName: "aerospike/aerospike-server-enterprise",
wantVersion: "latest",
},
{
name: "empty image",
args: args{
tag: "",
},
wantRegistry: "",
wantName: "",
wantVersion: "",
},
{
name: "version with digest",
args: args{
tag: "aerospike/aerospike-server-enterprise:8.1@sha256:abcdef",
},
wantRegistry: "index.docker.io",
wantName: "aerospike/aerospike-server-enterprise",
wantVersion: "8.1@sha256:abcdef",
},
{
name: "digest without version",
args: args{
tag: "aerospike/aerospike-server-enterprise@sha256:abcdef",
},
wantRegistry: "index.docker.io",
wantName: "aerospike/aerospike-server-enterprise",
wantVersion: "latest@sha256:abcdef",
},
{
name: "registry with port",
args: args{
tag: "my-registry.com:5000/aerospike/aerospike-server-enterprise:8.1",
},
wantRegistry: "my-registry.com:5000",
wantName: "aerospike/aerospike-server-enterprise",
wantVersion: "8.1",
},
{
name: "registry with ip",
args: args{
tag: "127.0.0.1:5000/aerospike/aerospike-server-enterprise:8.1",
},
wantRegistry: "127.0.0.1:5000",
wantName: "aerospike/aerospike-server-enterprise",
wantVersion: "8.1",
},
{
name: "localhost registry",
args: args{
tag: "localhost:5000/aerospike/aerospike-server-enterprise:8.1",
},
wantRegistry: "localhost:5000",
wantName: "aerospike/aerospike-server-enterprise",
wantVersion: "8.1",
},
{
name: "library image",
args: args{
tag: "aerospike-server-enterprise:8.1",
},
wantRegistry: "index.docker.io",
wantName: "library/aerospike-server-enterprise",
wantVersion: "8.1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotRegistry, gotName, gotVersion := ParseDockerImageTag(tt.args.tag)
if gotRegistry != tt.wantRegistry {
t.Errorf("ParseDockerImageTag() gotRegistry = %v, want %v", gotRegistry, tt.wantRegistry)
}

if gotName != tt.wantName {
t.Errorf("ParseDockerImageTag() gotName = %v, want %v", gotName, tt.wantName)
}

if gotVersion != tt.wantVersion {
t.Errorf("ParseDockerImageTag() gotVersion = %v, want %v", gotVersion, tt.wantVersion)
}
})
}
}