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
11 changes: 10 additions & 1 deletion api/v1alpha1/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ type KubernetesPodSpec struct {
}

// KubernetesContainerSpec defines the desired state of the Kubernetes container resource.
// +kubebuilder:validation:XValidation:rule="!has(self.image) || !has(self.imageRepository)",message="Either image or imageRepository can be set."
type KubernetesContainerSpec struct {
// List of environment variables to set in the container.
//
Expand All @@ -227,11 +228,19 @@ type KubernetesContainerSpec struct {
// +optional
SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`

// Image specifies the EnvoyProxy container image to be used, instead of the default image.
// Image specifies the EnvoyProxy container image to be used including a tag, instead of the default image.
// This field is mutually exclusive with ImageRepository.
//
// +optional
Image *string `json:"image,omitempty"`

// ImageRepository specifies the container image repository to be used without specifying a tag.
// The default tag will be used.
// This field is mutually exclusive with Image.
//
// +optional
ImageRepository *string `json:"imageRepository,omitempty"`

// VolumeMounts are volumes to mount into the container's filesystem.
// Cannot be updated.
//
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -604,8 +604,15 @@ spec:
type: object
type: array
image:
description: Image specifies the EnvoyProxy container
image to be used, instead of the default image.
description: |-
Image specifies the EnvoyProxy container image to be used including a tag, instead of the default image.
This field is mutually exclusive with ImageRepository.
type: string
imageRepository:
description: |-
ImageRepository specifies the container image repository to be used without specifying a tag.
The default tag will be used.
This field is mutually exclusive with Image.
type: string
resources:
description: |-
Expand Down Expand Up @@ -931,6 +938,9 @@ spec:
type: object
type: array
type: object
x-kubernetes-validations:
- message: Either image or imageRepository can be set.
rule: '!has(self.image) || !has(self.imageRepository)'
name:
description: |-
Name of the daemonSet.
Expand Down Expand Up @@ -4431,8 +4441,15 @@ spec:
type: object
type: array
image:
description: Image specifies the EnvoyProxy container
image to be used, instead of the default image.
description: |-
Image specifies the EnvoyProxy container image to be used including a tag, instead of the default image.
This field is mutually exclusive with ImageRepository.
type: string
imageRepository:
description: |-
ImageRepository specifies the container image repository to be used without specifying a tag.
The default tag will be used.
This field is mutually exclusive with Image.
type: string
resources:
description: |-
Expand Down Expand Up @@ -4758,6 +4775,9 @@ spec:
type: object
type: array
type: object
x-kubernetes-validations:
- message: Either image or imageRepository can be set.
rule: '!has(self.image) || !has(self.imageRepository)'
initContainers:
description: |-
List of initialization containers belonging to the pod.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,15 @@ spec:
type: object
type: array
image:
description: Image specifies the EnvoyProxy container
image to be used, instead of the default image.
description: |-
Image specifies the EnvoyProxy container image to be used including a tag, instead of the default image.
This field is mutually exclusive with ImageRepository.
type: string
imageRepository:
description: |-
ImageRepository specifies the container image repository to be used without specifying a tag.
The default tag will be used.
This field is mutually exclusive with Image.
type: string
resources:
description: |-
Expand Down Expand Up @@ -930,6 +937,9 @@ spec:
type: object
type: array
type: object
x-kubernetes-validations:
- message: Either image or imageRepository can be set.
rule: '!has(self.image) || !has(self.imageRepository)'
name:
description: |-
Name of the daemonSet.
Expand Down Expand Up @@ -4430,8 +4440,15 @@ spec:
type: object
type: array
image:
description: Image specifies the EnvoyProxy container
image to be used, instead of the default image.
description: |-
Image specifies the EnvoyProxy container image to be used including a tag, instead of the default image.
This field is mutually exclusive with ImageRepository.
type: string
imageRepository:
description: |-
ImageRepository specifies the container image repository to be used without specifying a tag.
The default tag will be used.
This field is mutually exclusive with Image.
type: string
resources:
description: |-
Expand Down Expand Up @@ -4757,6 +4774,9 @@ spec:
type: object
type: array
type: object
x-kubernetes-validations:
- message: Either image or imageRepository can be set.
rule: '!has(self.image) || !has(self.imageRepository)'
initContainers:
description: |-
List of initialization containers belonging to the pod.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/avast/retry-go v3.0.0+incompatible
github.com/cenkalti/backoff/v4 v4.3.0
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f
github.com/containers/image/v5 v5.34.3
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/docker/cli v28.2.2+incompatible
github.com/docker/docker v27.5.1+incompatible
Expand Down Expand Up @@ -180,7 +181,6 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/containers/image/v5 v5.34.3 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.2.1 // indirect
github.com/containers/storage v1.57.2 // indirect
Expand Down
46 changes: 45 additions & 1 deletion internal/infrastructure/kubernetes/proxy/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"fmt"
"path/filepath"

"github.com/containers/image/v5/docker/reference"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/ptr"
Expand Down Expand Up @@ -115,10 +116,15 @@
return nil, err
}

proxyImage, err := resolveProxyImage(containerSpec)
if err != nil {
return nil, err
}

Check warning on line 122 in internal/infrastructure/kubernetes/proxy/resource.go

View check run for this annotation

Codecov / codecov/patch

internal/infrastructure/kubernetes/proxy/resource.go#L121-L122

Added lines #L121 - L122 were not covered by tests

containers := []corev1.Container{
{
Name: envoyContainerName,
Image: *containerSpec.Image,
Image: proxyImage,
ImagePullPolicy: corev1.PullIfNotPresent,
Command: []string{"envoy"},
Args: args,
Expand Down Expand Up @@ -479,3 +485,41 @@
sc.ReadOnlyRootFilesystem = nil
return sc
}

func resolveProxyImage(containerSpec *egv1a1.KubernetesContainerSpec) (string, error) {
if containerSpec == nil {
return "", fmt.Errorf("containerSpec is nil")
}

repo := ptr.Deref(containerSpec.ImageRepository, "")
if repo != "" {
tag, err := getImageTag(egv1a1.DefaultEnvoyProxyImage)
if err != nil {
return "", err
}

Check warning on line 499 in internal/infrastructure/kubernetes/proxy/resource.go

View check run for this annotation

Codecov / codecov/patch

internal/infrastructure/kubernetes/proxy/resource.go#L498-L499

Added lines #L498 - L499 were not covered by tests
return fmt.Sprintf("%s:%s", repo, tag), nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have any validation in place to ensure user is not passing a tag in imageRepository?

Copy link
Member Author

@sudiptob2 sudiptob2 Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, a validation could be useful, but in my opinion, it's redundant. There are many ways an imageRepository string can be invalid—not just tag. Also, the image field doesn’t currently validate whether a tag is present. So, I think we can skip this validation in this case as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to track this with a GH issue, so we dont forget about general image validation

}

image := ptr.Deref(containerSpec.Image, "")
if image != "" {
return image, nil
}

return egv1a1.DefaultEnvoyProxyImage, nil
}

// getImageTag parses a Docker/OCI image reference and returns the tag if present.
// Returns an error if parsing fails or if no tag is found.
func getImageTag(image string) (string, error) {
ref, err := reference.ParseNormalizedNamed(image)
if err != nil {
return "", fmt.Errorf("failed to parse image reference %q: %w", image, err)
}

tagged, ok := ref.(reference.Tagged)
if !ok {
return "", fmt.Errorf("no tag found in image reference %q", image)
}

return tagged.Tag(), nil
}
114 changes: 114 additions & 0 deletions internal/infrastructure/kubernetes/proxy/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package proxy

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -61,3 +62,116 @@ func TestExpectedShutdownManagerSecurityContext(t *testing.T) {
})
}
}

func TestResolveProxyImage(t *testing.T) {
defaultImage := egv1a1.DefaultEnvoyProxyImage
defaultTag := "distroless-dev"

tests := []struct {
name string
container *egv1a1.KubernetesContainerSpec
expected string
expectError bool
}{
{
name: "nil containerSpec",
container: nil,
expectError: true,
},
{
name: "imageRepository set",
container: &egv1a1.KubernetesContainerSpec{
ImageRepository: ptr.To("envoyproxy/envoy"),
},
expected: fmt.Sprintf("envoyproxy/envoy:%s", defaultTag),
},
{
name: "image set",
container: &egv1a1.KubernetesContainerSpec{
Image: ptr.To("envoyproxy/envoy:v1.2.3"),
},
expected: "envoyproxy/envoy:v1.2.3",
},
{
name: "neither set",
container: &egv1a1.KubernetesContainerSpec{},
expected: defaultImage,
},
{
name: "both image and imageRepository set (invalid per CRD, but still testable)",
container: &egv1a1.KubernetesContainerSpec{
Image: ptr.To("envoyproxy/envoy:v1.2.3"),
ImageRepository: ptr.To("envoyproxy/envoy"),
},
expected: fmt.Sprintf("envoyproxy/envoy:%s", defaultTag),
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
image, err := resolveProxyImage(tc.container)

if tc.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tc.expected, image)
}
})
}
}

func TestGetImageTag(t *testing.T) {
tests := []struct {
name string
image string
expectedTag string
expectErr bool
}{
{
name: "valid image with tag",
image: "docker.io/envoyproxy/envoy:distroless-v1.34.1",
expectedTag: "distroless-v1.34.1",
expectErr: false,
},
{
name: "image without tag",
image: "docker.io/envoyproxy/envoy",
expectErr: true,
},
{
name: "image with digest but no tag",
image: "docker.io/envoyproxy/envoy@sha256:abcdef123456",
expectErr: true,
},
{
name: "localhost with port and tag",
image: "localhost:5000/myimage:v2.0",
expectedTag: "v2.0",
expectErr: false,
},
{
name: "invalid image format",
image: "!!!not-a-valid-image###",
expectErr: true,
},
{
name: "ghcr image with tag",
image: "ghcr.io/org/image:latest",
expectedTag: "latest",
expectErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tag, err := getImageTag(tt.image)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.expectedTag, tag)
}
})
}
}
3 changes: 2 additions & 1 deletion site/content/en/latest/api/extension_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -2751,7 +2751,8 @@ _Appears in:_
| `env` | _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#envvar-v1-core) array_ | false | | List of environment variables to set in the container. |
| `resources` | _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#resourcerequirements-v1-core)_ | false | | Resources required by this container.<br />More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ |
| `securityContext` | _[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#securitycontext-v1-core)_ | false | | SecurityContext defines the security options the container should be run with.<br />If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext.<br />More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ |
| `image` | _string_ | false | | Image specifies the EnvoyProxy container image to be used, instead of the default image. |
| `image` | _string_ | false | | Image specifies the EnvoyProxy container image to be used including a tag, instead of the default image.<br />This field is mutually exclusive with ImageRepository. |
| `imageRepository` | _string_ | false | | ImageRepository specifies the container image repository to be used without specifying a tag.<br />The default tag will be used.<br />This field is mutually exclusive with Image. |
| `volumeMounts` | _[VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#volumemount-v1-core) array_ | false | | VolumeMounts are volumes to mount into the container's filesystem.<br />Cannot be updated. |


Expand Down
Loading