Compare commits
28 Commits
v3.4.1-alp
...
v3.3.2-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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.ServiceMeshOptions = cfg.ServiceMeshOptions
|
||||
s.GatewayOptions = cfg.GatewayOptions
|
||||
s.MonitoringOptions = cfg.MonitoringOptions
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
1
hack/verify-gofmt.sh
Normal file → Executable 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'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
|
||||
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
|
||||
|
||||
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
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 {
|
||||
cacheClient := NewSimpleCache()
|
||||
cacheClient, _ := NewInMemoryCache(nil, nil)
|
||||
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
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 (
|
||||
"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, "")
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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{})
|
||||
}
|
||||
|
||||
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 {
|
||||
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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)`,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
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{
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user