Repository: kubernetes-sigs/cluster-api-operator

Issue:
CAPI operator incorrectly finds the manager container if the number of containers is >1
**What steps did you take and what happened:**
I've deployed the CAPI operator on the kind cluster and created `ControlPlaneProvider` object for the k0sproject-k0smotron provider with the following configuration:
```
apiVersion: operator.cluster.x-k8s.io/v1alpha2
kind: ControlPlaneProvider
metadata:
  name: k0sproject-k0smotron
  namespace: kcm-system
spec:
  manager:
    health: {}
    maxConcurrentReconciles: 5
    metrics: {}
    verbosity: 10
    webhook: {}
  version: v1.4.2
```

The k0sproject-k0smotron provider was deployed, but settings were not properly applied.

**What did you expect to happen:**

I expect these settings are applied for the manager container, but since the k0sproject-k0smotron provider has >1 container, these settings were applied to the first container, which is `kube-proxy`.

It looks like `findManagerContainer` function always gets the first container from the deployment: https://github.com/kubernetes-sigs/cluster-api-operator/blob/v0.18.1/internal/controller/component_customizer.go#L178

I think other providers with >1 containers may also be affected.

<details>
<summary>K0smotron control plane containers</summary>

```
      containers:
      - args:
        - --secure-listen-address=0.0.0.0:8443
        - --upstream=http://127.0.0.1:8080/
        - --logtostderr=true
        - --v=10
        - --max-concurrent-reconciles=5
        image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1
        imagePullPolicy: IfNotPresent
        name: kube-rbac-proxy
        ports:
        - containerPort: 8443
          name: https
          protocol: TCP
        resources:
          limits:
            cpu: 500m
            memory: 128Mi
          requests:
            cpu: 5m
            memory: 64Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      - args:
        - --enable-controller=control-plane
        - --health-probe-bind-address=:8081
        - --metrics-bind-address=127.0.0.1:8080
        - --leader-elect
        command:
        - /manager
        env:
        - name: SSH_KNOWN_HOSTS
        image: quay.io/k0sproject/k0smotron:v1.4.2
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthz
            port: 8081
            scheme: HTTP
          initialDelaySeconds: 15
          periodSeconds: 20
          successThreshold: 1
          timeoutSeconds: 1
        name: manager
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /readyz
            port: 8081
            scheme: HTTP
          initialDelaySeconds: 5
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            cpu: 500m
            memory: 128Mi
          requests:
            cpu: 10m
            memory: 64Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
```

</details>

**Environment:**

- Cluster-api-operator version: `v0.18.1`
- Cluster-api version: `1.9.6`
- Minikube/KIND version: `kind v0.26.0 go1.23.4 darwin/arm64`
- Kubernetes version: (use `kubectl version`): ` v1.32.2`
- OS (e.g. from `/etc/os-release`):
```
ProductName:		macOS
ProductVersion:		14.7.4
BuildVersion:		23H420
```

