Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d938161ad3 | ||
|
|
c8e131fc13 | ||
|
|
839a31ac1d | ||
|
|
a0ba5f6085 | ||
|
|
658497aa0a | ||
|
|
a47bf848df | ||
|
|
dbb3f04b9e | ||
|
|
705ea4af40 | ||
|
|
366d1e16e4 | ||
|
|
690d5be824 | ||
|
|
c0419ddab5 | ||
|
|
80b0301f79 | ||
|
|
7162d41310 | ||
|
|
6b10d346ca | ||
|
|
6a0d5ba93c | ||
|
|
d87a782257 | ||
|
|
82e55578a8 | ||
|
|
5b9c357160 | ||
|
|
c385dd92e4 | ||
|
|
1e1b2bd594 | ||
|
|
951b86648c | ||
|
|
04433c139d | ||
|
|
3b8c28d21e | ||
|
|
9489718270 | ||
|
|
54df6b8c8c | ||
|
|
d917905529 | ||
|
|
cd6f940f1d | ||
|
|
921a8f068b | ||
|
|
641aa1dfcf |
@@ -242,4 +242,5 @@ func (s *KubeSphereControllerManagerOptions) MergeConfig(cfg *controllerconfig.C
|
|||||||
s.MultiClusterOptions = cfg.MultiClusterOptions
|
s.MultiClusterOptions = cfg.MultiClusterOptions
|
||||||
s.ServiceMeshOptions = cfg.ServiceMeshOptions
|
s.ServiceMeshOptions = cfg.ServiceMeshOptions
|
||||||
s.GatewayOptions = cfg.GatewayOptions
|
s.GatewayOptions = cfg.GatewayOptions
|
||||||
|
s.MonitoringOptions = cfg.MonitoringOptions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1"
|
openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1"
|
||||||
"kubesphere.io/kubesphere/pkg/utils/clusterclient"
|
"kubesphere.io/kubesphere/pkg/utils/clusterclient"
|
||||||
@@ -41,9 +44,6 @@ import (
|
|||||||
auditingclient "kubesphere.io/kubesphere/pkg/simple/client/auditing/elasticsearch"
|
auditingclient "kubesphere.io/kubesphere/pkg/simple/client/auditing/elasticsearch"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||||
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
|
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
|
||||||
eventsclient "kubesphere.io/kubesphere/pkg/simple/client/events/elasticsearch"
|
eventsclient "kubesphere.io/kubesphere/pkg/simple/client/events/elasticsearch"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
||||||
@@ -59,9 +59,8 @@ type ServerRunOptions struct {
|
|||||||
ConfigFile string
|
ConfigFile string
|
||||||
GenericServerRunOptions *genericoptions.ServerRunOptions
|
GenericServerRunOptions *genericoptions.ServerRunOptions
|
||||||
*apiserverconfig.Config
|
*apiserverconfig.Config
|
||||||
|
schemeOnce sync.Once
|
||||||
//
|
DebugMode bool
|
||||||
DebugMode bool
|
|
||||||
|
|
||||||
// Enable gops or not.
|
// Enable gops or not.
|
||||||
GOPSEnabled bool
|
GOPSEnabled bool
|
||||||
@@ -71,6 +70,7 @@ func NewServerRunOptions() *ServerRunOptions {
|
|||||||
s := &ServerRunOptions{
|
s := &ServerRunOptions{
|
||||||
GenericServerRunOptions: genericoptions.NewServerRunOptions(),
|
GenericServerRunOptions: genericoptions.NewServerRunOptions(),
|
||||||
Config: apiserverconfig.New(),
|
Config: apiserverconfig.New(),
|
||||||
|
schemeOnce: sync.Once{},
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@@ -87,7 +87,6 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) {
|
|||||||
s.AuthorizationOptions.AddFlags(fss.FlagSet("authorization"), s.AuthorizationOptions)
|
s.AuthorizationOptions.AddFlags(fss.FlagSet("authorization"), s.AuthorizationOptions)
|
||||||
s.DevopsOptions.AddFlags(fss.FlagSet("devops"), s.DevopsOptions)
|
s.DevopsOptions.AddFlags(fss.FlagSet("devops"), s.DevopsOptions)
|
||||||
s.SonarQubeOptions.AddFlags(fss.FlagSet("sonarqube"), s.SonarQubeOptions)
|
s.SonarQubeOptions.AddFlags(fss.FlagSet("sonarqube"), s.SonarQubeOptions)
|
||||||
s.RedisOptions.AddFlags(fss.FlagSet("redis"), s.RedisOptions)
|
|
||||||
s.S3Options.AddFlags(fss.FlagSet("s3"), s.S3Options)
|
s.S3Options.AddFlags(fss.FlagSet("s3"), s.S3Options)
|
||||||
s.OpenPitrixOptions.AddFlags(fss.FlagSet("openpitrix"), s.OpenPitrixOptions)
|
s.OpenPitrixOptions.AddFlags(fss.FlagSet("openpitrix"), s.OpenPitrixOptions)
|
||||||
s.NetworkOptions.AddFlags(fss.FlagSet("network"), s.NetworkOptions)
|
s.NetworkOptions.AddFlags(fss.FlagSet("network"), s.NetworkOptions)
|
||||||
@@ -176,21 +175,23 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS
|
|||||||
apiServer.SonarClient = sonarqube.NewSonar(sonarClient.SonarQube())
|
apiServer.SonarClient = sonarqube.NewSonar(sonarClient.SonarQube())
|
||||||
}
|
}
|
||||||
|
|
||||||
var cacheClient cache.Interface
|
// If debug mode is on or CacheOptions is nil, will create a fake cache.
|
||||||
if s.RedisOptions != nil && len(s.RedisOptions.Host) != 0 {
|
if s.CacheOptions.Type != "" {
|
||||||
if s.RedisOptions.Host == fakeInterface && s.DebugMode {
|
if s.DebugMode {
|
||||||
apiServer.CacheClient = cache.NewSimpleCache()
|
s.CacheOptions.Type = cache.DefaultCacheType
|
||||||
} else {
|
|
||||||
cacheClient, err = cache.NewRedisClient(s.RedisOptions, stopCh)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to connect to redis service, please check redis status, error: %v", err)
|
|
||||||
}
|
|
||||||
apiServer.CacheClient = cacheClient
|
|
||||||
}
|
}
|
||||||
|
cacheClient, err := cache.New(s.CacheOptions, stopCh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create cache, error: %v", err)
|
||||||
|
}
|
||||||
|
apiServer.CacheClient = cacheClient
|
||||||
} else {
|
} else {
|
||||||
klog.Warning("ks-apiserver starts without redis provided, it will use in memory cache. " +
|
s.CacheOptions = &cache.Options{Type: cache.DefaultCacheType}
|
||||||
"This may cause inconsistencies when running ks-apiserver with multiple replicas.")
|
// fake cache has no error to return
|
||||||
apiServer.CacheClient = cache.NewSimpleCache()
|
cacheClient, _ := cache.New(s.CacheOptions, stopCh)
|
||||||
|
apiServer.CacheClient = cacheClient
|
||||||
|
klog.Warning("ks-apiserver starts without cache provided, it will use in memory cache. " +
|
||||||
|
"This may cause inconsistencies when running ks-apiserver with multiple replicas, and memory leak risk")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.EventsOptions.Host != "" {
|
if s.EventsOptions.Host != "" {
|
||||||
@@ -222,7 +223,7 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS
|
|||||||
apiServer.ClusterClient = cc
|
apiServer.ClusterClient = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
apiServer.OpenpitrixClient = openpitrixv1.NewOpenpitrixClient(informerFactory, apiServer.KubernetesClient.KubeSphere(), s.OpenPitrixOptions, apiServer.ClusterClient, stopCh)
|
apiServer.OpenpitrixClient = openpitrixv1.NewOpenpitrixClient(informerFactory, apiServer.KubernetesClient.KubeSphere(), s.OpenPitrixOptions, apiServer.ClusterClient)
|
||||||
|
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: fmt.Sprintf(":%d", s.GenericServerRunOptions.InsecurePort),
|
Addr: fmt.Sprintf(":%d", s.GenericServerRunOptions.InsecurePort),
|
||||||
@@ -241,9 +242,11 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS
|
|||||||
}
|
}
|
||||||
|
|
||||||
sch := scheme.Scheme
|
sch := scheme.Scheme
|
||||||
if err := apis.AddToScheme(sch); err != nil {
|
s.schemeOnce.Do(func() {
|
||||||
klog.Fatalf("unable add APIs to scheme: %v", err)
|
if err := apis.AddToScheme(sch); err != nil {
|
||||||
}
|
klog.Fatalf("unable add APIs to scheme: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
apiServer.RuntimeCache, err = runtimecache.New(apiServer.KubernetesClient.Config(), runtimecache.Options{Scheme: sch})
|
apiServer.RuntimeCache, err = runtimecache.New(apiServer.KubernetesClient.Config(), runtimecache.Options{Scheme: sch})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ find_files() {
|
|||||||
-o -wholename '*/third_party/*' \
|
-o -wholename '*/third_party/*' \
|
||||||
-o -wholename '*/vendor/*' \
|
-o -wholename '*/vendor/*' \
|
||||||
-o -wholename './staging/src/kubesphere.io/client-go/*vendor/*' \
|
-o -wholename './staging/src/kubesphere.io/client-go/*vendor/*' \
|
||||||
|
-o -wholename './staging/src/kubesphere.io/api/*/zz_generated.deepcopy.go' \
|
||||||
\) -prune \
|
\) -prune \
|
||||||
\) -name '*.go'
|
\) -name '*.go'
|
||||||
}
|
}
|
||||||
|
|||||||
1
hack/verify-gofmt.sh
Normal file → Executable file
1
hack/verify-gofmt.sh
Normal file → Executable file
@@ -44,6 +44,7 @@ find_files() {
|
|||||||
-o -wholename '*/third_party/*' \
|
-o -wholename '*/third_party/*' \
|
||||||
-o -wholename '*/vendor/*' \
|
-o -wholename '*/vendor/*' \
|
||||||
-o -wholename './staging/src/kubesphere.io/client-go/*vendor/*' \
|
-o -wholename './staging/src/kubesphere.io/client-go/*vendor/*' \
|
||||||
|
-o -wholename './staging/src/kubesphere.io/api/*/zz_generated.deepcopy.go' \
|
||||||
-o -wholename '*/bindata.go' \
|
-o -wholename '*/bindata.go' \
|
||||||
\) -prune \
|
\) -prune \
|
||||||
\) -name '*.go'
|
\) -name '*.go'
|
||||||
|
|||||||
@@ -394,6 +394,10 @@ func waitForCacheSync(discoveryClient discovery.DiscoveryInterface, sharedInform
|
|||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
klog.Warningf("group version %s not exists in the cluster", groupVersion)
|
||||||
|
continue
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to fetch group version resources %s: %s", groupVersion, err)
|
return fmt.Errorf("failed to fetch group version resources %s: %s", groupVersion, err)
|
||||||
}
|
}
|
||||||
for _, resourceName := range resourceNames {
|
for _, resourceName := range resourceNames {
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ func (b *Backend) sendEvents(events *v1alpha1.EventList) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
stopCh := make(chan struct{})
|
stopCh := make(chan struct{})
|
||||||
|
skipReturnSender := false
|
||||||
|
|
||||||
send := func() {
|
send := func() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), b.getSenderTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), b.getSenderTimeout)
|
||||||
@@ -149,6 +150,7 @@ func (b *Backend) sendEvents(events *v1alpha1.EventList) {
|
|||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
klog.Error("Get auditing event sender timeout")
|
klog.Error("Get auditing event sender timeout")
|
||||||
|
skipReturnSender = true
|
||||||
return
|
return
|
||||||
case b.senderCh <- struct{}{}:
|
case b.senderCh <- struct{}{}:
|
||||||
}
|
}
|
||||||
@@ -182,7 +184,9 @@ func (b *Backend) sendEvents(events *v1alpha1.EventList) {
|
|||||||
go send()
|
go send()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
<-b.senderCh
|
if !skipReturnSender {
|
||||||
|
<-b.senderCh
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apiserver/pkg/apis/audit"
|
"k8s.io/apiserver/pkg/apis/audit"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
|
||||||
devopsv1alpha3 "kubesphere.io/api/devops/v1alpha3"
|
devopsv1alpha3 "kubesphere.io/api/devops/v1alpha3"
|
||||||
|
"kubesphere.io/api/iam/v1alpha2"
|
||||||
|
|
||||||
auditv1alpha1 "kubesphere.io/kubesphere/pkg/apiserver/auditing/v1alpha1"
|
auditv1alpha1 "kubesphere.io/kubesphere/pkg/apiserver/auditing/v1alpha1"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||||
@@ -192,7 +192,7 @@ func (a *auditing) LogRequestObject(req *http.Request, info *request.RequestInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Level.GreaterOrEqual(audit.LevelRequest) || e.Verb == "create") && req.ContentLength > 0 {
|
if a.needAnalyzeRequestBody(e, req) {
|
||||||
body, err := ioutil.ReadAll(req.Body)
|
body, err := ioutil.ReadAll(req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
@@ -212,11 +212,45 @@ func (a *auditing) LogRequestObject(req *http.Request, info *request.RequestInfo
|
|||||||
e.ObjectRef.Name = obj.Name
|
e.ObjectRef.Name = obj.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for recording disable and enable user
|
||||||
|
if e.ObjectRef.Resource == "users" && e.Verb == "update" {
|
||||||
|
u := &v1alpha2.User{}
|
||||||
|
if err := json.Unmarshal(body, u); err == nil {
|
||||||
|
if u.Status.State == v1alpha2.UserActive {
|
||||||
|
e.Verb = "enable"
|
||||||
|
} else if u.Status.State == v1alpha2.UserDisabled {
|
||||||
|
e.Verb = "disable"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *auditing) needAnalyzeRequestBody(e *auditv1alpha1.Event, req *http.Request) bool {
|
||||||
|
|
||||||
|
if req.ContentLength <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Level.GreaterOrEqual(audit.LevelRequest) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Verb == "create" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// for recording disable and enable user
|
||||||
|
if e.ObjectRef.Resource == "users" && e.Verb == "update" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (a *auditing) LogResponseObject(e *auditv1alpha1.Event, resp *ResponseCapture) {
|
func (a *auditing) LogResponseObject(e *auditv1alpha1.Event, resp *ResponseCapture) {
|
||||||
|
|
||||||
e.StageTimestamp = metav1.NowMicro()
|
e.StageTimestamp = metav1.NowMicro()
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func init() {
|
|||||||
type ldapProvider struct {
|
type ldapProvider struct {
|
||||||
// Host and optional port of the LDAP server in the form "host:port".
|
// Host and optional port of the LDAP server in the form "host:port".
|
||||||
// If the port is not supplied, 389 for insecure or StartTLS connections, 636
|
// If the port is not supplied, 389 for insecure or StartTLS connections, 636
|
||||||
Host string `json:"host,omitempty" yaml:"managerDN"`
|
Host string `json:"host,omitempty" yaml:"host"`
|
||||||
// Timeout duration when reading data from remote server. Default to 15s.
|
// Timeout duration when reading data from remote server. Default to 15s.
|
||||||
ReadTimeout int `json:"readTimeout" yaml:"readTimeout"`
|
ReadTimeout int `json:"readTimeout" yaml:"readTimeout"`
|
||||||
// If specified, connections will use the ldaps:// protocol
|
// If specified, connections will use the ldaps:// protocol
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ type Config struct {
|
|||||||
ServiceMeshOptions *servicemesh.Options `json:"servicemesh,omitempty" yaml:"servicemesh,omitempty" mapstructure:"servicemesh"`
|
ServiceMeshOptions *servicemesh.Options `json:"servicemesh,omitempty" yaml:"servicemesh,omitempty" mapstructure:"servicemesh"`
|
||||||
NetworkOptions *network.Options `json:"network,omitempty" yaml:"network,omitempty" mapstructure:"network"`
|
NetworkOptions *network.Options `json:"network,omitempty" yaml:"network,omitempty" mapstructure:"network"`
|
||||||
LdapOptions *ldap.Options `json:"-,omitempty" yaml:"ldap,omitempty" mapstructure:"ldap"`
|
LdapOptions *ldap.Options `json:"-,omitempty" yaml:"ldap,omitempty" mapstructure:"ldap"`
|
||||||
RedisOptions *cache.Options `json:"redis,omitempty" yaml:"redis,omitempty" mapstructure:"redis"`
|
CacheOptions *cache.Options `json:"cache,omitempty" yaml:"cache,omitempty" mapstructure:"cache"`
|
||||||
S3Options *s3.Options `json:"s3,omitempty" yaml:"s3,omitempty" mapstructure:"s3"`
|
S3Options *s3.Options `json:"s3,omitempty" yaml:"s3,omitempty" mapstructure:"s3"`
|
||||||
OpenPitrixOptions *openpitrix.Options `json:"openpitrix,omitempty" yaml:"openpitrix,omitempty" mapstructure:"openpitrix"`
|
OpenPitrixOptions *openpitrix.Options `json:"openpitrix,omitempty" yaml:"openpitrix,omitempty" mapstructure:"openpitrix"`
|
||||||
MonitoringOptions *prometheus.Options `json:"monitoring,omitempty" yaml:"monitoring,omitempty" mapstructure:"monitoring"`
|
MonitoringOptions *prometheus.Options `json:"monitoring,omitempty" yaml:"monitoring,omitempty" mapstructure:"monitoring"`
|
||||||
@@ -189,7 +189,7 @@ func New() *Config {
|
|||||||
ServiceMeshOptions: servicemesh.NewServiceMeshOptions(),
|
ServiceMeshOptions: servicemesh.NewServiceMeshOptions(),
|
||||||
NetworkOptions: network.NewNetworkOptions(),
|
NetworkOptions: network.NewNetworkOptions(),
|
||||||
LdapOptions: ldap.NewOptions(),
|
LdapOptions: ldap.NewOptions(),
|
||||||
RedisOptions: cache.NewRedisOptions(),
|
CacheOptions: cache.NewCacheOptions(),
|
||||||
S3Options: s3.NewS3Options(),
|
S3Options: s3.NewS3Options(),
|
||||||
OpenPitrixOptions: openpitrix.NewOptions(),
|
OpenPitrixOptions: openpitrix.NewOptions(),
|
||||||
MonitoringOptions: prometheus.NewPrometheusOptions(),
|
MonitoringOptions: prometheus.NewPrometheusOptions(),
|
||||||
@@ -292,8 +292,8 @@ func (conf *Config) ToMap() map[string]bool {
|
|||||||
// Remove invalid options before serializing to json or yaml
|
// Remove invalid options before serializing to json or yaml
|
||||||
func (conf *Config) stripEmptyOptions() {
|
func (conf *Config) stripEmptyOptions() {
|
||||||
|
|
||||||
if conf.RedisOptions != nil && conf.RedisOptions.Host == "" {
|
if conf.CacheOptions != nil && conf.CacheOptions.Type == "" {
|
||||||
conf.RedisOptions = nil
|
conf.CacheOptions = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.DevopsOptions != nil && conf.DevopsOptions.Host == "" {
|
if conf.DevopsOptions != nil && conf.DevopsOptions.Host == "" {
|
||||||
|
|||||||
@@ -88,11 +88,9 @@ func newTestConfig() (*Config, error) {
|
|||||||
MaxCap: 100,
|
MaxCap: 100,
|
||||||
PoolName: "ldap",
|
PoolName: "ldap",
|
||||||
},
|
},
|
||||||
RedisOptions: &cache.Options{
|
CacheOptions: &cache.Options{
|
||||||
Host: "localhost",
|
Type: "redis",
|
||||||
Port: 6379,
|
Options: map[string]interface{}{},
|
||||||
Password: "KUBESPHERE_REDIS_PASSWORD",
|
|
||||||
DB: 0,
|
|
||||||
},
|
},
|
||||||
S3Options: &s3.Options{
|
S3Options: &s3.Options{
|
||||||
Endpoint: "http://minio.openpitrix-system.svc",
|
Endpoint: "http://minio.openpitrix-system.svc",
|
||||||
@@ -236,9 +234,6 @@ func TestGet(t *testing.T) {
|
|||||||
saveTestConfig(t, conf)
|
saveTestConfig(t, conf)
|
||||||
defer cleanTestConfig(t)
|
defer cleanTestConfig(t)
|
||||||
|
|
||||||
conf.RedisOptions.Password = "P@88w0rd"
|
|
||||||
os.Setenv("KUBESPHERE_REDIS_PASSWORD", "P@88w0rd")
|
|
||||||
|
|
||||||
conf2, err := TryLoadFromDisk()
|
conf2, err := TryLoadFromDisk()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -251,7 +246,7 @@ func TestGet(t *testing.T) {
|
|||||||
func TestStripEmptyOptions(t *testing.T) {
|
func TestStripEmptyOptions(t *testing.T) {
|
||||||
var config Config
|
var config Config
|
||||||
|
|
||||||
config.RedisOptions = &cache.Options{Host: ""}
|
config.CacheOptions = &cache.Options{Type: ""}
|
||||||
config.DevopsOptions = &jenkins.Options{Host: ""}
|
config.DevopsOptions = &jenkins.Options{Host: ""}
|
||||||
config.MonitoringOptions = &prometheus.Options{Endpoint: ""}
|
config.MonitoringOptions = &prometheus.Options{Endpoint: ""}
|
||||||
config.SonarQubeOptions = &sonarqube.Options{Host: ""}
|
config.SonarQubeOptions = &sonarqube.Options{Host: ""}
|
||||||
@@ -284,7 +279,7 @@ func TestStripEmptyOptions(t *testing.T) {
|
|||||||
|
|
||||||
config.stripEmptyOptions()
|
config.stripEmptyOptions()
|
||||||
|
|
||||||
if config.RedisOptions != nil ||
|
if config.CacheOptions != nil ||
|
||||||
config.DevopsOptions != nil ||
|
config.DevopsOptions != nil ||
|
||||||
config.MonitoringOptions != nil ||
|
config.MonitoringOptions != nil ||
|
||||||
config.SonarQubeOptions != nil ||
|
config.SonarQubeOptions != nil ||
|
||||||
|
|||||||
@@ -246,8 +246,6 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
|||||||
// parsing successful, so we now know the proper value for .Parts
|
// parsing successful, so we now know the proper value for .Parts
|
||||||
requestInfo.Parts = currentParts
|
requestInfo.Parts = currentParts
|
||||||
|
|
||||||
requestInfo.ResourceScope = r.resolveResourceScope(requestInfo)
|
|
||||||
|
|
||||||
// parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
|
// parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
|
||||||
switch {
|
switch {
|
||||||
case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb):
|
case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb):
|
||||||
@@ -260,6 +258,8 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
|||||||
requestInfo.Resource = requestInfo.Parts[0]
|
requestInfo.Resource = requestInfo.Parts[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestInfo.ResourceScope = r.resolveResourceScope(requestInfo)
|
||||||
|
|
||||||
// if there's no name on the request and we thought it was a get before, then the actual verb is a list or a watch
|
// if there's no name on the request and we thought it was a get before, then the actual verb is a list or a watch
|
||||||
if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
|
if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
|
||||||
opts := metainternalversion.ListOptions{}
|
opts := metainternalversion.ListOptions{}
|
||||||
|
|||||||
@@ -418,6 +418,15 @@ func (c *clusterController) syncCluster(key string) error {
|
|||||||
Message: "Cluster can not join federation control plane",
|
Message: "Cluster can not join federation control plane",
|
||||||
}
|
}
|
||||||
c.updateClusterCondition(cluster, federationNotReadyCondition)
|
c.updateClusterCondition(cluster, federationNotReadyCondition)
|
||||||
|
notReadyCondition := clusterv1alpha1.ClusterCondition{
|
||||||
|
Type: clusterv1alpha1.ClusterReady,
|
||||||
|
Status: v1.ConditionFalse,
|
||||||
|
LastUpdateTime: metav1.Now(),
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: "Cluster join federation control plane failed",
|
||||||
|
Message: "Cluster is Not Ready now",
|
||||||
|
}
|
||||||
|
c.updateClusterCondition(cluster, notReadyCondition)
|
||||||
|
|
||||||
_, err = c.ksClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{})
|
_, err = c.ksClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -380,6 +380,7 @@ func (h *iamHandler) ListWorkspaceRoles(request *restful.Request, response *rest
|
|||||||
queryParam.Filters[iamv1alpha2.ScopeWorkspace] = query.Value(workspace)
|
queryParam.Filters[iamv1alpha2.ScopeWorkspace] = query.Value(workspace)
|
||||||
// shared workspace role template
|
// shared workspace role template
|
||||||
if string(queryParam.Filters[query.FieldLabel]) == fmt.Sprintf("%s=%s", iamv1alpha2.RoleTemplateLabel, "true") ||
|
if string(queryParam.Filters[query.FieldLabel]) == fmt.Sprintf("%s=%s", iamv1alpha2.RoleTemplateLabel, "true") ||
|
||||||
|
strings.Contains(queryParam.LabelSelector, iamv1alpha2.RoleTemplateLabel) ||
|
||||||
queryParam.Filters[iamv1alpha2.AggregateTo] != "" {
|
queryParam.Filters[iamv1alpha2.AggregateTo] != "" {
|
||||||
delete(queryParam.Filters, iamv1alpha2.ScopeWorkspace)
|
delete(queryParam.Filters, iamv1alpha2.ScopeWorkspace)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ type openpitrixHandler struct {
|
|||||||
openpitrix openpitrix.Interface
|
openpitrix openpitrix.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOpenpitrixClient(ksInformers informers.InformerFactory, ksClient versioned.Interface, option *openpitrixoptions.Options, cc clusterclient.ClusterClients, stopCh <-chan struct{}) openpitrix.Interface {
|
func NewOpenpitrixClient(ksInformers informers.InformerFactory, ksClient versioned.Interface, option *openpitrixoptions.Options, cc clusterclient.ClusterClients) openpitrix.Interface {
|
||||||
var s3Client s3.Interface
|
var s3Client s3.Interface
|
||||||
if option != nil && option.S3Options != nil && len(option.S3Options.Endpoint) != 0 {
|
if option != nil && option.S3Options != nil && len(option.S3Options.Endpoint) != 0 {
|
||||||
var err error
|
var err error
|
||||||
@@ -62,7 +62,7 @@ func NewOpenpitrixClient(ksInformers informers.InformerFactory, ksClient version
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return openpitrix.NewOpenpitrixOperator(ksInformers, ksClient, s3Client, cc, stopCh)
|
return openpitrix.NewOpenpitrixOperator(ksInformers, ksClient, s3Client, cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *openpitrixHandler) CreateRepo(req *restful.Request, resp *restful.Response) {
|
func (h *openpitrixHandler) CreateRepo(req *restful.Request, resp *restful.Response) {
|
||||||
|
|||||||
@@ -48,15 +48,17 @@ func NewHandler(o *servicemesh.Options, client kubernetes.Interface, cache cache
|
|||||||
if o != nil && o.KialiQueryHost != "" {
|
if o != nil && o.KialiQueryHost != "" {
|
||||||
sa, err := client.CoreV1().ServiceAccounts(KubesphereNamespace).Get(context.TODO(), KubeSphereServiceAccount, metav1.GetOptions{})
|
sa, err := client.CoreV1().ServiceAccounts(KubesphereNamespace).Get(context.TODO(), KubeSphereServiceAccount, metav1.GetOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
secret, err := client.CoreV1().Secrets(KubesphereNamespace).Get(context.TODO(), sa.Secrets[0].Name, metav1.GetOptions{})
|
if len(sa.Secrets) > 0 {
|
||||||
if err == nil {
|
secret, err := client.CoreV1().Secrets(KubesphereNamespace).Get(context.TODO(), sa.Secrets[0].Name, metav1.GetOptions{})
|
||||||
return &Handler{
|
if err == nil {
|
||||||
opt: o,
|
return &Handler{
|
||||||
client: kiali.NewDefaultClient(
|
opt: o,
|
||||||
cache,
|
client: kiali.NewDefaultClient(
|
||||||
string(secret.Data["token"]),
|
cache,
|
||||||
o.KialiQueryHost,
|
string(secret.Data["token"]),
|
||||||
),
|
o.KialiQueryHost,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
klog.Warningf("get ServiceAccount's Secret failed %v", err)
|
klog.Warningf("get ServiceAccount's Secret failed %v", err)
|
||||||
|
|||||||
@@ -202,30 +202,40 @@ func (h *tenantHandler) CreateNamespace(request *restful.Request, response *rest
|
|||||||
response.WriteEntity(created)
|
response.WriteEntity(created)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *tenantHandler) CreateWorkspaceTemplate(request *restful.Request, response *restful.Response) {
|
func (h *tenantHandler) CreateWorkspaceTemplate(req *restful.Request, resp *restful.Response) {
|
||||||
var workspace tenantv1alpha2.WorkspaceTemplate
|
var workspace tenantv1alpha2.WorkspaceTemplate
|
||||||
|
|
||||||
err := request.ReadEntity(&workspace)
|
err := req.ReadEntity(&workspace)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
api.HandleBadRequest(response, request, err)
|
api.HandleBadRequest(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
requestUser, ok := request.UserFrom(req.Request.Context())
|
||||||
|
if !ok {
|
||||||
|
err := fmt.Errorf("cannot obtain user info")
|
||||||
|
klog.Errorln(err)
|
||||||
|
api.HandleForbidden(resp, req, err)
|
||||||
|
}
|
||||||
|
|
||||||
created, err := h.tenant.CreateWorkspaceTemplate(&workspace)
|
created, err := h.tenant.CreateWorkspaceTemplate(requestUser, &workspace)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
api.HandleNotFound(response, request, err)
|
api.HandleNotFound(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
api.HandleBadRequest(response, request, err)
|
if errors.IsForbidden(err) {
|
||||||
|
api.HandleForbidden(resp, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.HandleBadRequest(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.WriteEntity(created)
|
resp.WriteEntity(created)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *tenantHandler) DeleteWorkspaceTemplate(request *restful.Request, response *restful.Response) {
|
func (h *tenantHandler) DeleteWorkspaceTemplate(request *restful.Request, response *restful.Response) {
|
||||||
@@ -253,42 +263,53 @@ func (h *tenantHandler) DeleteWorkspaceTemplate(request *restful.Request, respon
|
|||||||
response.WriteEntity(servererr.None)
|
response.WriteEntity(servererr.None)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *tenantHandler) UpdateWorkspaceTemplate(request *restful.Request, response *restful.Response) {
|
func (h *tenantHandler) UpdateWorkspaceTemplate(req *restful.Request, resp *restful.Response) {
|
||||||
workspaceName := request.PathParameter("workspace")
|
workspaceName := req.PathParameter("workspace")
|
||||||
var workspace tenantv1alpha2.WorkspaceTemplate
|
var workspace tenantv1alpha2.WorkspaceTemplate
|
||||||
|
|
||||||
err := request.ReadEntity(&workspace)
|
err := req.ReadEntity(&workspace)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
api.HandleBadRequest(response, request, err)
|
api.HandleBadRequest(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if workspaceName != workspace.Name {
|
if workspaceName != workspace.Name {
|
||||||
err := fmt.Errorf("the name of the object (%s) does not match the name on the URL (%s)", workspace.Name, workspaceName)
|
err := fmt.Errorf("the name of the object (%s) does not match the name on the URL (%s)", workspace.Name, workspaceName)
|
||||||
klog.Errorf("%+v", err)
|
klog.Errorf("%+v", err)
|
||||||
api.HandleBadRequest(response, request, err)
|
api.HandleBadRequest(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updated, err := h.tenant.UpdateWorkspaceTemplate(&workspace)
|
requestUser, ok := request.UserFrom(req.Request.Context())
|
||||||
|
if !ok {
|
||||||
|
err := fmt.Errorf("cannot obtain user info")
|
||||||
|
klog.Errorln(err)
|
||||||
|
api.HandleForbidden(resp, req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := h.tenant.UpdateWorkspaceTemplate(requestUser, &workspace)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
api.HandleNotFound(response, request, err)
|
api.HandleNotFound(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if errors.IsBadRequest(err) {
|
if errors.IsBadRequest(err) {
|
||||||
api.HandleBadRequest(response, request, err)
|
api.HandleBadRequest(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
api.HandleInternalError(response, request, err)
|
if errors.IsForbidden(err) {
|
||||||
|
api.HandleForbidden(resp, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.HandleInternalError(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.WriteEntity(updated)
|
resp.WriteEntity(updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *tenantHandler) DescribeWorkspaceTemplate(request *restful.Request, response *restful.Response) {
|
func (h *tenantHandler) DescribeWorkspaceTemplate(request *restful.Request, response *restful.Response) {
|
||||||
@@ -520,33 +541,44 @@ func (h *tenantHandler) PatchNamespace(request *restful.Request, response *restf
|
|||||||
response.WriteEntity(patched)
|
response.WriteEntity(patched)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *tenantHandler) PatchWorkspaceTemplate(request *restful.Request, response *restful.Response) {
|
func (h *tenantHandler) PatchWorkspaceTemplate(req *restful.Request, resp *restful.Response) {
|
||||||
workspaceName := request.PathParameter("workspace")
|
workspaceName := req.PathParameter("workspace")
|
||||||
var data json.RawMessage
|
var data json.RawMessage
|
||||||
err := request.ReadEntity(&data)
|
err := req.ReadEntity(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
api.HandleBadRequest(response, request, err)
|
api.HandleBadRequest(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
patched, err := h.tenant.PatchWorkspaceTemplate(workspaceName, data)
|
requestUser, ok := request.UserFrom(req.Request.Context())
|
||||||
|
if !ok {
|
||||||
|
err := fmt.Errorf("cannot obtain user info")
|
||||||
|
klog.Errorln(err)
|
||||||
|
api.HandleForbidden(resp, req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patched, err := h.tenant.PatchWorkspaceTemplate(requestUser, workspaceName, data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
api.HandleNotFound(response, request, err)
|
api.HandleNotFound(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if errors.IsBadRequest(err) {
|
if errors.IsBadRequest(err) {
|
||||||
api.HandleBadRequest(response, request, err)
|
api.HandleBadRequest(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
api.HandleInternalError(response, request, err)
|
if errors.IsNotFound(err) {
|
||||||
|
api.HandleForbidden(resp, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.HandleInternalError(resp, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.WriteEntity(patched)
|
resp.WriteEntity(patched)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *tenantHandler) ListClusters(r *restful.Request, response *restful.Response) {
|
func (h *tenantHandler) ListClusters(r *restful.Request, response *restful.Response) {
|
||||||
|
|||||||
@@ -47,12 +47,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MasterLabel = "node-role.kubernetes.io/master"
|
MasterLabel = "node-role.kubernetes.io/master"
|
||||||
SidecarInject = "sidecar.istio.io/inject"
|
SidecarInject = "sidecar.istio.io/inject"
|
||||||
gatewayPrefix = "kubesphere-router-"
|
gatewayPrefix = "kubesphere-router-"
|
||||||
workingNamespace = "kubesphere-controls-system"
|
workingNamespace = "kubesphere-controls-system"
|
||||||
globalGatewayname = gatewayPrefix + "kubesphere-system"
|
globalGatewayNameSuffix = "kubesphere-system"
|
||||||
helmPatch = `{"metadata":{"annotations":{"meta.helm.sh/release-name":"%s-ingress","meta.helm.sh/release-namespace":"%s"},"labels":{"helm.sh/chart":"ingress-nginx-3.35.0","app.kubernetes.io/managed-by":"Helm","app":null,"component":null,"tier":null}},"spec":{"selector":null}}`
|
globalGatewayName = gatewayPrefix + globalGatewayNameSuffix
|
||||||
|
helmPatch = `{"metadata":{"annotations":{"meta.helm.sh/release-name":"%s-ingress","meta.helm.sh/release-namespace":"%s"},"labels":{"helm.sh/chart":"ingress-nginx-3.35.0","app.kubernetes.io/managed-by":"Helm","app":null,"component":null,"tier":null}},"spec":{"selector":null}}`
|
||||||
)
|
)
|
||||||
|
|
||||||
type GatewayOperator interface {
|
type GatewayOperator interface {
|
||||||
@@ -90,6 +91,10 @@ func (c *gatewayOperator) getWorkingNamespace(namespace string) string {
|
|||||||
if ns == "" {
|
if ns == "" {
|
||||||
ns = namespace
|
ns = namespace
|
||||||
}
|
}
|
||||||
|
// Convert the global gateway query parameter
|
||||||
|
if namespace == globalGatewayNameSuffix {
|
||||||
|
ns = workingNamespace
|
||||||
|
}
|
||||||
return ns
|
return ns
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +102,7 @@ func (c *gatewayOperator) getWorkingNamespace(namespace string) string {
|
|||||||
func (c *gatewayOperator) overrideDefaultValue(gateway *v1alpha1.Gateway, namespace string) *v1alpha1.Gateway {
|
func (c *gatewayOperator) overrideDefaultValue(gateway *v1alpha1.Gateway, namespace string) *v1alpha1.Gateway {
|
||||||
// override default name
|
// override default name
|
||||||
gateway.Name = fmt.Sprint(gatewayPrefix, namespace)
|
gateway.Name = fmt.Sprint(gatewayPrefix, namespace)
|
||||||
if gateway.Name != globalGatewayname {
|
if gateway.Name != globalGatewayName {
|
||||||
gateway.Spec.Controller.Scope = v1alpha1.Scope{Enabled: true, Namespace: namespace}
|
gateway.Spec.Controller.Scope = v1alpha1.Scope{Enabled: true, Namespace: namespace}
|
||||||
}
|
}
|
||||||
gateway.Namespace = c.getWorkingNamespace(namespace)
|
gateway.Namespace = c.getWorkingNamespace(namespace)
|
||||||
@@ -108,7 +113,7 @@ func (c *gatewayOperator) overrideDefaultValue(gateway *v1alpha1.Gateway, namesp
|
|||||||
func (c *gatewayOperator) getGlobalGateway() *v1alpha1.Gateway {
|
func (c *gatewayOperator) getGlobalGateway() *v1alpha1.Gateway {
|
||||||
globalkey := types.NamespacedName{
|
globalkey := types.NamespacedName{
|
||||||
Namespace: workingNamespace,
|
Namespace: workingNamespace,
|
||||||
Name: globalGatewayname,
|
Name: globalGatewayName,
|
||||||
}
|
}
|
||||||
|
|
||||||
global := &v1alpha1.Gateway{}
|
global := &v1alpha1.Gateway{}
|
||||||
@@ -331,7 +336,7 @@ func (c *gatewayOperator) UpgradeGateway(namespace string) (*v1alpha1.Gateway, e
|
|||||||
if l == nil {
|
if l == nil {
|
||||||
return nil, fmt.Errorf("invalid operation, no legacy gateway was found")
|
return nil, fmt.Errorf("invalid operation, no legacy gateway was found")
|
||||||
}
|
}
|
||||||
if l.Namespace != c.options.Namespace {
|
if l.Namespace != c.getWorkingNamespace(namespace) {
|
||||||
return nil, fmt.Errorf("invalid operation, can't upgrade legacy gateway when working namespace changed")
|
return nil, fmt.Errorf("invalid operation, can't upgrade legacy gateway when working namespace changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package openpitrix
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -104,7 +105,7 @@ func newApplicationOperator(cached reposcache.ReposCache, informers externalvers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// save icon data and helm application
|
// save icon data and helm application
|
||||||
func (c *applicationOperator) createApp(app *v1alpha1.HelmApplication, iconData []byte) (*v1alpha1.HelmApplication, error) {
|
func (c *applicationOperator) createApp(app *v1alpha1.HelmApplication, iconData string) (*v1alpha1.HelmApplication, error) {
|
||||||
exists, err := c.getHelmAppByName(app.GetWorkspace(), app.GetTrueName())
|
exists, err := c.getHelmAppByName(app.GetWorkspace(), app.GetTrueName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -112,11 +113,18 @@ func (c *applicationOperator) createApp(app *v1alpha1.HelmApplication, iconData
|
|||||||
if exists != nil {
|
if exists != nil {
|
||||||
return nil, appItemExists
|
return nil, appItemExists
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(iconData, "http://") || strings.HasPrefix(iconData, "https://") {
|
||||||
if len(iconData) != 0 {
|
app.Spec.Icon = iconData
|
||||||
|
} else if len(iconData) != 0 {
|
||||||
// save icon attachment
|
// save icon attachment
|
||||||
iconId := idutils.GetUuid(v1alpha1.HelmAttachmentPrefix)
|
iconId := idutils.GetUuid(v1alpha1.HelmAttachmentPrefix)
|
||||||
err = c.backingStoreClient.Upload(iconId, iconId, bytes.NewBuffer(iconData), len(iconData))
|
decodeString, err := base64.StdEncoding.DecodeString(iconData)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("decodeString icon failed, error: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.backingStoreClient.Upload(iconId, iconId, bytes.NewBuffer(decodeString), len(iconData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("save icon attachment failed, error: %s", err)
|
klog.Errorf("save icon attachment failed, error: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -168,6 +176,7 @@ func (c *applicationOperator) ValidatePackage(request *ValidatePackageRequest) (
|
|||||||
result.VersionName = chrt.GetVersionName()
|
result.VersionName = chrt.GetVersionName()
|
||||||
result.Description = chrt.GetDescription()
|
result.Description = chrt.GetDescription()
|
||||||
result.URL = chrt.GetUrls()
|
result.URL = chrt.GetUrls()
|
||||||
|
result.Icon = chrt.GetIcon()
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ type openpitrixOperator struct {
|
|||||||
CategoryInterface
|
CategoryInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOpenpitrixOperator(ksInformers ks_informers.InformerFactory, ksClient versioned.Interface, s3Client s3.Interface, cc clusterclient.ClusterClients, stopCh <-chan struct{}) Interface {
|
func NewOpenpitrixOperator(ksInformers ks_informers.InformerFactory, ksClient versioned.Interface, s3Client s3.Interface, cc clusterclient.ClusterClients) Interface {
|
||||||
klog.Infof("start helm repo informer")
|
klog.Infof("start helm repo informer")
|
||||||
cachedReposData := reposcache.NewReposCache()
|
cachedReposData := reposcache.NewReposCache()
|
||||||
helmReposInformer := ksInformers.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmRepos().Informer()
|
helmReposInformer := ksInformers.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmRepos().Informer()
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ func (c *repoOperator) ListRepos(conditions *params.Conditions, orderBy string,
|
|||||||
start, end := (&query.Pagination{Limit: limit, Offset: offset}).GetValidPagination(totalCount)
|
start, end := (&query.Pagination{Limit: limit, Offset: offset}).GetValidPagination(totalCount)
|
||||||
repos = repos[start:end]
|
repos = repos[start:end]
|
||||||
items := make([]interface{}, 0, len(repos))
|
items := make([]interface{}, 0, len(repos))
|
||||||
for i, j := offset, 0; i < len(repos) && j < limit; i, j = i+1, j+1 {
|
for i := range repos {
|
||||||
items = append(items, convertRepo(repos[i]))
|
items = append(items, convertRepo(repos[i]))
|
||||||
}
|
}
|
||||||
return &models.PageableResponse{Items: items, TotalCount: totalCount}, nil
|
return &models.PageableResponse{Items: items, TotalCount: totalCount}, nil
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ type AppVersionReview struct {
|
|||||||
type CreateAppRequest struct {
|
type CreateAppRequest struct {
|
||||||
|
|
||||||
// app icon
|
// app icon
|
||||||
Icon strfmt.Base64 `json:"icon,omitempty"`
|
Icon string `json:"icon,omitempty"`
|
||||||
|
|
||||||
// isv
|
// isv
|
||||||
Isv string `json:"isv,omitempty"`
|
Isv string `json:"isv,omitempty"`
|
||||||
@@ -413,6 +413,8 @@ type ValidatePackageResponse struct {
|
|||||||
|
|
||||||
// app version name.eg.[0.1.0]
|
// app version name.eg.[0.1.0]
|
||||||
VersionName string `json:"version_name,omitempty"`
|
VersionName string `json:"version_name,omitempty"`
|
||||||
|
|
||||||
|
Icon string `json:"icon,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateAppVersionRequest struct {
|
type CreateAppVersionRequest struct {
|
||||||
@@ -713,7 +715,7 @@ type Repo struct {
|
|||||||
// selectors
|
// selectors
|
||||||
Selectors RepoSelectors `json:"selectors"`
|
Selectors RepoSelectors `json:"selectors"`
|
||||||
|
|
||||||
// status eg.[active|deleted]
|
// status eg.[successful|failed|syncing]
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
|
|
||||||
// record status changed time
|
// record status changed time
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ func convertAppVersion(in *v1alpha1.HelmApplicationVersion) *AppVersion {
|
|||||||
if in.Spec.Metadata != nil {
|
if in.Spec.Metadata != nil {
|
||||||
out.Description = in.Spec.Description
|
out.Description = in.Spec.Description
|
||||||
out.Icon = in.Spec.Icon
|
out.Icon = in.Spec.Icon
|
||||||
|
out.Home = in.Spec.Home
|
||||||
}
|
}
|
||||||
|
|
||||||
// The field Maintainers and Sources were a string field, so I encode the helm field's maintainers and sources,
|
// The field Maintainers and Sources were a string field, so I encode the helm field's maintainers and sources,
|
||||||
@@ -431,6 +432,10 @@ func convertRepo(in *v1alpha1.HelmRepo) *Repo {
|
|||||||
out.Name = in.GetTrueName()
|
out.Name = in.GetTrueName()
|
||||||
|
|
||||||
out.Status = in.Status.State
|
out.Status = in.Status.State
|
||||||
|
// set default status `syncing` when helmrepo not reconcile yet
|
||||||
|
if out.Status == "" {
|
||||||
|
out.Status = v1alpha1.RepoStateSyncing
|
||||||
|
}
|
||||||
date := strfmt.DateTime(time.Unix(in.CreationTimestamp.Unix(), 0))
|
date := strfmt.DateTime(time.Unix(in.CreationTimestamp.Unix(), 0))
|
||||||
out.CreateTime = &date
|
out.CreateTime = &date
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ limitations under the License.
|
|||||||
package pod
|
package pod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@@ -31,7 +34,13 @@ const (
|
|||||||
fieldNodeName = "nodeName"
|
fieldNodeName = "nodeName"
|
||||||
fieldPVCName = "pvcName"
|
fieldPVCName = "pvcName"
|
||||||
fieldServiceName = "serviceName"
|
fieldServiceName = "serviceName"
|
||||||
|
fieldPhase = "phase"
|
||||||
fieldStatus = "status"
|
fieldStatus = "status"
|
||||||
|
|
||||||
|
statusTypeWaitting = "Waiting"
|
||||||
|
statusTypeRunning = "Running"
|
||||||
|
statusTypeError = "Error"
|
||||||
|
statusTypeCompleted = "Completed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type podsGetter struct {
|
type podsGetter struct {
|
||||||
@@ -90,6 +99,9 @@ func (p *podsGetter) filter(object runtime.Object, filter query.Filter) bool {
|
|||||||
case fieldServiceName:
|
case fieldServiceName:
|
||||||
return p.podBelongToService(pod, string(filter.Value))
|
return p.podBelongToService(pod, string(filter.Value))
|
||||||
case fieldStatus:
|
case fieldStatus:
|
||||||
|
_, statusType := p.getPodStatus(pod)
|
||||||
|
return statusType == string(filter.Value)
|
||||||
|
case fieldPhase:
|
||||||
return string(pod.Status.Phase) == string(filter.Value)
|
return string(pod.Status.Phase) == string(filter.Value)
|
||||||
default:
|
default:
|
||||||
return v1alpha3.DefaultObjectMetaFilter(pod.ObjectMeta, filter)
|
return v1alpha3.DefaultObjectMetaFilter(pod.ObjectMeta, filter)
|
||||||
@@ -117,3 +129,133 @@ func (p *podsGetter) podBelongToService(item *corev1.Pod, serviceName string) bo
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getPodStatus refer to `kubectl get po` result.
|
||||||
|
// https://github.com/kubernetes/kubernetes/blob/45279654db87f4908911569c07afc42804f0e246/pkg/printers/internalversion/printers.go#L820-920
|
||||||
|
// podStatusPhase = []string("Pending", "Running","Succeeded","Failed","Unknown")
|
||||||
|
// podStatusReasons = []string{"Evicted", "NodeAffinity", "NodeLost", "Shutdown", "UnexpectedAdmissionError"}
|
||||||
|
// containerWaitingReasons = []string{"ContainerCreating", "CrashLoopBackOff", "CreateContainerConfigError", "ErrImagePull", "ImagePullBackOff", "CreateContainerError", "InvalidImageName"}
|
||||||
|
// containerTerminatedReasons = []string{"OOMKilled", "Completed", "Error", "ContainerCannotRun", "DeadlineExceeded", "Evicted"}
|
||||||
|
func (p *podsGetter) getPodStatus(pod *corev1.Pod) (string, string) {
|
||||||
|
reason := string(pod.Status.Phase)
|
||||||
|
|
||||||
|
if pod.Status.Reason != "" {
|
||||||
|
reason = pod.Status.Reason
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
todo: upgrade k8s.io/api version
|
||||||
|
|
||||||
|
// If the Pod carries {type:PodScheduled, reason:WaitingForGates}, set reason to 'SchedulingGated'.
|
||||||
|
for _, condition := range pod.Status.Conditions {
|
||||||
|
if condition.Type == corev1.PodScheduled && condition.Reason == corev1.PodReasonSchedulingGated {
|
||||||
|
reason = corev1.PodReasonSchedulingGated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
initializing := false
|
||||||
|
for i := range pod.Status.InitContainerStatuses {
|
||||||
|
container := pod.Status.InitContainerStatuses[i]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0:
|
||||||
|
continue
|
||||||
|
case container.State.Terminated != nil:
|
||||||
|
// initialization is failed
|
||||||
|
if len(container.State.Terminated.Reason) == 0 {
|
||||||
|
if container.State.Terminated.Signal != 0 {
|
||||||
|
reason = fmt.Sprintf("Init:Signal:%d", container.State.Terminated.Signal)
|
||||||
|
} else {
|
||||||
|
reason = fmt.Sprintf("Init:ExitCode:%d", container.State.Terminated.ExitCode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reason = "Init:" + container.State.Terminated.Reason
|
||||||
|
}
|
||||||
|
initializing = true
|
||||||
|
case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
|
||||||
|
reason = "Init:" + container.State.Waiting.Reason
|
||||||
|
initializing = true
|
||||||
|
default:
|
||||||
|
reason = fmt.Sprintf("Init:%d/%d", i, len(pod.Spec.InitContainers))
|
||||||
|
initializing = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !initializing {
|
||||||
|
|
||||||
|
hasRunning := false
|
||||||
|
for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
|
||||||
|
container := pod.Status.ContainerStatuses[i]
|
||||||
|
|
||||||
|
if container.State.Waiting != nil && container.State.Waiting.Reason != "" {
|
||||||
|
reason = container.State.Waiting.Reason
|
||||||
|
} else if container.State.Terminated != nil && container.State.Terminated.Reason != "" {
|
||||||
|
reason = container.State.Terminated.Reason
|
||||||
|
} else if container.State.Terminated != nil && container.State.Terminated.Reason == "" {
|
||||||
|
if container.State.Terminated.Signal != 0 {
|
||||||
|
reason = fmt.Sprintf("Signal:%d", container.State.Terminated.Signal)
|
||||||
|
} else {
|
||||||
|
reason = fmt.Sprintf("ExitCode:%d", container.State.Terminated.ExitCode)
|
||||||
|
}
|
||||||
|
} else if container.Ready && container.State.Running != nil {
|
||||||
|
hasRunning = true
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// change pod status back to "Running" if there is at least one container still reporting as "Running" status
|
||||||
|
if reason == "Completed" && hasRunning {
|
||||||
|
if hasPodReadyCondition(pod.Status.Conditions) {
|
||||||
|
reason = "Running"
|
||||||
|
} else {
|
||||||
|
reason = "NotReady"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pod.DeletionTimestamp != nil && pod.Status.Reason == "NodeLost" {
|
||||||
|
reason = "Unknown"
|
||||||
|
} else if pod.DeletionTimestamp != nil {
|
||||||
|
reason = "Terminating"
|
||||||
|
}
|
||||||
|
|
||||||
|
statusType := statusTypeWaitting
|
||||||
|
switch reason {
|
||||||
|
case "Running":
|
||||||
|
statusType = statusTypeRunning
|
||||||
|
case "Failed":
|
||||||
|
statusType = statusTypeError
|
||||||
|
case "Error":
|
||||||
|
statusType = statusTypeError
|
||||||
|
case "Completed":
|
||||||
|
statusType = statusTypeCompleted
|
||||||
|
case "Succeeded":
|
||||||
|
if isPodReadyConditionReason(pod.Status.Conditions, "PodCompleted") {
|
||||||
|
statusType = statusTypeCompleted
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if strings.HasPrefix(reason, "OutOf") {
|
||||||
|
statusType = statusTypeError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reason, statusType
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPodReadyCondition(conditions []corev1.PodCondition) bool {
|
||||||
|
for _, condition := range conditions {
|
||||||
|
if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPodReadyConditionReason(conditions []corev1.PodCondition, reason string) bool {
|
||||||
|
for _, condition := range conditions {
|
||||||
|
if condition.Type == corev1.PodReady && condition.Reason != reason {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func TestListPods(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test status filter",
|
"test phase filter",
|
||||||
"default",
|
"default",
|
||||||
&query.Query{
|
&query.Query{
|
||||||
Pagination: &query.Pagination{
|
Pagination: &query.Pagination{
|
||||||
@@ -89,7 +89,7 @@ func TestListPods(t *testing.T) {
|
|||||||
Ascending: false,
|
Ascending: false,
|
||||||
Filters: map[query.Field]query.Value{
|
Filters: map[query.Field]query.Value{
|
||||||
query.FieldNamespace: query.Value("default"),
|
query.FieldNamespace: query.Value("default"),
|
||||||
fieldStatus: query.Value(corev1.PodRunning),
|
fieldPhase: query.Value(corev1.PodRunning),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&api.ListResult{
|
&api.ListResult{
|
||||||
@@ -163,6 +163,7 @@ var (
|
|||||||
Phase: corev1.PodRunning,
|
Phase: corev1.PodRunning,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pods = []interface{}{foo1, foo2, foo3, foo4, foo5}
|
pods = []interface{}{foo1, foo2, foo3, foo4, foo5}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
@@ -69,6 +71,8 @@ import (
|
|||||||
loggingclient "kubesphere.io/kubesphere/pkg/simple/client/logging"
|
loggingclient "kubesphere.io/kubesphere/pkg/simple/client/logging"
|
||||||
meteringclient "kubesphere.io/kubesphere/pkg/simple/client/metering"
|
meteringclient "kubesphere.io/kubesphere/pkg/simple/client/metering"
|
||||||
monitoringclient "kubesphere.io/kubesphere/pkg/simple/client/monitoring"
|
monitoringclient "kubesphere.io/kubesphere/pkg/simple/client/monitoring"
|
||||||
|
"kubesphere.io/kubesphere/pkg/utils/clusterclient"
|
||||||
|
jsonpatchutil "kubesphere.io/kubesphere/pkg/utils/josnpatchutil"
|
||||||
"kubesphere.io/kubesphere/pkg/utils/stringutils"
|
"kubesphere.io/kubesphere/pkg/utils/stringutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,10 +82,10 @@ type Interface interface {
|
|||||||
ListWorkspaces(user user.Info, queryParam *query.Query) (*api.ListResult, error)
|
ListWorkspaces(user user.Info, queryParam *query.Query) (*api.ListResult, error)
|
||||||
GetWorkspace(workspace string) (*tenantv1alpha1.Workspace, error)
|
GetWorkspace(workspace string) (*tenantv1alpha1.Workspace, error)
|
||||||
ListWorkspaceTemplates(user user.Info, query *query.Query) (*api.ListResult, error)
|
ListWorkspaceTemplates(user user.Info, query *query.Query) (*api.ListResult, error)
|
||||||
CreateWorkspaceTemplate(workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error)
|
CreateWorkspaceTemplate(user user.Info, workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error)
|
||||||
DeleteWorkspaceTemplate(workspace string, opts metav1.DeleteOptions) error
|
DeleteWorkspaceTemplate(workspace string, opts metav1.DeleteOptions) error
|
||||||
UpdateWorkspaceTemplate(workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error)
|
UpdateWorkspaceTemplate(user user.Info, workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error)
|
||||||
PatchWorkspaceTemplate(workspace string, data json.RawMessage) (*tenantv1alpha2.WorkspaceTemplate, error)
|
PatchWorkspaceTemplate(user user.Info, workspace string, data json.RawMessage) (*tenantv1alpha2.WorkspaceTemplate, error)
|
||||||
DescribeWorkspaceTemplate(workspace string) (*tenantv1alpha2.WorkspaceTemplate, error)
|
DescribeWorkspaceTemplate(workspace string) (*tenantv1alpha2.WorkspaceTemplate, error)
|
||||||
ListNamespaces(user user.Info, workspace string, query *query.Query) (*api.ListResult, error)
|
ListNamespaces(user user.Info, workspace string, query *query.Query) (*api.ListResult, error)
|
||||||
ListDevOpsProjects(user user.Info, workspace string, query *query.Query) (*api.ListResult, error)
|
ListDevOpsProjects(user user.Info, workspace string, query *query.Query) (*api.ListResult, error)
|
||||||
@@ -117,6 +121,7 @@ type tenantOperator struct {
|
|||||||
auditing auditing.Interface
|
auditing auditing.Interface
|
||||||
mo monitoring.MonitoringOperator
|
mo monitoring.MonitoringOperator
|
||||||
opRelease openpitrix.ReleaseInterface
|
opRelease openpitrix.ReleaseInterface
|
||||||
|
clusterClient clusterclient.ClusterClients
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(informers informers.InformerFactory, k8sclient kubernetes.Interface, ksclient kubesphere.Interface, evtsClient eventsclient.Client, loggingClient loggingclient.Client, auditingclient auditingclient.Client, am am.AccessManagementInterface, im im.IdentityManagementInterface, authorizer authorizer.Authorizer, monitoringclient monitoringclient.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, opClient openpitrix.Interface) Interface {
|
func New(informers informers.InformerFactory, k8sclient kubernetes.Interface, ksclient kubesphere.Interface, evtsClient eventsclient.Client, loggingClient loggingclient.Client, auditingclient auditingclient.Client, am am.AccessManagementInterface, im im.IdentityManagementInterface, authorizer authorizer.Authorizer, monitoringclient monitoringclient.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, opClient openpitrix.Interface) Interface {
|
||||||
@@ -132,6 +137,7 @@ func New(informers informers.InformerFactory, k8sclient kubernetes.Interface, ks
|
|||||||
auditing: auditing.NewEventsOperator(auditingclient),
|
auditing: auditing.NewEventsOperator(auditingclient),
|
||||||
mo: monitoring.NewMonitoringOperator(monitoringclient, nil, k8sclient, informers, resourceGetter, nil),
|
mo: monitoring.NewMonitoringOperator(monitoringclient, nil, k8sclient, informers, resourceGetter, nil),
|
||||||
opRelease: opClient,
|
opRelease: opClient,
|
||||||
|
clusterClient: clusterclient.NewClusterClient(informers.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Clusters()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,15 +476,111 @@ func (t *tenantOperator) PatchNamespace(workspace string, namespace *corev1.Name
|
|||||||
return t.k8sclient.CoreV1().Namespaces().Patch(context.Background(), namespace.Name, types.MergePatchType, data, metav1.PatchOptions{})
|
return t.k8sclient.CoreV1().Namespaces().Patch(context.Background(), namespace.Name, types.MergePatchType, data, metav1.PatchOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tenantOperator) PatchWorkspaceTemplate(workspace string, data json.RawMessage) (*tenantv1alpha2.WorkspaceTemplate, error) {
|
func (t *tenantOperator) PatchWorkspaceTemplate(user user.Info, workspace string, data json.RawMessage) (*tenantv1alpha2.WorkspaceTemplate, error) {
|
||||||
return t.ksclient.TenantV1alpha2().WorkspaceTemplates().Patch(context.Background(), workspace, types.MergePatchType, data, metav1.PatchOptions{})
|
var manageWorkspaceTemplateRequest bool
|
||||||
|
clusterNames := sets.NewString()
|
||||||
|
|
||||||
|
patchs, err := jsonpatchutil.Parse(data)
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(patchs) > 0 {
|
||||||
|
for _, patch := range patchs {
|
||||||
|
path, err := patch.Path()
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the request path is cluster, just collecting cluster name to set and continue to check cluster permission later.
|
||||||
|
// Or indicate that want to manage the workspace templates, so check if user has the permission to manage workspace templates.
|
||||||
|
if strings.HasPrefix(path, "/spec/placement") {
|
||||||
|
if patch.Kind() != "add" && patch.Kind() != "remove" {
|
||||||
|
err := errors.NewBadRequest("not support operation type")
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clusterValue := make(map[string]interface{})
|
||||||
|
err := jsonpatchutil.GetValue(patch, &clusterValue)
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the placement is empty, the first patch need fill with "clusters" field.
|
||||||
|
if cName := clusterValue["name"]; cName != nil {
|
||||||
|
cn, ok := cName.(string)
|
||||||
|
if ok {
|
||||||
|
clusterNames.Insert(cn)
|
||||||
|
}
|
||||||
|
} else if cluster := clusterValue["clusters"]; cluster != nil {
|
||||||
|
clusterRefrences := []typesv1beta1.GenericClusterReference{}
|
||||||
|
err := mapstructure.Decode(cluster, &clusterRefrences)
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, v := range clusterRefrences {
|
||||||
|
clusterNames.Insert(v.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
manageWorkspaceTemplateRequest = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if manageWorkspaceTemplateRequest {
|
||||||
|
err := t.checkWorkspaceTemplatePermission(user, workspace)
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if clusterNames.Len() > 0 {
|
||||||
|
err := t.checkClusterPermission(user, clusterNames.List())
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.ksclient.TenantV1alpha2().WorkspaceTemplates().Patch(context.Background(), workspace, types.JSONPatchType, data, metav1.PatchOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tenantOperator) CreateWorkspaceTemplate(workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error) {
|
func (t *tenantOperator) CreateWorkspaceTemplate(user user.Info, workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error) {
|
||||||
|
if len(workspace.Spec.Placement.Clusters) != 0 {
|
||||||
|
clusters := make([]string, 0)
|
||||||
|
for _, v := range workspace.Spec.Placement.Clusters {
|
||||||
|
clusters = append(clusters, v.Name)
|
||||||
|
}
|
||||||
|
err := t.checkClusterPermission(user, clusters)
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return t.ksclient.TenantV1alpha2().WorkspaceTemplates().Create(context.Background(), workspace, metav1.CreateOptions{})
|
return t.ksclient.TenantV1alpha2().WorkspaceTemplates().Create(context.Background(), workspace, metav1.CreateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tenantOperator) UpdateWorkspaceTemplate(workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error) {
|
func (t *tenantOperator) UpdateWorkspaceTemplate(user user.Info, workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error) {
|
||||||
|
if len(workspace.Spec.Placement.Clusters) != 0 {
|
||||||
|
clusters := make([]string, 0)
|
||||||
|
for _, v := range workspace.Spec.Placement.Clusters {
|
||||||
|
clusters = append(clusters, v.Name)
|
||||||
|
}
|
||||||
|
err := t.checkClusterPermission(user, clusters)
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return t.ksclient.TenantV1alpha2().WorkspaceTemplates().Update(context.Background(), workspace, metav1.UpdateOptions{})
|
return t.ksclient.TenantV1alpha2().WorkspaceTemplates().Update(context.Background(), workspace, metav1.UpdateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1081,6 +1183,16 @@ func (t *tenantOperator) MeteringHierarchy(user user.Info, queryParam *meteringv
|
|||||||
return resourceStats, nil
|
return resourceStats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tenantOperator) getClusterRoleBindingsByUser(clusterName, user string) (*rbacv1.ClusterRoleBindingList, error) {
|
||||||
|
kubernetesClientSet, err := t.clusterClient.GetKubernetesClientSet(clusterName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return kubernetesClientSet.RbacV1().ClusterRoleBindings().
|
||||||
|
List(context.Background(),
|
||||||
|
metav1.ListOptions{LabelSelector: labels.FormatLabels(map[string]string{"iam.kubesphere.io/user-ref": user})})
|
||||||
|
}
|
||||||
|
|
||||||
func contains(objects []runtime.Object, object runtime.Object) bool {
|
func contains(objects []runtime.Object, object runtime.Object) bool {
|
||||||
for _, item := range objects {
|
for _, item := range objects {
|
||||||
if item == object {
|
if item == object {
|
||||||
@@ -1106,3 +1218,78 @@ func stringContains(str string, subStrs []string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tenantOperator) checkWorkspaceTemplatePermission(user user.Info, workspace string) error {
|
||||||
|
deleteWST := authorizer.AttributesRecord{
|
||||||
|
User: user,
|
||||||
|
Verb: authorizer.VerbDelete,
|
||||||
|
APIGroup: tenantv1alpha2.SchemeGroupVersion.Group,
|
||||||
|
APIVersion: tenantv1alpha2.SchemeGroupVersion.Version,
|
||||||
|
Resource: tenantv1alpha2.ResourcePluralWorkspaceTemplate,
|
||||||
|
ResourceRequest: true,
|
||||||
|
ResourceScope: request.GlobalScope,
|
||||||
|
}
|
||||||
|
authorize, reason, err := t.authorizer.Authorize(deleteWST)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if authorize != authorizer.DecisionAllow {
|
||||||
|
return errors.NewForbidden(tenantv1alpha2.Resource(tenantv1alpha2.ResourcePluralWorkspaceTemplate), workspace, fmt.Errorf(reason))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tenantOperator) checkClusterPermission(user user.Info, clusters []string) error {
|
||||||
|
// Checking whether the user can manage the cluster requires authentication from two aspects.
|
||||||
|
// First check whether the user has relevant global permissions,
|
||||||
|
// and then check whether the user has relevant cluster permissions in the target cluster
|
||||||
|
|
||||||
|
for _, clusterName := range clusters {
|
||||||
|
|
||||||
|
cluster, err := t.ksclient.ClusterV1alpha1().Clusters().Get(context.Background(), clusterName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cluster.Labels["cluster.kubesphere.io/visibility"] == "public" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCluster := authorizer.AttributesRecord{
|
||||||
|
User: user,
|
||||||
|
Verb: authorizer.VerbDelete,
|
||||||
|
APIGroup: clusterv1alpha1.SchemeGroupVersion.Group,
|
||||||
|
APIVersion: clusterv1alpha1.SchemeGroupVersion.Version,
|
||||||
|
Resource: clusterv1alpha1.ResourcesPluralCluster,
|
||||||
|
Cluster: clusterName,
|
||||||
|
ResourceRequest: true,
|
||||||
|
ResourceScope: request.GlobalScope,
|
||||||
|
}
|
||||||
|
authorize, _, err := t.authorizer.Authorize(deleteCluster)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if authorize == authorizer.DecisionAllow {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := t.getClusterRoleBindingsByUser(clusterName, user.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
allowed := false
|
||||||
|
for _, clusterRolebinding := range list.Items {
|
||||||
|
if clusterRolebinding.RoleRef.Name == iamv1alpha2.ClusterAdmin {
|
||||||
|
allowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowed {
|
||||||
|
return errors.NewForbidden(clusterv1alpha1.Resource(clusterv1alpha1.ResourcesPluralCluster), clusterName, fmt.Errorf("user is not allowed to use the cluster %s", clusterName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
// Time allowed to write a message to the peer.
|
// Time allowed to write a message to the peer.
|
||||||
writeWait = 10 * time.Second
|
writeWait = 10 * time.Second
|
||||||
|
// ctrl+d to close terminal.
|
||||||
|
endOfTransmission = "\u0004"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PtyHandler is what remotecommand expects from a pty
|
// PtyHandler is what remotecommand expects from a pty
|
||||||
@@ -76,7 +78,7 @@ type TerminalMessage struct {
|
|||||||
Rows, Cols uint16
|
Rows, Cols uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// TerminalSize handles pty->process resize events
|
// Next handles pty->process resize events
|
||||||
// Called in a loop from remotecommand as long as the process is running
|
// Called in a loop from remotecommand as long as the process is running
|
||||||
func (t TerminalSession) Next() *remotecommand.TerminalSize {
|
func (t TerminalSession) Next() *remotecommand.TerminalSize {
|
||||||
select {
|
select {
|
||||||
@@ -95,7 +97,7 @@ func (t TerminalSession) Read(p []byte) (int, error) {
|
|||||||
var msg TerminalMessage
|
var msg TerminalMessage
|
||||||
err := t.conn.ReadJSON(&msg)
|
err := t.conn.ReadJSON(&msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return copy(p, endOfTransmission), err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg.Op {
|
switch msg.Op {
|
||||||
@@ -105,7 +107,7 @@ func (t TerminalSession) Read(p []byte) (int, error) {
|
|||||||
t.sizeChan <- remotecommand.TerminalSize{Width: msg.Cols, Height: msg.Rows}
|
t.sizeChan <- remotecommand.TerminalSize{Width: msg.Cols, Height: msg.Rows}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("unknown message type '%s'", msg.Op)
|
return copy(p, endOfTransmission), fmt.Errorf("unknown message type '%s'", msg.Op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +217,7 @@ func (n *NodeTerminaler) getNSEnterPod() (*v1.Pod, error) {
|
|||||||
pod, err := n.client.CoreV1().Pods(n.Namespace).Get(context.Background(), n.PodName, metav1.GetOptions{})
|
pod, err := n.client.CoreV1().Pods(n.Namespace).Get(context.Background(), n.PodName, metav1.GetOptions{})
|
||||||
|
|
||||||
if err != nil || (pod.Status.Phase != v1.PodRunning && pod.Status.Phase != v1.PodPending) {
|
if err != nil || (pod.Status.Phase != v1.PodRunning && pod.Status.Phase != v1.PodPending) {
|
||||||
//pod has timed out, but has not been cleaned up
|
// pod has timed out, but has not been cleaned up
|
||||||
if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed {
|
if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed {
|
||||||
err := n.client.CoreV1().Pods(n.Namespace).Delete(context.Background(), n.PodName, metav1.DeleteOptions{})
|
err := n.client.CoreV1().Pods(n.Namespace).Delete(context.Background(), n.PodName, metav1.DeleteOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -328,7 +330,7 @@ func isValidShell(validShells []string, shell string) bool {
|
|||||||
|
|
||||||
func (t *terminaler) HandleSession(shell, namespace, podName, containerName string, conn *websocket.Conn) {
|
func (t *terminaler) HandleSession(shell, namespace, podName, containerName string, conn *websocket.Conn) {
|
||||||
var err error
|
var err error
|
||||||
validShells := []string{"sh", "bash"}
|
validShells := []string{"bash", "sh"}
|
||||||
|
|
||||||
session := &TerminalSession{conn: conn, sizeChan: make(chan remotecommand.TerminalSize)}
|
session := &TerminalSession{conn: conn, sizeChan: make(chan remotecommand.TerminalSize)}
|
||||||
|
|
||||||
|
|||||||
41
pkg/simple/client/cache/cache.go
vendored
41
pkg/simple/client/cache/cache.go
vendored
@@ -16,7 +16,17 @@ limitations under the License.
|
|||||||
|
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/klog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cacheFactories = make(map[string]CacheFactory)
|
||||||
|
)
|
||||||
|
|
||||||
var NeverExpire = time.Duration(0)
|
var NeverExpire = time.Duration(0)
|
||||||
|
|
||||||
@@ -39,3 +49,32 @@ type Interface interface {
|
|||||||
// Expires updates object's expiration time, return err if key doesn't exist
|
// Expires updates object's expiration time, return err if key doesn't exist
|
||||||
Expire(key string, duration time.Duration) error
|
Expire(key string, duration time.Duration) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DynamicOptions the options of the cache. For redis, options key can be "host", "port", "db", "password".
|
||||||
|
// For InMemoryCache, options key can be "cleanupperiod"
|
||||||
|
type DynamicOptions map[string]interface{}
|
||||||
|
|
||||||
|
func (o DynamicOptions) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
|
data, err := json.Marshal(o)
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterCacheFactory(factory CacheFactory) {
|
||||||
|
cacheFactories[factory.Type()] = factory
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(option *Options, stopCh <-chan struct{}) (Interface, error) {
|
||||||
|
if cacheFactories[option.Type] == nil {
|
||||||
|
err := fmt.Errorf("cache with type %s is not supported", option.Type)
|
||||||
|
klog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cache, err := cacheFactories[option.Type].Create(option.Options, stopCh)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("failed to create cache, error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|||||||
8
pkg/simple/client/cache/factory.go
vendored
Normal file
8
pkg/simple/client/cache/factory.go
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
type CacheFactory interface {
|
||||||
|
// Type unique type of the cache
|
||||||
|
Type() string
|
||||||
|
// Create relevant caches by type
|
||||||
|
Create(options DynamicOptions, stopCh <-chan struct{}) (Interface, error)
|
||||||
|
}
|
||||||
200
pkg/simple/client/cache/inmemory_cache.go
vendored
Normal file
200
pkg/simple/client/cache/inmemory_cache.go
vendored
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
|
||||||
|
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNoSuchKey = errors.New("no such key")
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeInMemoryCache = "InMemoryCache"
|
||||||
|
DefaultCacheType = typeInMemoryCache
|
||||||
|
|
||||||
|
defaultCleanupPeriod = 2 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
type simpleObject struct {
|
||||||
|
value string
|
||||||
|
neverExpire bool
|
||||||
|
expiredAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (so *simpleObject) IsExpired() bool {
|
||||||
|
if so.neverExpire {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if time.Now().After(so.expiredAt) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// InMemoryCacheOptions used to create inMemoryCache in memory.
|
||||||
|
// CleanupPeriod specifies cleans up expired token every period.
|
||||||
|
// Note the SimpleCache cannot be used in multi-replicas apiserver,
|
||||||
|
// which will lead to data inconsistency.
|
||||||
|
type InMemoryCacheOptions struct {
|
||||||
|
CleanupPeriod time.Duration `json:"cleanupPeriod" yaml:"cleanupPeriod" mapstructure:"cleanupperiod"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// imMemoryCache implements cache.Interface use memory objects, it should be used only for testing
|
||||||
|
type inMemoryCache struct {
|
||||||
|
store map[string]simpleObject
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInMemoryCache(options *InMemoryCacheOptions, stopCh <-chan struct{}) (Interface, error) {
|
||||||
|
var cleanupPeriod time.Duration
|
||||||
|
cache := &inMemoryCache{
|
||||||
|
store: make(map[string]simpleObject),
|
||||||
|
}
|
||||||
|
|
||||||
|
if options == nil || options.CleanupPeriod == 0 {
|
||||||
|
cleanupPeriod = defaultCleanupPeriod
|
||||||
|
} else {
|
||||||
|
cleanupPeriod = options.CleanupPeriod
|
||||||
|
}
|
||||||
|
go wait.Until(cache.cleanInvalidToken, cleanupPeriod, stopCh)
|
||||||
|
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inMemoryCache) cleanInvalidToken() {
|
||||||
|
for k, v := range s.store {
|
||||||
|
if v.IsExpired() {
|
||||||
|
delete(s.store, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inMemoryCache) Keys(pattern string) ([]string, error) {
|
||||||
|
// There is a little difference between go regexp and redis key pattern
|
||||||
|
// In redis, * means any character, while in go . means match everything.
|
||||||
|
pattern = strings.Replace(pattern, "*", ".", -1)
|
||||||
|
|
||||||
|
re, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var keys []string
|
||||||
|
for k := range s.store {
|
||||||
|
if re.MatchString(k) {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inMemoryCache) Set(key string, value string, duration time.Duration) error {
|
||||||
|
sobject := simpleObject{
|
||||||
|
value: value,
|
||||||
|
neverExpire: false,
|
||||||
|
expiredAt: time.Now().Add(duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
if duration == NeverExpire {
|
||||||
|
sobject.neverExpire = true
|
||||||
|
}
|
||||||
|
|
||||||
|
s.store[key] = sobject
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inMemoryCache) Del(keys ...string) error {
|
||||||
|
for _, key := range keys {
|
||||||
|
delete(s.store, key)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inMemoryCache) Get(key string) (string, error) {
|
||||||
|
if sobject, ok := s.store[key]; ok {
|
||||||
|
if sobject.neverExpire || time.Now().Before(sobject.expiredAt) {
|
||||||
|
return sobject.value, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ErrNoSuchKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inMemoryCache) Exists(keys ...string) (bool, error) {
|
||||||
|
for _, key := range keys {
|
||||||
|
if _, ok := s.store[key]; !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inMemoryCache) Expire(key string, duration time.Duration) error {
|
||||||
|
value, err := s.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sobject := simpleObject{
|
||||||
|
value: value,
|
||||||
|
neverExpire: false,
|
||||||
|
expiredAt: time.Now().Add(duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
if duration == NeverExpire {
|
||||||
|
sobject.neverExpire = true
|
||||||
|
}
|
||||||
|
|
||||||
|
s.store[key] = sobject
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type inMemoryCacheFactory struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *inMemoryCacheFactory) Type() string {
|
||||||
|
return typeInMemoryCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *inMemoryCacheFactory) Create(options DynamicOptions, stopCh <-chan struct{}) (Interface, error) {
|
||||||
|
var sOptions InMemoryCacheOptions
|
||||||
|
|
||||||
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: &sOptions,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(options); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewInMemoryCache(&sOptions, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterCacheFactory(&inMemoryCacheFactory{})
|
||||||
|
}
|
||||||
@@ -102,7 +102,7 @@ func TestDeleteAndExpireCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
cacheClient := NewSimpleCache()
|
cacheClient, _ := NewInMemoryCache(nil, nil)
|
||||||
|
|
||||||
t.Run(testCase.description, func(t *testing.T) {
|
t.Run(testCase.description, func(t *testing.T) {
|
||||||
err := load(cacheClient, dataSet)
|
err := load(cacheClient, dataSet)
|
||||||
33
pkg/simple/client/cache/options.go
vendored
33
pkg/simple/client/cache/options.go
vendored
@@ -18,25 +18,19 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Host string `json:"host" yaml:"host"`
|
Type string `json:"type"`
|
||||||
Port int `json:"port" yaml:"port"`
|
Options DynamicOptions `json:"options"`
|
||||||
Password string `json:"password" yaml:"password"`
|
|
||||||
DB int `json:"db" yaml:"db"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRedisOptions returns options points to nowhere,
|
// NewCacheOptions returns options points to nowhere,
|
||||||
// because redis is not required for some components
|
// because redis is not required for some components
|
||||||
func NewRedisOptions() *Options {
|
func NewCacheOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
Host: "",
|
Type: "",
|
||||||
Port: 0,
|
Options: map[string]interface{}{},
|
||||||
Password: "",
|
|
||||||
DB: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,20 +38,9 @@ func NewRedisOptions() *Options {
|
|||||||
func (r *Options) Validate() []error {
|
func (r *Options) Validate() []error {
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
|
|
||||||
if r.Port == 0 {
|
if r.Type == "" {
|
||||||
errors = append(errors, fmt.Errorf("invalid service port number"))
|
errors = append(errors, fmt.Errorf("invalid cache type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddFlags add option flags to command line flags,
|
|
||||||
// if redis-host left empty, the following options will be ignored.
|
|
||||||
func (r *Options) AddFlags(fs *pflag.FlagSet, s *Options) {
|
|
||||||
fs.StringVar(&r.Host, "redis-host", s.Host, "Redis connection URL. If left blank, means redis is unnecessary, "+
|
|
||||||
"redis will be disabled.")
|
|
||||||
|
|
||||||
fs.IntVar(&r.Port, "redis-port", s.Port, "")
|
|
||||||
fs.StringVar(&r.Password, "redis-password", s.Password, "")
|
|
||||||
fs.IntVar(&r.DB, "redis-db", s.DB, "")
|
|
||||||
}
|
|
||||||
|
|||||||
58
pkg/simple/client/cache/redis.go
vendored
58
pkg/simple/client/cache/redis.go
vendored
@@ -17,19 +17,31 @@ limitations under the License.
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-redis/redis"
|
"github.com/go-redis/redis"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
const typeRedis = "redis"
|
||||||
|
|
||||||
|
type redisClient struct {
|
||||||
client *redis.Client
|
client *redis.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRedisClient(option *Options, stopCh <-chan struct{}) (Interface, error) {
|
// redisOptions used to create a redis client.
|
||||||
var r Client
|
type redisOptions struct {
|
||||||
|
Host string `json:"host" yaml:"host" mapstructure:"host"`
|
||||||
|
Port int `json:"port" yaml:"port" mapstructure:"port"`
|
||||||
|
Password string `json:"password" yaml:"password" mapstructure:"password"`
|
||||||
|
DB int `json:"db" yaml:"db" mapstructure:"db"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRedisClient(option *redisOptions, stopCh <-chan struct{}) (Interface, error) {
|
||||||
|
var r redisClient
|
||||||
|
|
||||||
redisOptions := &redis.Options{
|
redisOptions := &redis.Options{
|
||||||
Addr: fmt.Sprintf("%s:%d", option.Host, option.Port),
|
Addr: fmt.Sprintf("%s:%d", option.Host, option.Port),
|
||||||
@@ -61,23 +73,23 @@ func NewRedisClient(option *Options, stopCh <-chan struct{}) (Interface, error)
|
|||||||
return &r, nil
|
return &r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Client) Get(key string) (string, error) {
|
func (r *redisClient) Get(key string) (string, error) {
|
||||||
return r.client.Get(key).Result()
|
return r.client.Get(key).Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Client) Keys(pattern string) ([]string, error) {
|
func (r *redisClient) Keys(pattern string) ([]string, error) {
|
||||||
return r.client.Keys(pattern).Result()
|
return r.client.Keys(pattern).Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Client) Set(key string, value string, duration time.Duration) error {
|
func (r *redisClient) Set(key string, value string, duration time.Duration) error {
|
||||||
return r.client.Set(key, value, duration).Err()
|
return r.client.Set(key, value, duration).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Client) Del(keys ...string) error {
|
func (r *redisClient) Del(keys ...string) error {
|
||||||
return r.client.Del(keys...).Err()
|
return r.client.Del(keys...).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Client) Exists(keys ...string) (bool, error) {
|
func (r *redisClient) Exists(keys ...string) (bool, error) {
|
||||||
existedKeys, err := r.client.Exists(keys...).Result()
|
existedKeys, err := r.client.Exists(keys...).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -86,6 +98,34 @@ func (r *Client) Exists(keys ...string) (bool, error) {
|
|||||||
return len(keys) == int(existedKeys), nil
|
return len(keys) == int(existedKeys), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Client) Expire(key string, duration time.Duration) error {
|
func (r *redisClient) Expire(key string, duration time.Duration) error {
|
||||||
return r.client.Expire(key, duration).Err()
|
return r.client.Expire(key, duration).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type redisFactory struct{}
|
||||||
|
|
||||||
|
func (rf *redisFactory) Type() string {
|
||||||
|
return typeRedis
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rf *redisFactory) Create(options DynamicOptions, stopCh <-chan struct{}) (Interface, error) {
|
||||||
|
var rOptions redisOptions
|
||||||
|
if err := mapstructure.Decode(options, &rOptions); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rOptions.Port == 0 {
|
||||||
|
return nil, errors.New("invalid service port number")
|
||||||
|
}
|
||||||
|
if len(rOptions.Host) == 0 {
|
||||||
|
return nil, errors.New("invalid service host")
|
||||||
|
}
|
||||||
|
client, err := NewRedisClient(&rOptions, stopCh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterCacheFactory(&redisFactory{})
|
||||||
|
}
|
||||||
|
|||||||
123
pkg/simple/client/cache/simple_cache.go
vendored
123
pkg/simple/client/cache/simple_cache.go
vendored
@@ -1,123 +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 cache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrNoSuchKey = errors.New("no such key")
|
|
||||||
|
|
||||||
type simpleObject struct {
|
|
||||||
value string
|
|
||||||
neverExpire bool
|
|
||||||
expiredAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleCache implements cache.Interface use memory objects, it should be used only for testing
|
|
||||||
type simpleCache struct {
|
|
||||||
store map[string]simpleObject
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSimpleCache() Interface {
|
|
||||||
return &simpleCache{store: make(map[string]simpleObject)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *simpleCache) Keys(pattern string) ([]string, error) {
|
|
||||||
// There is a little difference between go regexp and redis key pattern
|
|
||||||
// In redis, * means any character, while in go . means match everything.
|
|
||||||
pattern = strings.Replace(pattern, "*", ".", -1)
|
|
||||||
|
|
||||||
re, err := regexp.Compile(pattern)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var keys []string
|
|
||||||
for k := range s.store {
|
|
||||||
if re.MatchString(k) {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *simpleCache) Set(key string, value string, duration time.Duration) error {
|
|
||||||
sobject := simpleObject{
|
|
||||||
value: value,
|
|
||||||
neverExpire: false,
|
|
||||||
expiredAt: time.Now().Add(duration),
|
|
||||||
}
|
|
||||||
|
|
||||||
if duration == NeverExpire {
|
|
||||||
sobject.neverExpire = true
|
|
||||||
}
|
|
||||||
|
|
||||||
s.store[key] = sobject
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *simpleCache) Del(keys ...string) error {
|
|
||||||
for _, key := range keys {
|
|
||||||
delete(s.store, key)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *simpleCache) Get(key string) (string, error) {
|
|
||||||
if sobject, ok := s.store[key]; ok {
|
|
||||||
if sobject.neverExpire || time.Now().Before(sobject.expiredAt) {
|
|
||||||
return sobject.value, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", ErrNoSuchKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *simpleCache) Exists(keys ...string) (bool, error) {
|
|
||||||
for _, key := range keys {
|
|
||||||
if _, ok := s.store[key]; !ok {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *simpleCache) Expire(key string, duration time.Duration) error {
|
|
||||||
value, err := s.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sobject := simpleObject{
|
|
||||||
value: value,
|
|
||||||
neverExpire: false,
|
|
||||||
expiredAt: time.Now().Add(duration),
|
|
||||||
}
|
|
||||||
|
|
||||||
if duration == NeverExpire {
|
|
||||||
sobject.neverExpire = true
|
|
||||||
}
|
|
||||||
|
|
||||||
s.store[key] = sobject
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -38,6 +38,11 @@ func TestClient_Get(t *testing.T) {
|
|||||||
type args struct {
|
type args struct {
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inMemoryCache, err := cache.NewInMemoryCache(nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
token, _ := json.Marshal(
|
token, _ := json.Marshal(
|
||||||
&TokenResponse{
|
&TokenResponse{
|
||||||
Username: "test",
|
Username: "test",
|
||||||
@@ -92,7 +97,7 @@ func TestClient_Get(t *testing.T) {
|
|||||||
name: "Token",
|
name: "Token",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
Strategy: AuthStrategyToken,
|
Strategy: AuthStrategyToken,
|
||||||
cache: cache.NewSimpleCache(),
|
cache: inMemoryCache,
|
||||||
client: &MockClient{
|
client: &MockClient{
|
||||||
TokenResult: token,
|
TokenResult: token,
|
||||||
RequestResult: "fake",
|
RequestResult: "fake",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap"
|
"github.com/go-ldap/ldap"
|
||||||
@@ -63,8 +62,6 @@ type ldapInterfaceImpl struct {
|
|||||||
groupSearchBase string
|
groupSearchBase string
|
||||||
managerDN string
|
managerDN string
|
||||||
managerPassword string
|
managerPassword string
|
||||||
|
|
||||||
once sync.Once
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Interface = &ldapInterfaceImpl{}
|
var _ Interface = &ldapInterfaceImpl{}
|
||||||
@@ -95,7 +92,6 @@ func NewLdapClient(options *Options, stopCh <-chan struct{}) (Interface, error)
|
|||||||
groupSearchBase: options.GroupSearchBase,
|
groupSearchBase: options.GroupSearchBase,
|
||||||
managerDN: options.ManagerDN,
|
managerDN: options.ManagerDN,
|
||||||
managerPassword: options.ManagerPassword,
|
managerPassword: options.ManagerPassword,
|
||||||
once: sync.Once{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -103,9 +99,7 @@ func NewLdapClient(options *Options, stopCh <-chan struct{}) (Interface, error)
|
|||||||
client.close()
|
client.close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
client.once.Do(func() {
|
_ = client.createSearchBase()
|
||||||
_ = client.createSearchBase()
|
|
||||||
})
|
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ var promQLTemplates = map[string]string{
|
|||||||
"ingress_success_rate": `sum(rate(nginx_ingress_controller_requests{$1,$2,status!~"[4-5].*"}[$3])) / sum(rate(nginx_ingress_controller_requests{$1,$2}[$3]))`,
|
"ingress_success_rate": `sum(rate(nginx_ingress_controller_requests{$1,$2,status!~"[4-5].*"}[$3])) / sum(rate(nginx_ingress_controller_requests{$1,$2}[$3]))`,
|
||||||
"ingress_request_duration_average": `sum_over_time(nginx_ingress_controller_request_duration_seconds_sum{$1,$2}[$3])/sum_over_time(nginx_ingress_controller_request_duration_seconds_count{$1,$2}[$3])`,
|
"ingress_request_duration_average": `sum_over_time(nginx_ingress_controller_request_duration_seconds_sum{$1,$2}[$3])/sum_over_time(nginx_ingress_controller_request_duration_seconds_count{$1,$2}[$3])`,
|
||||||
"ingress_request_duration_50percentage": `histogram_quantile(0.50, sum by (le) (rate(nginx_ingress_controller_request_duration_seconds_bucket{$1,$2}[$3])))`,
|
"ingress_request_duration_50percentage": `histogram_quantile(0.50, sum by (le) (rate(nginx_ingress_controller_request_duration_seconds_bucket{$1,$2}[$3])))`,
|
||||||
"ingress_request_duration_95percentage": `histogram_quantile(0.90, sum by (le) (rate(nginx_ingress_controller_request_duration_seconds_bucket{$1,$2}[$3])))`,
|
"ingress_request_duration_95percentage": `histogram_quantile(0.95, sum by (le) (rate(nginx_ingress_controller_request_duration_seconds_bucket{$1,$2}[$3])))`,
|
||||||
"ingress_request_duration_99percentage": `histogram_quantile(0.99, sum by (le) (rate(nginx_ingress_controller_request_duration_seconds_bucket{$1,$2}[$3])))`,
|
"ingress_request_duration_99percentage": `histogram_quantile(0.99, sum by (le) (rate(nginx_ingress_controller_request_duration_seconds_bucket{$1,$2}[$3])))`,
|
||||||
"ingress_request_volume": `round(sum(irate(nginx_ingress_controller_requests{$1,$2}[$3])), 0.001)`,
|
"ingress_request_volume": `round(sum(irate(nginx_ingress_controller_requests{$1,$2}[$3])), 0.001)`,
|
||||||
"ingress_request_volume_by_ingress": `round(sum(irate(nginx_ingress_controller_requests{$1,$2}[$3])) by (ingress), 0.001)`,
|
"ingress_request_volume_by_ingress": `round(sum(irate(nginx_ingress_controller_requests{$1,$2}[$3])) by (ingress), 0.001)`,
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ func (h HelmVersionWrapper) GetKeywords() string {
|
|||||||
return strings.Join(h.ChartVersion.Keywords, ",")
|
return strings.Join(h.ChartVersion.Keywords, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h HelmVersionWrapper) GetRawKeywords() []string {
|
||||||
|
return h.ChartVersion.Keywords
|
||||||
|
}
|
||||||
|
|
||||||
func (h HelmVersionWrapper) GetRawMaintainers() []*v1alpha1.Maintainer {
|
func (h HelmVersionWrapper) GetRawMaintainers() []*v1alpha1.Maintainer {
|
||||||
mt := make([]*v1alpha1.Maintainer, 0, len(h.Maintainers))
|
mt := make([]*v1alpha1.Maintainer, 0, len(h.Maintainers))
|
||||||
for _, value := range h.Maintainers {
|
for _, value := range h.Maintainers {
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ func MergeRepoIndex(repo *v1alpha1.HelmRepo, index *helmrepo.IndexFile, existsSa
|
|||||||
|
|
||||||
allAppNames := make(map[string]struct{}, len(index.Entries))
|
allAppNames := make(map[string]struct{}, len(index.Entries))
|
||||||
for name, versions := range index.Entries {
|
for name, versions := range index.Entries {
|
||||||
|
if len(versions) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// add new applications
|
// add new applications
|
||||||
if application, exists := saved.Applications[name]; !exists {
|
if application, exists := saved.Applications[name]; !exists {
|
||||||
application = &Application{
|
application = &Application{
|
||||||
|
|||||||
@@ -50,5 +50,102 @@ func TestLoadRepo(t *testing.T) {
|
|||||||
_ = chartData
|
_ = chartData
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexData1 = `
|
||||||
|
apiVersion: v1
|
||||||
|
entries:
|
||||||
|
apisix: []
|
||||||
|
apisix-dashboard:
|
||||||
|
- apiVersion: v2
|
||||||
|
appVersion: 2.9.0
|
||||||
|
created: "2021-11-15T08:23:00.343784368Z"
|
||||||
|
description: A Helm chart for Apache APISIX Dashboard
|
||||||
|
digest: 76f794b1300f7bfb756ede352fe71eb863b89f1995b495e8b683990709e310ad
|
||||||
|
icon: https://apache.org/logos/res/apisix/apisix.png
|
||||||
|
maintainers:
|
||||||
|
- email: zhangjintao@apache.org
|
||||||
|
name: tao12345666333
|
||||||
|
name: apisix-dashboard
|
||||||
|
type: application
|
||||||
|
urls:
|
||||||
|
- https://charts.kubesphere.io/main/apisix-dashboard-0.3.0.tgz
|
||||||
|
version: 0.3.0
|
||||||
|
`
|
||||||
|
var indexData2 = `
|
||||||
|
apiVersion: v1
|
||||||
|
entries:
|
||||||
|
apisix:
|
||||||
|
- apiVersion: v2
|
||||||
|
appVersion: 2.10.0
|
||||||
|
created: "2021-11-15T08:23:00.343234584Z"
|
||||||
|
dependencies:
|
||||||
|
- condition: etcd.enabled
|
||||||
|
name: etcd
|
||||||
|
repository: https://charts.bitnami.com/bitnami
|
||||||
|
version: 6.2.6
|
||||||
|
- alias: dashboard
|
||||||
|
condition: dashboard.enabled
|
||||||
|
name: apisix-dashboard
|
||||||
|
repository: https://charts.apiseven.com
|
||||||
|
version: 0.3.0
|
||||||
|
- alias: ingress-controller
|
||||||
|
condition: ingress-controller.enabled
|
||||||
|
name: apisix-ingress-controller
|
||||||
|
repository: https://charts.apiseven.com
|
||||||
|
version: 0.8.0
|
||||||
|
description: A Helm chart for Apache APISIX
|
||||||
|
digest: fed38a11c0fb54d385144767227e43cb2961d1b50d36ea207fdd122bddd3de28
|
||||||
|
icon: https://apache.org/logos/res/apisix/apisix.png
|
||||||
|
maintainers:
|
||||||
|
- email: zhangjintao@apache.org
|
||||||
|
name: tao12345666333
|
||||||
|
name: apisix
|
||||||
|
type: application
|
||||||
|
urls:
|
||||||
|
- https://charts.kubesphere.io/main/apisix-0.7.2.tgz
|
||||||
|
version: 0.7.2
|
||||||
|
apisix-dashboard:
|
||||||
|
- apiVersion: v2
|
||||||
|
appVersion: 2.9.0
|
||||||
|
created: "2021-11-15T08:23:00.343784368Z"
|
||||||
|
description: A Helm chart for Apache APISIX Dashboard
|
||||||
|
digest: 76f794b1300f7bfb756ede352fe71eb863b89f1995b495e8b683990709e310ad
|
||||||
|
icon: https://apache.org/logos/res/apisix/apisix.png
|
||||||
|
maintainers:
|
||||||
|
- email: zhangjintao@apache.org
|
||||||
|
name: tao12345666333
|
||||||
|
name: apisix-dashboard
|
||||||
|
type: application
|
||||||
|
urls:
|
||||||
|
- https://charts.kubesphere.io/main/apisix-dashboard-0.3.0.tgz
|
||||||
|
version: 0.3.0
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestMergeRepo(t *testing.T) {
|
||||||
|
repoIndex1, err := loadIndex([]byte(indexData1))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to load repo index")
|
||||||
|
t.Failed()
|
||||||
|
}
|
||||||
|
existsSavedIndex := &SavedIndex{}
|
||||||
|
repoCR := &v1alpha1.HelmRepo{}
|
||||||
|
|
||||||
|
savedIndex1 := MergeRepoIndex(repoCR, repoIndex1, existsSavedIndex)
|
||||||
|
if len(savedIndex1.Applications) != 1 {
|
||||||
|
t.Errorf("faied to merge repo index with empty repo")
|
||||||
|
t.Failed()
|
||||||
|
}
|
||||||
|
|
||||||
|
repoIndex2, err := loadIndex([]byte(indexData2))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to load repo index")
|
||||||
|
t.Failed()
|
||||||
|
}
|
||||||
|
|
||||||
|
savedIndex2 := MergeRepoIndex(repoCR, repoIndex2, savedIndex1)
|
||||||
|
if len(savedIndex2.Applications) != 2 {
|
||||||
|
t.Errorf("faied to merge two repo index")
|
||||||
|
t.Failed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
@@ -30,6 +31,7 @@ import (
|
|||||||
|
|
||||||
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
||||||
|
|
||||||
|
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||||
clusterinformer "kubesphere.io/kubesphere/pkg/client/informers/externalversions/cluster/v1alpha1"
|
clusterinformer "kubesphere.io/kubesphere/pkg/client/informers/externalversions/cluster/v1alpha1"
|
||||||
clusterlister "kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1"
|
clusterlister "kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1"
|
||||||
)
|
)
|
||||||
@@ -54,6 +56,8 @@ type ClusterClients interface {
|
|||||||
GetClusterKubeconfig(string) (string, error)
|
GetClusterKubeconfig(string) (string, error)
|
||||||
Get(string) (*clusterv1alpha1.Cluster, error)
|
Get(string) (*clusterv1alpha1.Cluster, error)
|
||||||
GetInnerCluster(string) *innerCluster
|
GetInnerCluster(string) *innerCluster
|
||||||
|
GetKubernetesClientSet(string) (*kubernetes.Clientset, error)
|
||||||
|
GetKubeSphereClientSet(string) (*kubesphere.Clientset, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClusterClient(clusterInformer clusterinformer.ClusterInformer) ClusterClients {
|
func NewClusterClient(clusterInformer clusterinformer.ClusterInformer) ClusterClients {
|
||||||
@@ -182,3 +186,45 @@ func (c *clusterClients) IsHostCluster(cluster *clusterv1alpha1.Cluster) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *clusterClients) GetKubeSphereClientSet(name string) (*kubesphere.Clientset, error) {
|
||||||
|
kubeconfig, err := c.GetClusterKubeconfig(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
restConfig, err := newRestConfigFromString(kubeconfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientSet, err := kubesphere.NewForConfig(restConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clusterClients) GetKubernetesClientSet(name string) (*kubernetes.Clientset, error) {
|
||||||
|
kubeconfig, err := c.GetClusterKubeconfig(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
restConfig, err := newRestConfigFromString(kubeconfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientSet, err := kubernetes.NewForConfig(restConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRestConfigFromString(kubeconfig string) (*rest.Config, error) {
|
||||||
|
bytes, err := clientcmd.NewClientConfigFromBytes([]byte(kubeconfig))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bytes.ClientConfig()
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var sf *sonyflake.Sonyflake
|
var sf *sonyflake.Sonyflake
|
||||||
var upperMachineID uint16
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var st sonyflake.Settings
|
var st sonyflake.Settings
|
||||||
@@ -37,11 +36,18 @@ func init() {
|
|||||||
sf = sonyflake.NewSonyflake(sonyflake.Settings{
|
sf = sonyflake.NewSonyflake(sonyflake.Settings{
|
||||||
MachineID: lower16BitIP,
|
MachineID: lower16BitIP,
|
||||||
})
|
})
|
||||||
upperMachineID, _ = upper16BitIP()
|
}
|
||||||
|
if sf == nil {
|
||||||
|
sf = sonyflake.NewSonyflake(sonyflake.Settings{
|
||||||
|
MachineID: lower16BitIPv6,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIntId() uint64 {
|
func GetIntId() uint64 {
|
||||||
|
if sf == nil {
|
||||||
|
panic(errors.New("invalid snowflake instance"))
|
||||||
|
}
|
||||||
id, err := sf.NextID()
|
id, err := sf.NextID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -93,15 +99,6 @@ func lower16BitIP() (uint16, error) {
|
|||||||
return uint16(ip[2])<<8 + uint16(ip[3]), nil
|
return uint16(ip[2])<<8 + uint16(ip[3]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func upper16BitIP() (uint16, error) {
|
|
||||||
ip, err := IPv4()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint16(ip[0])<<8 + uint16(ip[1]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IPv4() (net.IP, error) {
|
func IPv4() (net.IP, error) {
|
||||||
as, err := net.InterfaceAddrs()
|
as, err := net.InterfaceAddrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -123,3 +120,34 @@ func IPv4() (net.IP, error) {
|
|||||||
}
|
}
|
||||||
return nil, errors.New("no ip address")
|
return nil, errors.New("no ip address")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lower16BitIPv6() (uint16, error) {
|
||||||
|
ip, err := IPv6()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint16(ip[14])<<8 + uint16(ip[15]), nil
|
||||||
|
}
|
||||||
|
func IPv6() (net.IP, error) {
|
||||||
|
as, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range as {
|
||||||
|
ipnet, ok := a.(*net.IPNet)
|
||||||
|
if !ok || ipnet.IP.IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ipnet.IP.To4() != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip := ipnet.IP.To16()
|
||||||
|
if ip == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return ip, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil, errors.New("no ip address")
|
||||||
|
}
|
||||||
|
|||||||
22
pkg/utils/josnpatchutil/jsonpatchutil.go
Normal file
22
pkg/utils/josnpatchutil/jsonpatchutil.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package josnpatchutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Parse(raw []byte) (jsonpatch.Patch, error) {
|
||||||
|
return jsonpatch.DecodePatch(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetValue(patch jsonpatch.Operation, value interface{}) error {
|
||||||
|
valueInterface, err := patch.ValueInterface()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mapstructure.Decode(valueInterface, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -287,9 +287,15 @@ func (c *cachedRepos) addRepo(repo *v1alpha1.HelmRepo, builtin bool) error {
|
|||||||
},
|
},
|
||||||
Spec: v1alpha1.HelmApplicationVersionSpec{
|
Spec: v1alpha1.HelmApplicationVersionSpec{
|
||||||
Metadata: &v1alpha1.Metadata{
|
Metadata: &v1alpha1.Metadata{
|
||||||
Name: hvw.GetName(),
|
Name: hvw.GetName(),
|
||||||
AppVersion: hvw.GetAppVersion(),
|
AppVersion: hvw.GetAppVersion(),
|
||||||
Version: hvw.GetVersion(),
|
Version: hvw.GetVersion(),
|
||||||
|
Description: hvw.GetDescription(),
|
||||||
|
Home: hvw.GetHome(),
|
||||||
|
Icon: hvw.GetIcon(),
|
||||||
|
Maintainers: hvw.GetRawMaintainers(),
|
||||||
|
Sources: hvw.GetRawSources(),
|
||||||
|
Keywords: hvw.GetRawKeywords(),
|
||||||
},
|
},
|
||||||
URLs: chartVersion.URLs,
|
URLs: chartVersion.URLs,
|
||||||
Digest: chartVersion.Digest,
|
Digest: chartVersion.Digest,
|
||||||
|
|||||||
Reference in New Issue
Block a user