Repository: kubesphere/ks-devops (go)

Issue:
When there are special characters in the visual editor shell, it throws an error
### What is version of KubeSphere DevOps has the issue?

v3.4.0

### How did you install the Kubernetes? Or what is the Kubernetes distribution?

kubekey

### What happened?

<img width="1440" alt="截屏2023-09-26 18 12 54" src="https://github.com/kubesphere/ks-devops/assets/5917832/b216d430-20e4-4e7e-900c-2b3e75a5ccda">

when there are " or \ characters in shell, will return a error



### Relevant log output

_No response_

### Additional information

_No response_

Pull request:
Fix shell validate failed
<!-- Thanks for sending a pull request! Here are some tips for you:

1. If you want **faster** PR reviews, read how: https://github.com/kubesphere/community/blob/master/developer-guide/development/the-pr-author-guide-to-getting-through-code-review.md
2. In case you want to know how your PR got reviewed, read: https://github.com/kubesphere/community/blob/master/developer-guide/development/code-review-guide.md
3. Here are some coding conventions followed by the KubeSphere community: https://github.com/kubesphere/community/blob/master/developer-guide/development/coding-conventions.md
4. Additional open-source best practice: https://github.com/LinuxSuRen/open-source-best-practice
-->

### What type of PR is this?
<!-- 
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design
/kind chore

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

### What this PR does / why we need it:

### Which issue(s) this PR fixes:
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
Please leave it or change # to be None if there is no corresponding issue that exists
-->
Fixes #

### Special notes for reviewers:
<!--
You can use the following command to let the DevOps SIG members help you to review your PR.
/cc @kubesphere/sig-devops 
And please avoid cc any individual.
-->
Please check the following list before waiting reviewers:

- [ ] Already committed the CRD files to [the Helm Chart](https://github.com/kubesphere-sigs/ks-devops-helm-chart/) if you created some new CRDs
- [ ] Already [added the permission](https://github.com/kubesphere/ks-installer/blob/9e063b085a0e43fdb3d0d9e3e7f4149146f14b9c/roles/ks-core/prepare/files/ks-init/role-templates.yaml) for the new API
- [ ] Already added the RBAC markers for the new controllers

### Does this PR introduce a user-facing change??
<!--
If no, just write "None" in the release-note block below.
If yes, a release note is required:
Enter your extended-release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

Please keep the note be same as your PR title if you believe it should be in the release notes.
-->
```release-note
Fix shell validate error
```

fix #1000 

Patch:
diff --git a/controllers/jenkins/pipeline/json_converter.go b/controllers/jenkins/pipeline/json_converter.go
index 2ed030e..5940480 100644
--- a/controllers/jenkins/pipeline/json_converter.go
+++ b/controllers/jenkins/pipeline/json_converter.go
@@ -112,6 +112,7 @@ func (r *JenkinsfileReconciler) reconcileJenkinsfileEditMode(pip *v1alpha3.Pipel
 	// Users are able to clean jenkinsfile
 	if jenkinsfile != "" {
 		var toJSONResult core.GenericResult
+		jenkinsfile = strings.ReplaceAll(jenkinsfile, "\\", "\\\\") // escape backslash
 		if toJSONResult, err = coreClient.ToJSON(jenkinsfile); err != nil || toJSONResult.GetStatus() != "success" {
 			r.log.Error(err, "failed to convert jenkinsfile to json format")
 			if err != nil {
@@ -168,9 +169,13 @@ func (r *JenkinsfileReconciler) reconcileJSONEditMode(pip *v1alpha3.Pipeline, pi
 			err = r.updateAnnotations(pip.Annotations, pipelineKey)
 			return
 		}
+		jenkinsfile := toResult.GetResult()
+		jenkinsfile = strings.ReplaceAll(jenkinsfile, "\\\\", "\\") // unescape backslash
+		jenkinsfile = strings.ReplaceAll(jenkinsfile, `\'`, `'`)    // unescape single quote
+
 		pip.Annotations[v1alpha3.PipelineJenkinsfileEditModeAnnoKey] = ""
 		pip.Annotations[v1alpha3.PipelineJenkinsfileValidateAnnoKey] = v1alpha3.PipelineJenkinsfileValidateSuccess
-		err = r.updateAnnotationsAndJenkinsfile(pip.Annotations, toResult.GetResult(), pipelineKey)
+		err = r.updateAnnotationsAndJenkinsfile(pip.Annotations, jenkinsfile, pipelineKey)
 	}
 	return
 }
diff --git a/pkg/api/devops/v1alpha3/steptemplate_render.go b/pkg/api/devops/v1alpha3/steptemplate_render.go
index 5a8cf32..004d55e 100644
--- a/pkg/api/devops/v1alpha3/steptemplate_render.go
+++ b/pkg/api/devops/v1alpha3/steptemplate_render.go
@@ -137,6 +137,10 @@ func dslRender(dslTpl string, param map[string]interface{}, secret *v1.Secret) (
 
 func shellRender(shellTpl string, param map[string]interface{}, secret *v1.Secret) (output string, err error) {
 	if output, err = dslRender(shellTpl, param, secret); err == nil {
+		escapedOutput := strings.ReplaceAll(output, `\`, `\\`)
+		escapedOutput = strings.ReplaceAll(escapedOutput, "\n", "\\n")
+		escapedOutput = strings.ReplaceAll(escapedOutput, `"`, `\"`)
+
 		output = fmt.Sprintf(`{
 "arguments": [
   {
@@ -148,7 +152,7 @@ func shellRender(shellTpl string, param map[string]interface{}, secret *v1.Secre
   }
 ],
 "name": "sh"
-}`, strings.ReplaceAll(output, "\n", "\\n"))
+}`, escapedOutput)
 	}
 	return
 }


Test patch:
diff --git a/pkg/api/devops/v1alpha3/steptemplate_render_test.go b/pkg/api/devops/v1alpha3/steptemplate_render_test.go
index b429d6c..ab9cf32 100644
--- a/pkg/api/devops/v1alpha3/steptemplate_render_test.go
+++ b/pkg/api/devops/v1alpha3/steptemplate_render_test.go
@@ -28,8 +28,16 @@ import (
 
 func Test_handler_stepTemplateRender(t *testing.T) {
 	stepTemplate := &StepTemplateSpec{
-		Template: `echo 1`,
-		Runtime:  "shell",
+		Template: `cat > log.sh << EOF
+        for ((i=1; i<=1000000; i++))
+        do
+            echo "Log message number \\$i: This is a sample log message."
+        done
+EOF
+
+        cat log.sh
+        bash log.sh`,
+		Runtime: "shell",
 	}
 	stepTemplateWithParameters := &StepTemplateSpec{
 		Template: `docker login -u $USERNAMEVARIABLE -p $PASSWORDVARIABLE
@@ -74,7 +82,7 @@ docker build {{.param.context}} -t {{.param.tag}} -f {{.param.dockerfile.path}}`
       "key": "script",
       "value": {
         "isLiteral": true,
-        "value": "echo 1"
+        "value": "cat > log.sh << EOF\n        for ((i=1; i<=1000000; i++))\n        do\n            echo \"Log message number \\\\$i: This is a sample log message.\"\n        done\nEOF\n\n        cat log.sh\n        bash log.sh"
       }
     }
   ],