Skip to content

Commit

Permalink
Add deployment patch to EnvoyProxy CR
Browse files Browse the repository at this point in the history
backports from upstream:
- envoyproxy#2374
- envoyproxy#1424
  • Loading branch information
bpdohall authored Dec 11, 2024
2 parents 98d3233 + c106a22 commit b5f9d1e
Show file tree
Hide file tree
Showing 23 changed files with 3,579 additions and 1,398 deletions.
37 changes: 37 additions & 0 deletions api/config/v1alpha1/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
package v1alpha1

import (
"encoding/json"
"fmt"

jsonpatch "github.com/evanphx/json-patch"
appv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/utils/pointer"
)

Expand Down Expand Up @@ -248,3 +254,34 @@ func (r *EnvoyGatewayProvider) GetEnvoyGatewayKubeProvider() *EnvoyGatewayKubern

return r.Kubernetes
}

// ApplyMergePatch applies a merge patch to a deployment based on the merge type
func (deployment *KubernetesDeploymentSpec) ApplyMergePatch(old *appv1.Deployment) (*appv1.Deployment, error) {
if deployment.Patch == nil {
return old, nil
}
var patchedJSON []byte
var err error
// Serialize the current deployment to JSON
originalJSON, err := json.Marshal(old)
if err != nil {
return nil, fmt.Errorf("error marshaling original deployment: %w", err)
}
switch {
case deployment.Patch.Type == nil || *deployment.Patch.Type == StrategicMerge:
patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, deployment.Patch.Value.Raw, appv1.Deployment{})
case *deployment.Patch.Type == JSONMerge:
patchedJSON, err = jsonpatch.MergePatch(originalJSON, deployment.Patch.Value.Raw)
default:
return nil, fmt.Errorf("unsupported merge type: %s", *deployment.Patch.Type)
}
if err != nil {
return nil, fmt.Errorf("error applying merge patch: %w", err)
}
// Deserialize the patched JSON into a new deployment object
var patchedDeployment appv1.Deployment
if err := json.Unmarshal(patchedJSON, &patchedDeployment); err != nil {
return nil, fmt.Errorf("error unmarshaling patched deployment: %w", err)
}
return &patchedDeployment, nil
}
43 changes: 42 additions & 1 deletion api/config/v1alpha1/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

package v1alpha1

import corev1 "k8s.io/api/core/v1"
import (
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)