/kind bug
[One or more /area label. See https://github.com/kubernetes-sigs/cluster-api-operator/labels?q=area for the list of labels]
 

Hints from issue comments:
@k8s-ci-robot: This issue is currently awaiting triage.

If CAPI Operator contributors determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>@eromanova: I understand it may be challenging to reliably identify the `manager` container, for example, if the Cluster API Operator determines it by name (then all providers should consistently follow this naming convention).

If the current logic assumes the manager container is always the first in the list, this requirement should be explicitly documented (sorry, I couldn’t find any official documentation about it). This way, providers can ensure compatibility with the CAPI Operator by placing the manager container first.@dmvolod: Hey, @eromanova 

Agree about container detection in deployment and this should be fixed.
Please also note, that `gcr.io/kubebuilder/kube-rbac-proxy` is deprecated and should be replaced with alternative approach.@dmvolod: /assign

Solution patch:
diff --git a/docs/README.md b/docs/README.md
index 4db7f35..318c1e2 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -95,19 +95,19 @@ The operator Helm chart supports a "quickstart" option for bootstrapping a manag
 > The `--wait` flag is REQUIRED for the helm install command to work with providers. If the --wait flag is not used, the helm install command will not wait for the resources to be created and will return immediately.
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure=docker:v1.4.2  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure.docker.version=v1.4.2  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
 ```
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system —set infrastructure="docker;azure"  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure.docker.enabled=true,infrastructure.azure.enabled=true  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
 ```
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system —set infrastructure="capd-custom-ns:docker:v1.4.2;capz-custom-ns:azure:v1.10.0"  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure.docker.namespace=capd-custom-ns,infrastructure.docker.version=v1.4.2,infrastructure.azure.namespace=capz-custom-ns,infrastructure.azure.version=v1.10.0  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
 ```
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set core=cluster-api:v1.4.2 --set controlPlane=kubeadm:v1.4.2 --set bootstrap=kubeadm:v1.4.2  --set infrastructure=docker:v1.4.2  --wait --timeout 90s
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set core.cluster-api.version=v1.4.2 --set controlPlane.kubeadm.version=v1.4.2 --set bootstrap.kubeadm.version=v1.4.2  --set infrastructure.docker.version=v1.4.2  --wait --timeout 90s
 ```
 
 For more complex operations, please refer to our API documentation.
@@ -124,7 +124,7 @@ The following commands will install cert-manager, CAPI operator itself with modi
 helm repo add jetstack https://charts.jetstack.io --force-update
 helm repo update
 helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure=docker:v1.5.0 --wait --timeout 90s
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure.docker.version=v1.5.0 --wait --timeout 90s
 ```
 
 ## Configuration
diff --git a/docs/book/src/01_user/02_quick-start.md b/docs/book/src/01_user/02_quick-start.md
index ab6bf04..def9deb 100644
--- a/docs/book/src/01_user/02_quick-start.md
+++ b/docs/book/src/01_user/02_quick-start.md
@@ -53,7 +53,7 @@ The `--wait` flag is REQUIRED for the helm install command to work. If the --wai
 </aside>
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure=docker --set configSecret.name=${CREDENTIALS_SECRET_NAME} --set configSecret.namespace=${CREDENTIALS_SECRET_NAMESPACE}  --wait --timeout 90s
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure.docker.enabled=true --set configSecret.name=${CREDENTIALS_SECRET_NAME} --set configSecret.namespace=${CREDENTIALS_SECRET_NAMESPACE}  --wait --timeout 90s
 ```
 
 Docker provider can be replaced by any provider supported by [clusterctl](https://cluster-api.sigs.k8s.io/reference/providers.html#infrastructure).
diff --git a/docs/book/src/02_installation/04_helm-chart-installation.md b/docs/book/src/02_installation/04_helm-chart-installation.md
index 13d1024..4c55622 100644
--- a/docs/book/src/02_installation/04_helm-chart-installation.md
+++ b/docs/book/src/02_installation/04_helm-chart-installation.md
@@ -21,19 +21,19 @@ The `--wait` flag is REQUIRED for the helm install command to work with provider
 </aside>
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure=docker:v1.4.2  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure.docker.version=v1.4.2  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
 ```
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system —set infrastructure="docker;azure"  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure.docker.enabled=true,infrastructure.azure.enabled=true  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
 ```
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system —set infrastructure="capd-custom-ns:docker:v1.4.2;capz-custom-ns:azure:v1.10.0"  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure.docker.namespace=capd-custom-ns,infrastructure.docker.version=v1.4.2,infrastructure.azure.namespace=capz-custom-ns,infrastructure.azure.version=v1.10.0  --wait --timeout 90s # core Cluster API with kubeadm bootstrap and control plane providers will also be installed
 ```
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set core=cluster-api:v1.4.2 --set controlPlane=kubeadm:v1.4.2 --set bootstrap=kubeadm:v1.4.2  --set infrastructure=docker:v1.4.2  --wait --timeout 90s
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set core.cluster-api.version=v1.4.2 --set controlPlane.kubeadm.version=v1.4.2 --set bootstrap.kubeadm.version=v1.4.2  --set infrastructure.docker.version=v1.4.2  --wait --timeout 90s
 ```
 
 For more complex operations, please refer to our API documentation.
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 72f0870..e8e1127 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -52,7 +52,7 @@ The `--wait` flag is REQUIRED for the helm install command to work. If the --wai
 </aside>
 
 ```bash
-helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure=docker --set cert-manager.enabled=true --set configSecret.name=${CREDENTIALS_SECRET_NAME} --set configSecret.namespace=${CREDENTIALS_SECRET_NAMESPACE}  --wait --timeout 90s
+helm install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system --set infrastructure.docker.enabled=true --set cert-manager.enabled=true --set configSecret.name=${CREDENTIALS_SECRET_NAME} --set configSecret.namespace=${CREDENTIALS_SECRET_NAMESPACE}  --wait --timeout 90s
 ```
 
 Docker provider can be replaced by any provider supported by [clusterctl](https://cluster-api.sigs.k8s.io/reference/providers.html#infrastructure).
diff --git a/internal/controller/component_customizer.go b/internal/controller/component_customizer.go
index 6819b86..2cb5085 100644
--- a/internal/controller/component_customizer.go
+++ b/internal/controller/component_customizer.go
@@ -179,7 +179,14 @@ func customizeDeploymentSpec(dSpec operatorv1.DeploymentSpec, d *appsv1.Deployme
 // findManagerContainer finds manager container in the provider deployment.
 func findManagerContainer(dSpec *appsv1.DeploymentSpec) *corev1.Container {
 	for ic := range dSpec.Template.Spec.Containers {
-		return &dSpec.Template.Spec.Containers[ic]
+		if dSpec.Template.Spec.Containers[ic].Name == managerContainerName {
+			return &dSpec.Template.Spec.Containers[ic]
+		}
+	}
+
+	// This is for backward compatibility before fixing the issue https://github.com/kubernetes-sigs/cluster-api-operator/issues/787
+	if len(dSpec.Template.Spec.Containers) > 0 {
+		return &dSpec.Template.Spec.Containers[0]
 	}
 
 	return nil



Test patch:
diff --git a/internal/controller/component_customizer_test.go b/internal/controller/component_customizer_test.go
index 2593098..a95f2b2 100644
--- a/internal/controller/component_customizer_test.go
+++ b/internal/controller/component_customizer_test.go
@@ -49,33 +49,39 @@ func TestCustomizeDeployment(t *testing.T) {
 					Name: "manager",
 				},
 				Spec: corev1.PodSpec{
-					Containers: []corev1.Container{{
-						Name:  "manager",
-						Image: "registry.k8s.io/a-manager:1.6.2",
-						Env: []corev1.EnvVar{
-							{
-								Name:  "test1",
-								Value: "value1",
-							},
+					Containers: []corev1.Container{
+						{
+							Name:  "kube-rbac-proxy",
+							Image: "registry.k8s.io/a-kube-rbac-proxy:v0.13.1",
 						},
-						Args: []string{"--webhook-port=2345"},
-						LivenessProbe: &corev1.Probe{
-							ProbeHandler: corev1.ProbeHandler{
-								HTTPGet: &corev1.HTTPGetAction{
-									Path: "/healthz",
-									Port: intstr.FromString("healthz"),
+						{
+							Name:  "manager",
+							Image: "registry.k8s.io/a-manager:1.6.2",
+							Env: []corev1.EnvVar{
+								{
+									Name:  "test1",
+									Value: "value1",
 								},
 							},
-						},
-						ReadinessProbe: &corev1.Probe{
-							ProbeHandler: corev1.ProbeHandler{
-								HTTPGet: &corev1.HTTPGetAction{
-									Path: "/readyz",
-									Port: intstr.FromString("healthz"),
+							Args: []string{"--webhook-port=2345"},
+							LivenessProbe: &corev1.Probe{
+								ProbeHandler: corev1.ProbeHandler{
+									HTTPGet: &corev1.HTTPGetAction{
+										Path: "/healthz",
+										Port: intstr.FromString("healthz"),
+									},
+								},
+							},
+							ReadinessProbe: &corev1.Probe{
+								ProbeHandler: corev1.ProbeHandler{
+									HTTPGet: &corev1.HTTPGetAction{
+										Path: "/readyz",
+										Port: intstr.FromString("healthz"),
+									},
 								},
 							},
 						},
-					}},
+					},
 				},
 			},
 		},
@@ -278,6 +284,10 @@ func TestCustomizeDeployment(t *testing.T) {
 					Template: corev1.PodTemplateSpec{
 						Spec: corev1.PodSpec{
 							Containers: []corev1.Container{
+								{
+									Name:  "kube-rbac-proxy",
+									Image: "registry.k8s.io/a-kube-rbac-proxy:v0.13.1",
+								},
 								{
 									Name:  "manager",
 									Image: "quay.io/dev/mydns:v3.4.2",
@@ -403,6 +413,10 @@ func TestCustomizeDeployment(t *testing.T) {
 								},
 							},
 							Containers: []corev1.Container{
+								{
+									Name:  "kube-rbac-proxy",
+									Image: "registry.k8s.io/a-kube-rbac-proxy:v0.13.1",
+								},
 								{
 									Name:  "manager",
 									Image: "quay.io/dev/mydns:v3.4.2",
@@ -506,6 +520,10 @@ func TestCustomizeDeployment(t *testing.T) {
 						},
 						Spec: corev1.PodSpec{
 							Containers: []corev1.Container{
+								{
+									Name:  "kube-rbac-proxy",
+									Image: "registry.k8s.io/a-kube-rbac-proxy:v0.13.1",
+								},
 								{
 									Name:  "manager",
 									Image: "registry.k8s.io/a-manager:1.6.2",