Skip to content

Commit

Permalink
[release-3.4] Bugfix 924 token inconsistent (#936)
Browse files Browse the repository at this point in the history
* add devops-tools: init-config

* update dockerfile for devops-tool

* init config by jwt if kubesphere not exist

---------

Co-authored-by: jackyu <jackyu@yunify.com>
  • Loading branch information
ks-ci-bot and yudong2015 authored Apr 5, 2023
1 parent 86a5df9 commit 1c10b65
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 7 deletions.
151 changes: 151 additions & 0 deletions cmd/tools/app/init_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
Copyright 2023 The KubeSphere Authors.
Licensed 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 app

import (
"context"
"fmt"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
jwt "kubesphere.io/devops/cmd/tools/jwt/app"
"kubesphere.io/devops/pkg/client/k8s"
"kubesphere.io/devops/pkg/config"
)

type initConfigOption struct {
*ToolOption

ksNamespace string
ksConfigMapName string
}

func (o *initConfigOption) preRunE(cmd *cobra.Command, args []string) (err error) {
o.K8sClient, err = k8s.NewKubernetesClient(k8s.NewKubernetesOptions())
return
}

func (o *initConfigOption) runE(cmd *cobra.Command, args []string) (err error) {
// if kubesphere-config in namespace kubesphere-system not exist, generate devops.password by jwt
// used for deploy devops independence
if _, _, err = o.getKubesphereConfig(o.ksNamespace, o.ksConfigMapName); err != nil {
if !errors.IsNotFound(err) {
klog.Error("check if kubesphere-config exist failed")
return
}
klog.Infof("update configmap %s in %s by jwt", o.ConfigMapName, o.Namespace)
err = jwt.JwtFunc("", o.Namespace, o.ConfigMapName)
return
}

klog.Infof("get patch configuration from configmap %s in %s", o.ksConfigMapName, o.ksNamespace)
var updateConfig map[string]interface{}
if updateConfig, err = o.getPatchConfigFromKs(); err != nil {
return
}
if len(updateConfig) == 0 {
klog.Info("nothing need to update in configmap, ignore")
return
}

klog.Infof("update configmap %s in %s ..", o.ConfigMapName, o.Namespace)
err = o.updateKubeSphereConfig(updateConfig)
return
}

func (o initConfigOption) getKubesphereConfig(namespace, configmapName string) (cm *v1.ConfigMap, ksYaml map[string]interface{}, err error) {
if cm, err = o.K8sClient.Kubernetes().CoreV1().ConfigMaps(namespace).
Get(context.TODO(), configmapName, metav1.GetOptions{}); err != nil {
return
}

ksYaml = map[string]interface{}{}
err = yaml.Unmarshal([]byte(cm.Data[config.DefaultConfigurationFileName]), ksYaml)
return
}

func (o *initConfigOption) getPatchConfigFromKs() (conf map[string]interface{}, err error) {
var ksConf, devopsConf map[string]interface{}
if _, ksConf, err = o.getKubesphereConfig(o.ksNamespace, o.ksConfigMapName); err != nil {
return
}
if _, devopsConf, err = o.getKubesphereConfig(o.Namespace, o.ConfigMapName); err != nil {
return
}

conf = map[string]interface{}{}

password := ksConf["devops"].(map[string]interface{})["password"].(string)
devopsPassword := devopsConf["devops"].(map[string]interface{})["password"].(string)
if password == "" {
err = fmt.Errorf("the password in configmap %s is nil", o.ksConfigMapName)
return
}
if devopsPassword != password {
conf["devops"] = map[string]interface{}{"password": password}
}

if sonarqube, exist := ksConf["sonarQube"]; exist {
conf["sonarQube"] = sonarqube
}
return
}

func (o *initConfigOption) updateKubeSphereConfig(updateConfig map[string]interface{}) error {
devopsCm, devopsKsConf, err := o.getKubesphereConfig(o.Namespace, o.ConfigMapName)
if err != nil {
return fmt.Errorf("cannot found ConfigMap %s/%s, %v", o.Namespace, o.ConfigMapName, err)
}

patchedKubeSphereConfig, err := patchKubeSphereConfig(devopsKsConf, updateConfig)
if err != nil {
return fmt.Errorf("pathc kubesphere config error: %+v", err)
}
kubeSphereConfigBytes, err := yaml.Marshal(patchedKubeSphereConfig)
if err != nil {
return fmt.Errorf("cannot marshal KubeSphere configuration, %v", err)
}

devopsCm.Data["kubesphere.yaml"] = string(kubeSphereConfigBytes)
_, err = o.K8sClient.Kubernetes().CoreV1().ConfigMaps(o.Namespace).Update(context.TODO(), devopsCm, metav1.UpdateOptions{})
return err
}

// NewInitCmd creates a root command for init-config
func NewInitCmd() (cmd *cobra.Command) {
opt := &initConfigOption{
ToolOption: toolOpt,
}

initCmd := &cobra.Command{
Use: "init-config",
Short: "Initialize configurations for DevOps apiserver and controller-manager",
PreRunE: opt.preRunE,
RunE: opt.runE,
}

flags := initCmd.Flags()
flags.StringVar(&opt.ksNamespace, "ks-namespace", "kubesphere-system",
"The namespace of kubesphere namespace")
flags.StringVar(&opt.ksConfigMapName, "ks-configmap", "kubesphere-config",
"The name of kubesphere configmap")

return initCmd
}
55 changes: 55 additions & 0 deletions cmd/tools/app/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
Copyright 2023 The KubeSphere Authors.
Licensed 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 app

import (
"github.com/spf13/cobra"
"kubesphere.io/devops/pkg/client/k8s"
)

var toolOpt *ToolOption

type ToolOption struct {
Namespace string
ConfigMapName string

K8sClient k8s.Client
}

func (o *ToolOption) runHelpE(cmd *cobra.Command, args []string) error {
return cmd.Help()
}

// NewToolsCmd creates a root command for tools
func NewToolsCmd() (cmd *cobra.Command) {
toolOpt = &ToolOption{}

rootCmd := &cobra.Command{
Use: "devops-tool",
Short: "Tools for DevOps apiserver and controller-manager",
RunE: toolOpt.runHelpE,
}

flags := rootCmd.PersistentFlags()
flags.StringVarP(&toolOpt.Namespace, "namespace", "n", "kubesphere-devops-system",
"The namespace of DevOps service")
flags.StringVarP(&toolOpt.ConfigMapName, "configmap", "c", "devops-config",
"The configmap name of DevOps service")

rootCmd.AddCommand(NewInitCmd())
return rootCmd
}
44 changes: 44 additions & 0 deletions cmd/tools/app/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright 2023 The KubeSphere Authors.
Licensed 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 app

import (
"encoding/json"
jsonpatch "github.com/evanphx/json-patch"
)

// 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
}
return mergedMap, nil
}
36 changes: 36 additions & 0 deletions cmd/tools/jwt/app/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
Copyright 2023 The KubeSphere Authors.
Licensed 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 app

