Compare commits

...

28 Commits

Author SHA1 Message Date
KubeSphere CI Bot
c8e131fc13 [release-3.3] adjust Pod status filter (#5488)
adjust Pod status filter

Signed-off-by: frezes <zhangjunhao@kubesphere.io>

Signed-off-by: frezes <zhangjunhao@kubesphere.io>
Co-authored-by: frezes <zhangjunhao@kubesphere.io>
2023-01-17 14:26:01 +08:00
KubeSphere CI Bot
839a31ac1d [release-3.3] Fix:Goroutine leaks when getting audit event sender times out (#5475)
* Fix:Goroutine leaks when getting audit event sender times out

* make it more readable

Co-authored-by: hzhhong <hung.z.h916@gmail.com>
2023-01-13 11:14:33 +08:00
KubeSphere CI Bot
a0ba5f6085 [release-3.3] fix Home field fault in appstore application (#5474)
fix appstore app home field

Co-authored-by: xiaoliu <978911210@qq.com>
2023-01-13 11:14:25 +08:00
KubeSphere CI Bot
658497aa0a [release-3.3] fix: ks-apiserver panic error: ServiceAccount's Secret index out of r… (#5472)
fix: ks-apiserver panic error: ServiceAccount's Secret index out of range

Co-authored-by: peng wu <2030047311@qq.com>
2023-01-13 11:14:17 +08:00
KubeSphere CI Bot
a47bf848df [release-3.3] Fix missing maintainers in helm apps (#5473)
fix missing maintainers in helm apps

Co-authored-by: qingwave <854222409@qq.com>
2023-01-13 11:07:17 +08:00
hongzhouzi
dbb3f04b9e Resolved Conflict [release-3.3] Fix failed to cache resources if group version not found #5408 (#5466)
Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
2023-01-12 18:45:17 +08:00
hongzhouzi
705ea4af40 Resolved Conflict [release-3.3] Fix id generate error in IPv6-only environment. #5419 (#5465)
Resolved Conflict [release-3.3] Fix id generate error in IPv6-only environment. #5459

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
Co-authored-by: isyes <isyes@foxmail.com>
2023-01-12 18:26:17 +08:00
KubeSphere CI Bot
366d1e16e4 [release-3.3] fix: concurrent map read and map write caused by reloading in ks-apiserver (#5464)
fix: concurrent map read and map write caused by reloading in ks-apiserver.

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
Co-authored-by: hongzhouzi <hongzhouzi@kubesphere.io>
2023-01-12 17:55:17 +08:00
hongzhouzi
690d5be824 Resolved Conflict [release-3.3] fix: Resolved some data out of sync after live-reload. #5458 (#5462)
Resolved Conflict [fix: Resolved some data out of sync after live-reload.]

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
2023-01-12 17:44:17 +08:00
KubeSphere CI Bot
c0419ddab5 [release-3.3] add dynamic options for cache (#5325)
* add dynamic options for cache

* fixed bugs based on unit-test

* add doc for cache

* make cache implements be private

* Change simpleCache name to InMemoryCache

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>

* Remove fake cache and replacing to in memory cache with default parameter

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>
Co-authored-by: Wenhao Zhou <wenhaozhou@yunify.com>
2022-11-03 15:55:00 +08:00
KubeSphere CI Bot
80b0301f79 [release-3.3] Fix: globalrole has cluster management right can not manage cluster (#5334)
Fix: globalrole has permision of cluster management can not manage cluster

Co-authored-by: Wenhao Zhou <wenhaozhou@yunify.com>
2022-10-27 14:47:50 +08:00
KubeSphere CI Bot
7162d41310 [release-3.3] Check cluster permission for create/update workspacetemplate (#5310)
* add cluster authorization for create/update workspacetemplate

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>

add cluster authorization for create/update workspacetemplate

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>

* add handle forbidden err

* add forbidden error log

* allow to use clusters of public visibility

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>
Co-authored-by: Wenhao Zhou <wenhaozhou@yunify.com>
2022-10-21 09:55:41 +08:00
KubeSphere CI Bot
6b10d346ca [release-3.3] fix #5267 by renaming yaml struct tag (#5275)
fix #5267 by renaming yaml struct tag

Signed-off-by: chavacava <salvadorcavadini+github@gmail.com>

Signed-off-by: chavacava <salvadorcavadini+github@gmail.com>
Co-authored-by: chavacava <salvadorcavadini+github@gmail.com>
2022-10-08 14:34:33 +08:00
KubeSphere CI Bot
6a0d5ba93c [release-3.3] Fix: Can not resolve the resource scope correctly (#5274)
Fix: can not resolve the resource scope of clusters.cluster.kubesphere.io correctly

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>
Co-authored-by: Wenhao Zhou <wenhaozhou@yunify.com>
2022-10-08 13:58:57 +08:00
KubeSphere CI Bot
d87a782257 [release-3.3] Fix cluster gateway logs and resource status display exception (#5250)
Cluster gateway logs and resource status display exception

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
Co-authored-by: hongzhouzi <hongzhouzi@kubesphere.io>
2022-09-28 00:11:23 +08:00
KubeSphere CI Bot
82e55578a8 [release-3.3] fix gateway upgrade validate error. (#5236)
gateway upgrade validate error.

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
Co-authored-by: hongzhouzi <hongzhouzi@kubesphere.io>
2022-09-21 17:13:17 +08:00
KubeSphere CI Bot
5b9c357160 [release-3.3] Fix: when placement is empty return error (#5218)
Fix: when placement is empty return error

Co-authored-by: Wenhao Zhou <wenhaozhou@yunfiy.com>
2022-09-15 19:38:47 +08:00
KubeSphere CI Bot
c385dd92e4 [release-3.3] Add authorization control for patching workspacetemplates (#5217)
* update patch workspacetemplate for supporting patch with JsonPatchType and change the authorization processing

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>

* make goimports

* Fix: Of the type is not string will lead to panic

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>

* Add jsonpatchutil for handling json patch data

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>

* Updated patch workspacetemplate to to make the code run more efficiently

* fix: multiple clusterrolebindings cannot autorizate

* Correct wrong spelling

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>
Co-authored-by: Wenhao Zhou <wenhaozhou@yunify.com>
2022-09-15 19:32:47 +08:00
KubeSphere CI Bot
1e1b2bd594 [release-3.3] support recording disable and enable users in auditing (#5202)
support recording disable and enable users in auditing

Signed-off-by: wanjunlei <wanjunlei@kubesphere.io>

Signed-off-by: wanjunlei <wanjunlei@kubesphere.io>
Co-authored-by: wanjunlei <wanjunlei@kubesphere.io>
2022-09-08 10:25:41 +08:00
KubeSphere CI Bot
951b86648c [release-3.3] fix bug helm repo paging query (#5201)
* fix bug helmrepo paging query

* fix bug helmrepo paging query

* fix bug helm repo paging query

Co-authored-by: mayongxing <mayongxing@cmsr.chinamobile.com>
2022-09-08 10:17:41 +08:00
KubeSphere CI Bot
04433c139d [release-3.3] Fix: index out of range when merging two repo indexes (#5169)
Fix: index out of range when merging two repo indexes

Co-authored-by: LiHui <andrewli@kubesphere.io>
2022-08-25 16:06:36 +08:00
KubeSphere CI Bot
3b8c28d21e [release-3.3] Support for filtering workspace roles using labelSelector (#5162)
Support for filtering workspace roles using labelSelector

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>

Signed-off-by: Wenhao Zhou <wenhaozhou@yunify.com>
Co-authored-by: Wenhao Zhou <wenhaozhou@yunify.com>
2022-08-23 10:30:21 +08:00
KubeSphere CI Bot
9489718270 [release-3.3] fill field status of helmrepo in response (#5158)
fill field status of helmrepo in response

Signed-off-by: x893675 <x893675@icloud.com>

Signed-off-by: x893675 <x893675@icloud.com>
Co-authored-by: x893675 <x893675@icloud.com>
2022-08-22 16:15:00 +08:00
KubeSphere CI Bot
54df6b8c8c [release-3.3] fix cluster ready condition always true (#5137)
fix cluster ready condition always true

Signed-off-by: x893675 <x893675@icloud.com>

Signed-off-by: x893675 <x893675@icloud.com>
Co-authored-by: x893675 <x893675@icloud.com>
2022-08-16 14:12:46 +08:00
KubeSphere CI Bot
d917905529 [release-3.3] Fix ingress P95 delay time promql statement (#5132)
Fix ingress P95 delay time promql statement

Co-authored-by: Xinzhao Xu <z2d@jifangcheng.com>
2022-08-14 16:49:35 +08:00
KubeSphere CI Bot
cd6f940f1d [release-3.3] Adjust container terminal priority: bash, sh (#5076)
Adjust container terminal priority: bash, sh

Co-authored-by: tal66 <77445020+tal66@users.noreply.github.com>
2022-07-21 11:16:29 +08:00
KubeSphere CI Bot
921a8f068b [release-3.3] skip generated code when fmt code (#5079)
skip generated code when fmt code

Co-authored-by: LiHui <andrewli@kubesphere.io>
2022-07-21 11:16:14 +08:00
KubeSphere CI Bot
641aa1dfcf [release-3.3] close remote terminal.(#5023) (#5028)
close remote terminal.(kubesphere#5023)

Co-authored-by: lixueduan <li.xueduan@99cloud.net>
2022-07-06 18:08:34 +08:00
42 changed files with 1071 additions and 290 deletions

View File

@@ -242,4 +242,5 @@ func (s *KubeSphereControllerManagerOptions) MergeConfig(cfg *controllerconfig.C
s.MultiClusterOptions = cfg.MultiClusterOptions
s.ServiceMeshOptions = cfg.ServiceMeshOptions
s.GatewayOptions = cfg.GatewayOptions
s.MonitoringOptions = cfg.MonitoringOptions
}

View File

@@ -20,6 +20,9 @@ import (
"crypto/tls"
"flag"
"fmt"
"net/http"
"strings"
"sync"
openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1"
"kubesphere.io/kubesphere/pkg/utils/clusterclient"
@@ -41,9 +44,6 @@ import (
auditingclient "kubesphere.io/kubesphere/pkg/simple/client/auditing/elasticsearch"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"net/http"
"strings"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
eventsclient "kubesphere.io/kubesphere/pkg/simple/client/events/elasticsearch"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
@@ -59,9 +59,8 @@ type ServerRunOptions struct {
ConfigFile string
GenericServerRunOptions *genericoptions.ServerRunOptions
*apiserverconfig.Config
//
DebugMode bool
schemeOnce sync.Once
DebugMode bool
// Enable gops or not.
GOPSEnabled bool
@@ -71,6 +70,7 @@ func NewServerRunOptions() *ServerRunOptions {
s := &ServerRunOptions{
GenericServerRunOptions: genericoptions.NewServerRunOptions(),
Config: apiserverconfig.New(),
schemeOnce: sync.Once{},
}
return s
@@ -87,7 +87,6 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) {
s.AuthorizationOptions.AddFlags(fss.FlagSet("authorization"), s.AuthorizationOptions)
s.DevopsOptions.AddFlags(fss.FlagSet("devops"), s.DevopsOptions)
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.OpenPitrixOptions.AddFlags(fss.FlagSet("openpitrix"), s.OpenPitrixOptions)
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())
}
var cacheClient cache.Interface
if s.RedisOptions != nil && len(s.RedisOptions.Host) != 0 {
if s.RedisOptions.Host == fakeInterface && s.DebugMode {
apiServer.CacheClient = cache.NewSimpleCache()
} 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
// If debug mode is on or CacheOptions is nil, will create a fake cache.
if s.CacheOptions.Type != "" {
if s.DebugMode {
s.CacheOptions.Type = cache.DefaultCacheType
}
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 {
klog.Warning("ks-apiserver starts without redis provided, it will use in memory cache. " +
"This may cause inconsistencies when running ks-apiserver with multiple replicas.")
apiServer.CacheClient = cache.NewSimpleCache()
s.CacheOptions = &cache.Options{Type: cache.DefaultCacheType}
// fake cache has no error to return
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 != "" {
@@ -222,7 +223,7 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS
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{
Addr: fmt.Sprintf(":%d", s.GenericServerRunOptions.InsecurePort),
@@ -241,9 +242,11 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS
}
sch := scheme.Scheme
if err := apis.AddToScheme(sch); err != nil {
klog.Fatalf("unable add APIs to scheme: %v", err)
}
s.schemeOnce.Do(func() {
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})
if err != nil {

View File

@@ -39,6 +39,7 @@ find_files() {
-o -wholename '*/third_party/*' \
-o -wholename '*/vendor/*' \
-o -wholename './staging/src/kubesphere.io/client-go/*vendor/*' \
-o -wholename './staging/src/kubesphere.io/api/*/zz_generated.deepcopy.go' \
\) -prune \
\) -name '*.go'
}

1
hack/verify-gofmt.sh Normal file → Executable file
View File

@@ -44,6 +44,7 @@ find_files() {
-o -wholename '*/third_party/*' \
-o -wholename '*/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' \
\) -prune \
\) -name '*.go'

View File

@@ -394,6 +394,10 @@ func waitForCacheSync(discoveryClient discovery.DiscoveryInterface, sharedInform
return err
})
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)
}
for _, resourceName := range resourceNames {

View File

@@ -141,6 +141,7 @@ func (b *Backend) sendEvents(events *v1alpha1.EventList) {
defer cancel()
stopCh := make(chan struct{})
skipReturnSender := false
send := func() {
ctx, cancel := context.WithTimeout(context.Background(), b.getSenderTimeout)
@@ -149,6 +150,7 @@ func (b *Backend) sendEvents(events *v1alpha1.EventList) {
select {
case <-ctx.Done():
klog.Error("Get auditing event sender timeout")
skipReturnSender = true
return
case b.senderCh <- struct{}{}:
}
@@ -182,7 +184,9 @@ func (b *Backend) sendEvents(events *v1alpha1.EventList) {
go send()
defer func() {
<-b.senderCh
if !skipReturnSender {
<-b.senderCh
}
}()
select {

View File

@@ -33,8 +33,8 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/apis/audit"
"k8s.io/klog"
devopsv1alpha3 "kubesphere.io/api/devops/v1alpha3"
"kubesphere.io/api/iam/v1alpha2"
auditv1alpha1 "kubesphere.io/kubesphere/pkg/apiserver/auditing/v1alpha1"
"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)
if err != nil {
klog.Error(err)
@@ -212,11 +212,45 @@ func (a *auditing) LogRequestObject(req *http.Request, info *request.RequestInfo
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
}
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) {
e.StageTimestamp = metav1.NowMicro()

View File

@@ -45,7 +45,7 @@ func init() {
type ldapProvider struct {
// 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
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.
ReadTimeout int `json:"readTimeout" yaml:"readTimeout"`
// If specified, connections will use the ldaps:// protocol

View File

@@ -160,7 +160,7 @@ type Config struct {
ServiceMeshOptions *servicemesh.Options `json:"servicemesh,omitempty" yaml:"servicemesh,omitempty" mapstructure:"servicemesh"`
NetworkOptions *network.Options `json:"network,omitempty" yaml:"network,omitempty" mapstructure:"network"`
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"`
OpenPitrixOptions *openpitrix.Options `json:"openpitrix,omitempty" yaml:"openpitrix,omitempty" mapstructure:"openpitrix"`
MonitoringOptions *prometheus.Options `json:"monitoring,omitempty" yaml:"monitoring,omitempty" mapstructure:"monitoring"`
@@ -189,7 +189,7 @@ func New() *Config {
ServiceMeshOptions: servicemesh.NewServiceMeshOptions(),
NetworkOptions: network.NewNetworkOptions(),
LdapOptions: ldap.NewOptions(),
RedisOptions: cache.NewRedisOptions(),
CacheOptions: cache.NewCacheOptions(),
S3Options: s3.NewS3Options(),
OpenPitrixOptions: openpitrix.NewOptions(),
MonitoringOptions: prometheus.NewPrometheusOptions(),
@@ -292,8 +292,8 @@ func (conf *Config) ToMap() map[string]bool {
// Remove invalid options before serializing to json or yaml
func (conf *Config) stripEmptyOptions() {
if conf.RedisOptions != nil && conf.RedisOptions.Host == "" {
conf.RedisOptions = nil
if conf.CacheOptions != nil && conf.CacheOptions.Type == "" {
conf.CacheOptions = nil
}
if conf.DevopsOptions != nil && conf.DevopsOptions.Host == "" {

View File

@@ -88,11 +88,9 @@ func newTestConfig() (*Config, error) {
MaxCap: 100,
PoolName: "ldap",
},
RedisOptions: &cache.Options{
Host: "localhost",
Port: 6379,
Password: "KUBESPHERE_REDIS_PASSWORD",
DB: 0,
CacheOptions: &cache.Options{
Type: "redis",
Options: map[string]interface{}{},
},
S3Options: &s3.Options{
Endpoint: "http://minio.openpitrix-system.svc",
@@ -236,9 +234,6 @@ func TestGet(t *testing.T) {
saveTestConfig(t, conf)
defer cleanTestConfig(t)
conf.RedisOptions.Password = "P@88w0rd"
os.Setenv("KUBESPHERE_REDIS_PASSWORD", "P@88w0rd")
conf2, err := TryLoadFromDisk()
if err != nil {
t.Fatal(err)
@@ -251,7 +246,7 @@ func TestGet(t *testing.T) {
func TestStripEmptyOptions(t *testing.T) {
var config Config
config.RedisOptions = &cache.Options{Host: ""}
config.CacheOptions = &cache.Options{Type: ""}
config.DevopsOptions = &jenkins.Options{Host: ""}
config.MonitoringOptions = &prometheus.Options{Endpoint: ""}
config.SonarQubeOptions = &sonarqube.Options{Host: ""}
@@ -284,7 +279,7 @@ func TestStripEmptyOptions(t *testing.T) {
config.stripEmptyOptions()
if config.RedisOptions != nil ||
if config.CacheOptions != nil ||
config.DevopsOptions != nil ||
config.MonitoringOptions != nil ||
config.SonarQubeOptions != nil ||

View File

@@ -246,8 +246,6 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
// parsing successful, so we now know the proper value for .Parts
requestInfo.Parts = currentParts
requestInfo.ResourceScope = r.resolveResourceScope(requestInfo)
// parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
switch {
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.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 len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
opts := metainternalversion.ListOptions{}

View File

@@ -418,6 +418,15 @@ func (c *clusterController) syncCluster(key string) error {
Message: "Cluster can not join federation control plane",
}
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{})
if err != nil {

View File

@@ -380,6 +380,7 @@ func (h *iamHandler) ListWorkspaceRoles(request *restful.Request, response *rest
queryParam.Filters[iamv1alpha2.ScopeWorkspace] = query.Value(workspace)
// shared workspace role template
if string(queryParam.Filters[query.FieldLabel]) == fmt.Sprintf("%s=%s", iamv1alpha2.RoleTemplateLabel, "true") ||
strings.Contains(queryParam.LabelSelector, iamv1alpha2.RoleTemplateLabel) ||
queryParam.Filters[iamv1alpha2.AggregateTo] != "" {
delete(queryParam.Filters, iamv1alpha2.ScopeWorkspace)
}

View File

@@ -52,7 +52,7 @@ type openpitrixHandler struct {
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
if option != nil && option.S3Options != nil && len(option.S3Options.Endpoint) != 0 {
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) {

View File

@@ -48,15 +48,17 @@ func NewHandler(o *servicemesh.Options, client kubernetes.Interface, cache cache
if o != nil && o.KialiQueryHost != "" {
sa, err := client.CoreV1().ServiceAccounts(KubesphereNamespace).Get(context.TODO(), KubeSphereServiceAccount, metav1.GetOptions{})
if err == nil {
secret, err := client.CoreV1().Secrets(KubesphereNamespace).Get(context.TODO(), sa.Secrets[0].Name, metav1.GetOptions{})
if err == nil {
return &Handler{
opt: o,
client: kiali.NewDefaultClient(
cache,
string(secret.Data["token"]),
o.KialiQueryHost,
),
if len(sa.Secrets) > 0 {
secret, err := client.CoreV1().Secrets(KubesphereNamespace).Get(context.TODO(), sa.Secrets[0].Name, metav1.GetOptions{})
if err == nil {
return &Handler{
opt: o,
client: kiali.NewDefaultClient(
cache,
string(secret.Data["token"]),
o.KialiQueryHost,
),
}
}
}
klog.Warningf("get ServiceAccount's Secret failed %v", err)

View File

@@ -202,30 +202,40 @@ func (h *tenantHandler) CreateNamespace(request *restful.Request, response *rest
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
err := request.ReadEntity(&workspace)
err := req.ReadEntity(&workspace)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
api.HandleBadRequest(resp, req, err)
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 {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
api.HandleNotFound(resp, req, err)
return
}
api.HandleBadRequest(response, request, err)
if errors.IsForbidden(err) {
api.HandleForbidden(resp, req, err)
return
}
api.HandleBadRequest(resp, req, err)
return
}
response.WriteEntity(created)
resp.WriteEntity(created)
}
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)
}
func (h *tenantHandler) UpdateWorkspaceTemplate(request *restful.Request, response *restful.Response) {
workspaceName := request.PathParameter("workspace")
func (h *tenantHandler) UpdateWorkspaceTemplate(req *restful.Request, resp *restful.Response) {
workspaceName := req.PathParameter("workspace")
var workspace tenantv1alpha2.WorkspaceTemplate
err := request.ReadEntity(&workspace)
err := req.ReadEntity(&workspace)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
api.HandleBadRequest(resp, req, err)
return
}
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)
klog.Errorf("%+v", err)
api.HandleBadRequest(response, request, err)
api.HandleBadRequest(resp, req, err)
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 {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
api.HandleNotFound(resp, req, err)
return
}
if errors.IsBadRequest(err) {
api.HandleBadRequest(response, request, err)
api.HandleBadRequest(resp, req, err)
return
}
api.HandleInternalError(response, request, err)
if errors.IsForbidden(err) {
api.HandleForbidden(resp, req, err)
return
}
api.HandleInternalError(resp, req, err)
return
}
response.WriteEntity(updated)
resp.WriteEntity(updated)
}
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)
}
func (h *tenantHandler) PatchWorkspaceTemplate(request *restful.Request, response *restful.Response) {
workspaceName := request.PathParameter("workspace")
func (h *tenantHandler) PatchWorkspaceTemplate(req *restful.Request, resp *restful.Response) {
workspaceName := req.PathParameter("workspace")
var data json.RawMessage
err := request.ReadEntity(&data)
err := req.ReadEntity(&data)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
api.HandleBadRequest(resp, req, err)
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 {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
api.HandleNotFound(resp, req, err)
return
}
if errors.IsBadRequest(err) {
api.HandleBadRequest(response, request, err)
api.HandleBadRequest(resp, req, err)
return
}
api.HandleInternalError(response, request, err)
if errors.IsNotFound(err) {
api.HandleForbidden(resp, req, err)
return
}
api.HandleInternalError(resp, req, err)
return
}
response.WriteEntity(patched)
resp.WriteEntity(patched)
}
func (h *tenantHandler) ListClusters(r *restful.Request, response *restful.Response) {

View File

@@ -47,12 +47,13 @@ import (
)
const (
MasterLabel = "node-role.kubernetes.io/master"
SidecarInject = "sidecar.istio.io/inject"
gatewayPrefix = "kubesphere-router-"
workingNamespace = "kubesphere-controls-system"
globalGatewayname = gatewayPrefix + "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}}`
MasterLabel = "node-role.kubernetes.io/master"
SidecarInject = "sidecar.istio.io/inject"
gatewayPrefix = "kubesphere-router-"
workingNamespace = "kubesphere-controls-system"
globalGatewayNameSuffix = "kubesphere-system"
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 {
@@ -90,6 +91,10 @@ func (c *gatewayOperator) getWorkingNamespace(namespace string) string {
if ns == "" {
ns = namespace
}
// Convert the global gateway query parameter
if namespace == globalGatewayNameSuffix {
ns = workingNamespace
}
return ns
}
@@ -97,7 +102,7 @@ func (c *gatewayOperator) getWorkingNamespace(namespace string) string {
func (c *gatewayOperator) overrideDefaultValue(gateway *v1alpha1.Gateway, namespace string) *v1alpha1.Gateway {
// override default name
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.Namespace = c.getWorkingNamespace(namespace)
@@ -108,7 +113,7 @@ func (c *gatewayOperator) overrideDefaultValue(gateway *v1alpha1.Gateway, namesp
func (c *gatewayOperator) getGlobalGateway() *v1alpha1.Gateway {
globalkey := types.NamespacedName{
Namespace: workingNamespace,
Name: globalGatewayname,
Name: globalGatewayName,
}
global := &v1alpha1.Gateway{}
@@ -331,7 +336,7 @@ func (c *gatewayOperator) UpgradeGateway(namespace string) (*v1alpha1.Gateway, e
if l == nil {
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")
}

View File

@@ -46,7 +46,7 @@ type openpitrixOperator struct {
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")
cachedReposData := reposcache.NewReposCache()
helmReposInformer := ksInformers.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmRepos().Informer()

View File

@@ -302,7 +302,7 @@ func (c *repoOperator) ListRepos(conditions *params.Conditions, orderBy string,
start, end := (&query.Pagination{Limit: limit, Offset: offset}).GetValidPagination(totalCount)
repos = repos[start:end]
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]))
}
return &models.PageableResponse{Items: items, TotalCount: totalCount}, nil

View File

@@ -713,7 +713,7 @@ type Repo struct {
// selectors
Selectors RepoSelectors `json:"selectors"`
// status eg.[active|deleted]
// status eg.[successful|failed|syncing]
Status string `json:"status,omitempty"`
// record status changed time

View File

@@ -399,6 +399,7 @@ func convertAppVersion(in *v1alpha1.HelmApplicationVersion) *AppVersion {
if in.Spec.Metadata != nil {
out.Description = in.Spec.Description
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,
@@ -431,6 +432,10 @@ func convertRepo(in *v1alpha1.HelmRepo) *Repo {
out.Name = in.GetTrueName()
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))
out.CreateTime = &date

View File

@@ -17,6 +17,9 @@ limitations under the License.
package pod
import (
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@@ -31,7 +34,13 @@ const (
fieldNodeName = "nodeName"
fieldPVCName = "pvcName"
fieldServiceName = "serviceName"
fieldPhase = "phase"
fieldStatus = "status"
statusTypeWaitting = "Waiting"
statusTypeRunning = "Running"
statusTypeError = "Error"
statusTypeCompleted = "Completed"
)
type podsGetter struct {
@@ -90,6 +99,9 @@ func (p *podsGetter) filter(object runtime.Object, filter query.Filter) bool {
case fieldServiceName:
return p.podBelongToService(pod, string(filter.Value))
case fieldStatus:
_, statusType := p.getPodStatus(pod)
return statusType == string(filter.Value)
case fieldPhase:
return string(pod.Status.Phase) == string(filter.Value)
default:
return v1alpha3.DefaultObjectMetaFilter(pod.ObjectMeta, filter)
@@ -117,3 +129,133 @@ func (p *podsGetter) podBelongToService(item *corev1.Pod, serviceName string) bo
}
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
}

View File

@@ -78,7 +78,7 @@ func TestListPods(t *testing.T) {
nil,
},
{
"test status filter",
"test phase filter",
"default",
&query.Query{
Pagination: &query.Pagination{
@@ -89,7 +89,7 @@ func TestListPods(t *testing.T) {
Ascending: false,
Filters: map[query.Field]query.Value{
query.FieldNamespace: query.Value("default"),
fieldStatus: query.Value(corev1.PodRunning),
fieldPhase: query.Value(corev1.PodRunning),
},
},
&api.ListResult{
@@ -163,6 +163,7 @@ var (
Phase: corev1.PodRunning,
},
}
pods = []interface{}{foo1, foo2, foo3, foo4, foo5}
)

View File

@@ -24,7 +24,9 @@ import (
"strings"
"time"
"github.com/mitchellh/mapstructure"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
@@ -69,6 +71,8 @@ import (
loggingclient "kubesphere.io/kubesphere/pkg/simple/client/logging"
meteringclient "kubesphere.io/kubesphere/pkg/simple/client/metering"
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"
)
@@ -78,10 +82,10 @@ type Interface interface {
ListWorkspaces(user user.Info, queryParam *query.Query) (*api.ListResult, error)
GetWorkspace(workspace string) (*tenantv1alpha1.Workspace, 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
UpdateWorkspaceTemplate(workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error)
PatchWorkspaceTemplate(workspace string, data json.RawMessage) (*tenantv1alpha2.WorkspaceTemplate, error)
UpdateWorkspaceTemplate(user user.Info, workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error)
PatchWorkspaceTemplate(user user.Info, workspace string, data json.RawMessage) (*tenantv1alpha2.WorkspaceTemplate, error)
DescribeWorkspaceTemplate(workspace string) (*tenantv1alpha2.WorkspaceTemplate, 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)
@@ -117,6 +121,7 @@ type tenantOperator struct {
auditing auditing.Interface
mo monitoring.MonitoringOperator
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 {
@@ -132,6 +137,7 @@ func New(informers informers.InformerFactory, k8sclient kubernetes.Interface, ks
auditing: auditing.NewEventsOperator(auditingclient),
mo: monitoring.NewMonitoringOperator(monitoringclient, nil, k8sclient, informers, resourceGetter, nil),
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{})
}
func (t *tenantOperator) PatchWorkspaceTemplate(workspace string, data json.RawMessage) (*tenantv1alpha2.WorkspaceTemplate, error) {
return t.ksclient.TenantV1alpha2().WorkspaceTemplates().Patch(context.Background(), workspace, types.MergePatchType, data, metav1.PatchOptions{})
func (t *tenantOperator) PatchWorkspaceTemplate(user user.Info, workspace string, data json.RawMessage) (*tenantv1alpha2.WorkspaceTemplate, error) {
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{})
}
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{})
}
@@ -1081,6 +1183,16 @@ func (t *tenantOperator) MeteringHierarchy(user user.Info, queryParam *meteringv
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 {
for _, item := range objects {
if item == object {
@@ -1106,3 +1218,78 @@ func stringContains(str string, subStrs []string) bool {
}
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
}

View File

@@ -44,6 +44,8 @@ import (
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// ctrl+d to close terminal.
endOfTransmission = "\u0004"
)
// PtyHandler is what remotecommand expects from a pty
@@ -76,7 +78,7 @@ type TerminalMessage struct {
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
func (t TerminalSession) Next() *remotecommand.TerminalSize {
select {
@@ -95,7 +97,7 @@ func (t TerminalSession) Read(p []byte) (int, error) {
var msg TerminalMessage
err := t.conn.ReadJSON(&msg)
if err != nil {
return 0, err
return copy(p, endOfTransmission), err
}
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}
return 0, nil
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{})
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 {
err := n.client.CoreV1().Pods(n.Namespace).Delete(context.Background(), n.PodName, metav1.DeleteOptions{})
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) {
var err error
validShells := []string{"sh", "bash"}
validShells := []string{"bash", "sh"}
session := &TerminalSession{conn: conn, sizeChan: make(chan remotecommand.TerminalSize)}

View File

@@ -16,7 +16,17 @@ limitations under the License.
package cache
import "time"
import (
"encoding/json"
"fmt"
"time"
"k8s.io/klog"
)
var (
cacheFactories = make(map[string]CacheFactory)
)
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
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
View 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)
}

View 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{})
}

View File

@@ -102,7 +102,7 @@ func TestDeleteAndExpireCache(t *testing.T) {
}
for _, testCase := range testCases {
cacheClient := NewSimpleCache()
cacheClient, _ := NewInMemoryCache(nil, nil)
t.Run(testCase.description, func(t *testing.T) {
err := load(cacheClient, dataSet)

View File

@@ -18,25 +18,19 @@ package cache
import (
"fmt"
"github.com/spf13/pflag"
)
type Options struct {
Host string `json:"host" yaml:"host"`
Port int `json:"port" yaml:"port"`
Password string `json:"password" yaml:"password"`
DB int `json:"db" yaml:"db"`
Type string `json:"type"`
Options DynamicOptions `json:"options"`
}
// NewRedisOptions returns options points to nowhere,
// NewCacheOptions returns options points to nowhere,
// because redis is not required for some components
func NewRedisOptions() *Options {
func NewCacheOptions() *Options {
return &Options{
Host: "",
Port: 0,
Password: "",
DB: 0,
Type: "",
Options: map[string]interface{}{},
}
}
@@ -44,20 +38,9 @@ func NewRedisOptions() *Options {
func (r *Options) Validate() []error {
errors := make([]error, 0)
if r.Port == 0 {
errors = append(errors, fmt.Errorf("invalid service port number"))
if r.Type == "" {
errors = append(errors, fmt.Errorf("invalid cache type"))
}
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, "")
}

View File

@@ -17,19 +17,31 @@ limitations under the License.
package cache
import (
"errors"
"fmt"
"time"
"github.com/go-redis/redis"
"github.com/mitchellh/mapstructure"
"k8s.io/klog"
)
type Client struct {
const typeRedis = "redis"
type redisClient struct {
client *redis.Client
}
func NewRedisClient(option *Options, stopCh <-chan struct{}) (Interface, error) {
var r Client
// redisOptions used to create a redis 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{
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
}
func (r *Client) Get(key string) (string, error) {
func (r *redisClient) Get(key string) (string, error) {
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()
}
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()
}
func (r *Client) Del(keys ...string) error {
func (r *redisClient) Del(keys ...string) error {
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()
if err != nil {
return false, err
@@ -86,6 +98,34 @@ func (r *Client) Exists(keys ...string) (bool, error) {
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()
}
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{})
}

View File

@@ -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
}

View File

@@ -38,6 +38,11 @@ func TestClient_Get(t *testing.T) {
type args struct {
url string
}
inMemoryCache, err := cache.NewInMemoryCache(nil, nil)
if err != nil {
t.Fatal(err)
}
token, _ := json.Marshal(
&TokenResponse{
Username: "test",
@@ -92,7 +97,7 @@ func TestClient_Get(t *testing.T) {
name: "Token",
fields: fields{
Strategy: AuthStrategyToken,
cache: cache.NewSimpleCache(),
cache: inMemoryCache,
client: &MockClient{
TokenResult: token,
RequestResult: "fake",

View File

@@ -20,7 +20,6 @@ import (
"fmt"
"sort"
"strings"
"sync"
"time"
"github.com/go-ldap/ldap"
@@ -63,8 +62,6 @@ type ldapInterfaceImpl struct {
groupSearchBase string
managerDN string
managerPassword string
once sync.Once
}
var _ Interface = &ldapInterfaceImpl{}
@@ -95,7 +92,6 @@ func NewLdapClient(options *Options, stopCh <-chan struct{}) (Interface, error)
groupSearchBase: options.GroupSearchBase,
managerDN: options.ManagerDN,
managerPassword: options.ManagerPassword,
once: sync.Once{},
}
go func() {
@@ -103,9 +99,7 @@ func NewLdapClient(options *Options, stopCh <-chan struct{}) (Interface, error)
client.close()
}()
client.once.Do(func() {
_ = client.createSearchBase()
})
_ = client.createSearchBase()
return client, nil
}

View File

@@ -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_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_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_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)`,

View File

@@ -74,6 +74,10 @@ func (h HelmVersionWrapper) GetKeywords() string {
return strings.Join(h.ChartVersion.Keywords, ",")
}
func (h HelmVersionWrapper) GetRawKeywords() []string {
return h.ChartVersion.Keywords
}
func (h HelmVersionWrapper) GetRawMaintainers() []*v1alpha1.Maintainer {
mt := make([]*v1alpha1.Maintainer, 0, len(h.Maintainers))
for _, value := range h.Maintainers {

View File

@@ -99,6 +99,9 @@ func MergeRepoIndex(repo *v1alpha1.HelmRepo, index *helmrepo.IndexFile, existsSa
allAppNames := make(map[string]struct{}, len(index.Entries))
for name, versions := range index.Entries {
if len(versions) == 0 {
continue
}
// add new applications
if application, exists := saved.Applications[name]; !exists {
application = &Application{

View File

@@ -50,5 +50,102 @@ func TestLoadRepo(t *testing.T) {
_ = chartData
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()
}
}

View File

@@ -23,6 +23,7 @@ import (
"sync"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
@@ -30,6 +31,7 @@ import (
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
clusterinformer "kubesphere.io/kubesphere/pkg/client/informers/externalversions/cluster/v1alpha1"
clusterlister "kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1"
)
@@ -54,6 +56,8 @@ type ClusterClients interface {
GetClusterKubeconfig(string) (string, error)
Get(string) (*clusterv1alpha1.Cluster, error)
GetInnerCluster(string) *innerCluster
GetKubernetesClientSet(string) (*kubernetes.Clientset, error)
GetKubeSphereClientSet(string) (*kubesphere.Clientset, error)
}
func NewClusterClient(clusterInformer clusterinformer.ClusterInformer) ClusterClients {
@@ -182,3 +186,45 @@ func (c *clusterClients) IsHostCluster(cluster *clusterv1alpha1.Cluster) bool {
}
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()
}

View File

@@ -28,7 +28,6 @@ import (
)
var sf *sonyflake.Sonyflake
var upperMachineID uint16
func init() {
var st sonyflake.Settings
@@ -37,11 +36,18 @@ func init() {
sf = sonyflake.NewSonyflake(sonyflake.Settings{
MachineID: lower16BitIP,
})
upperMachineID, _ = upper16BitIP()
}
if sf == nil {
sf = sonyflake.NewSonyflake(sonyflake.Settings{
MachineID: lower16BitIPv6,
})
}
}
func GetIntId() uint64 {
if sf == nil {
panic(errors.New("invalid snowflake instance"))
}
id, err := sf.NextID()
if err != nil {
panic(err)
@@ -93,15 +99,6 @@ func lower16BitIP() (uint16, error) {
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) {
as, err := net.InterfaceAddrs()
if err != nil {
@@ -123,3 +120,34 @@ func IPv4() (net.IP, error) {
}
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")
}

View 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
}

View File

@@ -287,9 +287,15 @@ func (c *cachedRepos) addRepo(repo *v1alpha1.HelmRepo, builtin bool) error {
},
Spec: v1alpha1.HelmApplicationVersionSpec{
Metadata: &v1alpha1.Metadata{
Name: hvw.GetName(),
AppVersion: hvw.GetAppVersion(),
Version: hvw.GetVersion(),
Name: hvw.GetName(),
AppVersion: hvw.GetAppVersion(),
Version: hvw.GetVersion(),
Description: hvw.GetDescription(),
Home: hvw.GetHome(),
Icon: hvw.GetIcon(),
Maintainers: hvw.GetRawMaintainers(),
Sources: hvw.GetRawSources(),
Keywords: hvw.GetRawKeywords(),
},
URLs: chartVersion.URLs,
Digest: chartVersion.Digest,