You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ge...@apache.org on 2025/01/06 19:44:06 UTC

(solr-operator) branch main updated: Migrate from zkcli.sh to 'solr zk' CLI tool (#738)

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 <35...@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 <ge...@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{