// JwtFunc a agent func to generate and update devops.password that called by others in different package
func JwtFunc(secret, namespace, configmap string) (err error) {
opt := &jwtOption{
secret: secret,
output: "configmap",
overrideJenkinsToken: true,
namespace: namespace,
name: configmap,
k8sClientFactory: &DefaultK8sClientFactory{},
}

if err = opt.preRunE(nil, []string{}); err != nil {
return
}

err = opt.runE(nil, []string{})
return
}
31 changes: 31 additions & 0 deletions cmd/tools/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2023 KubeSphere Authors
Licensed 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 main

import (
"os"

"kubesphere.io/devops/cmd/tools/app"
)

func main() {
command := app.NewToolsCmd()
if err := command.Execute(); err != nil {
command.PrintErrf("execute command error: %+v", err)
os.Exit(1)
}
}
10 changes: 6 additions & 4 deletions config/dockerfiles/tools/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Build the manager binary
# Build the tool binary
FROM golang:1.17 as builder

ARG GOPROXY
Expand All @@ -15,17 +15,19 @@ COPY cmd/ cmd/
COPY pkg/ pkg/

# Build
RUN CGO_ENABLED=0 GO111MODULE=on go build -a -o jwt cmd/tools/jwt/jwt_cmd.go
RUN CGO_ENABLED=0 GO111MODULE=on go build -a -o devops-tool cmd/tools/main.go


FROM golang:1.17 as downloader
RUN go install github.com/linuxsuren/http-downloader@v0.0.49
RUN http-downloader install kubesphere-sigs/ks@v0.0.70


# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /workspace/jwt /jwt
COPY --from=builder /workspace/devops-tool /usr/local/bin/devops-tool
COPY --from=downloader /usr/local/bin/ks /usr/local/bin/ks
USER nonroot:nonroot

CMD ["/jwt"]
CMD ["devops-tool"]
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ require (
sigs.k8s.io/yaml v1.3.0
)

require (
github.com/evanphx/json-patch v4.12.0+incompatible
gopkg.in/yaml.v3 v3.0.1
)

require (
code.gitea.io/sdk/gitea v0.14.0 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
Expand All @@ -58,7 +63,6 @@ require (
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bluekeyes/go-gitdiff v0.4.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-logr/zapr v1.2.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
Expand Down Expand Up @@ -125,7 +129,6 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.51.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
moul.io/http2curl v1.0.0 // indirect
Expand Down
4 changes: 3 additions & 1 deletion pkg/client/devops/jenkins/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"github.com/spf13/pflag"
)

const DefaultAdminPassword = "119d76305a05e7a2a65d096f71feb77921"

type Options struct {
Host string `json:",omitempty" yaml:"host" description:"Jenkins service host address"`
Username string `json:",omitempty" yaml:"username" description:"Jenkins admin username"`
Expand Down Expand Up @@ -71,7 +73,7 @@ func (s *Options) Validate() []error {
errors = append(errors, fmt.Errorf("jenkins's username or password is empty"))
}

if s.Password == "119d76305a05e7a2a65d096f71feb77921" {
if s.Password == DefaultAdminPassword {
errors = append(errors, fmt.Errorf("the token of the Jenkins needs to update"))
}

Expand Down

0 comments on commit 1c10b65

Please sign in to comment.