Skip to content
Draft
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: 9 additions & 2 deletions api/core/v1beta1/flagd_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ import (

// FlagdSpec defines the desired state of Flagd
type FlagdSpec struct {
// Labels to be added to the Deployment
// +optional
PodLabels map[string]string `json:"podLabels,omitempty"`

// Annotations to be added to the Deployment
// +optional
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`

// Replicas defines the number of replicas to create for the service.
// Default: 1
// +optional
Expand Down Expand Up @@ -116,8 +124,7 @@ type GatewayApiSpec struct {
}

// FlagdStatus defines the observed state of Flagd
type FlagdStatus struct {
}
type FlagdStatus struct{}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
Expand Down
14 changes: 14 additions & 0 deletions api/core/v1beta1/zz_generated.deepcopy.go

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

10 changes: 10 additions & 0 deletions config/crd/bases/core.openfeature.dev_flagds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,16 @@ spec:
required:
- hosts
type: object
podAnnotations:
additionalProperties:
type: string
description: Annotations to be added to the Deployment
type: object
podLabels:
additionalProperties:
type: string
description: Labels to be added to the Deployment
type: object
replicas:
default: 1
description: |-
Expand Down
43 changes: 34 additions & 9 deletions internal/controller/core/flagd/resources/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
api "github.com/open-feature/open-feature-operator/apis/core/v1beta1"
"github.com/open-feature/open-feature-operator/internal/common"
"github.com/open-feature/open-feature-operator/internal/common/flagdinjector"
"github.com/open-feature/open-feature-operator/internal/controller/core/flagd/common"
resources "github.com/open-feature/open-feature-operator/internal/controller/core/flagd/common"
"golang.org/x/exp/maps"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand All @@ -37,24 +37,49 @@ func (r *FlagdDeployment) AreObjectsEqual(o1 client.Object, o2 client.Object) bo
return false
}

return reflect.DeepEqual(oldDeployment.Spec, newDeployment.Spec)
return reflect.DeepEqual(oldDeployment.Spec, newDeployment.Spec) && reflect.DeepEqual(oldDeployment.ObjectMeta.Annotations, newDeployment.ObjectMeta.Annotations) && reflect.DeepEqual(oldDeployment.ObjectMeta.Labels, newDeployment.ObjectMeta.Labels)
}

func (r *FlagdDeployment) GetResource(ctx context.Context, flagd *api.Flagd) (client.Object, error) {
labels := map[string]string{
func (r *FlagdDeployment) getLabels(flagd *api.Flagd) map[string]string {
labels := map[string]string{}

// FlagdConfig has lowest priority
if len(r.FlagdConfig.Labels) > 0 {
maps.Copy(labels, r.FlagdConfig.Labels)
}

// PodLabels have higher priority than FlagdConfig
maps.Copy(labels, flagd.Spec.PodLabels)

// Default labels highest priority
maps.Copy(labels, map[string]string{
"app": flagd.Name,
"app.kubernetes.io/name": flagd.Name,
"app.kubernetes.io/managed-by": common.ManagedByAnnotationValue,
"app.kubernetes.io/version": r.FlagdConfig.Tag,
}
if len(r.FlagdConfig.Labels) > 0 {
maps.Copy(labels, r.FlagdConfig.Labels)
}
// No "built-in" annotations to merge at this time. If adding them follow the same pattern as labels.
})

return labels
}

func (r *FlagdDeployment) getAnnotations(flagd *api.Flagd) map[string]string {
annotations := map[string]string{}

// FlagdConfig has lowest priority
if len(r.FlagdConfig.Annotations) > 0 {
maps.Copy(annotations, r.FlagdConfig.Annotations)
}

// PodAnnotations have higher priority than FlagdConfig
maps.Copy(annotations, flagd.Spec.PodAnnotations)

return annotations
}

func (r *FlagdDeployment) GetResource(ctx context.Context, flagd *api.Flagd) (client.Object, error) {
labels := r.getLabels(flagd)
annotations := r.getAnnotations(flagd)

deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: flagd.Name,
Expand Down
226 changes: 224 additions & 2 deletions internal/controller/core/flagd/resources/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/golang/mock/gomock"
api "github.com/open-feature/open-feature-operator/apis/core/v1beta1"
"github.com/open-feature/open-feature-operator/internal/common"
commonfake "github.com/open-feature/open-feature-operator/internal/common/flagdinjector/fake"
resources "github.com/open-feature/open-feature-operator/internal/controller/core/flagd/common"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -251,7 +252,7 @@ func Test_areDeploymentsEqual(t *testing.T) {
want bool
}{
{
name: "has changed",
name: "has spec changed",
args: args{
old: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Expand All @@ -266,6 +267,46 @@ func Test_areDeploymentsEqual(t *testing.T) {
},
want: false,
},
{
name: "has labels changed",
args: args{
old: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"key": "old",
},
},
},
new: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"key": "new",
},
},
},
},
want: false,
},
{
name: "has annotations changed",
args: args{
old: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"key": "old",
},
},
},
new: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"key": "new",
},
},
},
},
want: false,
},
{
name: "has not changed",
args: args{
Expand Down Expand Up @@ -309,7 +350,6 @@ func Test_areDeploymentsEqual(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

d := &FlagdDeployment{}
got := d.AreObjectsEqual(tt.args.old, tt.args.new)

Expand All @@ -318,6 +358,188 @@ func Test_areDeploymentsEqual(t *testing.T) {
}
}

func Test_getLabels(t *testing.T) {
const (
flagdConfigTag = "latest"
flagdName = "test-flagd"
)
type args struct {
flagdConfigLabels map[string]string
flagdLabels map[string]string
}
tests := []struct {
name string
args args
want map[string]string
}{
{
name: "no config labels, no flagd labels",
args: args{
flagdConfigLabels: nil,
flagdLabels: nil,
},
want: map[string]string{
"app": flagdName,
"app.kubernetes.io/name": flagdName,
"app.kubernetes.io/managed-by": common.ManagedByAnnotationValue,
"app.kubernetes.io/version": flagdConfigTag,
},
},
{
name: "unique config and flagd labels",
args: args{
flagdConfigLabels: map[string]string{
"config-label": "config-value",
},
flagdLabels: map[string]string{
"flagd-label": "flagd-value",
},
},
want: map[string]string{
"app": flagdName,
"app.kubernetes.io/name": flagdName,
"app.kubernetes.io/managed-by": common.ManagedByAnnotationValue,
"app.kubernetes.io/version": flagdConfigTag,
"config-label": "config-value",
"flagd-label": "flagd-value",
},
},
{
name: "overlapping config and flagd labels",
args: args{
flagdConfigLabels: map[string]string{
"overlapping": "config-value",
},
flagdLabels: map[string]string{
"overlapping": "flagd-value",
},
},
want: map[string]string{
"app": flagdName,
"app.kubernetes.io/name": flagdName,
"app.kubernetes.io/managed-by": common.ManagedByAnnotationValue,
"app.kubernetes.io/version": flagdConfigTag,
"overlapping": "flagd-value",
},
},
{
name: "overlapping default labels",
args: args{
flagdConfigLabels: map[string]string{
"app.kubernetes.io/name": "config-value",
},
flagdLabels: map[string]string{
"app.kubernetes.io/name": "flagd-value",
},
},
want: map[string]string{
"app": flagdName,
"app.kubernetes.io/name": flagdName,
"app.kubernetes.io/managed-by": common.ManagedByAnnotationValue,
"app.kubernetes.io/version": flagdConfigTag,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &FlagdDeployment{
FlagdConfig: resources.FlagdConfiguration{
Labels: tt.args.flagdConfigLabels,
Tag: flagdConfigTag,
},
}
flagd := &api.Flagd{
ObjectMeta: metav1.ObjectMeta{
Name: flagdName,
},
Spec: api.FlagdSpec{
PodLabels: tt.args.flagdLabels,
},
}

got := r.getLabels(flagd)

require.Equal(t, tt.want, got)
})
}
}

func Test_getAnnotations(t *testing.T) {
const (
flagdName = "test-flagd"
)
type args struct {
flagdConfigAnnotations map[string]string
flagdAnnotations map[string]string
}
tests := []struct {
name string
args args
want map[string]string
}{
{
name: "no config annotations, no flagd annotations",
args: args{
flagdConfigAnnotations: nil,
flagdAnnotations: nil,
},
want: map[string]string{},
},
{
name: "unique annotations and flagd annotations",
args: args{
flagdConfigAnnotations: map[string]string{
"config-annotation": "config-value",
},
flagdAnnotations: map[string]string{
"flagd-annotation": "flagd-value",
},
},
want: map[string]string{
"config-annotation": "config-value",
"flagd-annotation": "flagd-value",
},
},
{
name: "overlapping config and flagd labels",
args: args{
flagdConfigAnnotations: map[string]string{
"overlapping": "config-value",
},
flagdAnnotations: map[string]string{
"overlapping": "flagd-value",
},
},
want: map[string]string{
"overlapping": "flagd-value",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &FlagdDeployment{
FlagdConfig: resources.FlagdConfiguration{
Annotations: tt.args.flagdConfigAnnotations,
},
}
flagd := &api.Flagd{
ObjectMeta: metav1.ObjectMeta{
Name: flagdName,
},
Spec: api.FlagdSpec{
PodAnnotations: tt.args.flagdAnnotations,
},
}

got := r.getAnnotations(flagd)

require.Equal(t, tt.want, got)
})
}
}

func intPtr(i int32) *int32 {
return &i
}