This is an automated email from the ASF dual-hosted git repository.

thelabdude pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 1548258  Allow users to supply ACL credentials for provided ZK 
ensembles to configure Solr to set ACLs on znodes (#253)
1548258 is described below

commit 1548258ec07bfde23e97f0b428101be752f8da70
Author: Timothy Potter <thelabd...@gmail.com>
AuthorDate: Fri Apr 9 11:06:15 2021 -0600

    Allow users to supply ACL credentials for provided ZK ensembles to 
configure Solr to set ACLs on znodes (#253)
---
 api/v1beta1/solrcloud_types.go                   |  25 +++++
 api/v1beta1/zz_generated.deepcopy.go             |  17 ++++
 config/crd/bases/solr.apache.org_solrclouds.yaml | 114 +++++++++++++++++++++
 controllers/controller_utils_test.go             |  67 +++++++++++++
 controllers/solrcloud_controller_tls_test.go     |  32 ++++++
 controllers/solrcloud_controller_zk_test.go      | 120 +++++++++++++----------
 controllers/util/prometheus_exporter_util.go     |   2 +-
 controllers/util/solr_util.go                    |   3 +-
 controllers/util/zk_util.go                      |  35 ++++---
 docs/solr-cloud/solr-cloud-crd.md                |  19 +++-
 helm/solr-operator/crds/crds.yaml                | 114 +++++++++++++++++++++
 11 files changed, 479 insertions(+), 69 deletions(-)

diff --git a/api/v1beta1/solrcloud_types.go b/api/v1beta1/solrcloud_types.go
index 49ecd00..d2eeb13 100644
--- a/api/v1beta1/solrcloud_types.go
+++ b/api/v1beta1/solrcloud_types.go
@@ -556,6 +556,17 @@ func (ref *ZookeeperRef) withDefaults() (changed bool) {
        return changed
 }
 
+func (ref *ZookeeperRef) GetACLs() (allACL *ZookeeperACL, readOnlyACL 
*ZookeeperACL) {
+       if ref.ConnectionInfo != nil {
+               allACL = ref.ConnectionInfo.AllACL
+               readOnlyACL = ref.ConnectionInfo.ReadOnlyACL
+       } else if ref.ProvidedZookeeper != nil {
+               allACL = ref.ProvidedZookeeper.AllACL
+               readOnlyACL = ref.ProvidedZookeeper.ReadOnlyACL
+       }
+       return
+}
+
 // ZookeeperSpec defines the internal zookeeper ensemble to run with the given 
spec
 type ZookeeperSpec struct {
 
@@ -580,6 +591,16 @@ type ZookeeperSpec struct {
        // The ChRoot to connect solr at
        // +optional
        ChRoot string `json:"chroot,omitempty"`
+
+       // ZooKeeper ACL to use when connecting with ZK.
+       // This ACL should have ALL permission in the given chRoot.
+       // +optional
+       AllACL *ZookeeperACL `json:"acl,omitempty"`
+
+       // ZooKeeper ACL to use when connecting with ZK for reading operations.
+       // This ACL should have READ permission in the given chRoot.
+       // +optional
+       ReadOnlyACL *ZookeeperACL `json:"readOnlyAcl,omitempty"`
 }
 
 func (z *ZookeeperSpec) withDefaults() (changed bool) {
@@ -640,6 +661,10 @@ type ZookeeperPodPolicy struct {
        // +optional
        Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
 
+       // List of environment variables to set in the main ZK container.
+       // +optional
+       Env []corev1.EnvVar `json:"env,omitempty"`
+
        // Resources is the resource requirements for the container.
        // This field cannot be updated once the cluster is created.
        // +optional
diff --git a/api/v1beta1/zz_generated.deepcopy.go 
b/api/v1beta1/zz_generated.deepcopy.go
index 6810efe..b93ff93 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -1270,6 +1270,13 @@ func (in *ZookeeperPodPolicy) DeepCopyInto(out 
*ZookeeperPodPolicy) {
                        (*in)[i].DeepCopyInto(&(*out)[i])
                }
        }
+       if in.Env != nil {
+               in, out := &in.Env, &out.Env
+               *out = make([]v1.EnvVar, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
        in.Resources.DeepCopyInto(&out.Resources)
 }
 
@@ -1327,6 +1334,16 @@ func (in *ZookeeperSpec) DeepCopyInto(out 
*ZookeeperSpec) {
                (*in).DeepCopyInto(*out)
        }
        in.ZookeeperPod.DeepCopyInto(&out.ZookeeperPod)
+       if in.AllACL != nil {
+               in, out := &in.AllACL, &out.AllACL
+               *out = new(ZookeeperACL)
+               **out = **in
+       }
+       if in.ReadOnlyACL != nil {
+               in, out := &in.ReadOnlyACL, &out.ReadOnlyACL
+               *out = new(ZookeeperACL)
+               **out = **in
+       }
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ZookeeperSpec.
diff --git a/config/crd/bases/solr.apache.org_solrclouds.yaml 
b/config/crd/bases/solr.apache.org_solrclouds.yaml
index d27e6c1..f4fa881 100644
--- a/config/crd/bases/solr.apache.org_solrclouds.yaml
+++ b/config/crd/bases/solr.apache.org_solrclouds.yaml
@@ -4666,6 +4666,23 @@ spec:
                   provided:
                     description: 'Create a new Zookeeper Ensemble with the 
following spec Note: This option will not allow the SolrCloud to run across 
kube-clusters. Note: Requires   - The zookeeperOperator flag to be provided to 
the Solr Operator   - A zookeeper operator to be running'
                     properties:
+                      acl:
+                        description: ZooKeeper ACL to use when connecting with 
ZK. This ACL should have ALL permission in the given chRoot.
+                        properties:
+                          passwordKey:
+                            description: The name of the key in the given 
secret that contains the ACL password
+                            type: string
+                          secret:
+                            description: The name of the Kubernetes Secret 
that stores the username and password for the ACL. This secret must be in the 
same namespace as the solrCloud or prometheusExporter is running in.
+                            type: string
+                          usernameKey:
+                            description: The name of the key in the given 
secret that contains the ACL username
+                            type: string
+                        required:
+                        - passwordKey
+                        - secret
+                        - usernameKey
+                        type: object
                       chroot:
                         description: The ChRoot to connect solr at
                         type: string
@@ -4778,6 +4795,23 @@ spec:
                                 type: string
                             type: object
                         type: object
+                      readOnlyAcl:
+                        description: ZooKeeper ACL to use when connecting with 
ZK for reading operations. This ACL should have READ permission in the given 
chRoot.
+                        properties:
+                          passwordKey:
+                            description: The name of the key in the given 
secret that contains the ACL password
+                            type: string
+                          secret:
+                            description: The name of the Kubernetes Secret 
that stores the username and password for the ACL. This secret must be in the 
same namespace as the solrCloud or prometheusExporter is running in.
+                            type: string
+                          usernameKey:
+                            description: The name of the key in the given 
secret that contains the ACL username
+                            type: string
+                        required:
+                        - passwordKey
+                        - secret
+                        - usernameKey
+                        type: object
                       replicas:
                         description: Number of members to create up for the ZK 
ensemble Defaults to 3
                         format: int32
@@ -5126,6 +5160,86 @@ spec:
                                     type: array
                                 type: object
                             type: object
+                          env:
+                            description: List of environment variables to set 
in the main ZK container.
+                            items:
+                              description: EnvVar represents an environment 
variable present in a Container.
+                              properties:
+                                name:
+                                  description: Name of the environment 
variable. Must be a C_IDENTIFIER.
+                                  type: string
+                                value:
+                                  description: 'Variable references 
$(VAR_NAME) are expanded using the previous defined environment variables in 
the container and any service environment variables. If a variable cannot be 
resolved, the reference in the input string will be unchanged. The $(VAR_NAME) 
syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references 
will never be expanded, regardless of whether the variable exists or not. 
Defaults to "".'
+                                  type: string
+                                valueFrom:
+                                  description: Source for the environment 
variable's value. Cannot be used if value is not empty.
+                                  properties:
+                                    configMapKeyRef:
+                                      description: Selects a key of a 
ConfigMap.
+                                      properties:
+                                        key:
+                                          description: The key to select.
+                                          type: string
+                                        name:
+                                          description: 'Name of the referent. 
More info: 
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 
TODO: Add other useful fields. apiVersion, kind, uid?'
+                                          type: string
+                                        optional:
+                                          description: Specify whether the 
ConfigMap or its key must be defined
+                                          type: boolean
+                                      required:
+                                      - key
+                                      type: object
+                                    fieldRef:
+                                      description: 'Selects a field of the 
pod: supports metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`, 
`metadata.annotations[''<KEY>'']`, spec.nodeName, spec.serviceAccountName, 
status.hostIP, status.podIP, status.podIPs.'
+                                      properties:
+                                        apiVersion:
+                                          description: Version of the schema 
the FieldPath is written in terms of, defaults to "v1".
+                                          type: string
+                                        fieldPath:
+                                          description: Path of the field to 
select in the specified API version.
+                                          type: string
+                                      required:
+                                      - fieldPath
+                                      type: object
+                                    resourceFieldRef:
+                                      description: 'Selects a resource of the 
container: only resources limits and requests (limits.cpu, limits.memory, 
limits.ephemeral-storage, requests.cpu, requests.memory and 
requests.ephemeral-storage) are currently supported.'
+                                      properties:
+                                        containerName:
+                                          description: 'Container name: 
required for volumes, optional for env vars'
+                                          type: string
+                                        divisor:
+                                          anyOf:
+                                          - type: integer
+                                          - type: string
+                                          description: Specifies the output 
format of the exposed resources, defaults to "1"
+                                          pattern: 
^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                                          x-kubernetes-int-or-string: true
+                                        resource:
+                                          description: 'Required: resource to 
select'
+                                          type: string
+                                      required:
+                                      - resource
+                                      type: object
+                                    secretKeyRef:
+                                      description: Selects a key of a secret 
in the pod's namespace
+                                      properties:
+                                        key:
+                                          description: The key of the secret 
to select from.  Must be a valid secret key.
+                                          type: string
+                                        name:
+                                          description: 'Name of the referent. 
More info: 
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 
TODO: Add other useful fields. apiVersion, kind, uid?'
+                                          type: string
+                                        optional:
+                                          description: Specify whether the 
Secret or its key must be defined
+                                          type: boolean
+                                      required:
+                                      - key
+                                      type: object
+                                  type: object
+                              required:
+                              - name
+                              type: object
+                            type: array
                           nodeSelector:
                             additionalProperties:
                               type: string
diff --git a/controllers/controller_utils_test.go 
b/controllers/controller_utils_test.go
index 652e4b4..9430e7e 100644
--- a/controllers/controller_utils_test.go
+++ b/controllers/controller_utils_test.go
@@ -363,6 +363,73 @@ func testMapContainsOther(t *testing.T, mapName string, 
base map[string]string,
        }
 }
 
+func testACLEnvVars(t *testing.T, actualEnvVars []corev1.EnvVar) {
+       /*
+               This test verifies ACL related env vars are set correctly and 
in the correct order, but expects a very specific config to be used in your 
test SolrCloud config:
+
+                                       AllACL: &solr.ZookeeperACL{
+                                               SecretRef:   "secret-name",
+                                               UsernameKey: "user",
+                                               PasswordKey: "pass",
+                                       },
+                                       ReadOnlyACL: &solr.ZookeeperACL{
+                                               SecretRef:   "read-secret-name",
+                                               UsernameKey: "read-only-user",
+                                               PasswordKey: "read-only-pass",
+                                       },
+
+       */
+       f := false
+       zkAclEnvVars := []corev1.EnvVar{
+               {
+                       Name: "ZK_ALL_ACL_USERNAME",
+                       ValueFrom: &corev1.EnvVarSource{
+                               SecretKeyRef: &corev1.SecretKeySelector{
+                                       LocalObjectReference: 
corev1.LocalObjectReference{Name: "secret-name"},
+                                       Key:                  "user",
+                                       Optional:             &f,
+                               },
+                       },
+               },
+               {
+                       Name: "ZK_ALL_ACL_PASSWORD",
+                       ValueFrom: &corev1.EnvVarSource{
+                               SecretKeyRef: &corev1.SecretKeySelector{
+                                       LocalObjectReference: 
corev1.LocalObjectReference{Name: "secret-name"},
+                                       Key:                  "pass",
+                                       Optional:             &f,
+                               },
+                       },
+               },
+               {
+                       Name: "ZK_READ_ACL_USERNAME",
+                       ValueFrom: &corev1.EnvVarSource{
+                               SecretKeyRef: &corev1.SecretKeySelector{
+                                       LocalObjectReference: 
corev1.LocalObjectReference{Name: "read-secret-name"},
+                                       Key:                  "read-only-user",
+                                       Optional:             &f,
+                               },
+                       },
+               },
+               {
+                       Name: "ZK_READ_ACL_PASSWORD",
+                       ValueFrom: &corev1.EnvVarSource{
+                               SecretKeyRef: &corev1.SecretKeySelector{
+                                       LocalObjectReference: 
corev1.LocalObjectReference{Name: "read-secret-name"},
+                                       Key:                  "read-only-pass",
+                                       Optional:             &f,
+                               },
+                       },
+               },
+               {
+                       Name:      "SOLR_ZK_CREDS_AND_ACLS",
+                       Value:     
"-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider
 
-DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider
 -DzkDigestUsername=$(ZK_ALL_ACL_USERNAME) 
-DzkDigestPassword=$(ZK_ALL_ACL_PASSWORD) 
-DzkDigestReadonlyUsername=$(ZK_READ_ACL_USERNAME) 
-DzkDigestReadonlyPassword=$(ZK_READ_ACL_PASSWORD)",
+                       ValueFrom: nil,
+               },
+       }
+       assert.Equal(t, zkAclEnvVars, actualEnvVars, "ZK ACL Env Vars are not 
correct")
+}
+
 func cleanupTest(g *gomega.GomegaWithT, namespace string) {
        deleteOpts := []client.DeleteAllOfOption{
                client.InNamespace(namespace),
diff --git a/controllers/solrcloud_controller_tls_test.go 
b/controllers/solrcloud_controller_tls_test.go
index 2a52b42..db23661 100644
--- a/controllers/solrcloud_controller_tls_test.go
+++ b/controllers/solrcloud_controller_tls_test.go
@@ -55,6 +55,29 @@ func TestBasicAuthBootstrapSecurityJson(t *testing.T) {
        verifyReconcileWithSecurity(t, instance, false)
 }
 
+func TestBasicAuthBootstrapSecurityJsonWithZkACLs(t *testing.T) {
+       UseZkCRD(true)
+       instance := buildTestSolrCloud()
+       instance.Spec.SolrSecurity = 
&solr.SolrSecurityOptions{AuthenticationType: solr.Basic, ProbesRequireAuth: 
true}
+       zkReplicas := int32(1)
+       instance.Spec.ZookeeperRef = &solr.ZookeeperRef{
+               ProvidedZookeeper: &solr.ZookeeperSpec{
+                       Replicas: &zkReplicas,
+                       AllACL: &solr.ZookeeperACL{
+                               SecretRef:   "secret-name",
+                               UsernameKey: "user",
+                               PasswordKey: "pass",
+                       },
+                       ReadOnlyACL: &solr.ZookeeperACL{
+                               SecretRef:   "read-secret-name",
+                               UsernameKey: "read-only-user",
+                               PasswordKey: "read-only-pass",
+                       },
+               },
+       }
+       verifyReconcileWithSecurity(t, instance, false)
+}
+
 func TestBasicAuthBootstrapSecurityJsonDeleteSecret(t *testing.T) {
        instance := buildTestSolrCloud()
        instance.Spec.SolrSecurity = 
&solr.SolrSecurityOptions{AuthenticationType: solr.Basic}
@@ -473,6 +496,15 @@ func expectBasicAuthConfigOnPodTemplate(t *testing.T, 
instance *solr.SolrCloud,
                }
 
                if expectBootstrapSecret {
+                       // if the zookeeperRef has ACLs set, verify the env 
vars were set correctly for this initContainer
+                       allACL, _ := instance.Spec.ZookeeperRef.GetACLs()
+                       if allACL != nil {
+                               assert.Equal(t, 10, len(expInitContainer.Env))
+                               assert.Equal(t, "SOLR_OPTS", 
expInitContainer.Env[len(expInitContainer.Env)-2].Name)
+                               assert.Equal(t, "SECURITY_JSON", 
expInitContainer.Env[len(expInitContainer.Env)-1].Name)
+                               testACLEnvVars(t, 
expInitContainer.Env[3:len(expInitContainer.Env)-2])
+                       } // else this ref not using ACLs
+
                        assert.NotNil(t, expInitContainer, "Didn't find the 
setup-zk InitContainer in the sts!")
                        expCmd := 
"ZK_SECURITY_JSON=$(/opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost 
${ZK_HOST} -cmd get /security.json); " +
                                "if [ ${#ZK_SECURITY_JSON} -lt 3 ]; then " +
diff --git a/controllers/solrcloud_controller_zk_test.go 
b/controllers/solrcloud_controller_zk_test.go
index ae8e6fb..36699fc 100644
--- a/controllers/solrcloud_controller_zk_test.go
+++ b/controllers/solrcloud_controller_zk_test.go
@@ -18,6 +18,7 @@
 package controllers
 
 import (
+       "fmt"
        corev1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/types"
        "testing"
@@ -166,11 +167,7 @@ func TestZKACLsCloudReconcile(t *testing.T) {
 }
 
 func TestBothZKACLsCloudReconcile(t *testing.T) {
-       UseZkCRD(false)
-       g := gomega.NewGomegaWithT(t)
-
        replicas := int32(3)
-
        instance := &solr.SolrCloud{
                ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, 
Namespace: expectedCloudRequest.Namespace},
                Spec: solr.SolrCloudSpec{
@@ -198,6 +195,48 @@ func TestBothZKACLsCloudReconcile(t *testing.T) {
                        SolrOpts: "-Dextra -Dopts",
                },
        }
+       testZkACLsReconcile(t, false, instance, "host:7271/")
+}
+
+func TestZKACLsForProvidedReconcile(t *testing.T) {
+       replicas := int32(3)
+       zkReplicas := int32(1)
+       instance := &solr.SolrCloud{
+               ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, 
Namespace: expectedCloudRequest.Namespace},
+               Spec: solr.SolrCloudSpec{
+                       Replicas: &replicas,
+                       ZookeeperRef: &solr.ZookeeperRef{
+                               ProvidedZookeeper: &solr.ZookeeperSpec{
+                                       Replicas: &zkReplicas,
+                                       AllACL: &solr.ZookeeperACL{
+                                               SecretRef:   "secret-name",
+                                               UsernameKey: "user",
+                                               PasswordKey: "pass",
+                                       },
+                                       ReadOnlyACL: &solr.ZookeeperACL{
+                                               SecretRef:   "read-secret-name",
+                                               UsernameKey: "read-only-user",
+                                               PasswordKey: "read-only-pass",
+                                       },
+                               },
+                       },
+                       CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
+                               PodOptions: &solr.PodOptions{
+                                       EnvVariables: extraVars,
+                               },
+                       },
+                       SolrOpts: "-Dextra -Dopts",
+               },
+       }
+
+       expectedZkHost := 
fmt.Sprintf("%s-zookeeper-0.%s-zookeeper-headless.%s.svc.cluster.local:2181/",
+               cloudSsKey.Name, cloudSsKey.Name, cloudSsKey.Namespace)
+       testZkACLsReconcile(t, true, instance, expectedZkHost)
+}
+
+func testZkACLsReconcile(t *testing.T, useZkCRD bool, instance 
*solr.SolrCloud, expectedZkHost string) {
+       UseZkCRD(useZkCRD)
+       g := gomega.NewGomegaWithT(t)
 
        // Setup the Manager and Controller.  Wrap the Controller Reconcile 
function so it writes each request to a
        // channel when it is finished.
@@ -237,62 +276,14 @@ func TestBothZKACLsCloudReconcile(t *testing.T) {
 
        // Env Variable Tests
        expectedEnvVars := map[string]string{
-               "ZK_HOST":        "host:7271/",
+               "ZK_HOST":        expectedZkHost,
                "SOLR_HOST":      "$(POD_HOSTNAME)." + cloudHsKey.Name + "." + 
instance.Namespace,
                "SOLR_PORT":      "8983",
                "SOLR_NODE_PORT": "8983",
                "SOLR_OPTS":      "-DhostPort=$(SOLR_NODE_PORT) 
$(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
        }
        foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
-       f := false
-       zkAclEnvVars := []corev1.EnvVar{
-               {
-                       Name: "ZK_ALL_ACL_USERNAME",
-                       ValueFrom: &corev1.EnvVarSource{
-                               SecretKeyRef: &corev1.SecretKeySelector{
-                                       LocalObjectReference: 
corev1.LocalObjectReference{Name: "secret-name"},
-                                       Key:                  "user",
-                                       Optional:             &f,
-                               },
-                       },
-               },
-               {
-                       Name: "ZK_ALL_ACL_PASSWORD",
-                       ValueFrom: &corev1.EnvVarSource{
-                               SecretKeyRef: &corev1.SecretKeySelector{
-                                       LocalObjectReference: 
corev1.LocalObjectReference{Name: "secret-name"},
-                                       Key:                  "pass",
-                                       Optional:             &f,
-                               },
-                       },
-               },
-               {
-                       Name: "ZK_READ_ACL_USERNAME",
-                       ValueFrom: &corev1.EnvVarSource{
-                               SecretKeyRef: &corev1.SecretKeySelector{
-                                       LocalObjectReference: 
corev1.LocalObjectReference{Name: "read-secret-name"},
-                                       Key:                  "read-only-user",
-                                       Optional:             &f,
-                               },
-                       },
-               },
-               {
-                       Name: "ZK_READ_ACL_PASSWORD",
-                       ValueFrom: &corev1.EnvVarSource{
-                               SecretKeyRef: &corev1.SecretKeySelector{
-                                       LocalObjectReference: 
corev1.LocalObjectReference{Name: "read-secret-name"},
-                                       Key:                  "read-only-pass",
-                                       Optional:             &f,
-                               },
-                       },
-               },
-               {
-                       Name:      "SOLR_ZK_CREDS_AND_ACLS",
-                       Value:     
"-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider
 
-DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider
 -DzkDigestUsername=$(ZK_ALL_ACL_USERNAME) 
-DzkDigestPassword=$(ZK_ALL_ACL_PASSWORD) 
-DzkDigestReadonlyUsername=$(ZK_READ_ACL_USERNAME) 
-DzkDigestReadonlyPassword=$(ZK_READ_ACL_PASSWORD)",
-                       ValueFrom: nil,
-               },
-       }
-       assert.Equal(t, zkAclEnvVars, 
foundEnv[len(foundEnv)-8:len(foundEnv)-3], "ZK ACL Env Vars are not correct")
+       testACLEnvVars(t, foundEnv[len(foundEnv)-8:len(foundEnv)-3])
        assert.Equal(t, extraVars, foundEnv[len(foundEnv)-3:len(foundEnv)-1], 
"Extra Env Vars are not the same as the ones provided in podOptions")
        // Note that this check changes the variable foundEnv, so the values 
are no longer valid afterwards.
        // TODO: Make this not invalidate foundEnv
@@ -318,4 +309,25 @@ func TestBothZKACLsCloudReconcile(t *testing.T) {
 
        // Check the ingress
        expectNoIngress(g, cloudIKey)
+
+       // now update the env vars on the zk spec
+       if instance.Spec.ZookeeperRef.ProvidedZookeeper != nil {
+               err = testClient.Get(context.TODO(), 
expectedCloudRequest.NamespacedName, instance)
+               g.Expect(err).NotTo(gomega.HaveOccurred())
+               updateEnvVars := []corev1.EnvVar{
+                       {
+                               Name:  "VAR_1",
+                               Value: "VAL_1",
+                       },
+               }
+               instance.Spec.ZookeeperRef.ProvidedZookeeper.ZookeeperPod = 
solr.ZookeeperPodPolicy{Env: updateEnvVars}
+               err = testClient.Update(context.TODO(), instance)
+               g.Expect(err).NotTo(gomega.HaveOccurred())
+               g.Eventually(requests, 
timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
+               g.Eventually(requests, 
timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
+               lookup := &solr.SolrCloud{}
+               err = testClient.Get(context.TODO(), 
expectedCloudRequest.NamespacedName, lookup)
+               g.Expect(err).NotTo(gomega.HaveOccurred())
+               assert.EqualValues(t, updateEnvVars, 
lookup.Spec.ZookeeperRef.ProvidedZookeeper.ZookeeperPod.Env, "Updated ZK Pod 
env not reconciled!")
+       }
 }
diff --git a/controllers/util/prometheus_exporter_util.go 
b/controllers/util/prometheus_exporter_util.go
index 25c10e9..f82c899 100644
--- a/controllers/util/prometheus_exporter_util.go
+++ b/controllers/util/prometheus_exporter_util.go
@@ -103,7 +103,7 @@ func 
GenerateSolrPrometheusExporterDeployment(solrPrometheusExporter *solr.SolrP
                exporterArgs = append(exporterArgs, "-z", 
solrConnectionInfo.CloudZkConnnectionInfo.ZkConnectionString())
 
                // Add ACL information, if given, through Env Vars
-               if hasACLs, aclEnvs := 
AddACLsToEnv(solrConnectionInfo.CloudZkConnnectionInfo); hasACLs {
+               if hasACLs, aclEnvs := 
AddACLsToEnv(solrConnectionInfo.CloudZkConnnectionInfo.AllACL, 
solrConnectionInfo.CloudZkConnnectionInfo.ReadOnlyACL); hasACLs {
                        envVars = append(envVars, aclEnvs...)
 
                        // The $SOLR_ZK_CREDS_AND_ACLS parameter does not get 
picked up when running the Prometheus Exporter, it must be added to the 
JAVA_OPTS.
diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go
index 4288915..224291b 100644
--- a/controllers/util/solr_util.go
+++ b/controllers/util/solr_util.go
@@ -1206,7 +1206,8 @@ func createZkConnectionEnvVars(solrCloud *solr.SolrCloud, 
solrCloudStatus *solr.
        }
 
        // Add ACL information, if given, through Env Vars
-       if hasACLs, aclEnvs := 
AddACLsToEnv(solrCloud.Spec.ZookeeperRef.ConnectionInfo); hasACLs {
+       allACL, readOnlyACL := solrCloud.Spec.ZookeeperRef.GetACLs()
+       if hasACLs, aclEnvs := AddACLsToEnv(allACL, readOnlyACL); hasACLs {
                envVars = append(envVars, aclEnvs...)
 
                // The $SOLR_ZK_CREDS_AND_ACLS parameter does not get picked up 
when running solr, it must be added to the SOLR_OPTS.
diff --git a/controllers/util/zk_util.go b/controllers/util/zk_util.go
index 4241fd1..7e5ed40 100644
--- a/controllers/util/zk_util.go
+++ b/controllers/util/zk_util.go
@@ -85,6 +85,10 @@ func GenerateZookeeperCluster(solrCloud *solr.SolrCloud, 
zkSpec *solr.ZookeeperS
                zkCluster.Spec.Pod.NodeSelector = 
zkSpec.ZookeeperPod.NodeSelector
        }
 
+       if zkSpec.ZookeeperPod.Env != nil {
+               zkCluster.Spec.Pod.Env = zkSpec.ZookeeperPod.Env
+       }
+
        if solrCloud.Spec.SolrAddressability.KubeDomain != "" {
                zkCluster.Spec.KubernetesClusterDomain = 
solrCloud.Spec.SolrAddressability.KubeDomain
        }
@@ -172,6 +176,12 @@ func CopyZookeeperClusterFields(from, to 
*zk.ZookeeperCluster, logger logr.Logge
                to.Spec.Pod.Resources = from.Spec.Pod.Resources
        }
 
+       if !DeepEqualWithNils(to.Spec.Pod.Env, from.Spec.Pod.Env) {
+               logger.Info("Update required because field changed", "field", 
"Spec.Pod.Env", "from", to.Spec.Pod.Env, "to", from.Spec.Pod.Env)
+               requireUpdate = true
+               to.Spec.Pod.Env = from.Spec.Pod.Env
+       }
+
        if !DeepEqualWithNils(to.Spec.Pod.Tolerations, 
from.Spec.Pod.Tolerations) {
                logger.Info("Update required because field changed", "field", 
"Spec.Pod.Tolerations", "from", to.Spec.Pod.Tolerations, "to", 
from.Spec.Pod.Tolerations)
                requireUpdate = true
@@ -203,22 +213,23 @@ func CopyZookeeperClusterFields(from, to 
*zk.ZookeeperCluster, logger logr.Logge
 
 // AddACLsToEnv creates the neccessary environment variables for using ZK 
ACLs, and returns whether ACLs were provided.
 // info: Zookeeper Connection Information
-func AddACLsToEnv(info *solr.ZookeeperConnectionInfo) (hasACLs bool, envVars 
[]corev1.EnvVar) {
-       if info == nil || (info.AllACL == nil && info.ReadOnlyACL == nil) {
+func AddACLsToEnv(allACL *solr.ZookeeperACL, readOnlyACL *solr.ZookeeperACL) 
(hasACLs bool, envVars []corev1.EnvVar) {
+       if allACL == nil && readOnlyACL == nil {
                return false, envVars
        }
+
        f := false
        var zkDigests []string
-       if info.AllACL != nil {
+       if allACL != nil {
                envVars = append(envVars,
                        corev1.EnvVar{
                                Name: "ZK_ALL_ACL_USERNAME",
                                ValueFrom: &corev1.EnvVarSource{
                                        SecretKeyRef: &corev1.SecretKeySelector{
                                                LocalObjectReference: 
corev1.LocalObjectReference{
-                                                       Name: 
info.AllACL.SecretRef,
+                                                       Name: allACL.SecretRef,
                                                },
-                                               Key:      
info.AllACL.UsernameKey,
+                                               Key:      allACL.UsernameKey,
                                                Optional: &f,
                                        },
                                },
@@ -228,25 +239,25 @@ func AddACLsToEnv(info *solr.ZookeeperConnectionInfo) 
(hasACLs bool, envVars []c
                                ValueFrom: &corev1.EnvVarSource{
                                        SecretKeyRef: &corev1.SecretKeySelector{
                                                LocalObjectReference: 
corev1.LocalObjectReference{
-                                                       Name: 
info.AllACL.SecretRef,
+                                                       Name: allACL.SecretRef,
                                                },
-                                               Key:      
info.AllACL.PasswordKey,
+                                               Key:      allACL.PasswordKey,
                                                Optional: &f,
                                        },
                                },
                        })
                zkDigests = append(zkDigests, 
"-DzkDigestUsername=$(ZK_ALL_ACL_USERNAME)", 
"-DzkDigestPassword=$(ZK_ALL_ACL_PASSWORD)")
        }
-       if info.ReadOnlyACL != nil {
+       if readOnlyACL != nil {
                envVars = append(envVars,
                        corev1.EnvVar{
                                Name: "ZK_READ_ACL_USERNAME",
                                ValueFrom: &corev1.EnvVarSource{
                                        SecretKeyRef: &corev1.SecretKeySelector{
                                                LocalObjectReference: 
corev1.LocalObjectReference{
-                                                       Name: 
info.ReadOnlyACL.SecretRef,
+                                                       Name: 
readOnlyACL.SecretRef,
                                                },
-                                               Key:      
info.ReadOnlyACL.UsernameKey,
+                                               Key:      
readOnlyACL.UsernameKey,
                                                Optional: &f,
                                        },
                                },
@@ -256,9 +267,9 @@ func AddACLsToEnv(info *solr.ZookeeperConnectionInfo) 
(hasACLs bool, envVars []c
                                ValueFrom: &corev1.EnvVarSource{
                                        SecretKeyRef: &corev1.SecretKeySelector{
                                                LocalObjectReference: 
corev1.LocalObjectReference{
-                                                       Name: 
info.ReadOnlyACL.SecretRef,
+                                                       Name: 
readOnlyACL.SecretRef,
                                                },
-                                               Key:      
info.ReadOnlyACL.PasswordKey,
+                                               Key:      
readOnlyACL.PasswordKey,
                                                Optional: &f,
                                        },
                                },
diff --git a/docs/solr-cloud/solr-cloud-crd.md 
b/docs/solr-cloud/solr-cloud-crd.md
index e74f996..500d38f 100644
--- a/docs/solr-cloud/solr-cloud-crd.md
+++ b/docs/solr-cloud/solr-cloud-crd.md
@@ -130,7 +130,7 @@ All ACL fields are **required** if an ACL is used.
 
 - **`secret`** - The name of the secret, in the same namespace as the 
SolrCloud, that contains the admin ACL username and password.
 - **`usernameKey`** - The name of the key in the provided secret that stores 
the admin ACL username.
-- **`usernameKey`** - The name of the key in the provided secret that stores 
the admin ACL password.
+- **`passwordKey`** - The name of the key in the provided secret that stores 
the admin ACL password.
 
 ### Provided Instance
 
@@ -142,6 +142,23 @@ each solrCloud that has this option specified.
 
 The startup parameter `zookeeper-operator` must be provided on startup of the 
solr-operator for this parameter to be available.
 
+#### ACLs for Provided Ensembles
+_Since v0.3.0_
+
+If you want Solr to set ZK ACLs for znodes it creates in the `provided` 
ensemble, you can supply ACL credentials for an ADMIN and optionally a READ 
ONLY user using the following config settings: 
+- Admin: `SolrCloud.spec.zookeeperRef.provided.acl`
+- Read Only: `SolrCloud.spec.zookeeperRef.provided.readOnlyAcl`
+
+All ACL fields are **required** if an ACL is used.
+
+- **`secret`** - The name of the secret, in the same namespace as the 
SolrCloud, that contains the ACL username and password.
+- **`usernameKey`** - The name of the key in the provided secret that stores 
the admin ACL username.
+- **`passwordKey`** - The name of the key in the provided secret that stores 
the admin ACL password.
+
+**Warning**: There is a known issue with the Zookeeper operator where it 
deploys pods with `skipACL=yes`, see: 
https://github.com/pravega/zookeeper-operator/issues/316.
+This means that even if Solr sets the ACLs on znodes, they will not be 
enforced by Zookeeper. If your organization requires Solr to use ZK ACLs, then 
you'll need to 
+deploy Zookeeper to Kubernetes using another approach, such as using a Helm 
chart. 
+
 ## Override Built-in Solr Configuration Files
 _Since v0.2.7_
 
diff --git a/helm/solr-operator/crds/crds.yaml 
b/helm/solr-operator/crds/crds.yaml
index 4149f6f..e027ced 100644
--- a/helm/solr-operator/crds/crds.yaml
+++ b/helm/solr-operator/crds/crds.yaml
@@ -5807,6 +5807,23 @@ spec:
                   provided:
                     description: 'Create a new Zookeeper Ensemble with the 
following spec Note: This option will not allow the SolrCloud to run across 
kube-clusters. Note: Requires   - The zookeeperOperator flag to be provided to 
the Solr Operator   - A zookeeper operator to be running'
                     properties:
+                      acl:
+                        description: ZooKeeper ACL to use when connecting with 
ZK. This ACL should have ALL permission in the given chRoot.
+                        properties:
+                          passwordKey:
+                            description: The name of the key in the given 
secret that contains the ACL password
+                            type: string
+                          secret:
+                            description: The name of the Kubernetes Secret 
that stores the username and password for the ACL. This secret must be in the 
same namespace as the solrCloud or prometheusExporter is running in.
+                            type: string
+                          usernameKey:
+                            description: The name of the key in the given 
secret that contains the ACL username
+                            type: string
+                        required:
+                        - passwordKey
+                        - secret
+                        - usernameKey
+                        type: object
                       chroot:
                         description: The ChRoot to connect solr at
                         type: string
@@ -5919,6 +5936,23 @@ spec:
                                 type: string
                             type: object
                         type: object
+                      readOnlyAcl:
+                        description: ZooKeeper ACL to use when connecting with 
ZK for reading operations. This ACL should have READ permission in the given 
chRoot.
+                        properties:
+                          passwordKey:
+                            description: The name of the key in the given 
secret that contains the ACL password
+                            type: string
+                          secret:
+                            description: The name of the Kubernetes Secret 
that stores the username and password for the ACL. This secret must be in the 
same namespace as the solrCloud or prometheusExporter is running in.
+                            type: string
+                          usernameKey:
+                            description: The name of the key in the given 
secret that contains the ACL username
+                            type: string
+                        required:
+                        - passwordKey
+                        - secret
+                        - usernameKey
+                        type: object
                       replicas:
                         description: Number of members to create up for the ZK 
ensemble Defaults to 3
                         format: int32
@@ -6267,6 +6301,86 @@ spec:
                                     type: array
                                 type: object
                             type: object
+                          env:
+                            description: List of environment variables to set 
in the main ZK container.
+                            items:
+                              description: EnvVar represents an environment 
variable present in a Container.
+                              properties:
+                                name:
+                                  description: Name of the environment 
variable. Must be a C_IDENTIFIER.
+                                  type: string
+                                value:
+                                  description: 'Variable references 
$(VAR_NAME) are expanded using the previous defined environment variables in 
the container and any service environment variables. If a variable cannot be 
resolved, the reference in the input string will be unchanged. The $(VAR_NAME) 
syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references 
will never be expanded, regardless of whether the variable exists or not. 
Defaults to "".'
+                                  type: string
+                                valueFrom:
+                                  description: Source for the environment 
variable's value. Cannot be used if value is not empty.
+                                  properties:
+                                    configMapKeyRef:
+                                      description: Selects a key of a 
ConfigMap.
+                                      properties:
+                                        key:
+                                          description: The key to select.
+                                          type: string
+                                        name:
+                                          description: 'Name of the referent. 
More info: 
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 
TODO: Add other useful fields. apiVersion, kind, uid?'
+                                          type: string
+                                        optional:
+                                          description: Specify whether the 
ConfigMap or its key must be defined
+                                          type: boolean
+                                      required:
+                                      - key
+                                      type: object
+                                    fieldRef:
+                                      description: 'Selects a field of the 
pod: supports metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`, 
`metadata.annotations[''<KEY>'']`, spec.nodeName, spec.serviceAccountName, 
status.hostIP, status.podIP, status.podIPs.'
+                                      properties:
+                                        apiVersion:
+                                          description: Version of the schema 
the FieldPath is written in terms of, defaults to "v1".
+                                          type: string
+                                        fieldPath:
+                                          description: Path of the field to 
select in the specified API version.
+                                          type: string
+                                      required:
+                                      - fieldPath
+                                      type: object
+                                    resourceFieldRef:
+                                      description: 'Selects a resource of the 
container: only resources limits and requests (limits.cpu, limits.memory, 
limits.ephemeral-storage, requests.cpu, requests.memory and 
requests.ephemeral-storage) are currently supported.'
+                                      properties:
+                                        containerName:
+                                          description: 'Container name: 
required for volumes, optional for env vars'
+                                          type: string
+                                        divisor:
+                                          anyOf:
+                                          - type: integer
+                                          - type: string
+                                          description: Specifies the output 
format of the exposed resources, defaults to "1"
+                                          pattern: 
^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                                          x-kubernetes-int-or-string: true
+                                        resource:
+                                          description: 'Required: resource to 
select'
+                                          type: string
+                                      required:
+                                      - resource
+                                      type: object
+                                    secretKeyRef:
+                                      description: Selects a key of a secret 
in the pod's namespace
+                                      properties:
+                                        key:
+                                          description: The key of the secret 
to select from.  Must be a valid secret key.
+                                          type: string
+                                        name:
+                                          description: 'Name of the referent. 
More info: 
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 
TODO: Add other useful fields. apiVersion, kind, uid?'
+                                          type: string
+                                        optional:
+                                          description: Specify whether the 
Secret or its key must be defined
+                                          type: boolean
+                                      required:
+                                      - key
+                                      type: object
+                                  type: object
+                              required:
+                              - name
+                              type: object
+                            type: array
                           nodeSelector:
                             additionalProperties:
                               type: string

Reply via email to