This is a huge commit, it does following things:

1. refactor kubesphere dependency service client creation, we can
disable dependency by config
2. dependencies can be configured by configuration file
3. refactor cmd package using cobra.Command, so we can use hypersphere
to invoke command sepearately. Later we only need to build one image to
contains all kubesphere core components. One command to rule them all!
4. live reloading configuration currently not implemented
This commit is contained in:
Jeff
2019-09-03 15:20:22 +08:00
parent 52a1c2e619
commit 96d2ac4112
233 changed files with 26414 additions and 1927 deletions

View File

@@ -1,86 +0,0 @@
/*
Copyright 2018 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 admin_jenkins
import (
"flag"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/gojenkins"
"sync"
)
var (
jenkinsInitMutex sync.Mutex
jenkinsClient *gojenkins.Jenkins
jenkinsAdminAddress string
jenkinsAdminUsername string
jenkinsAdminPassword string
jenkinsMaxConn int
)
const (
JenkinsAllUserRoleName = "kubesphere-user"
)
func init() {
flag.StringVar(&jenkinsAdminAddress, "jenkins-address", "http://ks-jenkins.kubesphere-devops-system.svc/", "data source name")
flag.StringVar(&jenkinsAdminUsername, "jenkins-username", "admin", "username of jenkins")
flag.StringVar(&jenkinsAdminPassword, "jenkins-password", "passw0rd", "password of jenkins")
flag.IntVar(&jenkinsMaxConn, "jenkins-max-conn", 20, "max conn to jenkins")
}
func GetJenkins() *gojenkins.Jenkins {
jenkins := gojenkins.CreateJenkins(nil, jenkinsAdminAddress, jenkinsMaxConn, jenkinsAdminUsername, jenkinsAdminPassword)
return jenkins
}
func Client() *gojenkins.Jenkins {
if jenkinsClient == nil {
jenkinsInitMutex.Lock()
defer jenkinsInitMutex.Unlock()
if jenkinsClient == nil {
jenkins := GetJenkins()
jenkins, err := jenkins.Init()
if err != nil {
glog.Errorf("failed to connect jenkins, %+v", err)
return nil
}
globalRole, err := jenkins.GetGlobalRole(JenkinsAllUserRoleName)
if err != nil {
glog.Errorf("failed to get jenkins role, %+v", err)
return nil
}
if globalRole == nil {
_, err := jenkins.AddGlobalRole(JenkinsAllUserRoleName, gojenkins.GlobalPermissionIds{
GlobalRead: true,
}, true)
if err != nil {
glog.Errorf("failed to create jenkins global role, %+v", err)
return nil
}
}
_, err = jenkins.AddProjectRole(JenkinsAllUserRoleName, "\\n\\s*\\r", gojenkins.ProjectPermissionIds{
SCMTag: true,
}, true)
if err != nil {
glog.Errorf("failed to create jenkins project role, %+v", err)
return nil
}
jenkinsClient = jenkins
}
}
return jenkinsClient
}

View File

@@ -0,0 +1,109 @@
/*
Copyright 2018 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 devops
import (
"fmt"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/gojenkins"
"sync"
)
const (
JenkinsAllUserRoleName = "kubesphere-user"
)
type DevopsClient struct {
jenkinsClient *gojenkins.Jenkins
}
func NewDevopsClient(options *DevopsOptions) (*DevopsClient, error) {
var d DevopsClient
jenkins := gojenkins.CreateJenkins(nil, options.Host, options.MaxConnections, options.Username, options.Password)
jenkins, err := jenkins.Init()
if err != nil {
klog.Errorf("failed to connecto to jenkins role, %+v", err)
return nil, err
}
d.jenkinsClient = jenkins
err = d.initializeJenkins()
if err != nil {
klog.Error(err)
return nil, err
}
return &d, nil
}
func NewDevopsClientOrDie(options *DevopsOptions) *DevopsClient {
jenkins := gojenkins.CreateJenkins(nil, options.Host, options.MaxConnections, options.Username, options.Password)
jenkins, err := jenkins.Init()
if err != nil {
klog.Errorf("failed to connecto to jenkins role, %+v", err)
panic(err)
}
d := &DevopsClient{
jenkinsClient: jenkins,
}
err = d.initializeJenkins()
if err != nil {
klog.Error(err)
panic(err)
}
return d
}
func (c *DevopsClient) Jenkins() *gojenkins.Jenkins {
return c.jenkinsClient
}
var mutex = sync.Mutex{}
func (c *DevopsClient) initializeJenkins() error {
mutex.Lock()
defer mutex.Unlock()
if c.jenkinsClient == nil {
return fmt.Errorf("jenkins intialization failed")
}
globalRole, err := c.jenkinsClient.GetGlobalRole(JenkinsAllUserRoleName)
if err != nil {
klog.Error(err)
return err
}
// Jenkins uninitialized, create global role
if globalRole == nil {
_, err := c.jenkinsClient.AddGlobalRole(JenkinsAllUserRoleName, gojenkins.GlobalPermissionIds{GlobalRead: true}, true)
if err != nil {
klog.Error(err)
return err
}
}
_, err = c.jenkinsClient.AddProjectRole(JenkinsAllUserRoleName, "\\n\\s*\\r", gojenkins.ProjectPermissionIds{SCMTag: true}, true)
if err != nil {
klog.Error(err)
return err
}
return nil
}

View File

@@ -0,0 +1,81 @@
package devops
import (
"fmt"
"github.com/spf13/pflag"
)
type DevopsOptions struct {
Host string `json:",omitempty" yaml:",omitempty" description:"Jenkins service host address"`
Username string `json:",omitempty" yaml:",omitempty" description:"Jenkins admin username"`
Password string `json:",omitempty" yaml:",omitempty" description:"Jenkins admin password"`
MaxConnections int `json:"maxConnections,omitempty" yaml:"maxConnections,omitempty" description:"Maximum connections allowed to connect to Jenkins"`
}
// NewDevopsOptions returns a `zero` instance
func NewDevopsOptions() *DevopsOptions {
return &DevopsOptions{
Host: "",
Username: "",
Password: "",
MaxConnections: 100,
}
}
func (s *DevopsOptions) ApplyTo(options *DevopsOptions) {
if options == nil {
return
}
if s.Host != "" {
options.Host = s.Host
}
if s.Username != "" {
options.Username = s.Username
}
if s.Password != "" {
options.Password = s.Password
}
if s.MaxConnections > 0 {
options.MaxConnections = s.MaxConnections
}
}
//
func (s *DevopsOptions) Validate() []error {
errors := []error{}
// devops is not needed, ignore rest options
if s.Host == "" {
return errors
}
if s.Username == "" || s.Password == "" {
errors = append(errors, fmt.Errorf("jenkins's username or password is empty"))
}
if s.MaxConnections <= 0 {
errors = append(errors, fmt.Errorf("jenkins's maximum connections should be greater than 0"))
}
return errors
}
func (s *DevopsOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.Host, "jenkins-host", s.Host, ""+
"Jenkins service host address. If left blank, means Jenkins "+
"is unnecessary.")
fs.StringVar(&s.Username, "jenkins-username", s.Username, ""+
"Username for access to Jenkins service. Leave it blank if there isn't any.")
fs.StringVar(&s.Password, "jenkins-password", s.Password, ""+
"Password for access to Jenkins service, used pair with username.")
fs.IntVar(&s.MaxConnections, "jenkins-max-connections", s.MaxConnections, ""+
"Maximum allowed connections to Jenkins. ")
}

View File

@@ -1,56 +0,0 @@
/*
Copyright 2018 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 devops_mysql
import (
"flag"
"github.com/gocraft/dbr"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/db"
"sync"
"time"
)
var (
dbClientOnce sync.Once
dsn string
dbClient *db.Database
)
func init() {
flag.StringVar(&dsn, "devops-database-connection", "root:password@tcp(openpitrix-db.openpitrix-system.svc:3306)/devops", "data source name")
}
var defaultEventReceiver = db.EventReceiver{}
func OpenDatabase() *db.Database {
dbClientOnce.Do(func() {
conn, err := dbr.Open("mysql", dsn+"?parseTime=1&multiStatements=1&charset=utf8mb4&collation=utf8mb4_unicode_ci", &defaultEventReceiver)
if err != nil {
glog.Fatal(err)
}
conn.SetMaxIdleConns(100)
conn.SetMaxOpenConns(100)
conn.SetConnMaxLifetime(10 * time.Second)
dbClient = &db.Database{
Session: conn.NewSession(nil),
}
err = dbClient.Ping()
if err != nil {
glog.Error(err)
}
})
return dbClient
}

View File

@@ -0,0 +1,315 @@
package client
import (
"fmt"
goredis "github.com/go-redis/redis"
"kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
"kubesphere.io/kubesphere/pkg/simple/client/prometheus"
"kubesphere.io/kubesphere/pkg/simple/client/redis"
"kubesphere.io/kubesphere/pkg/simple/client/s2is3"
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
"sync"
)
type ClientSetNotEnabledError struct {
err error
}
func (e ClientSetNotEnabledError) Error() string {
return fmt.Sprintf("client set not enabled: %s", e.err.Error())
}
type ClientSetOptions struct {
mySQLOptions *mysql.MySQLOptions
redisOptions *redis.RedisOptions
kubernetesOptions *k8s.KubernetesOptions
devopsOptions *devops.DevopsOptions
sonarqubeOptions *sonarqube.SonarQubeOptions
ldapOptions *ldap.LdapOptions
s3Options *s2is3.S3Options
openPitrixOptions *openpitrix.OpenPitrixOptions
prometheusOptions *prometheus.PrometheusOptions
}
func NewClientSetOptions() *ClientSetOptions {
return &ClientSetOptions{
mySQLOptions: mysql.NewMySQLOptions(),
redisOptions: redis.NewRedisOptions(),
kubernetesOptions: k8s.NewKubernetesOptions(),
ldapOptions: ldap.NewLdapOptions(),
devopsOptions: devops.NewDevopsOptions(),
sonarqubeOptions: sonarqube.NewSonarQubeOptions(),
s3Options: s2is3.NewS3Options(),
openPitrixOptions: openpitrix.NewOpenPitrixOptions(),
prometheusOptions: prometheus.NewPrometheusOptions(),
}
}
func (c *ClientSetOptions) SetMySQLOptions(options *mysql.MySQLOptions) *ClientSetOptions {
c.mySQLOptions = options
return c
}
func (c *ClientSetOptions) SetRedisOptions(options *redis.RedisOptions) *ClientSetOptions {
c.redisOptions = options
return c
}
func (c *ClientSetOptions) SetKubernetesOptions(options *k8s.KubernetesOptions) *ClientSetOptions {
c.kubernetesOptions = options
return c
}
func (c *ClientSetOptions) SetDevopsOptions(options *devops.DevopsOptions) *ClientSetOptions {
c.devopsOptions = options
return c
}
func (c *ClientSetOptions) SetLdapOptions(options *ldap.LdapOptions) *ClientSetOptions {
c.ldapOptions = options
return c
}
func (c *ClientSetOptions) SetS3Options(options *s2is3.S3Options) *ClientSetOptions {
c.s3Options = options
return c
}
func (c *ClientSetOptions) SetOpenPitrixOptions(options *openpitrix.OpenPitrixOptions) *ClientSetOptions {
c.openPitrixOptions = options
return c
}
func (c *ClientSetOptions) SetPrometheusOptions(options *prometheus.PrometheusOptions) *ClientSetOptions {
c.prometheusOptions = options
return c
}
// ClientSet provide best of effort service to initialize clients,
// but there is no guarantee to return a valid client instance,
// so do validity check before use
type ClientSet struct {
csoptions *ClientSetOptions
stopCh <-chan struct{}
mySQLClient *mysql.MySQLClient
k8sClient *k8s.KubernetesClient
ldapClient *ldap.LdapClient
devopsClient *devops.DevopsClient
sonarQubeClient *sonarqube.SonarQubeClient
redisClient *redis.RedisClient
s3Client *s2is3.S3Client
prometheusClient *prometheus.PrometheusClient
openpitrixClient *openpitrix.OpenPitrixClient
}
var mutex sync.Mutex
// global clientsets instance
var sharedClientSet *ClientSet
func ClientSets() *ClientSet {
return sharedClientSet
}
func NewClientSetFactory(c *ClientSetOptions, stopCh <-chan struct{}) *ClientSet {
sharedClientSet = &ClientSet{csoptions: c, stopCh: stopCh}
if c.kubernetesOptions != nil {
sharedClientSet.k8sClient = k8s.NewKubernetesClientOrDie(c.kubernetesOptions)
}
return sharedClientSet
}
// lazy creating
func (cs *ClientSet) MySQL() (*mysql.Database, error) {
var err error
if cs.csoptions.mySQLOptions == nil || cs.csoptions.mySQLOptions.Host == "" {
return nil, ClientSetNotEnabledError{}
}
if cs.mySQLClient != nil {
return cs.mySQLClient.Database(), nil
} else {
mutex.Lock()
defer mutex.Unlock()
if cs.mySQLClient == nil {
cs.mySQLClient, err = mysql.NewMySQLClient(cs.csoptions.mySQLOptions, cs.stopCh)
if err != nil {
return nil, err
}
}
return cs.mySQLClient.Database(), nil
}
}
func (cs *ClientSet) Redis() (*goredis.Client, error) {
var err error
if cs.csoptions.redisOptions == nil || cs.csoptions.redisOptions.Host == "" {
return nil, ClientSetNotEnabledError{}
}
if cs.redisClient != nil {
return cs.redisClient.Redis(), nil
} else {
mutex.Lock()
defer mutex.Unlock()
if cs.redisClient == nil {
cs.redisClient, err = redis.NewRedisClient(cs.csoptions.redisOptions, cs.stopCh)
if err != nil {
return nil, err
}
}
return cs.redisClient.Redis(), nil
}
}
func (cs *ClientSet) Devops() (*devops.DevopsClient, error) {
var err error
if cs.csoptions.devopsOptions == nil || cs.csoptions.devopsOptions.Host == "" {
return nil, ClientSetNotEnabledError{}
}
if cs.devopsClient != nil {
return cs.devopsClient, nil
} else {
mutex.Lock()
defer mutex.Unlock()
if cs.devopsClient == nil {
cs.devopsClient, err = devops.NewDevopsClient(cs.csoptions.devopsOptions)
if err != nil {
return nil, err
}
}
return cs.devopsClient, nil
}
}
func (cs *ClientSet) SonarQube() (*sonarqube.SonarQubeClient, error) {
var err error
if cs.csoptions.sonarqubeOptions == nil || cs.csoptions.sonarqubeOptions.Host == "" {
return nil, ClientSetNotEnabledError{}
}
if cs.sonarQubeClient != nil {
return cs.sonarQubeClient, nil
} else {
mutex.Lock()
defer mutex.Unlock()
if cs.sonarQubeClient == nil {
cs.sonarQubeClient, err = sonarqube.NewSonarQubeClient(cs.csoptions.sonarqubeOptions)
if err != nil {
return nil, err
}
}
return cs.sonarQubeClient, nil
}
}
func (cs *ClientSet) Ldap() (*ldap.LdapClient, error) {
var err error
if cs.csoptions.ldapOptions == nil || cs.csoptions.ldapOptions.Host == "" {
return nil, ClientSetNotEnabledError{}
}
if cs.ldapClient != nil {
return cs.ldapClient, nil
} else {
mutex.Lock()
defer mutex.Unlock()
if cs.ldapClient == nil {
cs.ldapClient, err = ldap.NewLdapClient(cs.csoptions.ldapOptions, cs.stopCh)
if err != nil {
return nil, err
}
}
return cs.ldapClient, nil
}
}
// since kubernetes client is required, we will
// create it on setup
func (cs *ClientSet) K8s() *k8s.KubernetesClient {
return cs.k8sClient
}
func (cs *ClientSet) S3() (*s2is3.S3Client, error) {
var err error
if cs.csoptions.s3Options == nil || cs.csoptions.s3Options.Endpoint == "" {
return nil, ClientSetNotEnabledError{}
}
if cs.s3Client != nil {
return cs.s3Client, nil
} else {
mutex.Lock()
defer mutex.Unlock()
if cs.s3Client == nil {
cs.s3Client, err = s2is3.NewS3Client(cs.csoptions.s3Options)
if err != nil {
return nil, err
}
}
return cs.s3Client, nil
}
}
func (cs *ClientSet) OpenPitrix() (*openpitrix.OpenPitrixClient, error) {
var err error
if cs.csoptions.openPitrixOptions == nil || cs.csoptions.openPitrixOptions.APIServer == "" {
return nil, ClientSetNotEnabledError{}
}
if cs.openpitrixClient != nil {
return cs.openpitrixClient, nil
} else {
cs.openpitrixClient, err = openpitrix.NewOpenPitrixClient(cs.csoptions.openPitrixOptions)
if err != nil {
return nil, err
}
return cs.openpitrixClient, nil
}
}
func (cs *ClientSet) Prometheus() (*prometheus.PrometheusClient, error) {
var err error
if cs.csoptions.prometheusOptions == nil || cs.csoptions.prometheusOptions.Endpoint == "" {
return nil, ClientSetNotEnabledError{}
}
if cs.prometheusClient != nil {
return cs.prometheusClient, nil
} else {
mutex.Lock()
defer mutex.Unlock()
if cs.prometheusClient == nil {
cs.prometheusClient, err = prometheus.NewPrometheusClient(cs.csoptions.prometheusOptions)
if err != nil {
return nil, err
}
}
return cs.prometheusClient, nil
}
}

View File

@@ -1,79 +0,0 @@
/*
Copyright 2019 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 k8s
import (
"flag"
"log"
"os"
"sync"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
var (
kubeConfigFile string
k8sClient *kubernetes.Clientset
k8sClientOnce sync.Once
KubeConfig *rest.Config
MasterURL string
)
func init() {
flag.StringVar(&kubeConfigFile, "kubeconfig-path", "", "path to kubeconfig file")
flag.StringVar(&MasterURL, "master-url", "", "kube-apiserver url, only needed when out of cluster")
}
func Client() *kubernetes.Clientset {
k8sClientOnce.Do(func() {
config, err := Config()
if err != nil {
log.Fatalln(err)
}
k8sClient = kubernetes.NewForConfigOrDie(config)
KubeConfig = config
})
return k8sClient
}
func Config() (kubeConfig *rest.Config, err error) {
if _, err = os.Stat(kubeConfigFile); err == nil {
kubeConfig, err = clientcmd.BuildConfigFromFlags(MasterURL, kubeConfigFile)
} else {
kubeConfig, err = rest.InClusterConfig()
}
if err != nil {
return nil, err
}
kubeConfig.QPS = 1e6
kubeConfig.Burst = 1e6
return kubeConfig, nil
}

View File

@@ -1,46 +0,0 @@
/*
Copyright 2019 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 k8s
import (
"log"
"sync"
ks "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
)
var (
ksClient *ks.Clientset
ksClientOnce sync.Once
)
func KsClient() *ks.Clientset {
ksClientOnce.Do(func() {
config, err := Config()
if err != nil {
log.Fatalln(err)
}
ksClient = ks.NewForConfigOrDie(config)
})
return ksClient
}

View File

@@ -0,0 +1,100 @@
package k8s
import (
s2i "github.com/kubesphere/s2ioperator/pkg/client/clientset/versioned"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
)
type KubernetesClient struct {
// kubernetes client interface
k8s *kubernetes.Clientset
// generated clientset
ks *kubesphere.Clientset
s2i *s2i.Clientset
master string
config *rest.Config
}
// NewKubernetesClient
func NewKubernetesClientOrDie(options *KubernetesOptions) *KubernetesClient {
config, err := clientcmd.BuildConfigFromFlags("", options.KubeConfig)
if err != nil {
panic(err)
}
config.QPS = options.QPS
config.Burst = options.Burst
k := &KubernetesClient{
k8s: kubernetes.NewForConfigOrDie(config),
ks: kubesphere.NewForConfigOrDie(config),
s2i: s2i.NewForConfigOrDie(config),
master: config.Host,
config: config,
}
if options.Master != "" {
k.master = options.Master
}
return k
}
func NewKubernetesClient(options *KubernetesOptions) (*KubernetesClient, error) {
config, err := clientcmd.BuildConfigFromFlags("", options.KubeConfig)
if err != nil {
return nil, err
}
config.QPS = options.QPS
config.Burst = options.Burst
var k KubernetesClient
k.k8s, err = kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
k.ks, err = kubesphere.NewForConfig(config)
if err != nil {
return nil, err
}
k.s2i, err = s2i.NewForConfig(config)
if err != nil {
return nil, err
}
k.master = options.Master
k.config = config
return &k, nil
}
func (k *KubernetesClient) Kubernetes() kubernetes.Interface {
return k.k8s
}
func (k *KubernetesClient) KubeSphere() kubesphere.Interface {
return k.ks
}
func (k *KubernetesClient) S2i() s2i.Interface {
return k.s2i
}
// master address used to generate kubeconfig for downloading
func (k *KubernetesClient) Master() string {
return k.master
}
func (k *KubernetesClient) Config() *rest.Config {
return k.config
}

View File

@@ -0,0 +1,59 @@
package k8s
import (
"github.com/spf13/pflag"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
"os"
)
type KubernetesOptions struct {
// kubeconfig path, if not specified, will use
// in cluster way to create clientset
KubeConfig string `json:"kubeconfig" yaml:"kubeconfig"`
// kubernetes apiserver public address, used to generate kubeconfig
// for downloading, default to host defined in kubeconfig
// +optional
Master string `json:"master,omitempty" yaml:"master,omitempty"`
// kubernetes clientset qps
// +optional
QPS float32 `json:"qps,omitemtpy" yaml:"qps,omitempty"`
// kubernetes clientset burst
// +optional
Burst int `json:"burst,omitempty" yaml:"burst,omitempty"`
}
// NewKubernetesOptions returns a `zero` instance
func NewKubernetesOptions() *KubernetesOptions {
return &KubernetesOptions{
KubeConfig: "",
QPS: 1e6,
Burst: 1e6,
}
}
func (k *KubernetesOptions) Validate() []error {
errors := []error{}
if k.KubeConfig != "" {
if _, err := os.Stat(k.KubeConfig); err != nil {
errors = append(errors, err)
}
}
return errors
}
func (k *KubernetesOptions) ApplyTo(options *KubernetesOptions) {
reflectutils.Override(options, k)
}
func (k *KubernetesOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&k.KubeConfig, "kubeconfig", k.KubeConfig, ""+
"Path for kubernetes kubeconfig file, if left blank, will use "+
"in cluster way.")
fs.StringVar(&k.Master, "master", k.Master, ""+
"Used to generate kubeconfig for downloading, if not specified, will use host in kubeconfig.")
}

View File

@@ -1,46 +0,0 @@
/*
Copyright 2019 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 k8s
import (
"log"
"sync"
s2i "github.com/kubesphere/s2ioperator/pkg/client/clientset/versioned"
)
var (
s2iClient *s2i.Clientset
s2iClientOnce sync.Once
)
func S2iClient() *s2i.Clientset {
s2iClientOnce.Do(func() {
config, err := Config()
if err != nil {
log.Fatalln(err)
}
s2iClient = s2i.NewForConfigOrDie(config)
})
return s2iClient
}

View File

@@ -0,0 +1 @@
package kubesphere

View File

@@ -0,0 +1,87 @@
/*
Copyright 2019 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 ldap
import (
"github.com/go-ldap/ldap"
"k8s.io/klog"
)
type LdapClient struct {
pool Pool
options *LdapOptions
}
// panic if cannot connect to ldap service
func NewLdapClient(options *LdapOptions, stopCh <-chan struct{}) (*LdapClient, error) {
pool, err := NewChannelPool(8, 64, "kubesphere", func(s string) (ldap.Client, error) {
conn, err := ldap.Dial("tcp", options.Host)
if err != nil {
return nil, err
}
return conn, nil
}, []uint16{ldap.LDAPResultAdminLimitExceeded, ldap.ErrorNetwork})
if err != nil {
klog.Error(err)
pool.Close()
return nil, err
}
client := &LdapClient{
pool: pool,
options: options,
}
go func() {
<-stopCh
if client.pool != nil {
client.pool.Close()
}
}()
return client, nil
}
func (l *LdapClient) Ldap() ldap.Client {
if l.pool != nil {
conn, err := l.pool.Get()
if err != nil {
klog.Error(err)
return nil
}
err = conn.Bind(l.options.ManagerDN, l.options.ManagerPassword)
if err != nil {
conn.Close()
klog.Error(err)
return nil
}
return conn
}
return nil
}
func (l *LdapClient) GroupSearchBase() string {
return l.options.GroupSearchBase
}
func (l *LdapClient) UserSearchBase() string {
return l.options.UserSearchBase
}

View File

@@ -1,84 +0,0 @@
/*
Copyright 2019 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 ldap
import (
"flag"
"github.com/go-ldap/ldap"
"github.com/golang/glog"
"log"
"sync"
)
var (
once sync.Once
pool Pool
ldapHost string
ManagerDN string
ManagerPassword string
UserSearchBase string
GroupSearchBase string
poolSize int
)
func init() {
flag.StringVar(&ldapHost, "ldap-server", "localhost:389", "ldap server host")
flag.StringVar(&ManagerDN, "ldap-manager-dn", "cn=admin,dc=example,dc=org", "ldap manager dn")
flag.StringVar(&ManagerPassword, "ldap-manager-password", "admin", "ldap manager password")
flag.StringVar(&UserSearchBase, "ldap-user-search-base", "ou=Users,dc=example,dc=org", "ldap user search base")
flag.StringVar(&GroupSearchBase, "ldap-group-search-base", "ou=Groups,dc=example,dc=org", "ldap group search base")
flag.IntVar(&poolSize, "ldap-pool-size", 64, "ldap connection pool size")
}
func ldapClientPool() Pool {
once.Do(func() {
var err error
pool, err = NewChannelPool(8, poolSize, "kubesphere", func(s string) (ldap.Client, error) {
conn, err := ldap.Dial("tcp", ldapHost)
if err != nil {
return nil, err
}
return conn, nil
}, []uint16{ldap.LDAPResultTimeLimitExceeded, ldap.ErrorNetwork})
if err != nil {
log.Fatalln(err)
}
})
return pool
}
func Client() (ldap.Client, error) {
conn, err := ldapClientPool().Get()
if err != nil {
glog.Errorln("get ldap connection from pool", err)
return nil, err
}
err = conn.Bind(ManagerDN, ManagerPassword)
if err != nil {
conn.Close()
glog.Errorln("bind manager dn", err)
return nil, err
}
return conn, nil
}

View File

@@ -0,0 +1,53 @@
package ldap
import (
"github.com/spf13/pflag"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
)
type LdapOptions struct {
Host string `json:"host,omitempty" yaml:"host,omitempty"`
ManagerDN string `json:"managerDN,omitempty" yaml:"managerDN,omitempty"`
ManagerPassword string `json:"managerPassword,omitempty" yaml:"managerPassword,omitempty"`
UserSearchBase string `json:"userSearchBase,omitempty" yaml:"userSearchBase,omitempty"`
GroupSearchBase string `json:"groupSearchBase,omitempty" yaml:"groupSearchBase,omitempty"`
}
// NewLdapOptions return a default option
// which host field point to nowhere.
func NewLdapOptions() *LdapOptions {
return &LdapOptions{
Host: "",
ManagerDN: "cn=admin,dc=example,dc=org",
UserSearchBase: "ou=Users,dc=example,dc=org",
GroupSearchBase: "ou=Groups,dc=example,dc=org",
}
}
func (l *LdapOptions) Validate() []error {
errors := []error{}
return errors
}
func (l *LdapOptions) ApplyTo(options *LdapOptions) {
reflectutils.Override(options, l)
}
func (l *LdapOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&l.Host, "ldap-host", l.Host, ""+
"Ldap service host, if left blank, all of the following options will "+
"be ignored and ldap will be disabled.")
fs.StringVar(&l.ManagerDN, "ldap-manager-dn", l.ManagerDN, ""+
"Ldap manager account domain name.")
fs.StringVar(&l.ManagerPassword, "ldap-manager-password", l.ManagerPassword, ""+
"Ldap manager account password.")
fs.StringVar(&l.UserSearchBase, "ldap-user-search-base", l.UserSearchBase, ""+
"Ldap user search base.")
fs.StringVar(&l.GroupSearchBase, "ldap-group-search-base", l.GroupSearchBase, ""+
"Ldap group search base.")
}

View File

@@ -0,0 +1,109 @@
/*
Copyright 2018 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 mysql
import (
"strings"
"github.com/gocraft/dbr"
)
const (
placeholder = "?"
)
type EqCondition struct {
dbr.Builder
Column string
Value interface{}
}
// Copy From vendor/github.com/gocraft/dbr/condition.go:36
func buildCmp(d dbr.Dialect, buf dbr.Buffer, pred string, column string, value interface{}) error {
buf.WriteString(d.QuoteIdent(column))
buf.WriteString(" ")
buf.WriteString(pred)
buf.WriteString(" ")
buf.WriteString(placeholder)
buf.WriteValue(value)
return nil
}
// And creates AND from a list of conditions
func And(cond ...dbr.Builder) dbr.Builder {
return dbr.And(cond...)
}
// Or creates OR from a list of conditions
func Or(cond ...dbr.Builder) dbr.Builder {
return dbr.Or(cond...)
}
func escape(str string) string {
return strings.Map(func(r rune) rune {
switch r {
case '%', '\'', '^', '[', ']', '!', '_':
return ' '
}
return r
}, str)
}
func Like(column string, value string) dbr.Builder {
value = "%" + strings.TrimSpace(escape(value)) + "%"
return dbr.BuildFunc(func(d dbr.Dialect, buf dbr.Buffer) error {
return buildCmp(d, buf, "LIKE", column, value)
})
}
// Eq is `=`.
// When value is nil, it will be translated to `IS NULL`.
// When value is a slice, it will be translated to `IN`.
// Otherwise it will be translated to `=`.
func Eq(column string, value interface{}) dbr.Builder {
return &EqCondition{
Builder: dbr.Eq(column, value),
Column: column,
Value: value,
}
}
// Neq is `!=`.
// When value is nil, it will be translated to `IS NOT NULL`.
// When value is a slice, it will be translated to `NOT IN`.
// Otherwise it will be translated to `!=`.
func Neq(column string, value interface{}) dbr.Builder {
return dbr.Neq(column, value)
}
// Gt is `>`.
func Gt(column string, value interface{}) dbr.Builder {
return dbr.Gt(column, value)
}
// Gte is '>='.
func Gte(column string, value interface{}) dbr.Builder {
return dbr.Gte(column, value)
}
// Lt is '<'.
func Lt(column string, value interface{}) dbr.Builder {
return dbr.Lt(column, value)
}
// Lte is `<=`.
func Lte(column string, value interface{}) dbr.Builder {
return dbr.Lte(column, value)
}

View File

@@ -0,0 +1,88 @@
/*
Copyright 2018 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 mysql
import (
"testing"
"github.com/gocraft/dbr"
"github.com/gocraft/dbr/dialect"
"github.com/stretchr/testify/assert"
)
// Ref: https://github.com/gocraft/dbr/blob/5d59a8b3aa915660960329efb3af5513e7a0db07/condition_test.go
func TestCondition(t *testing.T) {
for _, test := range []struct {
cond dbr.Builder
query string
value []interface{}
}{
{
cond: Eq("col", 1),
query: "`col` = ?",
value: []interface{}{1},
},
{
cond: Eq("col", nil),
query: "`col` IS NULL",
value: nil,
},
{
cond: Eq("col", []int{}),
query: "0",
value: nil,
},
{
cond: Neq("col", 1),
query: "`col` != ?",
value: []interface{}{1},
},
{
cond: Neq("col", nil),
query: "`col` IS NOT NULL",
value: nil,
},
{
cond: Gt("col", 1),
query: "`col` > ?",
value: []interface{}{1},
},
{
cond: Gte("col", 1),
query: "`col` >= ?",
value: []interface{}{1},
},
{
cond: Lt("col", 1),
query: "`col` < ?",
value: []interface{}{1},
},
{
cond: Lte("col", 1),
query: "`col` <= ?",
value: []interface{}{1},
},
{
cond: And(Lt("a", 1), Or(Gt("b", 2), Neq("c", 3))),
query: "(`a` < ?) AND ((`b` > ?) OR (`c` != ?))",
value: []interface{}{1, 2, 3},
},
} {
buf := dbr.NewBuffer()
err := test.cond.Build(dialect.MySQL, buf)
assert.NoError(t, err)
assert.Equal(t, test.query, buf.String())
assert.Equal(t, test.value, buf.Value())
}
}

View File

@@ -0,0 +1,283 @@
/*
Copyright 2018 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 mysql
import (
"database/sql"
"fmt"
"strings"
_ "github.com/go-sql-driver/mysql"
"github.com/gocraft/dbr"
)
const (
DefaultSelectLimit = 200
)
func GetLimit(n uint64) uint64 {
if n < 0 {
n = 0
}
if n > DefaultSelectLimit {
n = DefaultSelectLimit
}
return n
}
func GetOffset(n uint64) uint64 {
if n < 0 {
n = 0
}
return n
}
type InsertHook func(query *InsertQuery)
type UpdateHook func(query *UpdateQuery)
type DeleteHook func(query *DeleteQuery)
type Database struct {
*dbr.Session
InsertHook InsertHook
UpdateHook UpdateHook
DeleteHook DeleteHook
}
type SelectQuery struct {
*dbr.SelectBuilder
JoinCount int // for join filter
}
type InsertQuery struct {
*dbr.InsertBuilder
Hook InsertHook
}
type DeleteQuery struct {
*dbr.DeleteBuilder
Hook DeleteHook
}
type UpdateQuery struct {
*dbr.UpdateBuilder
Hook UpdateHook
}
type UpsertQuery struct {
table string
*dbr.Session
whereConds map[string]string
upsertValues map[string]interface{}
}
// SelectQuery
// Example: Select().From().Where().Limit().Offset().OrderDir().Load()
// Select().From().Where().Limit().Offset().OrderDir().LoadOne()
// Select().From().Where().Count()
// SelectAll().From().Where().Limit().Offset().OrderDir().Load()
// SelectAll().From().Where().Limit().Offset().OrderDir().LoadOne()
// SelectAll().From().Where().Count()
func (db *Database) Select(columns ...string) *SelectQuery {
return &SelectQuery{db.Session.Select(columns...), 0}
}
func (db *Database) SelectBySql(query string, value ...interface{}) *SelectQuery {
return &SelectQuery{db.Session.SelectBySql(query, value...), 0}
}
func (db *Database) SelectAll(columns ...string) *SelectQuery {
return &SelectQuery{db.Session.Select("*"), 0}
}
func (b *SelectQuery) Join(table, on interface{}) *SelectQuery {
b.SelectBuilder.Join(table, on)
return b
}
func (b *SelectQuery) JoinAs(table string, alias string, on interface{}) *SelectQuery {
b.SelectBuilder.Join(dbr.I(table).As(alias), on)
return b
}
func (b *SelectQuery) From(table string) *SelectQuery {
b.SelectBuilder.From(table)
return b
}
func (b *SelectQuery) Where(query interface{}, value ...interface{}) *SelectQuery {
b.SelectBuilder.Where(query, value...)
return b
}
func (b *SelectQuery) GroupBy(col ...string) *SelectQuery {
b.SelectBuilder.GroupBy(col...)
return b
}
func (b *SelectQuery) Distinct() *SelectQuery {
b.SelectBuilder.Distinct()
return b
}
func (b *SelectQuery) Limit(n uint64) *SelectQuery {
n = GetLimit(n)
b.SelectBuilder.Limit(n)
return b
}
func (b *SelectQuery) Offset(n uint64) *SelectQuery {
n = GetLimit(n)
b.SelectBuilder.Offset(n)
return b
}
func (b *SelectQuery) OrderDir(col string, isAsc bool) *SelectQuery {
b.SelectBuilder.OrderDir(col, isAsc)
return b
}
func (b *SelectQuery) Load(value interface{}) (int, error) {
return b.SelectBuilder.Load(value)
}
func (b *SelectQuery) LoadOne(value interface{}) error {
return b.SelectBuilder.LoadOne(value)
}
func getColumns(dbrColumns []interface{}) string {
var columns []string
for _, column := range dbrColumns {
if c, ok := column.(string); ok {
columns = append(columns, c)
}
}
return strings.Join(columns, ", ")
}
func (b *SelectQuery) Count() (count uint32, err error) {
// cache SelectStmt
selectStmt := b.SelectStmt
limit := selectStmt.LimitCount
offset := selectStmt.OffsetCount
column := selectStmt.Column
isDistinct := selectStmt.IsDistinct
order := selectStmt.Order
b.SelectStmt.LimitCount = -1
b.SelectStmt.OffsetCount = -1
b.SelectStmt.Column = []interface{}{"COUNT(*)"}
b.SelectStmt.Order = []dbr.Builder{}
if isDistinct {
b.SelectStmt.Column = []interface{}{fmt.Sprintf("COUNT(DISTINCT %s)", getColumns(column))}
b.SelectStmt.IsDistinct = false
}
err = b.LoadOne(&count)
// fallback SelectStmt
selectStmt.LimitCount = limit
selectStmt.OffsetCount = offset
selectStmt.Column = column
selectStmt.IsDistinct = isDistinct
selectStmt.Order = order
b.SelectStmt = selectStmt
return
}
// InsertQuery
// Example: InsertInto().Columns().Record().Exec()
func (db *Database) InsertInto(table string) *InsertQuery {
return &InsertQuery{db.Session.InsertInto(table), db.InsertHook}
}
func (b *InsertQuery) Exec() (sql.Result, error) {
result, err := b.InsertBuilder.Exec()
if b.Hook != nil && err == nil {
defer b.Hook(b)
}
return result, err
}
func (b *InsertQuery) Columns(columns ...string) *InsertQuery {
b.InsertBuilder.Columns(columns...)
return b
}
func (b *InsertQuery) Record(structValue interface{}) *InsertQuery {
b.InsertBuilder.Record(structValue)
return b
}
// DeleteQuery
// Example: DeleteFrom().Where().Limit().Exec()
func (db *Database) DeleteFrom(table string) *DeleteQuery {
return &DeleteQuery{db.Session.DeleteFrom(table), db.DeleteHook}
}
func (b *DeleteQuery) Where(query interface{}, value ...interface{}) *DeleteQuery {
b.DeleteBuilder.Where(query, value...)
return b
}
func (b *DeleteQuery) Limit(n uint64) *DeleteQuery {
b.DeleteBuilder.Limit(n)
return b
}
func (b *DeleteQuery) Exec() (sql.Result, error) {
result, err := b.DeleteBuilder.Exec()
if b.Hook != nil && err == nil {
defer b.Hook(b)
}
return result, err
}
// UpdateQuery
// Example: Update().Set().Where().Exec()
func (db *Database) Update(table string) *UpdateQuery {
return &UpdateQuery{db.Session.Update(table), db.UpdateHook}
}
func (b *UpdateQuery) Exec() (sql.Result, error) {
result, err := b.UpdateBuilder.Exec()
if b.Hook != nil && err == nil {
defer b.Hook(b)
}
return result, err
}
func (b *UpdateQuery) Set(column string, value interface{}) *UpdateQuery {
b.UpdateBuilder.Set(column, value)
return b
}
func (b *UpdateQuery) SetMap(m map[string]interface{}) *UpdateQuery {
b.UpdateBuilder.SetMap(m)
return b
}
func (b *UpdateQuery) Where(query interface{}, value ...interface{}) *UpdateQuery {
b.UpdateBuilder.Where(query, value...)
return b
}
func (b *UpdateQuery) Limit(n uint64) *UpdateQuery {
b.UpdateBuilder.Limit(n)
return b
}

View File

@@ -0,0 +1,31 @@
/*
Copyright 2018 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 mysql
import (
"github.com/gocraft/dbr"
)
// package errors
var (
ErrNotFound = dbr.ErrNotFound
ErrNotSupported = dbr.ErrNotSupported
ErrTableNotSpecified = dbr.ErrTableNotSpecified
ErrColumnNotSpecified = dbr.ErrColumnNotSpecified
ErrInvalidPointer = dbr.ErrInvalidPointer
ErrPlaceholderCount = dbr.ErrPlaceholderCount
ErrInvalidSliceLength = dbr.ErrInvalidSliceLength
ErrCantConvertToTime = dbr.ErrCantConvertToTime
ErrInvalidTimestring = dbr.ErrInvalidTimestring
)

View File

@@ -0,0 +1,55 @@
/*
Copyright 2018 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 mysql
import (
"github.com/golang/glog"
)
// EventReceiver is a sentinel EventReceiver; use it if the caller doesn't supply one
type EventReceiver struct{}
// Event receives a simple notification when various events occur
func (n *EventReceiver) Event(eventName string) {
}
// EventKv receives a notification when various events occur along with
// optional key/value data
func (n *EventReceiver) EventKv(eventName string, kvs map[string]string) {
}
// EventErr receives a notification of an error if one occurs
func (n *EventReceiver) EventErr(eventName string, err error) error {
return err
}
// EventErrKv receives a notification of an error if one occurs along with
// optional key/value data
func (n *EventReceiver) EventErrKv(eventName string, err error, kvs map[string]string) error {
glog.Errorf("%+v", err)
glog.Errorf("%s: %+v", eventName, kvs)
return err
}
// Timing receives the time an event took to happen
func (n *EventReceiver) Timing(eventName string, nanoseconds int64) {
}
// TimingKv receives the time an event took to happen along with optional key/value data
func (n *EventReceiver) TimingKv(eventName string, nanoseconds int64, kvs map[string]string) {
// TODO: Change logger level to debug
glog.Infof("%s spend %.2fms: %+v", eventName, float32(nanoseconds)/1000000, kvs)
}

View File

@@ -0,0 +1,82 @@
/*
Copyright 2018 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 mysql
import (
"fmt"
"github.com/gocraft/dbr"
"k8s.io/klog"
)
type MySQLClient struct {
database *Database
}
func NewMySQLClient(options *MySQLOptions, stopCh <-chan struct{}) (*MySQLClient, error) {
var m MySQLClient
conn, err := dbr.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/devops?parseTime=1&multiStatements=1&charset=utf8mb4&collation=utf8mb4_unicode_ci", options.Username, options.Password, options.Host), nil)
if err != nil {
klog.Error("unable to connect to mysql", err)
return nil, err
}
conn.SetMaxIdleConns(options.MaxIdleConnections)
conn.SetConnMaxLifetime(options.MaxConnectionLifeTime)
conn.SetMaxOpenConns(options.MaxOpenConnections)
m.database = &Database{
Session: conn.NewSession(nil),
}
go func() {
<-stopCh
if err := conn.Close(); err != nil {
klog.Warning("error happened during closing mysql connection", err)
}
}()
return &m, nil
}
func NewMySQLClientOrDie(options *MySQLOptions, stopCh <-chan struct{}) *MySQLClient {
var m MySQLClient
conn, err := dbr.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/devops?parseTime=1&multiStatements=1&charset=utf8mb4&collation=utf8mb4_unicode_ci", options.Username, options.Password, options.Host), nil)
if err != nil {
klog.Error("unable to connect to mysql", err)
panic(err)
}
conn.SetMaxIdleConns(options.MaxIdleConnections)
conn.SetConnMaxLifetime(options.MaxConnectionLifeTime)
conn.SetMaxOpenConns(options.MaxOpenConnections)
m.database = &Database{
Session: conn.NewSession(nil),
}
go func() {
<-stopCh
if err := conn.Close(); err != nil {
klog.Warning("error happened during closing mysql connection", err)
}
}()
return &m
}
func (m *MySQLClient) Database() *Database {
return m.database
}

View File

@@ -0,0 +1,43 @@
package mysql
import (
"github.com/spf13/pflag"
reflectutils "kubesphere.io/kubesphere/pkg/utils/reflectutils"
"time"
)
type MySQLOptions struct {
Host string `json:"host,omitempty" yaml:"host,omitempty" description:"MySQL service host address"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"-" yaml:"password,omitempty"`
MaxIdleConnections int `json:"maxIdleConnections,omitempty" yaml:"maxIdleConnections,omitempty"`
MaxOpenConnections int `json:"maxOpenConnections,omitempty" yaml:"maxOpenConnections,omitempty"`
MaxConnectionLifeTime time.Duration `json:"maxConnectionLifeTime,omitempty" yaml:"maxConnectionLifeTime,omitempty"`
}
// NewMySQLOptions create a `zero` value instance
func NewMySQLOptions() *MySQLOptions {
return &MySQLOptions{}
}
func (m *MySQLOptions) Validate() []error {
errors := []error{}
return errors
}
func (m *MySQLOptions) ApplyTo(options *MySQLOptions) {
reflectutils.Override(options, m)
}
func (m *MySQLOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&m.Host, "mysql-host", m.Host, ""+
"MySQL service host address. If left blank, following options will be ignored.")
fs.StringVar(&m.Username, "mysql-username", m.Username, ""+
"Username for access to mysql service.")
fs.StringVar(&m.Password, "mysql-password", m.Password, ""+
"Password for access to mysql, should be used pair with password.")
}

View File

@@ -25,7 +25,6 @@ import (
"net/http"
"strconv"
"strings"
"time"
)
const (
@@ -35,86 +34,9 @@ const (
StateSuffix = "-StatefulSet"
)
type Cluster struct {
ClusterID string `json:"cluster_id"`
Name string `json:"name"`
AppID string `json:"app_id"`
VersionID string `json:"version_id"`
Status string `json:"status"`
UpdateTime time.Time `json:"status_time"`
CreateTime time.Time `json:"create_time"`
RunTimeId string `json:"runtime_id"`
Description string `json:"description"`
ClusterRoleSets []ClusterRole `json:"cluster_role_set"`
}
type ClusterRole struct {
ClusterID string `json:"cluster_id"`
Role string `json:"role"`
}
type ClusterList struct {
Total int `json:"total_count"`
Clusters []Cluster `json:"cluster_set"`
}
type VersionList struct {
Total int `json:"total_count"`
Versions []version `json:"app_version_set"`
}
type version struct {
Name string `json:"name"`
VersionID string `json:"version_id"`
}
type runtime struct {
RuntimeID string `json:"runtime_id"`
Zone string `json:"zone"`
}
type runtimeList struct {
Total int `json:"total_count"`
Runtimes []runtime `json:"runtime_set"`
}
type app struct {
AppId string `json:"app_id"`
Name string `json:"name"`
ChartName string `json:"chart_name"`
RepoId string `json:"repo_id"`
}
type repo struct {
RepoId string `json:"repo_id"`
Name string `json:"name"`
Url string `json:"url"`
}
type appList struct {
Total int `json:"total_count"`
Apps []app `json:"app_set"`
}
type repoList struct {
Total int `json:"total_count"`
Repos []repo `json:"repo_set"`
}
type CreateClusterRequest struct {
AppId string `json:"app_id" description:"ID of app to run in cluster, e.g. app-AA3A3y3zEgEM"`
VersionId string `json:"version_id" description:"app version, e.g. appv-154gXYx5RKRp"`
RuntimeId string `json:"runtime_id" description:"ID of runtime, e.g. runtime-wWwXL0LzWqEr"`
Conf string `json:"conf" description:"conf a json string, include cpu, memory info of cluster"`
}
type DeleteClusterRequest struct {
ClusterId []string `json:"cluster_id" description:"cluster ID"`
}
func GetAppInfo(appId string) (string, string, string, error) {
url := fmt.Sprintf("%s/v1/apps?app_id=%s", openpitrixAPIServer, appId)
resp, err := makeHttpRequest("GET", url, "")
func (c *OpenPitrixClient) GetAppInfo(appId string) (string, string, string, error) {
url := fmt.Sprintf("%s/v1/apps?app_id=%s", c.apiServer, appId)
resp, err := c.makeHttpRequest("GET", url, "")
if err != nil {
glog.Error(err)
return Unknown, Unknown, Unknown, err
@@ -134,14 +56,10 @@ func GetAppInfo(appId string) (string, string, string, error) {
return apps.Apps[0].ChartName, apps.Apps[0].RepoId, apps.Apps[0].AppId, nil
}
func GetCluster(clusterId string) (*Cluster, error) {
if strings.HasSuffix(openpitrixAPIServer, "/") {
openpitrixAPIServer = strings.TrimSuffix(openpitrixAPIServer, "/")
}
func (c *OpenPitrixClient) GetCluster(clusterId string) (*Cluster, error) {
url := fmt.Sprintf("%s/v1/clusters?cluster_id=%s", c.apiServer, clusterId)
url := fmt.Sprintf("%s/v1/clusters?cluster_id=%s", openpitrixAPIServer, clusterId)
resp, err := makeHttpRequest("GET", url, "")
resp, err := c.makeHttpRequest("GET", url, "")
if err != nil {
glog.Error(err)
return nil, err
@@ -162,14 +80,11 @@ func GetCluster(clusterId string) (*Cluster, error) {
return &clusterList.Clusters[0], nil
}
func ListClusters(runtimeId, searchWord, status string, limit, offset int) (*ClusterList, error) {
if strings.HasSuffix(openpitrixAPIServer, "/") {
openpitrixAPIServer = strings.TrimSuffix(openpitrixAPIServer, "/")
}
func (c *OpenPitrixClient) ListClusters(runtimeId, searchWord, status string, limit, offset int) (*ClusterList, error) {
defaultStatus := "status=active&status=stopped&status=pending&status=ceased"
url := fmt.Sprintf("%s/v1/clusters?limit=%s&offset=%s", openpitrixAPIServer, strconv.Itoa(limit), strconv.Itoa(offset))
url := fmt.Sprintf("%s/v1/clusters?limit=%s&offset=%s", c.apiServer, strconv.Itoa(limit), strconv.Itoa(offset))
if searchWord != "" {
url = fmt.Sprintf("%s&search_word=%s", url, searchWord)
@@ -185,7 +100,7 @@ func ListClusters(runtimeId, searchWord, status string, limit, offset int) (*Clu
url = fmt.Sprintf("%s&runtime_id=%s", url, runtimeId)
}
resp, err := makeHttpRequest("GET", url, "")
resp, err := c.makeHttpRequest("GET", url, "")
if err != nil {
glog.Errorf("request %s failed, reason: %s", url, err)
return nil, err
@@ -201,9 +116,9 @@ func ListClusters(runtimeId, searchWord, status string, limit, offset int) (*Clu
return &clusterList, nil
}
func GetRepo(repoId string) (string, error) {
url := fmt.Sprintf("%s/v1/repos?repo_id=%s", openpitrixAPIServer, repoId)
resp, err := makeHttpRequest("GET", url, "")
func (c *OpenPitrixClient) GetRepo(repoId string) (string, error) {
url := fmt.Sprintf("%s/v1/repos?repo_id=%s", c.apiServer, repoId)
resp, err := c.makeHttpRequest("GET", url, "")
if err != nil {
glog.Error(err)
return Unknown, err
@@ -223,9 +138,9 @@ func GetRepo(repoId string) (string, error) {
return repos.Repos[0].Name, nil
}
func GetVersion(versionId string) (string, error) {
versionUrl := fmt.Sprintf("%s/v1/app_versions?version_id=%s", openpitrixAPIServer, versionId)
resp, err := makeHttpRequest("GET", versionUrl, "")
func (c *OpenPitrixClient) GetVersion(versionId string) (string, error) {
versionUrl := fmt.Sprintf("%s/v1/app_versions?version_id=%s", c.apiServer, versionId)
resp, err := c.makeHttpRequest("GET", versionUrl, "")
if err != nil {
glog.Error(err)
return Unknown, err
@@ -244,10 +159,10 @@ func GetVersion(versionId string) (string, error) {
return versions.Versions[0].Name, nil
}
func GetRuntime(runtimeId string) (string, error) {
func (c *OpenPitrixClient) GetRuntime(runtimeId string) (string, error) {
versionUrl := fmt.Sprintf("%s/v1/runtimes?runtime_id=%s", openpitrixAPIServer, runtimeId)
resp, err := makeHttpRequest("GET", versionUrl, "")
versionUrl := fmt.Sprintf("%s/v1/runtimes?runtime_id=%s", c.apiServer, runtimeId)
resp, err := c.makeHttpRequest("GET", versionUrl, "")
if err != nil {
glog.Error(err)
return Unknown, err
@@ -267,9 +182,9 @@ func GetRuntime(runtimeId string) (string, error) {
return runtimes.Runtimes[0].Zone, nil
}
func CreateCluster(request CreateClusterRequest) error {
func (c *OpenPitrixClient) CreateCluster(request CreateClusterRequest) error {
versionUrl := fmt.Sprintf("%s/v1/clusters/create", openpitrixAPIServer)
versionUrl := fmt.Sprintf("%s/v1/clusters/create", c.apiServer)
data, err := json.Marshal(request)
@@ -278,7 +193,7 @@ func CreateCluster(request CreateClusterRequest) error {
return err
}
data, err = makeHttpRequest("POST", versionUrl, string(data))
data, err = c.makeHttpRequest("POST", versionUrl, string(data))
if err != nil {
glog.Error(err)
@@ -288,9 +203,9 @@ func CreateCluster(request CreateClusterRequest) error {
return nil
}
func DeleteCluster(request DeleteClusterRequest) error {
func (c *OpenPitrixClient) DeleteCluster(request DeleteClusterRequest) error {
versionUrl := fmt.Sprintf("%s/v1/clusters/delete", openpitrixAPIServer)
versionUrl := fmt.Sprintf("%s/v1/clusters/delete", c.apiServer)
data, err := json.Marshal(request)
@@ -299,7 +214,7 @@ func DeleteCluster(request DeleteClusterRequest) error {
return err
}
data, err = makeHttpRequest("POST", versionUrl, string(data))
data, err = c.makeHttpRequest("POST", versionUrl, string(data))
if err != nil {
glog.Error(err)
@@ -309,7 +224,7 @@ func DeleteCluster(request DeleteClusterRequest) error {
return nil
}
func makeHttpRequest(method, url, data string) ([]byte, error) {
func (c *OpenPitrixClient) makeHttpRequest(method, url, data string) ([]byte, error) {
var req *http.Request
var err error
@@ -319,7 +234,7 @@ func makeHttpRequest(method, url, data string) ([]byte, error) {
req, err = http.NewRequest(method, url, strings.NewReader(data))
}
req.Header.Add("Authorization", openpitrixProxyToken)
req.Header.Add("Authorization", c.token)
if err != nil {
glog.Error(err)
@@ -329,7 +244,7 @@ func makeHttpRequest(method, url, data string) ([]byte, error) {
resp, err := http.DefaultClient.Do(req)
if err != nil {
err := fmt.Errorf("Request to %s failed, method: %s,token: %s, reason: %s ", url, method, openpitrixProxyToken, err)
err := fmt.Errorf("Request to %s failed, method: %s,token: %s, reason: %s ", url, method, c.apiServer, err)
glog.Error(err)
return nil, err
}

View File

@@ -20,105 +20,64 @@ package openpitrix
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"github.com/golang/glog"
"io/ioutil"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"net/http"
"strings"
"sync"
"time"
)
var (
openpitrixAPIServer string
openpitrixProxyToken string
once sync.Once
c client
)
type RunTime struct {
RuntimeId string `json:"runtime_id"`
RuntimeUrl string `json:"runtime_url"`
Name string `json:"name"`
Provider string `json:"provider"`
Zone string `json:"zone"`
RuntimeCredential string `json:"runtime_credential"`
func NewOpenPitrixClient(options *OpenPitrixOptions) (*OpenPitrixClient, error) {
return &OpenPitrixClient{
client: &http.Client{
Timeout: time.Duration(3) * time.Second,
},
apiServer: options.APIServer,
token: options.Token,
}, nil
}
type Interface interface {
CreateRuntime(runtime *RunTime) error
DeleteRuntime(runtimeId string) error
}
type cluster struct {
Status string `json:"status"`
ClusterId string `json:"cluster_id"`
}
type Error struct {
status int
message string
}
func (e Error) Error() string {
return fmt.Sprintf("status: %d,message: %s", e.status, e.message)
}
type client struct {
client http.Client
}
func init() {
flag.StringVar(&openpitrixAPIServer, "openpitrix-api-server", "http://openpitrix-api-gateway.openpitrix-system.svc:9100", "openpitrix api server")
flag.StringVar(&openpitrixProxyToken, "openpitrix-proxy-token", "", "openpitrix proxy token")
}
func Client() Interface {
once.Do(func() {
c = client{client: http.Client{}}
})
return c
}
func (c client) CreateRuntime(runtime *RunTime) error {
func (c *OpenPitrixClient) CreateRuntime(runtime *RunTime) error {
data, err := json.Marshal(runtime)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/v1/runtimes", openpitrixAPIServer), bytes.NewReader(data))
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/v1/runtimes", c.apiServer), bytes.NewReader(data))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", openpitrixProxyToken)
req.Header.Add("Authorization", c.token)
resp, err := c.client.Do(req)
if err != nil {
glog.Error(err)
klog.Error(err)
return err
}
defer resp.Body.Close()
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
glog.Error(err)
klog.Error(err)
return err
}
if resp.StatusCode > http.StatusOK {
err = Error{resp.StatusCode, string(data)}
glog.Error(err)
klog.Error(err)
return err
}
return nil
}
func (c client) deleteClusters(clusters []cluster) error {
func (c *OpenPitrixClient) deleteClusters(clusters []cluster) error {
clusterId := make([]string, 0)
for _, cluster := range clusters {
@@ -137,16 +96,16 @@ func (c client) deleteClusters(clusters []cluster) error {
ClusterId: clusterId,
}
data, _ := json.Marshal(deleteRequest)
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/v1/clusters/delete", openpitrixAPIServer), bytes.NewReader(data))
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/v1/clusters/delete", c.apiServer), bytes.NewReader(data))
if err != nil {
return err
}
req.Header.Add("Authorization", openpitrixProxyToken)
req.Header.Add("Authorization", c.token)
resp, err := c.client.Do(req)
if err != nil {
glog.Error(err)
klog.Error(err)
return err
}
@@ -154,44 +113,44 @@ func (c client) deleteClusters(clusters []cluster) error {
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
glog.Error(err)
klog.Error(err)
return err
}
if resp.StatusCode > http.StatusOK {
err = Error{resp.StatusCode, string(data)}
glog.Error(err)
klog.Error(err)
return err
}
return nil
}
func (c client) listClusters(runtimeId string) ([]cluster, error) {
func (c *OpenPitrixClient) listClusters(runtimeId string) ([]cluster, error) {
limit := 200
offset := 0
clusters := make([]cluster, 0)
for {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v1/clusters?runtime_id=%s&limit=%d&offset=%d", openpitrixAPIServer, runtimeId, limit, offset), nil)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v1/clusters?runtime_id=%s&limit=%d&offset=%d", c.apiServer, runtimeId, limit, offset), nil)
if err != nil {
glog.Error(err)
klog.Error(err)
return nil, err
}
req.Header.Add("Authorization", openpitrixProxyToken)
req.Header.Add("Authorization", c.token)
resp, err := c.client.Do(req)
if err != nil {
glog.Error(err)
klog.Error(err)
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
glog.Error(err)
klog.Error(err)
return nil, err
}
@@ -199,7 +158,7 @@ func (c client) listClusters(runtimeId string) ([]cluster, error) {
if resp.StatusCode > http.StatusOK {
err = Error{resp.StatusCode, string(data)}
glog.Error(err)
klog.Error(err)
return nil, err
}
listClusterResponse := struct {
@@ -209,7 +168,7 @@ func (c client) listClusters(runtimeId string) ([]cluster, error) {
err = json.Unmarshal(data, &listClusterResponse)
if err != nil {
glog.Error(err)
klog.Error(err)
return nil, err
}
@@ -225,18 +184,18 @@ func (c client) listClusters(runtimeId string) ([]cluster, error) {
return clusters, nil
}
func (c client) DeleteRuntime(runtimeId string) error {
func (c *OpenPitrixClient) DeleteRuntime(runtimeId string) error {
clusters, err := c.listClusters(runtimeId)
if err != nil {
glog.Error(err)
klog.Error(err)
return err
}
err = c.deleteClusters(clusters)
if err != nil {
glog.Error(err)
klog.Error(err)
return err
}

View File

@@ -0,0 +1,43 @@
package openpitrix
import (
"fmt"
"github.com/spf13/pflag"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
)
type OpenPitrixOptions struct {
APIServer string `json:"apiServer,omitempty" yaml:"apiServer,omitempty"`
Token string `json:"token,omitempty" yaml:"token,omitempty"`
}
func NewOpenPitrixOptions() *OpenPitrixOptions {
return &OpenPitrixOptions{
APIServer: "",
Token: "",
}
}
func (s *OpenPitrixOptions) ApplyTo(options *OpenPitrixOptions) {
reflectutils.Override(s, options)
}
func (s *OpenPitrixOptions) Validate() []error {
errs := []error{}
if s.APIServer != "" {
if s.Token == "" {
errs = append(errs, fmt.Errorf("OpenPitrix access token cannot be empty"))
}
}
return errs
}
func (s *OpenPitrixOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.APIServer, "openpitrix-apiserver", s.APIServer, ""+
"OpenPitrix api gateway endpoint, if left blank, following options will be ignored.")
fs.StringVar(&s.Token, "openpitrix-token", s.Token, ""+
"OpenPitrix api access token.")
}

View File

@@ -0,0 +1,117 @@
package openpitrix
import (
"fmt"
"net/http"
"time"
)
type Cluster struct {
ClusterID string `json:"cluster_id"`
Name string `json:"name"`
AppID string `json:"app_id"`
VersionID string `json:"version_id"`
Status string `json:"status"`
UpdateTime time.Time `json:"status_time"`
CreateTime time.Time `json:"create_time"`
RunTimeId string `json:"runtime_id"`
Description string `json:"description"`
ClusterRoleSets []ClusterRole `json:"cluster_role_set"`
}
type ClusterRole struct {
ClusterID string `json:"cluster_id"`
Role string `json:"role"`
}
type ClusterList struct {
Total int `json:"total_count"`
Clusters []Cluster `json:"cluster_set"`
}
type VersionList struct {
Total int `json:"total_count"`
Versions []version `json:"app_version_set"`
}
type version struct {
Name string `json:"name"`
VersionID string `json:"version_id"`
}
type runtime struct {
RuntimeID string `json:"runtime_id"`
Zone string `json:"zone"`
}
type runtimeList struct {
Total int `json:"total_count"`
Runtimes []runtime `json:"runtime_set"`
}
type app struct {
AppId string `json:"app_id"`
Name string `json:"name"`
ChartName string `json:"chart_name"`
RepoId string `json:"repo_id"`
}
type repo struct {
RepoId string `json:"repo_id"`
Name string `json:"name"`
Url string `json:"url"`
}
type appList struct {
Total int `json:"total_count"`
Apps []app `json:"app_set"`
}
type repoList struct {
Total int `json:"total_count"`
Repos []repo `json:"repo_set"`
}
type CreateClusterRequest struct {
AppId string `json:"app_id" description:"ID of app to run in cluster, e.g. app-AA3A3y3zEgEM"`
VersionId string `json:"version_id" description:"app version, e.g. appv-154gXYx5RKRp"`
RuntimeId string `json:"runtime_id" description:"ID of runtime, e.g. runtime-wWwXL0LzWqEr"`
Conf string `json:"conf" description:"conf a json string, include cpu, memory info of cluster"`
}
type DeleteClusterRequest struct {
ClusterId []string `json:"cluster_id" description:"cluster ID"`
}
type RunTime struct {
RuntimeId string `json:"runtime_id"`
RuntimeUrl string `json:"runtime_url"`
Name string `json:"name"`
Provider string `json:"provider"`
Zone string `json:"zone"`
RuntimeCredential string `json:"runtime_credential"`
}
type Interface interface {
CreateRuntime(runtime *RunTime) error
DeleteRuntime(runtimeId string) error
}
type cluster struct {
Status string `json:"status"`
ClusterId string `json:"cluster_id"`
}
type Error struct {
status int
message string
}
func (e Error) Error() string {
return fmt.Sprintf("status: %d,message: %s", e.status, e.message)
}
type OpenPitrixClient struct {
client *http.Client
apiServer string
token string
}

View File

@@ -0,0 +1,37 @@
package prometheus
import (
"github.com/spf13/pflag"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
)
type PrometheusOptions struct {
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
SecondaryEndpoint string `json:"secondaryEndpoint,omitempty" yaml:"secondaryEndpoint,omitempty"`
}
func NewPrometheusOptions() *PrometheusOptions {
return &PrometheusOptions{
Endpoint: "",
SecondaryEndpoint: "",
}
}
func (s *PrometheusOptions) Validate() []error {
errs := []error{}
return errs
}
func (s *PrometheusOptions) ApplyTo(options *PrometheusOptions) {
reflectutils.Override(s, options)
}
func (s *PrometheusOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.Endpoint, "prometheus-endpoint", s.Endpoint, ""+
"Prometheus service endpoint which stores KubeSphere monitoring data, if left "+
"blank, will use builtin metrics-server as data source.")
fs.StringVar(&s.SecondaryEndpoint, "prometheus-secondary-endpoint", s.SecondaryEndpoint, ""+
"Prometheus secondary service endpoint, if left empty and endpoint is set, will use endpoint instead.")
}

View File

@@ -0,0 +1,67 @@
/*
Copyright 2019 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 prometheus
import (
"github.com/golang/glog"
"io/ioutil"
"net/http"
"time"
)
type PrometheusClient struct {
client *http.Client
endpoint string
secondaryEndpoint string
}
func NewPrometheusClient(options *PrometheusOptions) (*PrometheusClient, error) {
return &PrometheusClient{
client: &http.Client{
Timeout: time.Duration(3) * time.Second,
},
endpoint: options.Endpoint,
secondaryEndpoint: options.SecondaryEndpoint,
}, nil
}
func (c *PrometheusClient) SendMonitoringRequest(queryType string, params string) string {
return c.sendMonitoringRequest(c.endpoint, queryType, params)
}
func (c *PrometheusClient) SendSecondaryMonitoringRequest(queryType string, params string) string {
return c.sendMonitoringRequest(c.secondaryEndpoint, queryType, params)
}
func (c *PrometheusClient) sendMonitoringRequest(endpoint string, queryType string, params string) string {
epurl := endpoint + queryType + params
response, err := c.client.Get(epurl)
if err != nil {
glog.Error(err)
} else {
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
glog.Error(err)
}
return string(contents)
}
return ""
}

View File

@@ -1,203 +0,0 @@
/*
Copyright 2019 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 prometheus
import (
"flag"
"io/ioutil"
"kubesphere.io/kubesphere/pkg/informers"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/emicklei/go-restful"
"github.com/golang/glog"
)
const (
DefaultQueryStep = "10m"
DefaultQueryTimeout = "10s"
RangeQueryType = "query_range?"
DefaultQueryType = "query?"
)
// Kubesphere sets up two Prometheus servers to balance monitoring workloads
var (
PrometheusEndpoint string // For monitoring node, namespace, pod ... level resources
SecondaryPrometheusEndpoint string // For monitoring components including etcd, apiserver, coredns, etc.
)
func init() {
flag.StringVar(&PrometheusEndpoint, "prometheus-endpoint", "http://prometheus-k8s.kubesphere-monitoring-system.svc:9090/api/v1/", "For physical and k8s resource monitoring, including node, namespace, pod, etc.")
flag.StringVar(&SecondaryPrometheusEndpoint, "secondary-prometheus-endpoint", "http://prometheus-k8s-system.kubesphere-monitoring-system.svc:9090/api/v1/", "For k8s component monitoring, including etcd, apiserver, coredns, etc.")
}
type MonitoringRequestParams struct {
Params url.Values
QueryType string
SortMetricName string
SortType string
PageNum string
LimitNum string
Tp string
MetricsFilter string
ResourcesFilter string
MetricsName string
WorkloadName string
NodeId string
WsName string
NsName string
PodName string
PVCName string
StorageClassName string
ContainerName string
WorkloadKind string
ComponentName string
}
var client = &http.Client{}
func SendMonitoringRequest(prometheusEndpoint string, queryType string, params string) string {
epurl := prometheusEndpoint + queryType + params
response, err := client.Get(epurl)
if err != nil {
glog.Error(err)
} else {
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
glog.Error(err)
}
return string(contents)
}
return ""
}
func ParseMonitoringRequestParams(request *restful.Request) *MonitoringRequestParams {
instantTime := strings.Trim(request.QueryParameter("time"), " ")
start := strings.Trim(request.QueryParameter("start"), " ")
end := strings.Trim(request.QueryParameter("end"), " ")
step := strings.Trim(request.QueryParameter("step"), " ")
timeout := strings.Trim(request.QueryParameter("timeout"), " ")
sortMetricName := strings.Trim(request.QueryParameter("sort_metric"), " ")
sortType := strings.Trim(request.QueryParameter("sort_type"), " ")
pageNum := strings.Trim(request.QueryParameter("page"), " ")
limitNum := strings.Trim(request.QueryParameter("limit"), " ")
tp := strings.Trim(request.QueryParameter("type"), " ")
metricsFilter := strings.Trim(request.QueryParameter("metrics_filter"), " ")
resourcesFilter := strings.Trim(request.QueryParameter("resources_filter"), " ")
metricsName := strings.Trim(request.QueryParameter("metrics_name"), " ")
workloadName := strings.Trim(request.PathParameter("workload"), " ")
nodeId := strings.Trim(request.PathParameter("node"), " ")
wsName := strings.Trim(request.PathParameter("workspace"), " ")
nsName := strings.Trim(request.PathParameter("namespace"), " ")
podName := strings.Trim(request.PathParameter("pod"), " ")
pvcName := strings.Trim(request.PathParameter("pvc"), " ")
storageClassName := strings.Trim(request.PathParameter("storageclass"), " ")
containerName := strings.Trim(request.PathParameter("container"), " ")
workloadKind := strings.Trim(request.PathParameter("kind"), " ")
componentName := strings.Trim(request.PathParameter("component"), " ")
var requestParams = MonitoringRequestParams{
SortMetricName: sortMetricName,
SortType: sortType,
PageNum: pageNum,
LimitNum: limitNum,
Tp: tp,
MetricsFilter: metricsFilter,
ResourcesFilter: resourcesFilter,
MetricsName: metricsName,
WorkloadName: workloadName,
NodeId: nodeId,
WsName: wsName,
NsName: nsName,
PodName: podName,
PVCName: pvcName,
StorageClassName: storageClassName,
ContainerName: containerName,
WorkloadKind: workloadKind,
ComponentName: componentName,
}
if timeout == "" {
timeout = DefaultQueryTimeout
}
if step == "" {
step = DefaultQueryStep
}
// Whether query or query_range request
u := url.Values{}
if start != "" && end != "" {
u.Set("start", convertTimeGranularity(start))
u.Set("end", convertTimeGranularity(end))
u.Set("step", step)
u.Set("timeout", timeout)
// range query start time must be greater than the namespace creation time
if nsName != "" {
nsLister := informers.SharedInformerFactory().Core().V1().Namespaces().Lister()
ns, err := nsLister.Get(nsName)
if err == nil {
queryStartTime := u.Get("start")
nsCreationTime := strconv.FormatInt(ns.CreationTimestamp.Unix(), 10)
if nsCreationTime > queryStartTime {
u.Set("start", nsCreationTime)
}
}
}
requestParams.QueryType = RangeQueryType
requestParams.Params = u
return &requestParams
}
if instantTime != "" {
u.Set("time", instantTime)
u.Set("timeout", timeout)
requestParams.QueryType = DefaultQueryType
requestParams.Params = u
return &requestParams
} else {
u.Set("timeout", timeout)
requestParams.QueryType = DefaultQueryType
requestParams.Params = u
return &requestParams
}
}
func convertTimeGranularity(ts string) string {
timeFloat, err := strconv.ParseFloat(ts, 64)
if err != nil {
glog.Errorf("convert second timestamp %s to minute timestamp failed", ts)
return strconv.FormatInt(int64(time.Now().Unix()), 10)
}
timeInt := int64(timeFloat)
// convert second timestamp to minute timestamp
secondTime := time.Unix(timeInt, 0).Truncate(time.Minute).Unix()
return strconv.FormatInt(secondTime, 10)
}

View File

@@ -0,0 +1,57 @@
package redis
import (
"fmt"
"github.com/spf13/pflag"
"kubesphere.io/kubesphere/pkg/utils/net"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
)
type RedisOptions struct {
Host string
Port int
Password string
DB int
}
// NewRedisOptions returns options points to nowhere,
// because redis is not required for some components
func NewRedisOptions() *RedisOptions {
return &RedisOptions{
Host: "",
Port: 6379,
Password: "",
DB: 0,
}
}
func (r *RedisOptions) Validate() []error {
errors := make([]error, 0)
if r.Host != "" {
if !net.IsValidPort(r.Port) {
errors = append(errors, fmt.Errorf("--redis-port is out of range"))
}
}
return errors
}
func (r *RedisOptions) ApplyTo(options *RedisOptions) {
reflectutils.Override(options, r)
}
func (r *RedisOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&r.Host, "--redis-host", r.Host, ""+
"Redis service host address. If left blank, means redis is unnecessary, "+
"redis will be disabled")
fs.IntVar(&r.Port, "--redis-port", r.Port, ""+
"Redis service port number.")
fs.StringVar(&r.Password, "--redis-password", r.Password, ""+
"Redis service password if necessary, default to empty")
fs.IntVar(&r.DB, "--redis-db", r.DB, ""+
"Redis service database index, default to 0.")
}

View File

@@ -18,48 +18,49 @@
package redis
import (
"flag"
"log"
"os"
"os/signal"
"sync"
"syscall"
"fmt"
"github.com/go-redis/redis"
"k8s.io/klog"
)
var (
redisHost string
redisPassword string
redisDB int
redisClientOnce sync.Once
redisClient *redis.Client
)
func init() {
flag.StringVar(&redisHost, "redis-server", "localhost:6379", "redis server host")
flag.StringVar(&redisPassword, "redis-password", "", "redis password")
flag.IntVar(&redisDB, "redis-db", 0, "redis db")
type RedisClient struct {
client *redis.Client
}
func Client() *redis.Client {
func NewRedisClientOrDie(options *RedisOptions, stopCh <-chan struct{}) *RedisClient {
client, err := NewRedisClient(options, stopCh)
if err != nil {
panic(err)
}
redisClientOnce.Do(func() {
redisClient = redis.NewClient(&redis.Options{
Addr: redisHost,
Password: redisPassword,
DB: redisDB,
})
if err := redisClient.Ping().Err(); err != nil {
log.Fatalln(err)
}
c := make(chan os.Signal, 0)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
redisClient.Close()
}()
return client
}
func NewRedisClient(option *RedisOptions, stopCh <-chan struct{}) (*RedisClient, error) {
var r RedisClient
r.client = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", option.Host, option.Port),
Password: option.Password,
DB: option.DB,
})
return redisClient
if err := r.client.Ping().Err(); err != nil {
klog.Error("unable to reach redis host", err)
r.client.Close()
return nil, err
}
go func() {
<-stopCh
if err := r.client.Close(); err != nil {
klog.Error(err)
}
}()
return &r, nil
}
func (r *RedisClient) Redis() *redis.Client {
return r.client
}

View File

@@ -0,0 +1,61 @@
package s2is3
import (
"github.com/spf13/pflag"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
)
type S3Options struct {
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
Region string `json:"region,omitempty" yaml:"region,omitempty"`
DisableSSL bool `json:"disableSSL,omitempty" yaml:"disableSSL,omitempty"`
ForcePathStyle bool `json:"forcePathStyle,omitempty" yaml:"forePathStyle,omitempty"`
AccessKeyID string `json:"accessKeyID,omitempty" yaml:"accessKeyID,omitempty"`
SecretAccessKey string `json:"secretAccessKey,omitempty" yaml:"secretAccessKey,omitempty"`
SessionToken string `json:"sessionToken,omitempty" yaml:"sessionToken,omitempty"`
Bucket string `json:"bucket,omitempty" yaml:"bucket,omitempty"`
}
func NewS3Options() *S3Options {
return &S3Options{
Endpoint: "",
Region: "",
DisableSSL: true,
ForcePathStyle: true,
AccessKeyID: "",
SecretAccessKey: "",
SessionToken: "",
Bucket: "",
}
}
func (s *S3Options) Validate() []error {
errors := []error{}
return errors
}
func (s *S3Options) ApplyTo(options *S3Options) {
reflectutils.Override(options, s)
}
func (s *S3Options) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.Endpoint, "s3-endpoint", s.Endpoint, ""+
"Endpoint to access to s3 object storage service, if left blank, the following options "+
"will be ignored.")
fs.StringVar(&s.Region, "s3-region", s.Region, ""+
"Region of s3 that will access to, like us-east-1.")
fs.StringVar(&s.AccessKeyID, "s3-access-key-id", "AKIAIOSFODNN7EXAMPLE", "access key of s2i s3")
fs.StringVar(&s.SecretAccessKey, "s3-secret-access-key", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "secret access key of s2i s3")
fs.StringVar(&s.SessionToken, "s3-session-token", s.SessionToken, "session token of s2i s3")
fs.StringVar(&s.Bucket, "s3-bucket", "s2i-binaries", "bucket name of s2i s3")
fs.BoolVar(&s.DisableSSL, "s3-disable-SSL", s.DisableSSL, "disable ssl")
fs.BoolVar(&s.ForcePathStyle, "s3-force-path-style", true, "force path style")
}

View File

@@ -1,83 +1,79 @@
package s2is3
import (
"flag"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"k8s.io/klog"
"sync"
)
var (
s3Region string
s3Endpoint string
s3DisableSSL bool
s3ForcePathStyle bool
s3AccessKeyID string
s3SecretAccessKey string
s3SessionToken string
s3Bucket string
)
var (
s2iS3 *s3.S3
s2iS3Session *session.Session
sessionInitMutex sync.Mutex
clientInitMutex sync.Mutex
)
func init() {
flag.StringVar(&s3Region, "s2i-s3-region", "us-east-1", "region of s2i s3")
flag.StringVar(&s3Endpoint, "s2i-s3-endpoint", "http://ks-minio.kubesphere-system.svc", "endpoint of s2i s3")
flag.StringVar(&s3AccessKeyID, "s2i-s3-access-key-id", "AKIAIOSFODNN7EXAMPLE", "access key of s2i s3")
flag.StringVar(&s3SecretAccessKey, "s2i-s3-secret-access-key", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "secret access key of s2i s3")
flag.StringVar(&s3SessionToken, "s2i-s3-session-token", "", "session token of s2i s3")
flag.StringVar(&s3Bucket, "s2i-s3-bucket", "s2i-binaries", "bucket name of s2i s3")
flag.BoolVar(&s3DisableSSL, "s2i-s3-disable-SSL", true, "disable ssl")
flag.BoolVar(&s3ForcePathStyle, "s2i-s3-force-path-style", true, "force path style")
type S3Client struct {
s3Client *s3.S3
s3Session *session.Session
bucket string
}
func Client() *s3.S3 {
if s2iS3 != nil {
return s2iS3
func NewS3Client(options *S3Options) (*S3Client, error) {
cred := credentials.NewStaticCredentials(options.AccessKeyID, options.SecretAccessKey, options.SessionToken)
config := aws.Config{
Region: aws.String(options.Region),
Endpoint: aws.String(options.Endpoint),
DisableSSL: aws.Bool(options.DisableSSL),
S3ForcePathStyle: aws.Bool(options.ForcePathStyle),
Credentials: cred,
}
clientInitMutex.Lock()
defer clientInitMutex.Unlock()
if s2iS3Session == nil {
if sess := Session(); sess != nil {
klog.Error("failed to connect to s2i s3")
return nil
}
}
s2iS3 = s3.New(s2iS3Session)
return s2iS3
}
func Session() *session.Session {
if s2iS3Session != nil {
return s2iS3Session
}
sessionInitMutex.Lock()
defer sessionInitMutex.Unlock()
creds := credentials.NewStaticCredentials(
s3AccessKeyID, s3SecretAccessKey, s3SessionToken,
)
config := &aws.Config{
Region: aws.String(s3Region),
Endpoint: aws.String(s3Endpoint),
DisableSSL: aws.Bool(s3DisableSSL),
S3ForcePathStyle: aws.Bool(s3ForcePathStyle),
Credentials: creds,
}
sess, err := session.NewSession(config)
s, err := session.NewSession(&config)
if err != nil {
klog.Errorf("failed to connect to s2i s3: %+v", err)
return nil
klog.Error(err)
return nil, err
}
s2iS3Session = sess
return s2iS3Session
var c S3Client
c.s3Client = s3.New(s)
c.s3Session = s
c.bucket = options.Bucket
return &c, nil
}
func Bucket() *string {
return aws.String(s3Bucket)
// NewS3ClientOrDie creates S3Client and panics if there is an error
func NewS3ClientOrDie(options *S3Options) *S3Client {
cred := credentials.NewStaticCredentials(options.AccessKeyID, options.SecretAccessKey, options.SessionToken)
config := aws.Config{
Region: aws.String(options.Region),
Endpoint: aws.String(options.Endpoint),
DisableSSL: aws.Bool(options.DisableSSL),
S3ForcePathStyle: aws.Bool(options.ForcePathStyle),
Credentials: cred,
}
s, err := session.NewSession(&config)
if err != nil {
panic(err)
}
client := s3.New(s)
return &S3Client{
s3Client: client,
s3Session: s,
bucket: options.Bucket,
}
}
func (s *S3Client) Client() *s3.S3 {
return s.s3Client
}
func (s *S3Client) Session() *session.Session {
return s.s3Session
}
func (s *S3Client) Bucket() *string {
return aws.String(s.bucket)
}

View File

@@ -0,0 +1,59 @@
package servicemesh
import "github.com/spf13/pflag"
type ServiceMeshOptions struct {
// istio pilot discovery service url
IstioPilotHost string `json:"istioPilotHost,omitempty" yaml:"istioPilotHost,omitempty"`
// jaeger query service url
JaegerQueryHost string `json:"jaegerQueryHost,omitempty" yaml:"jaegerQueryHost,omitempty"`
// prometheus service url for servicemesh metrics
ServicemeshPrometheusHost string `json:"servicemeshPrometheusHost,omitempty" yaml:"servicemeshPrometheusHost,omitempty"`
}
// NewServiceMeshOptions returns a `zero` instance
func NewServiceMeshOptions() *ServiceMeshOptions {
return &ServiceMeshOptions{
IstioPilotHost: "",
JaegerQueryHost: "",
ServicemeshPrometheusHost: "",
}
}
func (s *ServiceMeshOptions) Validate() []error {
errors := []error{}
return errors
}
func (s *ServiceMeshOptions) ApplyTo(options *ServiceMeshOptions) {
if options == nil {
return
}
if s.ServicemeshPrometheusHost != "" {
options.ServicemeshPrometheusHost = s.ServicemeshPrometheusHost
}
if s.JaegerQueryHost != "" {
options.JaegerQueryHost = s.JaegerQueryHost
}
if s.IstioPilotHost != "" {
options.IstioPilotHost = s.IstioPilotHost
}
}
func (s *ServiceMeshOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.IstioPilotHost, "istio-pilot-host", s.IstioPilotHost, ""+
"istio pilot discovery service url")
fs.StringVar(&s.JaegerQueryHost, "jaeger-query-host", s.JaegerQueryHost, ""+
"jaeger query service url")
fs.StringVar(&s.ServicemeshPrometheusHost, "servicemesh-prometheus-host", s.ServicemeshPrometheusHost, ""+
"prometheus service for servicemesh")
}

View File

@@ -0,0 +1,49 @@
package sonarqube
import (
"github.com/spf13/pflag"
)
type SonarQubeOptions struct {
Host string `json:",omitempty" yaml:",omitempty" description:"SonarQube service host address"`
Token string `json:",omitempty" yaml:",omitempty" description:"SonarQube service token"`
}
func NewSonarQubeOptions() *SonarQubeOptions {
return &SonarQubeOptions{
Host: "",
Token: "",
}
}
func NewDefaultSonarQubeOptions() *SonarQubeOptions {
return NewSonarQubeOptions()
}
func (s *SonarQubeOptions) Validate() []error {
errors := []error{}
return errors
}
func (s *SonarQubeOptions) ApplyTo(options *SonarQubeOptions) {
if options == nil {
return
}
if s.Host != "" {
options.Host = s.Host
}
if s.Token != "" {
options.Token = s.Token
}
}
func (s *SonarQubeOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.Host, "sonarqube-host", s.Host, ""+
"Sonarqube service address if enabled.")
fs.StringVar(&s.Token, "sonarqube-token", s.Token, ""+
"Sonarqube service access token.")
}

View File

@@ -1,49 +0,0 @@
package sonarqube
import (
"flag"
"github.com/golang/glog"
"github.com/kubesphere/sonargo/sonar"
"strings"
"sync"
)
var (
sonarAddress string
sonarToken string
sonarOnce sync.Once
sonarClient *sonargo.Client
)
func init() {
flag.StringVar(&sonarAddress, "sonar-address", "", "sonar server host")
flag.StringVar(&sonarToken, "sonar-token", "", "sonar token")
}
func Client() *sonargo.Client {
sonarOnce.Do(func() {
if sonarAddress == "" {
sonarClient = nil
glog.Info("skip sonar init")
return
}
if !strings.HasSuffix(sonarAddress, "/") {
sonarAddress += "/"
}
client, err := sonargo.NewClientWithToken(sonarAddress+"api/", sonarToken)
if err != nil {
glog.Error("failed to connect to sonar")
return
}
_, _, err = client.Projects.Search(nil)
if err != nil {
glog.Errorf("failed to search sonar projects [%+v]", err)
return
}
glog.Info("init sonar client success")
sonarClient = client
})
return sonarClient
}

View File

@@ -0,0 +1,54 @@
package sonarqube
import (
"fmt"
"github.com/kubesphere/sonargo/sonar"
"k8s.io/klog"
"strings"
)
type SonarQubeClient struct {
client *sonargo.Client
}
func NewSonarQubeClient(options *SonarQubeOptions) (*SonarQubeClient, error) {
var endpoint string
if strings.HasSuffix(options.Host, "/") {
endpoint = fmt.Sprintf("%sapi/", options.Host)
} else {
endpoint = fmt.Sprintf("%s/api/", options.Host)
}
sonar, err := sonargo.NewClientWithToken(endpoint, options.Token)
if err != nil {
klog.Errorf("failed to connect to sonarqube service, %+v", err)
return nil, err
}
return &SonarQubeClient{client: sonar}, err
}
func NewSonarQubeClientOrDie(options *SonarQubeOptions) *SonarQubeClient {
var endpoint string
if strings.HasSuffix(options.Host, "/") {
endpoint = fmt.Sprintf("%sapi/", options.Host)
} else {
endpoint = fmt.Sprintf("%s/api/", options.Host)
}
sonar, err := sonargo.NewClientWithToken(endpoint, options.Token)
if err != nil {
klog.Errorf("failed to connect to sonarqube service, %+v", err)
panic(err)
}
return &SonarQubeClient{client: sonar}
}
// return sonarqube client
// Also we can wrap some methods to avoid direct use sonar client
func (s *SonarQubeClient) SonarQube() *sonargo.Client {
return s.client
}