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

gerlowskija 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 6bd0fce  Migrate from zkcli.sh to 'solr zk' CLI tool (#738)
6bd0fce is described below

commit 6bd0fce03445dfffb66bd8d646b85864c287fe4a
Author: Michael <35507196+mcarro...@users.noreply.github.com>
AuthorDate: Mon Jan 6 14:44:01 2025 -0500

    Migrate from zkcli.sh to 'solr zk' CLI tool (#738)
    
    zkcli.sh is deprecated and will be removed in Solr 10.  Our existing
    use of it also wasn't quite flexible enough to gracefully handle
    missing "security.json" nodes.
    
    Migrating to "bin/solr zk" in our "setup-zk" initContainer fixes both
    of these issues.
    
    Closes #720
    Closes #731
    
    ---------
    
    Co-authored-by: Jason Gerlowski <gerlowsk...@apache.org>
---
 .../solrcloud_controller_basic_auth_test.go        | 11 ++-
 controllers/util/solr_security_util.go             | 18 ++++-
 docs/development.md                                |  5 ++
 helm/solr-operator/Chart.yaml                      |  9 +++
 tests/e2e/solrcloud_security_json_test.go          | 69 +++++++++++++++++
 tests/e2e/test_utils_test.go                       | 88 ++++++++++++++++++++++
 6 files changed, 192 insertions(+), 8 deletions(-)

diff --git a/controllers/solrcloud_controller_basic_auth_test.go 
b/controllers/solrcloud_controller_basic_auth_test.go
index cd3fbf5..c271393 100644
--- a/controllers/solrcloud_controller_basic_auth_test.go
+++ b/controllers/solrcloud_controller_basic_auth_test.go
@@ -351,10 +351,13 @@ func expectBasicAuthConfigOnPodTemplateWithGomega(g 
Gomega, solrCloud *solrv1bet
 
 func expectPutSecurityJsonInZkCmd(g Gomega, expInitContainer 
*corev1.Container) {
        g.Expect(expInitContainer).To(Not(BeNil()), "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 || echo 'failed-to-get-security.json'); " +
-               "if [ ${#ZK_SECURITY_JSON} -lt 3 ]; then " +
-               "echo $SECURITY_JSON > /tmp/security.json; " +
-               "/opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost 
${ZK_HOST} -cmd putfile /security.json /tmp/security.json; echo \"put 
security.json in ZK\"; fi"
+       expCmd := "solr zk cp zk:/security.json /tmp/current_security.json 
>/dev/null 2>&1;  " +
+               "GET_CURRENT_SECURITY_JSON_EXIT_CODE=$?; if [ 
${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 0 ]; then " +
+               "if [ ! -s /tmp/current_security.json ] || grep -q '^{}$' 
/tmp/current_security.json ]; then  " +
+               "echo $SECURITY_JSON > /tmp/security.json; solr zk cp 
/tmp/security.json zk:/security.json >/dev/null 2>&1; " +
+               " echo 'Blank security.json found. Put new security.json in 
ZK'; fi; elif [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 1 ]; then " +
+               " echo $SECURITY_JSON > /tmp/security.json; solr zk cp 
/tmp/security.json zk:/security.json >/dev/null 2>&1; " +
+               " echo 'No security.json found. Put new security.json in ZK'; 
fi"
        g.Expect(expInitContainer.Command[2]).To(ContainSubstring(expCmd), 
"setup-zk initContainer not configured to bootstrap security.json!")
 }
 
diff --git a/controllers/util/solr_security_util.go 
b/controllers/util/solr_security_util.go
index 97f7e60..2eb8679 100644
--- a/controllers/util/solr_security_util.go
+++ b/controllers/util/solr_security_util.go
@@ -237,10 +237,20 @@ func addHostHeaderToProbe(httpGet *corev1.HTTPGetAction, 
host string) {
 }
 
 func cmdToPutSecurityJsonInZk() string {
-       scriptsDir := "/opt/solr/server/scripts/cloud-scripts"
-       cmd := " ZK_SECURITY_JSON=$(%s/zkcli.sh -zkhost ${ZK_HOST} -cmd get 
/security.json || echo 'failed-to-get-security.json'); "
-       cmd += "if [ ${#ZK_SECURITY_JSON} -lt 3 ]; then echo $SECURITY_JSON > 
/tmp/security.json; %s/zkcli.sh -zkhost ${ZK_HOST} -cmd putfile /security.json 
/tmp/security.json; echo \"put security.json in ZK\"; fi"
-       return fmt.Sprintf(cmd, scriptsDir, scriptsDir)
+       cmd := " solr zk cp zk:/security.json /tmp/current_security.json 
>/dev/null 2>&1; " +
+               " GET_CURRENT_SECURITY_JSON_EXIT_CODE=$?; " +
+               "if [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 0 ]; then " + 
// JSON already exists
+               "if [ ! -s /tmp/current_security.json ] || grep -q '^{}$' 
/tmp/current_security.json ]; then " + // File doesn't exist, is empty, or is 
just '{}'
+               " echo $SECURITY_JSON > /tmp/security.json;" +
+               " solr zk cp /tmp/security.json zk:/security.json >/dev/null 
2>&1; " +
+               " echo 'Blank security.json found. Put new security.json in 
ZK'; " +
+               "fi; " + // TODO: Consider checking a diff and still applying 
over the top
+               "elif [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 1 ]; then " 
+ // JSON doesn't exist, but not other error types
+               " echo $SECURITY_JSON > /tmp/security.json;" +
+               " solr zk cp /tmp/security.json zk:/security.json >/dev/null 
2>&1; " +
+               " echo 'No security.json found. Put new security.json in ZK'; " 
+
+               "fi"
+       return cmd
 }
 
 // Add auth data to the supplied Context using secrets already resolved 
(stored in the SecurityConfig)
diff --git a/docs/development.md b/docs/development.md
index f8d005a..cf9907c 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -55,6 +55,11 @@ export PATH="$PATH:$GOPATH/bin" # You likely want to add 
this line to your ~/.ba
 make install-dependencies
 ```
 
+Note: if you have previously installed/older versions of dependencies, you can 
first clear these dependencies with:
+```bash
+make clean
+```
+
 ## Build the Solr CRDs
 
 If you have changed anything in the [APIs directory](/api/v1beta1), you will 
need to run the following command to regenerate all Solr CRDs.
diff --git a/helm/solr-operator/Chart.yaml b/helm/solr-operator/Chart.yaml
index af4b173..44ca43e 100644
--- a/helm/solr-operator/Chart.yaml
+++ b/helm/solr-operator/Chart.yaml
@@ -103,6 +103,15 @@ annotations:
           url: https://github.com/apache/solr-operator/issues/682
         - name: Github PR
           url: https://github.com/apache/solr-operator/pull/692
+    - kind: fixed
+      description: setup-zk initContainer now gracefully handles absent 
security.json on initial upload.
+      links:
+        - name: Github Issue
+          url: https://github.com/apache/solr-operator/issues/720
+        - name: Additional Github Issue
+          url: https://github.com/apache/solr-operator/issues/731
+        - name: Github PR
+          url: https://github.com/apache/solr-operator/pull/738
   artifacthub.io/images: |
     - name: solr-operator
       image: apache/solr-operator:v0.9.0-prerelease
diff --git a/tests/e2e/solrcloud_security_json_test.go 
b/tests/e2e/solrcloud_security_json_test.go
new file mode 100644
index 0000000..1ceef5e
--- /dev/null
+++ b/tests/e2e/solrcloud_security_json_test.go
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 e2e
+
+import (
+       "context"
+       solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+       "k8s.io/utils/pointer"
+)
+
+var _ = FDescribe("E2E - SolrCloud - Security JSON", func() {
+       var (
+               solrCloud *solrv1beta1.SolrCloud
+       )
+
+       BeforeEach(func() {
+               solrCloud = generateBaseSolrCloudWithSecurityJSON(1)
+       })
+
+       JustBeforeEach(func(ctx context.Context) {
+               By("generating the security.json secret and basic auth secret")
+               generateSolrSecuritySecret(ctx, solrCloud)
+               generateSolrBasicAuthSecret(ctx, solrCloud)
+
+               By("creating the SolrCloud")
+               Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed())
+
+               DeferCleanup(func(ctx context.Context) {
+                       cleanupTest(ctx, solrCloud)
+               })
+
+               By("Waiting for the SolrCloud to come up healthy")
+               solrCloud = expectSolrCloudToBeReady(ctx, solrCloud)
+
+               By("creating a first Solr Collection")
+               createAndQueryCollection(ctx, solrCloud, "basic", 1, 1)
+       })
+
+       FContext("Provided Zookeeper", func() {
+               BeforeEach(func() {
+                       solrCloud.Spec.ZookeeperRef = &solrv1beta1.ZookeeperRef{
+                               ProvidedZookeeper: &solrv1beta1.ZookeeperSpec{
+                                       Replicas:  pointer.Int32(1),
+                                       Ephemeral: &solrv1beta1.ZKEphemeral{},
+                               },
+                       }
+               })
+
+               // All testing will be done in the "JustBeforeEach" logic, no 
additional tests required here
+               FIt("Starts correctly", func(ctx context.Context) {})
+       })
+})
diff --git a/tests/e2e/test_utils_test.go b/tests/e2e/test_utils_test.go
index 95677d3..203b93e 100644
--- a/tests/e2e/test_utils_test.go
+++ b/tests/e2e/test_utils_test.go
@@ -581,6 +581,94 @@ func generateBaseSolrCloud(replicas int) 
*solrv1beta1.SolrCloud {
        }
 }
 
+// Uses default password from docs : SolrRocks
+// The hash is generated as: base64(sha256(sha256(salt+password))) 
base64(salt))
+// See 
https://solr.apache.org/guide/solr/latest/deployment-guide/basic-authentication-plugin.html
+func generateSolrSecuritySecret(ctx context.Context, solrCloud 
*solrv1beta1.SolrCloud) {
+       securityJsonSecret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      solrCloud.Name + "-security-secret",
+                       Namespace: solrCloud.Namespace,
+               },
+               StringData: map[string]string{
+                       "security.json": `{
+                               "authentication": {
+                               "blockUnknown": false,
+                               "class": "solr.BasicAuthPlugin",
+                               "credentials": {
+                                       "test-oper": 
"IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= 
Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c="
+                               },
+                               "realm": "Solr Basic Auth",
+                               "forwardCredentials": false
+                               },
+                               "authorization": {
+                               "class": "solr.RuleBasedAuthorizationPlugin",
+                               "user-role": {
+                                       "test-oper": "test-oper"
+                               },
+                               "permissions": [
+                                       {
+                                       "name": "cluster",
+                                       "role": null
+                                       },
+                                       {
+                                       "name": "collections",
+                                       "role": null,
+                                       "collection": "*"
+                                       }
+                               ]
+                               }
+                       }`,
+               },
+               Type: corev1.SecretTypeOpaque,
+       }
+       Expect(k8sClient.Create(ctx, securityJsonSecret)).To(Succeed(), "Failed 
to create secret for security json in namespace "+solrCloud.Namespace)
+
+       expectSecret(ctx, solrCloud, securityJsonSecret.Name)
+       return
+}
+
+func generateSolrBasicAuthSecret(ctx context.Context, solrCloud 
*solrv1beta1.SolrCloud) {
+       basicAuthSecret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      solrCloud.Name + "-basic-auth-secret",
+                       Namespace: solrCloud.Namespace,
+               },
+               // Using default creds
+               StringData: map[string]string{
+                       "username": "test-oper",
+                       "password": "SolrRocks",
+               },
+               Type: corev1.SecretTypeBasicAuth,
+       }
+       Expect(k8sClient.Create(ctx, basicAuthSecret)).To(Succeed(), "Failed to 
create secret for basic auth in namespace "+solrCloud.Namespace)
+
+       expectSecret(ctx, solrCloud, basicAuthSecret.Name)
+       return
+}
+
+func generateBaseSolrCloudWithSecurityJSON(replicas int) 
*solrv1beta1.SolrCloud {
+       solrCloud := generateBaseSolrCloud(replicas)
+
+       // Ensure SolrSecurity is initialized
+       if solrCloud.Spec.SolrSecurity == nil {
+               solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{}
+       }
+
+       solrCloud.Spec.SolrSecurity.BootstrapSecurityJson = 
&corev1.SecretKeySelector{
+               LocalObjectReference: corev1.LocalObjectReference{
+                       Name: solrCloud.Name + "-security-secret",
+               },
+               Key: "security.json",
+       }
+
+       solrCloud.Spec.SolrSecurity.AuthenticationType = "Basic"
+
+       solrCloud.Spec.SolrSecurity.BasicAuthSecret = solrCloud.Name + 
"-basic-auth-secret"
+
+       return solrCloud
+}
+
 func generateBaseSolrCloudWithPlacementPolicy(replicas int, placementPlugin 
string) *solrv1beta1.SolrCloud {
        solrCloud := generateBaseSolrCloud(replicas)
        solrCloud.Spec.CustomSolrKubeOptions.PodOptions.EnvVariables = 
[]corev1.EnvVar{

Reply via email to