Skip to content

Commit

Permalink
Migrate SonarQube configuration from kubesphere-config into devops-co…
Browse files Browse the repository at this point in the history
…nfig (#245)

* Migrate SonarQube configuration from kubesphere-config into devops-config

Signed-off-by: John Niang <[email protected]>

* Rename mergeMap to patchKubeSphereConfig using json-patch library

Signed-off-by: John Niang <[email protected]>
  • Loading branch information
JohnNiang committed Dec 1, 2021
1 parent e1d85c3 commit b8bb7bc
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 42 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/Masterminds/semver v1.5.0
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/Pallinder/go-randomdata v1.2.0
github.com/evanphx/json-patch v4.9.0+incompatible
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1
github.com/huandu/xstrings v1.3.2 // indirect
github.com/linuxsuren/cobra-extension v0.0.11
Expand Down
125 changes: 83 additions & 42 deletions kubectl-plugin/config/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package config

import (
"context"
"encoding/json"
"errors"
"fmt"

jsonpatch "github.com/evanphx/json-patch"
"github.com/kubesphere-sigs/ks/kubectl-plugin/types"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"strings"
)

func newMigrateCmd(client dynamic.Interface) (cmd *cobra.Command) {
Expand Down Expand Up @@ -48,12 +50,26 @@ func (o *migrateOption) preRunE(cmd *cobra.Command, args []string) (err error) {

func (o *migrateOption) runE(cmd *cobra.Command, args []string) (err error) {
if err = o.updateKubeSphereConfig("kubesphere-config", "kubesphere-system", map[string]interface{}{
"enable": false,
"devopsServiceAddress": o.service,
"devops": map[string]interface{}{
"enable": false,
"devopsServiceAddress": o.service,
},
}); err != nil {
return
}

patchData := make(map[string]interface{})

kubesphereConfig, _, err := o.getKubeSphereConfig("kubesphere-config", "kubesphere-system")
if err != nil {
return err
}
if sonarQube, found, err := unstructured.NestedMap(kubesphereConfig, "sonarQube"); err != nil {
return err
} else if found {
patchData["sonarQube"] = sonarQube
}

var password string
if password, err = o.getDevOpsPassword(); password == "" {
if err == nil {
Expand All @@ -62,35 +78,38 @@ func (o *migrateOption) runE(cmd *cobra.Command, args []string) (err error) {
err = fmt.Errorf("the password of Jenkins is empty, it might caused by: %v", err)
}
} else if err == nil {
err = o.updateKubeSphereConfig("devops-config", o.namespace, map[string]interface{}{
patchData["devops"] = map[string]interface{}{
"password": password,
})
}
}
return

return o.updateKubeSphereConfig("devops-config", o.namespace, patchData)
}

func (o *migrateOption) updateKubeSphereConfig(name, namespace string, ksdataMap map[string]interface{}) (err error) {
var rawConfigMap *unstructured.Unstructured
if rawConfigMap, err = o.client.Resource(types.GetConfigMapSchema()).Namespace(namespace).
Get(context.TODO(), name, metav1.GetOptions{}); err == nil {
data := rawConfigMap.Object["data"]
dataMap := data.(map[string]interface{})
func (o *migrateOption) updateKubeSphereConfig(name, namespace string, ksdataMap map[string]interface{}) error {
kubeSphereConfig, rawConfigMap, err := o.getKubeSphereConfig(name, namespace)
if err != nil {
return fmt.Errorf("cannot found ConfigMap %s/%s, %v", namespace, name, err)
}

result := updateAuthWithObj(dataMap["kubesphere.yaml"].(string), ksdataMap)
if strings.TrimSpace(result) == "" {
err = fmt.Errorf("error happend when parse kubesphere-config")
return
}
patchedKubeSphereConfig, err := patchKubeSphereConfig(kubeSphereConfig, ksdataMap)
if err != nil {
return err
}
kubeSphereConfigBytes, err := yaml.Marshal(patchedKubeSphereConfig)
if err != nil {
return fmt.Errorf("cannot marshal KubeSphere configuration, %v", err)
}

rawConfigMap.Object["data"] = map[string]interface{}{
"kubesphere.yaml": result,
}
_, err = o.client.Resource(types.GetConfigMapSchema()).Namespace(namespace).Update(context.TODO(),
rawConfigMap, metav1.UpdateOptions{})
} else {
err = fmt.Errorf("cannot found configmap kubesphere-config, %v", err)
rawConfigMap.Object["data"] = map[string]interface{}{
"kubesphere.yaml": string(kubeSphereConfigBytes),
}
return
if _, err = o.client.Resource(types.GetConfigMapSchema()).
Namespace(namespace).
Update(context.TODO(), rawConfigMap, metav1.UpdateOptions{}); err != nil {
return err
}
return nil
}

func (o *migrateOption) getDevOpsPassword() (password string, err error) {
Expand All @@ -117,23 +136,45 @@ func (o *migrateOption) getDevOpsPassword() (password string, err error) {
return
}

func updateAuthWithObj(yamlf string, dataMap map[string]interface{}) string {
mapData := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(yamlf), mapData); err == nil {
var obj interface{}
var ok bool
var mapObj map[string]interface{}
if obj, ok = mapData["devops"]; ok {
mapObj = obj.(map[string]interface{})
} else {
mapObj = make(map[string]interface{})
mapData["devops"] = mapObj
}
func (o *migrateOption) getKubeSphereConfig(configMapName, namespace string) (map[string]interface{}, *unstructured.Unstructured, error) {
kubeSphereConfigCM, err := o.client.Resource((types.GetConfigMapSchema())).
Namespace(namespace).
Get(context.Background(), configMapName, metav1.GetOptions{})
if err != nil {
return nil, nil, fmt.Errorf("cannot found ConfigMap %s/%s, %v", namespace, configMapName, err)
}
kubeSphereConfigYAMLString, found, err := unstructured.NestedString(kubeSphereConfigCM.UnstructuredContent(), "data", "kubesphere.yaml")
if err != nil {
return nil, nil, err
}
if !found {
return nil, nil, fmt.Errorf("cannot found 'kubesphere.yaml' configuration in ConfigMap %s/%s", namespace, configMapName)
}
kubeSphereConfig := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(kubeSphereConfigYAMLString), kubeSphereConfig); err != nil {
return nil, nil, err
}
return kubeSphereConfig, kubeSphereConfigCM, nil
}

for key, val := range dataMap {
mapObj[key] = val
}
// patchKubeSphereConfig patches patch map into KubeSphereConfig map.
// Refer to https://github.com/evanphx/json-patch#create-and-apply-a-merge-patch.
func patchKubeSphereConfig(kubeSphereConfig map[string]interface{}, patch map[string]interface{}) (map[string]interface{}, error) {
kubeSphereConfigBytes, err := json.Marshal(kubeSphereConfig)
if err != nil {
return nil, err
}
patchBytes, err := json.Marshal(patch)
if err != nil {
return nil, err
}
mergedBytes, err := jsonpatch.MergePatch(kubeSphereConfigBytes, patchBytes)
if err != nil {
return nil, err
}
mergedMap := make(map[string]interface{})
if err := json.Unmarshal(mergedBytes, &mergedMap); err != nil {
return nil, err
}
resultData, _ := yaml.Marshal(mapData)
return string(resultData)
return mergedMap, nil
}
173 changes: 173 additions & 0 deletions kubectl-plugin/config/migrate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package config

import (
"reflect"
"testing"
)

func Test_patchKubeSphereConfig(t *testing.T) {
type args struct {
kubeSphereConfig map[string]interface{}
patch map[string]interface{}
}
tests := []struct {
name string
args args
want map[string]interface{}
wantErr bool
}{{
name: "Patch non-exist fields recursively",
args: args{
kubeSphereConfig: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
},
},
patch: map[string]interface{}{
"devops": map[string]interface{}{
"password": "fake password",
},
},
},
want: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
"password": "fake password",
},
},
}, {
name: "Patch non-exist field",
args: args{
kubeSphereConfig: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
},
},
patch: map[string]interface{}{
"sonarQube": map[string]interface{}{
"token": "fake token",
},
},
},
want: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
},
"sonarQube": map[string]interface{}{
"token": "fake token",
},
},
}, {
name: "Patch existing field",
args: args{
kubeSphereConfig: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
},
"sonarQube": map[string]interface{}{
"token": "fake token",
},
},
patch: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": false,
},
},
},
want: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": false,
},
"sonarQube": map[string]interface{}{
"token": "fake token",
},
},
}, {
name: "Report an error if patch is nil",
args: args{
kubeSphereConfig: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
},
},
patch: nil,
},
wantErr: true,
}, {
name: "Report an error if KubeSphereConfig is nil",
args: args{
kubeSphereConfig: nil,
patch: map[string]interface{}{},
},
wantErr: true,
}, {
name: "Patch with different type",
args: args{
kubeSphereConfig: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
},
},
patch: map[string]interface{}{
"devops": "awesome",
},
},
want: map[string]interface{}{
"devops": "awesome",
},
}, {
name: "Patch with map[string]string type",
args: args{
kubeSphereConfig: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
"password": "fake password",
},
},
patch: map[string]interface{}{
"devops": map[string]string{
"password": "patch password",
},
},
},
want: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
"password": "patch password",
},
},
}, {
name: "Patch without map[string]interface{}",
args: args{
kubeSphereConfig: map[string]interface{}{
"devops": map[string]bool{
"enabled": true,
},
},
patch: map[string]interface{}{
"devops": map[string]bool{
"disabled": false,
},
},
},
want: map[string]interface{}{
"devops": map[string]interface{}{
"enabled": true,
"disabled": false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := patchKubeSphereConfig(tt.args.kubeSphereConfig, tt.args.patch)
if (err != nil) != tt.wantErr {
t.Errorf("patchKubeSphereConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("patchKubeSphereConfig() = %+v, want = %+v", got, tt.want)
}
})
}
}

0 comments on commit b8bb7bc

Please sign in to comment.