diff --git a/api/actions/process_stats.go b/api/actions/process_stats.go index ab7ae541a..3b939c32f 100644 --- a/api/actions/process_stats.go +++ b/api/actions/process_stats.go @@ -19,7 +19,6 @@ import ( const ( ApplicationContainerName = "application" - EnvCFInstanceIndex = "CF_INSTANCE_INDEX" LabelGUID = "korifi.cloudfoundry.org/guid" stateStarting = "STARTING" stateRunning = "RUNNING" @@ -155,47 +154,23 @@ func (a *ProcessStats) FetchStats(ctx context.Context, authInfo authorization.In } func extractIndex(pod corev1.Pod) (int, error) { - container, err := extractProcessContainer(pod.Spec.Containers) - if err != nil { - return 0, err - } - - indexString, err := extractEnvVarFromContainer(*container, EnvCFInstanceIndex) - if err != nil { - return 0, err + indexString, exists := pod.ObjectMeta.Labels[korifiv1alpha1.PodIndexLabelKey] + if !exists { + return 0, fmt.Errorf("%s label not found", korifiv1alpha1.PodIndexLabelKey) } index, err := strconv.Atoi(indexString) if err != nil { - return 0, fmt.Errorf("%s is not a valid index: %w", EnvCFInstanceIndex, err) + return 0, fmt.Errorf("%s is not a valid index: %w", korifiv1alpha1.PodIndexLabelKey, err) } if index < 0 { - return 0, fmt.Errorf("%s is not a valid index: instance indexes can't be negative", EnvCFInstanceIndex) + return 0, fmt.Errorf("%s is not a valid index: instance indexes can't be negative", korifiv1alpha1.PodIndexLabelKey) } return index, nil } -func extractProcessContainer(containers []corev1.Container) (*corev1.Container, error) { - for i, c := range containers { - if c.Name == ApplicationContainerName { - return &containers[i], nil - } - } - return nil, fmt.Errorf("container %q not found", ApplicationContainerName) -} - -func extractEnvVarFromContainer(container corev1.Container, envVar string) (string, error) { - envs := container.Env - for _, e := range envs { - if e.Name == envVar { - return e.Value, nil - } - } - return "", fmt.Errorf("%s not set", envVar) -} - // Logic from Kubernetes in Action 2nd Edition - Ch 6. // DOWN => !pod || !pod.conditions.PodScheduled // CRASHED => any(pod.ContainerStatuses.State isA Terminated) diff --git a/api/actions/process_stats_test.go b/api/actions/process_stats_test.go index 156bb0c0e..94839e158 100644 --- a/api/actions/process_stats_test.go +++ b/api/actions/process_stats_test.go @@ -206,32 +206,19 @@ var _ = Describe("ProcessStats", func() { }) }) - When("there are no stats for the application container", func() { + When("the pod-index label is not set", func() { BeforeEach(func() { - podMetrics[0].Pod.Spec.Containers[0].Name = "i-contain-no-app" + delete(podMetrics[0].Pod.ObjectMeta.Labels, korifiv1alpha1.PodIndexLabelKey) }) It("returns an error", func() { - Expect(responseErr).To(MatchError(`container "application" not found`)) + Expect(responseErr).To(MatchError(ContainSubstring("label not found"))) }) }) - When("the CF_INSTANCE_INDEX env var is not set", func() { + When("the pod-index label value cannot be parsed to an int", func() { BeforeEach(func() { - podMetrics[0].Pod.Spec.Containers[0].Env = []corev1.EnvVar{} - }) - - It("returns an error", func() { - Expect(responseErr).To(MatchError(`CF_INSTANCE_INDEX not set`)) - }) - }) - - When("the CF_INSTANCE_INDEX env var value cannot be parsed to an int", func() { - BeforeEach(func() { - podMetrics[0].Pod.Spec.Containers[0].Env = []corev1.EnvVar{{ - Name: "CF_INSTANCE_INDEX", - Value: "one", - }} + podMetrics[0].Pod.ObjectMeta.Labels[korifiv1alpha1.PodIndexLabelKey] = "one" }) It("returns an error", func() { @@ -239,12 +226,9 @@ var _ = Describe("ProcessStats", func() { }) }) - When("the CF_INSTANCE_INDEX env var value is a negative integer", func() { + When("the pod-index label value is a negative integer", func() { BeforeEach(func() { - podMetrics[0].Pod.Spec.Containers[0].Env = []corev1.EnvVar{{ - Name: "CF_INSTANCE_INDEX", - Value: "-1", - }} + podMetrics[0].Pod.ObjectMeta.Labels[korifiv1alpha1.PodIndexLabelKey] = "-1" }) It("returns an error", func() { @@ -316,7 +300,8 @@ func createPod(index, version string) corev1.Pod { }, ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ - LabelVersionKey: version, + LabelVersionKey: version, + korifiv1alpha1.PodIndexLabelKey: index, }, }, Spec: corev1.PodSpec{ @@ -324,12 +309,6 @@ func createPod(index, version string) corev1.Pod { { Name: "application", Image: "some-image", - Env: []corev1.EnvVar{ - { - Name: "CF_INSTANCE_INDEX", - Value: index, - }, - }, }, }, }, diff --git a/controllers/api/v1alpha1/shared_types.go b/controllers/api/v1alpha1/shared_types.go index 6ed8e87e4..4745d0041 100644 --- a/controllers/api/v1alpha1/shared_types.go +++ b/controllers/api/v1alpha1/shared_types.go @@ -19,6 +19,8 @@ const ( CFRouteGUIDLabelKey = "korifi.cloudfoundry.org/route-guid" CFTaskGUIDLabelKey = "korifi.cloudfoundry.org/task-guid" + PodIndexLabelKey = "apps.kubernetes.io/pod-index" + StagingConditionType = "Staging" SucceededConditionType = "Succeeded" diff --git a/controllers/main.go b/controllers/main.go index be2b683f1..52d188ace 100644 --- a/controllers/main.go +++ b/controllers/main.go @@ -62,7 +62,6 @@ import ( jobtaskrunnercontrollers "code.cloudfoundry.org/korifi/job-task-runner/controllers" "code.cloudfoundry.org/korifi/kpack-image-builder/controllers" kpackimagebuilderfinalizer "code.cloudfoundry.org/korifi/kpack-image-builder/controllers/webhooks/finalizer" - statesetfulrunnerv1 "code.cloudfoundry.org/korifi/statefulset-runner/api/v1" statefulsetcontrollers "code.cloudfoundry.org/korifi/statefulset-runner/controllers" "code.cloudfoundry.org/korifi/tools" "code.cloudfoundry.org/korifi/tools/image" @@ -556,13 +555,6 @@ func main() { os.Exit(1) } - if controllerConfig.IncludeStatefulsetRunner { - if err = statesetfulrunnerv1.NewSTSPodDefaulter().SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "Pod") - os.Exit(1) - } - } - if controllerConfig.IncludeKpackImageBuilder { kpackimagebuilderfinalizer.NewKpackImageBuilderFinalizerWebhook().SetupWebhookWithManager(mgr) } diff --git a/helm/korifi/statefulset-runner/manifests.yaml b/helm/korifi/statefulset-runner/manifests.yaml deleted file mode 100644 index ad0f2c64b..000000000 --- a/helm/korifi/statefulset-runner/manifests.yaml +++ /dev/null @@ -1,30 +0,0 @@ ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - name: korifi-statefulset-runner-mutating-webhook-configuration - annotations: - cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/korifi-controllers-serving-cert' -webhooks: - - admissionReviewVersions: - - v1 - clientConfig: - service: - name: korifi-controllers-webhook-service - namespace: '{{ .Release.Namespace }}' - path: /mutate--v1-pod - failurePolicy: Fail - name: mstspod.korifi.cloudfoundry.org - rules: - - apiGroups: - - "" - apiVersions: - - v1 - operations: - - CREATE - resources: - - pods - sideEffects: None - objectSelector: - matchLabels: - korifi.cloudfoundry.org/add-stsr-index: "true" diff --git a/statefulset-runner/Makefile b/statefulset-runner/Makefile index c90d24b9c..c70271bf2 100644 --- a/statefulset-runner/Makefile +++ b/statefulset-runner/Makefile @@ -32,22 +32,12 @@ help: ## Display this help. export GOBIN = $(shell pwd)/bin export PATH := $(shell pwd)/bin:$(PATH) -webhooks-file = ../helm/korifi/statefulset-runner/manifests.yaml .PHONY: manifests manifests: bin/controller-gen bin/yq controller-gen \ paths="./..." \ rbac:roleName=korifi-statefulset-runner-appworkload-manager-role \ - webhook \ - output:rbac:artifacts:config=../helm/korifi/statefulset-runner \ - output:webhook:artifacts:config=../helm/korifi/statefulset-runner - - yq -i 'with(.metadata; .annotations["cert-manager.io/inject-ca-from"]="{{ .Release.Namespace }}/korifi-controllers-serving-cert")' $(webhooks-file) - yq -i 'with(.metadata; .name="korifi-statefulset-runner-" + .name)' $(webhooks-file) - yq -i 'with(.webhooks[]; .clientConfig.service.namespace="{{ .Release.Namespace }}")' $(webhooks-file) - yq -i 'with(.webhooks[]; .clientConfig.service.name="korifi-controllers-" + .clientConfig.service.name)' $(webhooks-file) - yq -i 'with(.webhooks[]; .objectSelector.matchLabels["korifi.cloudfoundry.org/add-stsr-index"]="true")' $(webhooks-file) - + output:rbac:artifacts:config=../helm/korifi/statefulset-runner .PHONY: generate generate: bin/controller-gen diff --git a/statefulset-runner/api/v1/pod_webhook.go b/statefulset-runner/api/v1/pod_webhook.go deleted file mode 100644 index fe3255ff3..000000000 --- a/statefulset-runner/api/v1/pod_webhook.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1 - -import ( - "context" - "fmt" - "regexp" - - "code.cloudfoundry.org/korifi/statefulset-runner/controllers" - - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -// log is for logging in this package. -var podlog = logf.Log.WithName("pod-resource") - -type STSPodDefaulter struct{} - -func NewSTSPodDefaulter() *STSPodDefaulter { - return &STSPodDefaulter{} -} - -func (r *STSPodDefaulter) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(&corev1.Pod{}). - WithDefaulter(r). - Complete() -} - -// Mutate path is found here: https://github.com/kubernetes-sigs/controller-runtime/blob/15154aaa67679df320008ed45534f83ff3d6922d/pkg/builder/webhook.go#L201-L204 -//+kubebuilder:webhook:path=/mutate--v1-pod,mutating=true,failurePolicy=fail,sideEffects=None,groups="",resources=pods,verbs=create,versions=v1,name=mstspod.korifi.cloudfoundry.org,admissionReviewVersions=v1 - -var _ webhook.CustomDefaulter = &STSPodDefaulter{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *STSPodDefaulter) Default(ctx context.Context, obj runtime.Object) error { - podlog.V(1).Info("default", "name", obj.DeepCopyObject().GetObjectKind()) - - pod, ok := obj.(*corev1.Pod) - if !ok { - return apierrors.NewBadRequest(fmt.Sprintf("expected a Pod but got a %T", obj)) - } - - index, err := parseAppIndex(pod.Name) - if err != nil { - return err - } - - for c := range pod.Spec.Containers { - container := &pod.Spec.Containers[c] - if container.Name == controllers.ApplicationContainerName { - cfInstanceVar := corev1.EnvVar{Name: controllers.EnvCFInstanceIndex, Value: index} - container.Env = append(container.Env, cfInstanceVar) - - podlog.V(1).Info("patching instance index env var", controllers.EnvCFInstanceIndex, index) - - return nil - } - } - - return nil -} - -func parseAppIndex(podName string) (string, error) { - expression := `-(\d+)$` - r := regexp.MustCompile(expression) - match := r.FindStringSubmatch(podName) - - if len(match) == 0 { - return "", fmt.Errorf("pod %s name does not contain an index", podName) - } - - return match[1], nil -} diff --git a/statefulset-runner/api/v1/pod_webhook_test.go b/statefulset-runner/api/v1/pod_webhook_test.go deleted file mode 100644 index a4e4c973b..000000000 --- a/statefulset-runner/api/v1/pod_webhook_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package v1_test - -import ( - "github.com/google/uuid" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -var _ = Describe("StatefulSet Runner Pod Mutating Webhook", func() { - var ( - namespace string - stsPod *corev1.Pod - ) - - BeforeEach(func() { - namespace = uuid.NewString() - err := adminClient.Create(ctx, &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }) - Expect(err).NotTo(HaveOccurred()) - - stsPod = &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: uuid.NewString() + "-1", - Namespace: namespace, - }, - Spec: corev1.PodSpec{ - InitContainers: []corev1.Container{{ - Name: "init-1", - Image: "alpine", - Command: []string{"sleep", "1234"}, - }}, - Containers: []corev1.Container{{ - Name: "application", - Image: "alpine", - Command: []string{"sleep", "9876"}, - }}, - }, - } - }) - - JustBeforeEach(func() { - Expect(adminClient.Create(ctx, stsPod)).To(Succeed()) - lookupKey := client.ObjectKeyFromObject(stsPod) - Eventually(func(g Gomega) { - g.Expect(adminClient.Get(ctx, lookupKey, stsPod)).To(Succeed()) - }).Should(Succeed()) - }) - - When("the pod has the `korifi.cloudfoundry.org/add-stsr-index: \"true\"` label", func() { - BeforeEach(func() { - stsPod.Labels = map[string]string{ - "korifi.cloudfoundry.org/add-stsr-index": "true", - } - }) - - It("the application container has a CF_INSTANCE_INDEX ENVVAR", func() { - Expect(stsPod.Labels).To(HaveKeyWithValue("korifi.cloudfoundry.org/add-stsr-index", "true")) - Expect(stsPod.Spec.Containers[0].Env).NotTo(BeEmpty()) - Expect(stsPod.Spec.Containers[0].Env[0].Name).To(Equal("CF_INSTANCE_INDEX")) - Expect(stsPod.Spec.Containers[0].Env[0].Value).To(Equal("1")) - }) - }) - - When("the pod does not have the `korifi.cloudfoundry.org/add-stsr-index: \"true\"` label", func() { - It("the application container has a CF_INSTANCE_INDEX ENVVAR", func() { - Expect(stsPod.Spec.Containers[0].Env).To(BeEmpty()) - }) - }) -}) diff --git a/statefulset-runner/api/v1/webhook_suite_test.go b/statefulset-runner/api/v1/webhook_suite_test.go deleted file mode 100644 index 3716efac9..000000000 --- a/statefulset-runner/api/v1/webhook_suite_test.go +++ /dev/null @@ -1,86 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1_test - -import ( - "context" - "path/filepath" - "testing" - - "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" - v1 "code.cloudfoundry.org/korifi/statefulset-runner/api/v1" - "code.cloudfoundry.org/korifi/tests/helpers" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" - corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -var ( - adminClient client.Client - testEnv *envtest.Environment - ctx context.Context - stopManager context.CancelFunc - stopClientCache context.CancelFunc -) - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Webhook Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - ctx = context.Background() - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - ErrorIfCRDPathMissing: false, - WebhookInstallOptions: envtest.WebhookInstallOptions{ - Paths: []string{filepath.Join("..", "..", "..", "helm", "korifi", "statefulset-runner", "manifests.yaml")}, - }, - } - - _, err := testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - - Expect(v1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) - Expect(admissionv1beta1.AddToScheme(scheme.Scheme)).To(Succeed()) - Expect(corev1.AddToScheme(scheme.Scheme)).To(Succeed()) - - mgr := helpers.NewK8sManager(testEnv, filepath.Join("helm", "korifi", "statefulset-runner", "role.yaml")) - - adminClient, stopClientCache = helpers.NewCachedClient(testEnv.Config) - - Expect((&v1.STSPodDefaulter{}).SetupWebhookWithManager(mgr)).To(Succeed()) - - stopManager = helpers.StartK8sManager(mgr) -}) - -var _ = AfterSuite(func() { - stopClientCache() - stopManager() - Expect(testEnv.Stop()).To(Succeed()) -}) diff --git a/statefulset-runner/api/v1alpha1/.gitkeep b/statefulset-runner/api/v1alpha1/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/statefulset-runner/controllers/appworkload_controller.go b/statefulset-runner/controllers/appworkload_controller.go index bb932317b..e3eeb9c5f 100644 --- a/statefulset-runner/controllers/appworkload_controller.go +++ b/statefulset-runner/controllers/appworkload_controller.go @@ -49,12 +49,11 @@ const ( AnnotationAppID = "korifi.cloudfoundry.org/application-id" AnnotationProcessGUID = "korifi.cloudfoundry.org/process-guid" - LabelGUID = "korifi.cloudfoundry.org/guid" - LabelVersion = "korifi.cloudfoundry.org/version" - LabelAppGUID = "korifi.cloudfoundry.org/app-guid" - LabelAppWorkloadGUID = "korifi.cloudfoundry.org/appworkload-guid" - LabelProcessType = "korifi.cloudfoundry.org/process-type" - LabelStatefulSetRunnerIndex = "korifi.cloudfoundry.org/add-stsr-index" + LabelGUID = "korifi.cloudfoundry.org/guid" + LabelVersion = "korifi.cloudfoundry.org/version" + LabelAppGUID = "korifi.cloudfoundry.org/app-guid" + LabelAppWorkloadGUID = "korifi.cloudfoundry.org/appworkload-guid" + LabelProcessType = "korifi.cloudfoundry.org/process-type" ApplicationContainerName = "application" AppWorkloadReconcilerName = "statefulset-runner" diff --git a/statefulset-runner/controllers/appworkload_to_stset.go b/statefulset-runner/controllers/appworkload_to_stset.go index f54783e5f..00077067b 100644 --- a/statefulset-runner/controllers/appworkload_to_stset.go +++ b/statefulset-runner/controllers/appworkload_to_stset.go @@ -71,6 +71,14 @@ func (r *AppWorkloadToStatefulsetConverter) Convert(appWorkload *korifiv1alpha1. }, }, }, + { + Name: EnvCFInstanceIndex, + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: fmt.Sprintf("metadata.labels['%s']", korifiv1alpha1.PodIndexLabelKey), + }, + }, + }, { Name: EnvCFInstanceIP, ValueFrom: &corev1.EnvVarSource{ @@ -178,12 +186,11 @@ func (r *AppWorkloadToStatefulsetConverter) Convert(appWorkload *korifiv1alpha1. } labels := map[string]string{ - LabelGUID: appWorkload.Spec.GUID, - LabelProcessType: appWorkload.Spec.ProcessType, - LabelVersion: appWorkload.Spec.Version, - LabelAppGUID: appWorkload.Spec.AppGUID, - LabelAppWorkloadGUID: appWorkload.Name, - LabelStatefulSetRunnerIndex: "true", + LabelGUID: appWorkload.Spec.GUID, + LabelProcessType: appWorkload.Spec.ProcessType, + LabelVersion: appWorkload.Spec.Version, + LabelAppGUID: appWorkload.Spec.AppGUID, + LabelAppWorkloadGUID: appWorkload.Name, } statefulSet.Spec.Template.Labels = labels diff --git a/statefulset-runner/controllers/appworkload_to_stset_test.go b/statefulset-runner/controllers/appworkload_to_stset_test.go index 27d3d8060..0f598be25 100644 --- a/statefulset-runner/controllers/appworkload_to_stset_test.go +++ b/statefulset-runner/controllers/appworkload_to_stset_test.go @@ -235,11 +235,6 @@ var _ = Describe("AppWorkload to StatefulSet Converter", func() { Expect(statefulSet.Spec.Template.Labels).To(HaveKeyWithValue(controllers.LabelVersion, "version_1234")) }) - It("should set statefulset-runner-index as a label", func() { - Expect(statefulSet.Labels).To(HaveKeyWithValue(controllers.LabelStatefulSetRunnerIndex, "true")) - Expect(statefulSet.Spec.Template.Labels).To(HaveKeyWithValue(controllers.LabelStatefulSetRunnerIndex, "true")) - }) - It("should set guid as a label selector", func() { Expect(statefulSet.Spec.Selector.MatchLabels).To(HaveKeyWithValue(controllers.LabelGUID, "guid_1234")) }) @@ -337,6 +332,7 @@ var _ = Describe("AppWorkload to StatefulSet Converter", func() { Expect(container.Env).To(ContainElements( corev1.EnvVar{Name: controllers.EnvPodName, ValueFrom: expectedValFrom("metadata.name")}, corev1.EnvVar{Name: controllers.EnvCFInstanceGUID, ValueFrom: expectedValFrom("metadata.uid")}, + corev1.EnvVar{Name: controllers.EnvCFInstanceIndex, ValueFrom: expectedValFrom("metadata.labels['apps.kubernetes.io/pod-index']")}, corev1.EnvVar{Name: controllers.EnvCFInstanceInternalIP, ValueFrom: expectedValFrom("status.podIP")}, corev1.EnvVar{Name: controllers.EnvCFInstanceIP, ValueFrom: expectedValFrom("status.hostIP")}, corev1.EnvVar{Name: "bobs", ValueFrom: &corev1.EnvVarSource{ @@ -360,6 +356,7 @@ var _ = Describe("AppWorkload to StatefulSet Converter", func() { It("produces a statefulset with sorted env vars", func() { Expect(statefulSet.Spec.Template.Spec.Containers[0].Env).To(Equal([]corev1.EnvVar{ {Name: "CF_INSTANCE_GUID", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.uid"}}}, + {Name: "CF_INSTANCE_INDEX", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.labels['apps.kubernetes.io/pod-index']"}}}, {Name: "CF_INSTANCE_INTERNAL_IP", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}}}, {Name: "CF_INSTANCE_IP", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.hostIP"}}}, {Name: "POD_NAME", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}}},