const (
// DefaultDeploymentReplicas is the default number of deployment replicas.
Expand Down Expand Up @@ -44,6 +47,11 @@ const (

// KubernetesDeploymentSpec defines the desired state of the Kubernetes deployment resource.
type KubernetesDeploymentSpec struct {
// Patch defines how to perform the patch operation to deployment
//
// +optional
Patch *KubernetesPatchSpec `json:"patch,omitempty"`

// Replicas is the number of desired pods. Defaults to 1.
//
// +optional
Expand Down Expand Up @@ -83,6 +91,12 @@ type KubernetesPodSpec struct {
// If specified, the pod's tolerations.
// +optional
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`

// Volumes that can be mounted by containers belonging to the pod.
// More info: https://kubernetes.io/docs/concepts/storage/volumes
//
// +optional
Volumes []corev1.Volume `json:"volumes,omitempty"`
}

// KubernetesContainerSpec defines the desired state of the Kubernetes container resource.
Expand All @@ -104,6 +118,12 @@ type KubernetesContainerSpec struct {
//
// +optional
Image *string `json:"image,omitempty"`

// VolumeMounts are volumes to mount into the container's filesystem.
// Cannot be updated.
//
// +optional
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
}

// ServiceType string describes ingress methods for a service
Expand Down Expand Up @@ -152,3 +172,24 @@ const (
XDSHTTPListener XDSTranslatorHook = "HTTPListener"
XDSTranslation XDSTranslatorHook = "Translation"
)

// MergeType defines the type of merge operation
type MergeType string

const (
// StrategicMerge indicates a strategic merge patch type
StrategicMerge MergeType = "StrategicMerge"
// JSONMerge indicates a JSON merge patch type
JSONMerge MergeType = "JSONMerge"
)

// KubernetesPatchSpec defines how to perform the patch operation
type KubernetesPatchSpec struct {
// Type is the type of merge operation to perform
//
// By default, StrategicMerge is used as the patch type.
// +optional
Type *MergeType `json:"type,omitempty"`
// Object contains the raw configuration for merged object
Value apiextensionsv1.JSON `json:"value"`
}
19 changes: 19 additions & 0 deletions api/config/v1alpha1/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func validateProvider(spec *EnvoyProxySpec) []error {
if spec.Provider.Type != ProviderTypeKubernetes {
errs = append(errs, fmt.Errorf("unsupported provider type %v", spec.Provider.Type))
}
validateDeploymentErrs := validateDeployment(spec)
if len(validateDeploymentErrs) != 0 {
errs = append(errs, validateDeploymentErrs...)
}
validateServiceTypeErrs := validateServiceType(spec)
if len(validateServiceTypeErrs) != 0 {
errs = append(errs, validateServiceTypeErrs...)
Expand All @@ -70,6 +74,21 @@ func validateProvider(spec *EnvoyProxySpec) []error {
return errs
}

func validateDeployment(spec *EnvoyProxySpec) []error {
var errs []error
if spec.Provider.Kubernetes != nil && spec.Provider.Kubernetes.EnvoyDeployment != nil {
if patch := spec.Provider.Kubernetes.EnvoyDeployment.Patch; patch != nil {
if patch.Value.Raw == nil {
errs = append(errs, fmt.Errorf("envoy deployment patch object cannot be empty"))
}
if patch.Type != nil && *patch.Type != JSONMerge && *patch.Type != StrategicMerge {
errs = append(errs, fmt.Errorf("unsupported envoy deployment patch type %s", *patch.Type))
}
}
}
return errs
}

func validateServiceType(spec *EnvoyProxySpec) []error {
var errs []error
if spec.Provider.Kubernetes != nil && spec.Provider.Kubernetes.EnvoyService != nil {
Expand Down
95 changes: 95 additions & 0 deletions api/config/v1alpha1/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
)

var (
Expand Down Expand Up @@ -217,6 +219,99 @@ func TestValidateEnvoyProxy(t *testing.T) {
},
expected: false,
},
{
name: "should invalid when patch type is empty",
obj: &EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: EnvoyProxySpec{
Provider: &EnvoyProxyProvider{
Type: ProviderTypeKubernetes,
Kubernetes: &EnvoyProxyKubernetesProvider{
EnvoyDeployment: &KubernetesDeploymentSpec{
Patch: &KubernetesPatchSpec{
Value: v1.JSON{
Raw: []byte{},
},
},
},
},
},
},
},
expected: true,
}, {
name: "should invalid when patch object is empty",
obj: &EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: EnvoyProxySpec{
Provider: &EnvoyProxyProvider{
Type: ProviderTypeKubernetes,
Kubernetes: &EnvoyProxyKubernetesProvider{
EnvoyDeployment: &KubernetesDeploymentSpec{
Patch: &KubernetesPatchSpec{
Type: ptr.To(StrategicMerge),
},
},
},
},
},
},
expected: false,
}, {
name: "should valid when patch type and object are both not empty",
obj: &EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: EnvoyProxySpec{
Provider: &EnvoyProxyProvider{
Type: ProviderTypeKubernetes,
Kubernetes: &EnvoyProxyKubernetesProvider{
EnvoyDeployment: &KubernetesDeploymentSpec{
Patch: &KubernetesPatchSpec{
Type: ptr.To(StrategicMerge),
Value: v1.JSON{
Raw: []byte("{}"),
},
},
},
},
},
},
},
expected: true,
},
{
name: "should valid when patch type is empty and object is not empty",
obj: &EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: EnvoyProxySpec{
Provider: &EnvoyProxyProvider{
Type: ProviderTypeKubernetes,
Kubernetes: &EnvoyProxyKubernetesProvider{
EnvoyDeployment: &KubernetesDeploymentSpec{
Patch: &KubernetesPatchSpec{
Value: v1.JSON{
Raw: []byte("{}"),
},
},
},
},
},
},
},
expected: true,
},
}

for i := range testCases {
Expand Down
41 changes: 40 additions & 1 deletion api/config/v1alpha1/zz_generated.deepcopy.go

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

1 change: 0 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

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

Loading

0 comments on commit b5f9d1e

Please sign in to comment.