diff --git a/api/formance.com/v1beta1/benthos_types.go b/api/formance.com/v1beta1/benthos_types.go index 08490724..edeb7208 100644 --- a/api/formance.com/v1beta1/benthos_types.go +++ b/api/formance.com/v1beta1/benthos_types.go @@ -37,7 +37,7 @@ type BenthosSpec struct { //+optional Batching *Batching `json:"batching,omitempty"` //+optional - InitContainers []corev1.Container `json:"initContainers"` + InitContainers []corev1.Container `json:"initContainers"` ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` } diff --git a/internal/resources/applications/application.go b/internal/resources/applications/application.go index 03caef69..4ff79911 100644 --- a/internal/resources/applications/application.go +++ b/internal/resources/applications/application.go @@ -337,6 +337,30 @@ func (a Application) withJsonLogging(ctx core.Context) core.ObjectMutator[*appsv } } +func (a Application) withNodeIP(ctx core.Context) core.ObjectMutator[*appsv1.Deployment] { + return func(deployment *appsv1.Deployment) error { + nodeIPEnvVar := corev1.EnvVar{ + Name: "NODE_IP", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "status.hostIP", + }, + }, + } + + for i, container := range deployment.Spec.Template.Spec.InitContainers { + container.Env = append(container.Env, nodeIPEnvVar) + deployment.Spec.Template.Spec.InitContainers[i] = container + } + for i, container := range deployment.Spec.Template.Spec.Containers { + container.Env = append(container.Env, nodeIPEnvVar) + deployment.Spec.Template.Spec.Containers[i] = container + } + + return nil + } +} + func (a Application) handleDeployment(ctx core.Context, deploymentLabels map[string]string) error { condition := v1beta1.Condition{ Type: "DeploymentReady", @@ -358,6 +382,7 @@ func (a Application) handleDeployment(ctx core.Context, deploymentLabels map[str a.withStatefulHandling(ctx), a.withEELicence(ctx), a.withJsonLogging(ctx), + a.withNodeIP(ctx), core.WithController[*appsv1.Deployment](ctx.GetScheme(), a.owner), ) diff --git a/internal/resources/applications/application_test.go b/internal/resources/applications/application_test.go index a550e8dc..4e8157dc 100644 --- a/internal/resources/applications/application_test.go +++ b/internal/resources/applications/application_test.go @@ -62,3 +62,143 @@ func TestWithAnnotations(t *testing.T) { } } + +func TestWithNodeIP(t *testing.T) { + t.Parallel() + type testCase struct { + name string + deployment *appsv1.Deployment + } + + testCases := []testCase{ + { + name: "empty deployment", + deployment: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "main", + Image: "test:latest", + }, + }, + }, + }, + }, + }, + }, + { + name: "deployment with existing env vars", + deployment: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "main", + Image: "test:latest", + Env: []v1.EnvVar{ + {Name: "EXISTING_VAR", Value: "existing_value"}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "deployment with init containers", + deployment: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "init", + Image: "init:latest", + }, + }, + Containers: []v1.Container{ + { + Name: "main", + Image: "test:latest", + }, + }, + }, + }, + }, + }, + }, + { + name: "deployment with multiple containers", + deployment: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "init1", + Image: "init1:latest", + }, + { + Name: "init2", + Image: "init2:latest", + }, + }, + Containers: []v1.Container{ + { + Name: "main", + Image: "test:latest", + }, + { + Name: "sidecar", + Image: "sidecar:latest", + }, + }, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + app := Application{} + require.NoError(t, app.withNodeIP(nil)(tc.deployment)) + + // Verify NODE_IP is injected in all init containers + for i, container := range tc.deployment.Spec.Template.Spec.InitContainers { + found := false + for _, env := range container.Env { + if env.Name == "NODE_IP" { + found = true + require.NotNil(t, env.ValueFrom, "NODE_IP should use ValueFrom for init container %d", i) + require.NotNil(t, env.ValueFrom.FieldRef, "NODE_IP should use FieldRef for init container %d", i) + require.Equal(t, "status.hostIP", env.ValueFrom.FieldRef.FieldPath, "NODE_IP should reference status.hostIP for init container %d", i) + break + } + } + require.True(t, found, "NODE_IP env var not found in init container %d", i) + } + + // Verify NODE_IP is injected in all containers + for i, container := range tc.deployment.Spec.Template.Spec.Containers { + found := false + for _, env := range container.Env { + if env.Name == "NODE_IP" { + found = true + require.NotNil(t, env.ValueFrom, "NODE_IP should use ValueFrom for container %d", i) + require.NotNil(t, env.ValueFrom.FieldRef, "NODE_IP should use FieldRef for container %d", i) + require.Equal(t, "status.hostIP", env.ValueFrom.FieldRef.FieldPath, "NODE_IP should reference status.hostIP for container %d", i) + break + } + } + require.True(t, found, "NODE_IP env var not found in container %d", i) + } + }) + } +} diff --git a/internal/resources/orchestrations/deployments.go b/internal/resources/orchestrations/deployments.go index ff660a4d..80c21bf8 100644 --- a/internal/resources/orchestrations/deployments.go +++ b/internal/resources/orchestrations/deployments.go @@ -184,7 +184,7 @@ func createDeployment( Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ - ImagePullSecrets: imageConfiguration.PullSecrets, + ImagePullSecrets: imageConfiguration.PullSecrets, ServiceAccountName: serviceAccountName, Containers: []corev1.Container{{ Name: "api", diff --git a/internal/resources/payments/deployments.go b/internal/resources/payments/deployments.go index 625d90fb..00bacaae 100644 --- a/internal/resources/payments/deployments.go +++ b/internal/resources/payments/deployments.go @@ -290,7 +290,7 @@ func createFullDeployment( return nil } -func createWorkerDeployment(ctx core.Context, stack *v1beta1.Stack, payments *v1beta1.Payments, database *v1beta1.Database, imageConfiguration *registries.ImageConfiguration, env []v1.EnvVar, appOpts applications.ProbeOpts, ) error { +func createWorkerDeployment(ctx core.Context, stack *v1beta1.Stack, payments *v1beta1.Payments, database *v1beta1.Database, imageConfiguration *registries.ImageConfiguration, env []v1.EnvVar, appOpts applications.ProbeOpts) error { serviceAccountName, err := settings.GetAWSServiceAccount(ctx, stack.Name) if err != nil { return err diff --git a/internal/tests/application_test.go b/internal/tests/application_test.go index 33f1a08d..d0df01e5 100644 --- a/internal/tests/application_test.go +++ b/internal/tests/application_test.go @@ -58,4 +58,41 @@ var _ = Context("When creating a Application", func() { }).Should(HaveKeyWithValue("annotations", "annotations")) }) + It("Should inject NODE_IP env var in all containers", func() { + Eventually(func(g Gomega) { + deployment := &appsv1.Deployment{} + g.Expect(LoadResource(stack.Name, "ledger", deployment)).To(Succeed()) + + // Check init containers + for _, container := range deployment.Spec.Template.Spec.InitContainers { + found := false + for _, env := range container.Env { + if env.Name == "NODE_IP" { + found = true + g.Expect(env.ValueFrom).NotTo(BeNil(), "NODE_IP should use ValueFrom in init container %s", container.Name) + g.Expect(env.ValueFrom.FieldRef).NotTo(BeNil(), "NODE_IP should use FieldRef in init container %s", container.Name) + g.Expect(env.ValueFrom.FieldRef.FieldPath).To(Equal("status.hostIP"), "NODE_IP should reference status.hostIP in init container %s", container.Name) + break + } + } + g.Expect(found).To(BeTrue(), "NODE_IP env var not found in init container %s", container.Name) + } + + // Check regular containers + for _, container := range deployment.Spec.Template.Spec.Containers { + found := false + for _, env := range container.Env { + if env.Name == "NODE_IP" { + found = true + g.Expect(env.ValueFrom).NotTo(BeNil(), "NODE_IP should use ValueFrom in container %s", container.Name) + g.Expect(env.ValueFrom.FieldRef).NotTo(BeNil(), "NODE_IP should use FieldRef in container %s", container.Name) + g.Expect(env.ValueFrom.FieldRef.FieldPath).To(Equal("status.hostIP"), "NODE_IP should reference status.hostIP in container %s", container.Name) + break + } + } + g.Expect(found).To(BeTrue(), "NODE_IP env var not found in container %s", container.Name) + } + }).Should(Succeed()) + }) + })