diff --git a/cmd/controller-manager/app/controllers.go b/cmd/controller-manager/app/controllers.go index 3a739c943fc5b2c0f284c6464dcd9c7e10e12839..5c471ee698953254086ec2618e8411f994e25e6a 100644 --- a/cmd/controller-manager/app/controllers.go +++ b/cmd/controller-manager/app/controllers.go @@ -25,6 +25,7 @@ import ( "kubesphere.io/kubesphere/pkg/controller/s2ibinary" "kubesphere.io/kubesphere/pkg/controller/s2irun" "kubesphere.io/kubesphere/pkg/controller/storage/expansion" + "kubesphere.io/kubesphere/pkg/controller/user" "kubesphere.io/kubesphere/pkg/controller/virtualservice" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/simple/client/k8s" @@ -87,6 +88,11 @@ func AddControllers( kubernetesInformer.Apps().V1().ReplicaSets(), kubernetesInformer.Apps().V1().StatefulSets()) + userController := user.NewController( + client.Kubernetes(), + client.KubeSphere(), + kubesphereInformer.Iam().V1alpha2().Users()) + controllers := map[string]manager.Runnable{ "virtualservice-controller": vsController, "destinationrule-controller": drController, @@ -95,6 +101,7 @@ func AddControllers( "s2ibinary-controller": s2iBinaryController, "s2irun-controller": s2iRunController, "volumeexpansion-controller": volumeExpansionController, + "user-controller": userController, } for name, ctrl := range controllers { diff --git a/cmd/controller-manager/app/options/options.go b/cmd/controller-manager/app/options/options.go index 3f8f8f2affba183cba14aca77451d4fac63b740e..272bc40081ca93eef71a474eccf3892d79520b32 100644 --- a/cmd/controller-manager/app/options/options.go +++ b/cmd/controller-manager/app/options/options.go @@ -9,7 +9,6 @@ import ( kubesphereconfig "kubesphere.io/kubesphere/pkg/apiserver/config" "kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins" "kubesphere.io/kubesphere/pkg/simple/client/k8s" - "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "kubesphere.io/kubesphere/pkg/simple/client/s3" "strings" @@ -21,9 +20,7 @@ type KubeSphereControllerManagerOptions struct { DevopsOptions *jenkins.Options S3Options *s3.Options OpenPitrixOptions *openpitrix.Options - KubeSphereOptions *kubesphere.Options - - LeaderElection *leaderelection.LeaderElectionConfig + LeaderElection *leaderelection.LeaderElectionConfig } func NewKubeSphereControllerManagerOptions() *KubeSphereControllerManagerOptions { @@ -32,7 +29,6 @@ func NewKubeSphereControllerManagerOptions() *KubeSphereControllerManagerOptions DevopsOptions: jenkins.NewDevopsOptions(), S3Options: s3.NewS3Options(), OpenPitrixOptions: openpitrix.NewOptions(), - KubeSphereOptions: kubesphere.NewKubeSphereOptions(), LeaderElection: &leaderelection.LeaderElectionConfig{ LeaseDuration: 30 * time.Second, RenewDeadline: 15 * time.Second, diff --git a/cmd/controller-manager/app/server.go b/cmd/controller-manager/app/server.go index ea99db9fb7d580be7ed188dde19e00225be40711..647fb9c9804a692aed5e046c4cc7a34d092a39af 100644 --- a/cmd/controller-manager/app/server.go +++ b/cmd/controller-manager/app/server.go @@ -38,7 +38,6 @@ import ( "kubesphere.io/kubesphere/pkg/controller/workspace" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/simple/client/k8s" - kclient "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "kubesphere.io/kubesphere/pkg/utils/term" "os" @@ -50,11 +49,13 @@ func NewControllerManagerCommand() *cobra.Command { s := options.NewKubeSphereControllerManagerOptions() conf, err := controllerconfig.TryLoadFromDisk() if err == nil { + // make sure LeaderElection is not nil s = &options.KubeSphereControllerManagerOptions{ KubernetesOptions: conf.KubernetesOptions, DevopsOptions: conf.DevopsOptions, S3Options: conf.S3Options, OpenPitrixOptions: conf.OpenPitrixOptions, + LeaderElection: s.LeaderElection, } } @@ -96,8 +97,6 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) return err } - kubesphereClient := kclient.NewKubeSphereClient(s.KubeSphereOptions) - openpitrixClient, err := openpitrix.NewClient(s.OpenPitrixOptions) if err != nil { klog.Errorf("Failed to create openpitrix client %v", err) @@ -121,7 +120,6 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) */ informerFactory := informers.NewInformerFactories(kubernetesClient.Kubernetes(), kubernetesClient.KubeSphere(), kubernetesClient.Istio(), kubernetesClient.Application()) - informerFactory.Start(stopCh) run := func(ctx context.Context) { klog.V(0).Info("setting up manager") @@ -136,7 +134,7 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) } klog.V(0).Info("Setting up controllers") - err = workspace.Add(mgr, kubesphereClient) + err = workspace.Add(mgr) if err != nil { klog.Fatal("Unable to create workspace controller") } @@ -150,6 +148,9 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) klog.Fatalf("unable to register controllers to the manager: %v", err) } + // Start cache data after all informer is registered + informerFactory.Start(stopCh) + klog.V(0).Info("Starting the controllers.") if err = mgr.Start(stopCh); err != nil { klog.Fatalf("unable to run the manager: %v", err) diff --git a/cmd/ks-apiserver/app/options/options.go b/cmd/ks-apiserver/app/options/options.go index 5c41826b3f51427439bfd09582ff9b05814f8ae9..1267a09f1e3c499c2ea1f0a13f61b451a16cb2df 100644 --- a/cmd/ks-apiserver/app/options/options.go +++ b/cmd/ks-apiserver/app/options/options.go @@ -6,8 +6,9 @@ import ( "fmt" cliflag "k8s.io/component-base/cli/flag" "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/api/auth" "kubesphere.io/kubesphere/pkg/apiserver" + authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" + apiserverconfig "kubesphere.io/kubesphere/pkg/apiserver/config" "kubesphere.io/kubesphere/pkg/informers" genericoptions "kubesphere.io/kubesphere/pkg/server/options" "kubesphere.io/kubesphere/pkg/simple/client/cache" @@ -29,42 +30,32 @@ import ( type ServerRunOptions struct { ConfigFile string GenericServerRunOptions *genericoptions.ServerRunOptions - KubernetesOptions *k8s.KubernetesOptions - DevopsOptions *jenkins.Options - SonarQubeOptions *sonarqube.Options - ServiceMeshOptions *servicemesh.Options - MySQLOptions *mysql.Options - MonitoringOptions *prometheus.Options - S3Options *s3.Options - OpenPitrixOptions *openpitrix.Options - LoggingOptions *esclient.Options - LdapOptions *ldap.Options - CacheOptions *cache.Options - AuthenticateOptions *auth.AuthenticationOptions + *apiserverconfig.Config // DebugMode bool } func NewServerRunOptions() *ServerRunOptions { - - s := ServerRunOptions{ + s := &ServerRunOptions{ GenericServerRunOptions: genericoptions.NewServerRunOptions(), - KubernetesOptions: k8s.NewKubernetesOptions(), - DevopsOptions: jenkins.NewDevopsOptions(), - SonarQubeOptions: sonarqube.NewSonarQubeOptions(), - ServiceMeshOptions: servicemesh.NewServiceMeshOptions(), - MySQLOptions: mysql.NewMySQLOptions(), - MonitoringOptions: prometheus.NewPrometheusOptions(), - S3Options: s3.NewS3Options(), - OpenPitrixOptions: openpitrix.NewOptions(), - LoggingOptions: esclient.NewElasticSearchOptions(), - LdapOptions: ldap.NewOptions(), - CacheOptions: cache.NewRedisOptions(), - AuthenticateOptions: auth.NewAuthenticateOptions(), + Config: &apiserverconfig.Config{ + KubernetesOptions: k8s.NewKubernetesOptions(), + DevopsOptions: jenkins.NewDevopsOptions(), + SonarQubeOptions: sonarqube.NewSonarQubeOptions(), + ServiceMeshOptions: servicemesh.NewServiceMeshOptions(), + MySQLOptions: mysql.NewMySQLOptions(), + MonitoringOptions: prometheus.NewPrometheusOptions(), + S3Options: s3.NewS3Options(), + OpenPitrixOptions: openpitrix.NewOptions(), + LoggingOptions: esclient.NewElasticSearchOptions(), + LdapOptions: ldap.NewOptions(), + RedisOptions: cache.NewRedisOptions(), + AuthenticationOptions: authoptions.NewAuthenticateOptions(), + }, } - return &s + return s } func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) { @@ -72,12 +63,12 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) { fs.BoolVar(&s.DebugMode, "debug", false, "Don't enable this if you don't know what it means.") s.GenericServerRunOptions.AddFlags(fs, s.GenericServerRunOptions) s.KubernetesOptions.AddFlags(fss.FlagSet("kubernetes"), s.KubernetesOptions) - s.AuthenticateOptions.AddFlags(fss.FlagSet("authenticate"), s.AuthenticateOptions) + s.AuthenticationOptions.AddFlags(fss.FlagSet("authentication"), s.AuthenticationOptions) s.MySQLOptions.AddFlags(fss.FlagSet("mysql"), s.MySQLOptions) s.DevopsOptions.AddFlags(fss.FlagSet("devops"), s.DevopsOptions) s.SonarQubeOptions.AddFlags(fss.FlagSet("sonarqube"), s.SonarQubeOptions) s.LdapOptions.AddFlags(fss.FlagSet("ldap"), s.LdapOptions) - s.CacheOptions.AddFlags(fss.FlagSet("cache"), s.CacheOptions) + 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.ServiceMeshOptions.AddFlags(fss.FlagSet("servicemesh"), s.ServiceMeshOptions) @@ -100,7 +91,7 @@ const fakeInterface string = "FAKE" // NewAPIServer creates an APIServer instance using given options func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIServer, error) { apiServer := &apiserver.APIServer{ - AuthenticateOptions: s.AuthenticateOptions, + Config: s.Config, } kubernetesClient, err := k8s.NewKubernetesClient(s.KubernetesOptions) @@ -156,11 +147,11 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS } var cacheClient cache.Interface - if s.CacheOptions.Host != "" { - if s.CacheOptions.Host == fakeInterface && s.DebugMode { + if s.RedisOptions.Host != "" { + if s.RedisOptions.Host == fakeInterface && s.DebugMode { apiServer.CacheClient = cache.NewSimpleCache() } else { - cacheClient, err = cache.NewRedisClient(s.CacheOptions, stopCh) + cacheClient, err = cache.NewRedisClient(s.RedisOptions, stopCh) if err != nil { return nil, err } diff --git a/cmd/ks-apiserver/app/server.go b/cmd/ks-apiserver/app/server.go index 78caa399ef51bf2f7846977eff8413ad60d32f88..2f6d68f2b96796c3680adde2a1c6894cd3dae8b4 100644 --- a/cmd/ks-apiserver/app/server.go +++ b/cmd/ks-apiserver/app/server.go @@ -37,18 +37,7 @@ func NewAPIServerCommand() *cobra.Command { if err == nil { s = &options.ServerRunOptions{ GenericServerRunOptions: s.GenericServerRunOptions, - KubernetesOptions: conf.KubernetesOptions, - DevopsOptions: conf.DevopsOptions, - SonarQubeOptions: conf.SonarQubeOptions, - ServiceMeshOptions: conf.ServiceMeshOptions, - MySQLOptions: conf.MySQLOptions, - MonitoringOptions: conf.MonitoringOptions, - S3Options: conf.S3Options, - OpenPitrixOptions: conf.OpenPitrixOptions, - LoggingOptions: conf.LoggingOptions, - LdapOptions: conf.LdapOptions, - CacheOptions: conf.RedisOptions, - AuthenticateOptions: conf.AuthenticateOptions, + Config: conf, } } diff --git a/config/crds/iam.kubesphere.io_users.yaml b/config/crds/iam.kubesphere.io_users.yaml new file mode 100644 index 0000000000000000000000000000000000000000..eec35e012b4d550a7b0d9f119652f897ad5084ce --- /dev/null +++ b/config/crds/iam.kubesphere.io_users.yaml @@ -0,0 +1,117 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: (devel) + creationTimestamp: null + name: users.iam.kubesphere.io +spec: + additionalPrinterColumns: + - JSONPath: .spec.email + name: Email + type: string + - JSONPath: .status.state + name: Status + type: string + group: iam.kubesphere.io + names: + categories: + - iam + kind: User + listKind: UserList + plural: users + singular: user + scope: Cluster + subresources: {} + validation: + openAPIV3Schema: + description: User is the Schema for the users API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: UserSpec defines the desired state of User + properties: + description: + description: Description of the user. + type: string + displayName: + type: string + email: + description: Unique email address. + type: string + finalizers: + description: Finalizers is an opaque list of values that must be empty + to permanently remove object from storage. + items: + type: string + type: array + groups: + items: + type: string + type: array + lang: + description: The preferred written or spoken language for the user. + type: string + password: + type: string + required: + - email + - password + type: object + status: + description: UserStatus defines the observed state of User + properties: + conditions: + description: Represents the latest available observations of a namespace's + current state. + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of namespace controller condition. + type: string + required: + - status + - type + type: object + type: array + state: + description: The user status + type: string + type: object + required: + - spec + type: object + version: v1alpha2 + versions: + - name: v1alpha2 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/samples/iam_v1alpha2_user.yaml b/config/samples/iam_v1alpha2_user.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8bd1656236715e9b7c483087c38bd0daf1ff9ce1 --- /dev/null +++ b/config/samples/iam_v1alpha2_user.yaml @@ -0,0 +1,9 @@ +apiVersion: iam.kubesphere.io/v1alpha2 +kind: User +metadata: + labels: + controller-tools.k8s.io: "1.0" + name: admin +spec: + email: admin@kubesphere.io + password: d41d8cd98f00b204e9800998ecf8427e diff --git a/hack/generate_client.sh b/hack/generate_client.sh index a7740811f6dc0fc07a52b0403c9fb3b5dab8e87a..75b799bc82cc7a6704cb03767b17bc61d22bec61 100755 --- a/hack/generate_client.sh +++ b/hack/generate_client.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -GV="network:v1alpha1 servicemesh:v1alpha2 tenant:v1alpha1 devops:v1alpha1 tower:v1alpha1" +GV="network:v1alpha1 servicemesh:v1alpha2 tenant:v1alpha1 devops:v1alpha1 iam:v1alpha2 tower:v1alpha1" rm -rf ./pkg/client ./hack/generate_group.sh "client,lister,informer" kubesphere.io/kubesphere/pkg/client kubesphere.io/kubesphere/pkg/apis "$GV" --output-base=./ -h "$PWD/hack/boilerplate.go.txt" diff --git a/pkg/apis/addtoscheme_iam_v1alpha2.go b/pkg/apis/addtoscheme_iam_v1alpha2.go new file mode 100644 index 0000000000000000000000000000000000000000..3f2a962a5f4c402578fa001507c84d3b62b77197 --- /dev/null +++ b/pkg/apis/addtoscheme_iam_v1alpha2.go @@ -0,0 +1,26 @@ +/* +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 apis + +import ( + iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" +) + +func init() { + // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back + AddToSchemes = append(AddToSchemes, iamv1alpha2.SchemeBuilder.AddToScheme) +} diff --git a/pkg/apis/iam/group.go b/pkg/apis/iam/group.go new file mode 100644 index 0000000000000000000000000000000000000000..cd47ea5645e9d7f3e097410eb31243891cbce03a --- /dev/null +++ b/pkg/apis/iam/group.go @@ -0,0 +1,18 @@ +/* +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 iam contains iam API versions +package iam diff --git a/pkg/apis/iam/v1alpha2/doc.go b/pkg/apis/iam/v1alpha2/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..5ddef225cbde97bb9fcd4885ae6dc902d56d28cc --- /dev/null +++ b/pkg/apis/iam/v1alpha2/doc.go @@ -0,0 +1,23 @@ +/* +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 v1alpha2 contains API Schema definitions for the iam v1alpha2 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=kubesphere.io/kubesphere/pkg/apis/iam +// +k8s:defaulter-gen=TypeMeta +// +groupName=iam.kubesphere.io +package v1alpha2 diff --git a/pkg/apis/iam/v1alpha2/register.go b/pkg/apis/iam/v1alpha2/register.go new file mode 100644 index 0000000000000000000000000000000000000000..373dcf121a9431b0ea85dd3cfc9419b10fa055c1 --- /dev/null +++ b/pkg/apis/iam/v1alpha2/register.go @@ -0,0 +1,46 @@ +/* +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. +*/ + +// NOTE: Boilerplate only. Ignore this file. + +// Package v1alpha2 contains API Schema definitions for the iam v1alpha2 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=kubesphere.io/kubesphere/pkg/apis/iam +// +k8s:defaulter-gen=TypeMeta +// +groupName=iam.kubesphere.io +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/runtime/scheme" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "iam.kubesphere.io", Version: "v1alpha2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} + + // AddToScheme is required by pkg/client/... + AddToScheme = SchemeBuilder.AddToScheme +) + +// Resource is required by pkg/client/listers/... +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/pkg/apis/iam/v1alpha2/user_types.go b/pkg/apis/iam/v1alpha2/user_types.go new file mode 100644 index 0000000000000000000000000000000000000000..ee9034af7c2fed3bcfa838fad01dfa1f45f27950 --- /dev/null +++ b/pkg/apis/iam/v1alpha2/user_types.go @@ -0,0 +1,133 @@ +/* +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 v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// User is the Schema for the users API +// +k8s:openapi-gen=true +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient:nonNamespaced +// +kubebuilder:printcolumn:name="Email",type="string",JSONPath=".spec.email" +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state" +// +kubebuilder:resource:categories="iam",scope="Cluster" +type User struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec UserSpec `json:"spec"` + // +optional + Status UserStatus `json:"status,omitempty"` +} + +type FinalizerName string + +// UserSpec defines the desired state of User +type UserSpec struct { + // Unique email address. + Email string `json:"email"` + // The preferred written or spoken language for the user. + // +optional + Lang string `json:"lang,omitempty"` + // Description of the user. + // +optional + Description string `json:"description,omitempty"` + // +optional + DisplayName string `json:"displayName,omitempty"` + // +optional + Groups []string `json:"groups,omitempty"` + EncryptedPassword string `json:"password"` + + // Finalizers is an opaque list of values that must be empty to permanently remove object from storage. + // +optional + Finalizers []FinalizerName `json:"finalizers,omitempty"` +} + +type UserState string + +// These are the valid phases of a user. +const ( + // UserActive means the user is available. + UserActive UserState = "Active" + // UserDisabled means the user is disabled. + UserDisabled UserState = "Disabled" +) + +// UserStatus defines the observed state of User +type UserStatus struct { + // The user status + // +optional + State UserState `json:"state,omitempty"` + + // Represents the latest available observations of a namespace's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []UserCondition `json:"conditions,omitempty"` +} + +type UserCondition struct { + // Type of namespace controller condition. + Type UserConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status ConditionStatus `json:"status"` + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` + // +optional + Reason string `json:"reason,omitempty"` + // +optional + Message string `json:"message,omitempty"` +} + +type UserConditionType string + +// These are valid conditions of a user. +const ( + // UserLoginFailure contains information about user login. + UserLoginFailure UserConditionType = "UserLoginFailure" +) + +type ConditionStatus string + +// These are valid condition statuses. "ConditionTrue" means a resource is in the condition. +// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes +// can't decide if a resource is in the condition or not. In the future, we could add other +// intermediate conditions, e.g. ConditionDegraded. +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient:nonNamespaced + +// UserList contains a list of User +type UserList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []User `json:"items"` +} + +func init() { + SchemeBuilder.Register(&User{}, &UserList{}) +} diff --git a/pkg/apis/iam/v1alpha2/user_types_test.go b/pkg/apis/iam/v1alpha2/user_types_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a47667ef72f752d5adce861e1de6aa24758fa5b1 --- /dev/null +++ b/pkg/apis/iam/v1alpha2/user_types_test.go @@ -0,0 +1,58 @@ +/* + * + * Copyright 2020 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 v1alpha2 + +import ( + "testing" + + "github.com/onsi/gomega" + "golang.org/x/net/context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestStorageUser(t *testing.T) { + key := types.NamespacedName{ + Name: "foo", + } + created := &User{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }} + g := gomega.NewGomegaWithT(t) + + // Test Create + fetched := &User{} + g.Expect(c.Create(context.TODO(), created)).To(gomega.Succeed()) + + g.Expect(c.Get(context.TODO(), key, fetched)).To(gomega.Succeed()) + g.Expect(fetched).To(gomega.Equal(created)) + + // Test Updating the Labels + updated := fetched.DeepCopy() + updated.Labels = map[string]string{"hello": "world"} + g.Expect(c.Update(context.TODO(), updated)).To(gomega.Succeed()) + + g.Expect(c.Get(context.TODO(), key, fetched)).To(gomega.Succeed()) + g.Expect(fetched).To(gomega.Equal(updated)) + + // Test Delete + g.Expect(c.Delete(context.TODO(), fetched)).To(gomega.Succeed()) + g.Expect(c.Get(context.TODO(), key, fetched)).ToNot(gomega.Succeed()) +} diff --git a/pkg/apis/iam/v1alpha2/v1alpha2_suite_test.go b/pkg/apis/iam/v1alpha2/v1alpha2_suite_test.go new file mode 100644 index 0000000000000000000000000000000000000000..17192bd5fe22c93afc5af92ff0bdfe93148060eb --- /dev/null +++ b/pkg/apis/iam/v1alpha2/v1alpha2_suite_test.go @@ -0,0 +1,55 @@ +/* +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 v1alpha2 + +import ( + "log" + "os" + "path/filepath" + "testing" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +var cfg *rest.Config +var c client.Client + +func TestMain(m *testing.M) { + t := &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crds")}, + } + + err := SchemeBuilder.AddToScheme(scheme.Scheme) + if err != nil { + log.Fatal(err) + } + + if cfg, err = t.Start(); err != nil { + log.Fatal(err) + } + + if c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}); err != nil { + log.Fatal(err) + } + + code := m.Run() + t.Stop() + os.Exit(code) +} diff --git a/pkg/apis/iam/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/iam/v1alpha2/zz_generated.deepcopy.go new file mode 100644 index 0000000000000000000000000000000000000000..a705636805fd6ecaaadcb3d507fafe2bb1111391 --- /dev/null +++ b/pkg/apis/iam/v1alpha2/zz_generated.deepcopy.go @@ -0,0 +1,147 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *User) DeepCopyInto(out *User) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new User. +func (in *User) DeepCopy() *User { + if in == nil { + return nil + } + out := new(User) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *User) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserCondition) DeepCopyInto(out *UserCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserCondition. +func (in *UserCondition) DeepCopy() *UserCondition { + if in == nil { + return nil + } + out := new(UserCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserList) DeepCopyInto(out *UserList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]User, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserList. +func (in *UserList) DeepCopy() *UserList { + if in == nil { + return nil + } + out := new(UserList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UserList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserSpec) DeepCopyInto(out *UserSpec) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Finalizers != nil { + in, out := &in.Finalizers, &out.Finalizers + *out = make([]FinalizerName, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec. +func (in *UserSpec) DeepCopy() *UserSpec { + if in == nil { + return nil + } + out := new(UserSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserStatus) DeepCopyInto(out *UserStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]UserCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus. +func (in *UserStatus) DeepCopy() *UserStatus { + if in == nil { + return nil + } + out := new(UserStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 008bc4497f7c1adfe3a532918262fc61c301c267..76ca4426ae1677ca08a4820bd9e902b91d43e583 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -12,21 +12,19 @@ import ( unionauth "k8s.io/apiserver/pkg/authentication/request/union" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/api/auth" "kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/basic" "kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken" - oauth2 "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" "kubesphere.io/kubesphere/pkg/apiserver/authentication/request/anonymous" "kubesphere.io/kubesphere/pkg/apiserver/authentication/request/basictoken" "kubesphere.io/kubesphere/pkg/apiserver/authentication/token" "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory" "kubesphere.io/kubesphere/pkg/apiserver/authorization/path" unionauthorizer "kubesphere.io/kubesphere/pkg/apiserver/authorization/union" + apiserverconfig "kubesphere.io/kubesphere/pkg/apiserver/config" "kubesphere.io/kubesphere/pkg/apiserver/dispatch" "kubesphere.io/kubesphere/pkg/apiserver/filters" "kubesphere.io/kubesphere/pkg/apiserver/request" "kubesphere.io/kubesphere/pkg/informers" - devopsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha2" iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2" loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2" monitoringv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha2" @@ -35,8 +33,8 @@ import ( operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2" resourcesv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha2" resourcev1alpha3 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha3" + "kubesphere.io/kubesphere/pkg/kapis/serverconfig/v1alpha2" servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2" - tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2" terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2" "kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/im" @@ -75,7 +73,7 @@ type APIServer struct { // Server *http.Server - AuthenticateOptions *auth.AuthenticationOptions + Config *apiserverconfig.Config // webservice container, where all webservice defines container *restful.Container @@ -135,19 +133,19 @@ func (s *APIServer) PrepareRun() error { } func (s *APIServer) installKubeSphereAPIs() { - + urlruntime.Must(v1alpha2.AddToContainer(s.container, s.Config)) urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory)) // Need to refactor devops api registration, too much dependencies - urlruntime.Must(devopsv1alpha2.AddToContainer(s.container, s.DevopsClient, s.DBClient.Database(), nil, s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory(), s.S3Client)) + //urlruntime.Must(devopsv1alpha2.AddToContainer(s.container, s.DevopsClient, s.DBClient.Database(), nil, s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory(), s.S3Client)) urlruntime.Must(loggingv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.LoggingClient)) urlruntime.Must(monitoringv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.MonitoringClient)) urlruntime.Must(openpitrixv1.AddToContainer(s.container, s.InformerFactory, s.OpenpitrixClient)) urlruntime.Must(operationsv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes())) urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory)) - urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database())) + //urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database())) urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config())) - urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.AuthenticateOptions)) - urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.AuthenticateOptions, s.CacheClient), &oauth2.SimpleConfigManager{})) + urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.Config.AuthenticationOptions)) + urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient), s.Config.AuthenticationOptions)) urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container)) } @@ -187,7 +185,7 @@ func (s *APIServer) buildHandlerChain() { handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{}) handler = filters.WithMultipleClusterDispatcher(handler, dispatch.NewClusterDispatch(s.InformerFactory.KubeSphereSharedInformerFactory().Tower().V1alpha1().Agents().Lister())) - excludedPaths := []string{"/oauth/authorize", "/oauth/token"} + excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*"} pathAuthorizer, _ := path.NewAuthorizer(excludedPaths) authorizer := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator())) @@ -196,7 +194,7 @@ func (s *APIServer) buildHandlerChain() { authn := unionauth.New(anonymous.NewAuthenticator(), basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())), bearertoken.New(jwttoken.NewTokenAuthenticator( - token.NewJwtTokenIssuer(token.DefaultIssuerName, s.AuthenticateOptions, s.CacheClient)))) + token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient)))) handler = filters.WithAuthentication(handler, authn) handler = filters.WithRequestInfo(handler, requestInfoResolver) s.Server.Handler = handler diff --git a/pkg/apiserver/authentication/authenticators/jwttoken/jwt_token.go b/pkg/apiserver/authentication/authenticators/jwttoken/jwt_token.go index bdbe8bd49dc9ac202a048dbef4b8a0bc0bb3959c..a8bea8f1803326f86495a24a60b40dc19a92105d 100644 --- a/pkg/apiserver/authentication/authenticators/jwttoken/jwt_token.go +++ b/pkg/apiserver/authentication/authenticators/jwttoken/jwt_token.go @@ -23,7 +23,7 @@ func NewTokenAuthenticator(issuer token2.Issuer) authenticator.Token { } func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) { - providedUser, _, err := t.jwtTokenIssuer.Verify(token) + providedUser, err := t.jwtTokenIssuer.Verify(token) if err != nil { return nil, false, err } diff --git a/pkg/apiserver/authentication/identityprovider/identity_provider.go b/pkg/apiserver/authentication/identityprovider/identity_provider.go new file mode 100644 index 0000000000000000000000000000000000000000..1a5a4c2622dec10f6de2c14c61ded8c0e5a9a108 --- /dev/null +++ b/pkg/apiserver/authentication/identityprovider/identity_provider.go @@ -0,0 +1,63 @@ +/* + * + * Copyright 2020 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 identityprovider + +import ( + "errors" + "k8s.io/apiserver/pkg/authentication/user" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" +) + +var ( + ErrorIdentityProviderNotFound = errors.New("the identity provider was not found") + ErrorAlreadyRegistered = errors.New("the identity provider was not found") + oauthProviderCodecs = map[string]OAuthProviderCodec{} +) + +type OAuthProvider interface { + IdentityExchange(code string) (user.Info, error) +} + +type OAuthProviderCodec interface { + Type() string + Decode(options *oauth.DynamicOptions) (OAuthProvider, error) + Encode(provider OAuthProvider) (*oauth.DynamicOptions, error) +} + +func ResolveOAuthProvider(providerType string, options *oauth.DynamicOptions) (OAuthProvider, error) { + if codec, ok := oauthProviderCodecs[providerType]; ok { + return codec.Decode(options) + } + return nil, ErrorIdentityProviderNotFound +} + +func ResolveOAuthOptions(providerType string, provider OAuthProvider) (*oauth.DynamicOptions, error) { + if codec, ok := oauthProviderCodecs[providerType]; ok { + return codec.Encode(provider) + } + return nil, ErrorIdentityProviderNotFound +} + +func RegisterOAuthProviderCodec(codec OAuthProviderCodec) error { + if _, ok := oauthProviderCodecs[codec.Type()]; ok { + return ErrorAlreadyRegistered + } + oauthProviderCodecs[codec.Type()] = codec + return nil +} diff --git a/pkg/apiserver/authentication/oauth/oauth.go b/pkg/apiserver/authentication/oauth/oauth.go deleted file mode 100644 index 12a9a3973395133e8489313c224991e25372e6ad..0000000000000000000000000000000000000000 --- a/pkg/apiserver/authentication/oauth/oauth.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - * - * Copyright 2020 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 oauth - -import ( - "errors" - "golang.org/x/oauth2" -) - -var ConfigNotFound = errors.New("config not found") - -type Configuration interface { - Load(clientId string) (*oauth2.Config, error) -} diff --git a/pkg/apiserver/authentication/oauth/oauth_options.go b/pkg/apiserver/authentication/oauth/oauth_options.go new file mode 100644 index 0000000000000000000000000000000000000000..bb964dc740ada2a93cc28276d15233ea8fad1608 --- /dev/null +++ b/pkg/apiserver/authentication/oauth/oauth_options.go @@ -0,0 +1,235 @@ +/* + * + * Copyright 2020 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 oauth + +import ( + "errors" + "kubesphere.io/kubesphere/pkg/utils/sliceutil" + "net/url" + "time" +) + +type GrantHandlerType string +type MappingMethod string +type IdentityProviderType string + +const ( + // GrantHandlerAuto auto-approves client authorization grant requests + GrantHandlerAuto GrantHandlerType = "auto" + // GrantHandlerPrompt prompts the user to approve new client authorization grant requests + GrantHandlerPrompt GrantHandlerType = "prompt" + // GrantHandlerDeny auto-denies client authorization grant requests + GrantHandlerDeny GrantHandlerType = "deny" + // MappingMethodAuto The default value.The user will automatically create and mapping when login successful. + // Fails if a user with that user name is already mapped to another identity. + MappingMethodAuto MappingMethod = "auto" + // MappingMethodLookup Looks up an existing identity, user identity mapping, and user, but does not automatically + // provision users or identities. Using this method requires you to manually provision users. + MappingMethodLookup MappingMethod = "lookup" + // MappingMethodMixed A user entity can be mapped with multiple identifyProvider. + MappingMethodMixed MappingMethod = "mixed" +) + +var ( + ErrorClientNotFound = errors.New("the OAuth client was not found") + ErrorRedirectURLNotAllowed = errors.New("redirect URL is not allowed") +) + +type Options struct { + // LDAPPasswordIdentityProvider provider is used by default. + IdentityProviders []IdentityProviderOptions `json:"identityProviders,omitempty" yaml:"identityProviders,omitempty"` + + // Register additional OAuth clients. + Clients []Client `json:"clients,omitempty" yaml:"clients,omitempty"` + + // AccessTokenMaxAgeSeconds control the lifetime of access tokens. The default lifetime is 24 hours. + // 0 means no expiration. + AccessTokenMaxAge time.Duration `json:"accessTokenMaxAge" yaml:"accessTokenMaxAge"` + + // Inactivity timeout for tokens + // The value represents the maximum amount of time that can occur between + // consecutive uses of the token. Tokens become invalid if they are not + // used within this temporal window. The user will need to acquire a new + // token to regain access once a token times out. + // This value needs to be set only if the default set in configuration is + // not appropriate for this client. Valid values are: + // - 0: Tokens for this client never time out + // - X: Tokens time out if there is no activity + // The current minimum allowed value for X is 5 minutes + AccessTokenInactivityTimeout time.Duration `json:"accessTokenInactivityTimeout" yaml:"accessTokenInactivityTimeout"` +} + +type DynamicOptions map[string]interface{} + +type IdentityProviderOptions struct { + // The provider name. + Name string `json:"name" yaml:"name"` + + // Defines how new identities are mapped to users when they login. Allowed values are: + // - auto: The default value.The user will automatically create and mapping when login successful. + // Fails if a user with that user name is already mapped to another identity. + // - lookup: Looks up an existing identity, user identity mapping, and user, but does not automatically + // provision users or identities. Using this method requires you to manually provision users. + // - mixed: A user entity can be mapped with multiple identifyProvider. + MappingMethod MappingMethod `json:"mappingMethod" yaml:"mappingMethod"` + + // When true, unauthenticated token requests from web clients (like the web console) + // are redirected to a login page (with WWW-Authenticate challenge header) backed by this provider. + LoginRedirect bool `json:"loginRedirect" yaml:"loginRedirect"` + + // The type of identify provider + Type string `json:"type" yaml:"type"` + + // The options of identify provider + Provider *DynamicOptions `json:"provider,omitempty" yaml:"provider,omitempty"` +} + +type Token struct { + // AccessToken is the token that authorizes and authenticates + // the requests. + AccessToken string `json:"access_token"` + + // TokenType is the type of token. + // The Type method returns either this or "Bearer", the default. + TokenType string `json:"token_type,omitempty"` + + // RefreshToken is a token that's used by the application + // (as opposed to the user) to refresh the access token + // if it expires. + RefreshToken string `json:"refresh_token,omitempty"` + + // ExpiresIn is the optional expiration second of the access token. + ExpiresIn int `json:"expires_in,omitempty"` +} + +type Client struct { + // The name of the OAuth client is used as the client_id parameter when making requests to /oauth/authorize + // and /oauth/token. + Name string + + // Secret is the unique secret associated with a client + Secret string `json:"-" yaml:"secret,omitempty"` + + // RespondWithChallenges indicates whether the client wants authentication needed responses made + // in the form of challenges instead of redirects + RespondWithChallenges bool `json:"respondWithChallenges,omitempty" yaml:"respondWithChallenges,omitempty"` + + // RedirectURIs is the valid redirection URIs associated with a client + RedirectURIs []string `json:"redirectURIs,omitempty" yaml:"redirectURIs,omitempty"` + + // GrantMethod determines how to handle grants for this client. If no method is provided, the + // cluster default grant handling method will be used. Valid grant handling methods are: + // - auto: always approves grant requests, useful for trusted clients + // - prompt: prompts the end user for approval of grant requests, useful for third-party clients + // - deny: always denies grant requests, useful for black-listed clients + GrantMethod GrantHandlerType `json:"grantMethod,omitempty" yaml:"grantMethod,omitempty"` + + // ScopeRestrictions describes which scopes this client can request. Each requested scope + // is checked against each restriction. If any restriction matches, then the scope is allowed. + // If no restriction matches, then the scope is denied. + ScopeRestrictions []string `json:"scopeRestrictions,omitempty" yaml:"scopeRestrictions,omitempty"` + + // AccessTokenMaxAge overrides the default access token max age for tokens granted to this client. + AccessTokenMaxAge *time.Duration `json:"accessTokenMaxAge,omitempty" yaml:"accessTokenMaxAge,omitempty"` + + // AccessTokenInactivityTimeout overrides the default token + // inactivity timeout for tokens granted to this client. + AccessTokenInactivityTimeout *time.Duration `json:"accessTokenInactivityTimeout,omitempty" yaml:"accessTokenInactivityTimeout,omitempty"` +} + +var ( + // Allow any redirect URI if the redirectURI is defined in request + AllowAllRedirectURI = "*" + DefaultTokenMaxAge = time.Second * 86400 + DefaultAccessTokenInactivityTimeout = time.Duration(0) + DefaultClients = []Client{{ + Name: "default", + RespondWithChallenges: true, + RedirectURIs: []string{AllowAllRedirectURI}, + GrantMethod: GrantHandlerAuto, + ScopeRestrictions: []string{"full"}, + AccessTokenMaxAge: &DefaultTokenMaxAge, + AccessTokenInactivityTimeout: &DefaultAccessTokenInactivityTimeout, + }} +) + +func (o *Options) OAuthClient(name string) (Client, error) { + for _, found := range o.Clients { + if found.Name == name { + return found, nil + } + } + for _, defaultClient := range DefaultClients { + if defaultClient.Name == name { + return defaultClient, nil + } + } + return Client{}, ErrorClientNotFound +} +func (o *Options) IdentityProviderOptions(name string) (IdentityProviderOptions, error) { + for _, found := range o.IdentityProviders { + if found.Name == name { + return found, nil + } + } + return IdentityProviderOptions{}, ErrorClientNotFound +} + +func (c Client) anyRedirectAbleURI() []string { + uris := make([]string, 0) + for _, uri := range c.RedirectURIs { + _, err := url.Parse(uri) + if err == nil { + uris = append(uris, uri) + } + } + return uris +} + +func (c Client) ResolveRedirectURL(expectURL string) (string, error) { + // RedirectURIs is empty + if len(c.RedirectURIs) == 0 { + return "", ErrorRedirectURLNotAllowed + } + allowAllRedirectURI := sliceutil.HasString(c.RedirectURIs, AllowAllRedirectURI) + redirectAbleURIs := c.anyRedirectAbleURI() + + if expectURL == "" { + // Need to specify at least one RedirectURI + if len(redirectAbleURIs) > 0 { + return redirectAbleURIs[0], nil + } else { + return "", ErrorRedirectURLNotAllowed + } + } + if allowAllRedirectURI || sliceutil.HasString(redirectAbleURIs, expectURL) { + return expectURL, nil + } + + return "", ErrorRedirectURLNotAllowed +} + +func NewOptions() *Options { + return &Options{ + IdentityProviders: make([]IdentityProviderOptions, 0), + Clients: make([]Client, 0), + AccessTokenMaxAge: time.Hour * 24, + AccessTokenInactivityTimeout: 0, + } +} diff --git a/pkg/apiserver/authentication/oauth/oauth_options_test.go b/pkg/apiserver/authentication/oauth/oauth_options_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c7af4207506fe814e437bc4d0c8b31dce64d3d9b --- /dev/null +++ b/pkg/apiserver/authentication/oauth/oauth_options_test.go @@ -0,0 +1,104 @@ +/* + * + * Copyright 2020 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 oauth + +import ( + "github.com/google/go-cmp/cmp" + "testing" + "time" +) + +func TestDefaultAuthOptions(t *testing.T) { + oneDay := time.Second * 86400 + zero := time.Duration(0) + expect := Client{ + Name: "default", + RespondWithChallenges: true, + RedirectURIs: []string{AllowAllRedirectURI}, + GrantMethod: GrantHandlerAuto, + ScopeRestrictions: []string{"full"}, + AccessTokenMaxAge: &oneDay, + AccessTokenInactivityTimeout: &zero, + } + + options := NewOptions() + client, err := options.OAuthClient("default") + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(expect, client); len(diff) != 0 { + t.Errorf("%T differ (-got, +expected), %s", expect, diff) + } +} + +func TestClientResolveRedirectURL(t *testing.T) { + + options := NewOptions() + defaultClient, err := options.OAuthClient("default") + if err != nil { + t.Fatal(err) + } + tests := []struct { + Name string + client Client + expectError error + expectURL string + }{ + { + Name: "default client test", + client: defaultClient, + expectError: nil, + expectURL: "https://localhost:8080/auth/cb", + }, + { + Name: "custom client test", + client: Client{ + Name: "default", + RespondWithChallenges: true, + RedirectURIs: []string{"https://foo.bar.com/oauth/cb"}, + GrantMethod: GrantHandlerAuto, + ScopeRestrictions: []string{"full"}, + }, + expectError: ErrorRedirectURLNotAllowed, + expectURL: "https://foo.bar.com/oauth/err", + }, + { + Name: "custom client test", + client: Client{ + Name: "default", + RespondWithChallenges: true, + RedirectURIs: []string{AllowAllRedirectURI, "https://foo.bar.com/oauth/cb"}, + GrantMethod: GrantHandlerAuto, + ScopeRestrictions: []string{"full"}, + }, + expectError: nil, + expectURL: "https://foo.bar.com/oauth/err2", + }, + } + + for _, test := range tests { + redirectURL, err := test.client.ResolveRedirectURL(test.expectURL) + if err != test.expectError { + t.Errorf("expected error: %s, got: %s", test.expectError, err) + } + if test.expectError == nil && test.expectURL != redirectURL { + t.Errorf("expected redirect url: %s, got: %s", test.expectURL, redirectURL) + } + } +} diff --git a/pkg/apiserver/authentication/oauth/simple_config.go b/pkg/apiserver/authentication/oauth/simple_config.go deleted file mode 100644 index 7e05975617c7c0809a564835e7aa33c7a283b1e0..0000000000000000000000000000000000000000 --- a/pkg/apiserver/authentication/oauth/simple_config.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * - * Copyright 2020 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 oauth - -import "golang.org/x/oauth2" - -type SimpleConfigManager struct { -} - -func (s *SimpleConfigManager) Load(clientId string) (*oauth2.Config, error) { - if clientId == "kubesphere-console-client" { - return &oauth2.Config{ - ClientID: "8b21fef43889a28f2bd6", - ClientSecret: "xb21fef43889a28f2bd6", - Endpoint: oauth2.Endpoint{AuthURL: "http://ks-apiserver.kubesphere-system.svc/oauth/authorize", TokenURL: "http://ks-apiserver.kubesphere.io/oauth/token"}, - RedirectURL: "http://ks-console.kubesphere-system.svc/oauth/token/implicit", - Scopes: nil, - }, nil - } - return nil, ConfigNotFound -} diff --git a/pkg/api/auth/authenticate.go b/pkg/apiserver/authentication/options/authenticate_options.go similarity index 84% rename from pkg/api/auth/authenticate.go rename to pkg/apiserver/authentication/options/authenticate_options.go index 522e3163596a698de71c7f1426bcc84104d57f0a..a975990451e3900bb2c78fefc828e4f3c8a372d4 100644 --- a/pkg/api/auth/authenticate.go +++ b/pkg/apiserver/authentication/options/authenticate_options.go @@ -16,11 +16,12 @@ * / */ -package auth +package options import ( "fmt" "github.com/spf13/pflag" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" "time" ) @@ -32,15 +33,13 @@ type AuthenticationOptions struct { // maximum retries when authenticate failed MaxAuthenticateRetries int `json:"maxAuthenticateRetries" yaml:"maxAuthenticateRetries"` - // token validation duration, will refresh token expiration for each user request - // 0 means never expire - TokenExpiration time.Duration `json:"tokenExpiration" yaml:"tokenExpiration"` - // allow multiple users login at the same time MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"` // secret to signed jwt token - JwtSecret string `json:"jwtSecret" yaml:"jwtSecret"` + JwtSecret string `json:"-" yaml:"jwtSecret"` + + OAuthOptions *oauth.Options `json:"oauthOptions" yaml:"oauthOptions"` } func NewAuthenticateOptions() *AuthenticationOptions { @@ -48,7 +47,7 @@ func NewAuthenticateOptions() *AuthenticationOptions { AuthenticateRateLimiterMaxTries: 5, AuthenticateRateLimiterDuration: time.Minute * 30, MaxAuthenticateRetries: 0, - TokenExpiration: 0, + OAuthOptions: oauth.NewOptions(), MultipleLogin: false, JwtSecret: "", } @@ -68,7 +67,6 @@ func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *Authenticat fs.IntVar(&options.AuthenticateRateLimiterMaxTries, "authenticate-rate-limiter-max-retries", s.AuthenticateRateLimiterMaxTries, "") fs.DurationVar(&options.AuthenticateRateLimiterDuration, "authenticate-rate-limiter-duration", s.AuthenticateRateLimiterDuration, "") fs.IntVar(&options.MaxAuthenticateRetries, "authenticate-max-retries", s.MaxAuthenticateRetries, "") - fs.DurationVar(&options.TokenExpiration, "token-expiration", s.TokenExpiration, "Token expire duration, for example 30m/2h/1d, 0 means token never expire unless server restart.") fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.") fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.") } diff --git a/pkg/apiserver/authentication/token/issuer.go b/pkg/apiserver/authentication/token/issuer.go index 5c1fdfa1862e59ffdad79d1da0337e66084cb484..1661592caa779c729cdb19b7ffa8b6e72c3e321f 100644 --- a/pkg/apiserver/authentication/token/issuer.go +++ b/pkg/apiserver/authentication/token/issuer.go @@ -18,13 +18,15 @@ package token +import "time" + // Issuer issues token to user, tokens are required to perform mutating requests to resources type Issuer interface { // IssueTo issues a token a User, return error if issuing process failed - IssueTo(User) (string, *Claims, error) + IssueTo(user User, expiresIn time.Duration) (string, error) // Verify verifies a token, and return a User if it's a valid token, otherwise return error - Verify(string) (User, *Claims, error) + Verify(string) (User, error) // Revoke a token, Revoke(token string) error diff --git a/pkg/apiserver/authentication/token/jwt.go b/pkg/apiserver/authentication/token/jwt.go index 10e8db30e67241170cbbc667c1374de062d2ad74..ff2b18caf189108d7fc30b211f57493dc570ce6d 100644 --- a/pkg/apiserver/authentication/token/jwt.go +++ b/pkg/apiserver/authentication/token/jwt.go @@ -21,8 +21,8 @@ package token import ( "fmt" "github.com/dgrijalva/jwt-go" - "kubesphere.io/kubesphere/pkg/api/auth" "kubesphere.io/kubesphere/pkg/api/iam" + authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" "kubesphere.io/kubesphere/pkg/server/errors" "kubesphere.io/kubesphere/pkg/simple/client/cache" "time" @@ -44,35 +44,35 @@ type Claims struct { type jwtTokenIssuer struct { name string - options *auth.AuthenticationOptions + options *authoptions.AuthenticationOptions cache cache.Interface keyFunc jwt.Keyfunc } -func (s *jwtTokenIssuer) Verify(tokenString string) (User, *Claims, error) { +func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) { if len(tokenString) == 0 { - return nil, nil, errInvalidToken + return nil, errInvalidToken } _, err := s.cache.Get(tokenCacheKey(tokenString)) if err != nil { if err == cache.ErrNoSuchKey { - return nil, nil, errTokenExpired + return nil, errTokenExpired } - return nil, nil, err + return nil, err } clm := &Claims{} _, err = jwt.ParseWithClaims(tokenString, clm, s.keyFunc) if err != nil { - return nil, nil, err + return nil, err } - return &iam.User{Name: clm.Username, UID: clm.UID}, clm, nil + return &iam.User{Name: clm.Username, UID: clm.UID}, nil } -func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) { +func (s *jwtTokenIssuer) IssueTo(user User, expiresIn time.Duration) (string, error) { clm := &Claims{ Username: user.GetName(), UID: user.GetUID(), @@ -83,8 +83,8 @@ func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) { }, } - if s.options.TokenExpiration > 0 { - clm.ExpiresAt = clm.IssuedAt + int64(s.options.TokenExpiration.Seconds()) + if expiresIn > 0 { + clm.ExpiresAt = clm.IssuedAt + int64(expiresIn.Seconds()) } token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm) @@ -92,19 +92,19 @@ func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) { tokenString, err := token.SignedString([]byte(s.options.JwtSecret)) if err != nil { - return "", nil, err + return "", err } - s.cache.Set(tokenCacheKey(tokenString), tokenString, s.options.TokenExpiration) + s.cache.Set(tokenCacheKey(tokenString), tokenString, expiresIn) - return tokenString, clm, nil + return tokenString, nil } func (s *jwtTokenIssuer) Revoke(token string) error { return s.cache.Del(tokenCacheKey(token)) } -func NewJwtTokenIssuer(issuerName string, options *auth.AuthenticationOptions, cache cache.Interface) Issuer { +func NewJwtTokenIssuer(issuerName string, options *authoptions.AuthenticationOptions, cache cache.Interface) Issuer { return &jwtTokenIssuer{ name: issuerName, options: options, diff --git a/pkg/apiserver/authentication/token/jwt_test.go b/pkg/apiserver/authentication/token/jwt_test.go index bc7c0b380c758b6645c8d83feaa09bb61c624696..dac84abb509b2304df3115a37829a59656d594e5 100644 --- a/pkg/apiserver/authentication/token/jwt_test.go +++ b/pkg/apiserver/authentication/token/jwt_test.go @@ -20,14 +20,14 @@ package token import ( "github.com/google/go-cmp/cmp" - "kubesphere.io/kubesphere/pkg/api/auth" "kubesphere.io/kubesphere/pkg/api/iam" + authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" "kubesphere.io/kubesphere/pkg/simple/client/cache" "testing" ) func TestJwtTokenIssuer(t *testing.T) { - options := auth.NewAuthenticateOptions() + options := authoptions.NewAuthenticateOptions() options.JwtSecret = "kubesphere" issuer := NewJwtTokenIssuer(DefaultIssuerName, options, cache.NewSimpleCache()) @@ -54,12 +54,12 @@ func TestJwtTokenIssuer(t *testing.T) { } t.Run(testCase.description, func(t *testing.T) { - token, _, err := issuer.IssueTo(user) + token, err := issuer.IssueTo(user, 0) if err != nil { t.Fatal(err) } - got, _, err := issuer.Verify(token) + got, err := issuer.Verify(token) if err != nil { t.Fatal(err) } diff --git a/pkg/apiserver/config/config.go b/pkg/apiserver/config/config.go index d3cfb7d0889c665261a048af5fc9e0d87d423445..574194b2d1aaa8a177b8254cdbf112cb85d4a49b 100644 --- a/pkg/apiserver/config/config.go +++ b/pkg/apiserver/config/config.go @@ -5,13 +5,12 @@ import ( "github.com/emicklei/go-restful" "github.com/spf13/viper" "k8s.io/apimachinery/pkg/runtime/schema" - "kubesphere.io/kubesphere/pkg/api/auth" + authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" "kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/simple/client/alerting" "kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins" "kubesphere.io/kubesphere/pkg/simple/client/k8s" - "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "kubesphere.io/kubesphere/pkg/simple/client/ldap" "kubesphere.io/kubesphere/pkg/simple/client/logging/elasticsearch" "kubesphere.io/kubesphere/pkg/simple/client/monitoring/prometheus" @@ -63,23 +62,18 @@ const ( // Config defines everything needed for apiserver to deal with external services type Config struct { - MySQLOptions *mysql.Options `json:"mysql,omitempty" yaml:"mysql,omitempty" mapstructure:"mysql"` - DevopsOptions *jenkins.Options `json:"devops,omitempty" yaml:"devops,omitempty" mapstructure:"devops"` - SonarQubeOptions *sonarqube.Options `json:"sonarqube,omitempty" yaml:"sonarQube,omitempty" mapstructure:"sonarqube"` - KubernetesOptions *k8s.KubernetesOptions `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty" mapstructure:"kubernetes"` - ServiceMeshOptions *servicemesh.Options `json:"servicemesh,omitempty" yaml:"servicemesh,omitempty" mapstructure:"servicemesh"` - LdapOptions *ldap.Options `json:"ldap,omitempty" yaml:"ldap,omitempty" mapstructure:"ldap"` - RedisOptions *cache.Options `json:"redis,omitempty" yaml:"redis,omitempty" mapstructure:"redis"` - 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"` - LoggingOptions *elasticsearch.Options `json:"logging,omitempty" yaml:"logging,omitempty" mapstructure:"logging"` - - // Options below are only loaded from configuration file, no command line flags for these options now. - KubeSphereOptions *kubesphere.Options `json:"-" yaml:"kubesphere,omitempty" mapstructure:"kubesphere"` - - AuthenticateOptions *auth.AuthenticationOptions `json:"authentication,omitempty" yaml:"authenticate,omitempty" mapstructure:"authenticate"` - + MySQLOptions *mysql.Options `json:"mysql,omitempty" yaml:"mysql,omitempty" mapstructure:"mysql"` + DevopsOptions *jenkins.Options `json:"devops,omitempty" yaml:"devops,omitempty" mapstructure:"devops"` + SonarQubeOptions *sonarqube.Options `json:"sonarqube,omitempty" yaml:"sonarQube,omitempty" mapstructure:"sonarqube"` + KubernetesOptions *k8s.KubernetesOptions `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty" mapstructure:"kubernetes"` + ServiceMeshOptions *servicemesh.Options `json:"servicemesh,omitempty" yaml:"servicemesh,omitempty" mapstructure:"servicemesh"` + LdapOptions *ldap.Options `json:"ldap,omitempty" yaml:"ldap,omitempty" mapstructure:"ldap"` + RedisOptions *cache.Options `json:"redis,omitempty" yaml:"redis,omitempty" mapstructure:"redis"` + 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"` + LoggingOptions *elasticsearch.Options `json:"logging,omitempty" yaml:"logging,omitempty" mapstructure:"logging"` + AuthenticationOptions *authoptions.AuthenticationOptions `json:"authentication,omitempty" yaml:"authentication,omitempty" mapstructure:"authentication"` // Options used for enabling components, not actually used now. Once we switch Alerting/Notification API to kubesphere, // we can add these options to kubesphere command lines AlertingOptions *alerting.Options `json:"alerting,omitempty" yaml:"alerting,omitempty" mapstructure:"alerting"` @@ -89,21 +83,20 @@ type Config struct { // newConfig creates a default non-empty Config func New() *Config { return &Config{ - MySQLOptions: mysql.NewMySQLOptions(), - DevopsOptions: jenkins.NewDevopsOptions(), - SonarQubeOptions: sonarqube.NewSonarQubeOptions(), - KubernetesOptions: k8s.NewKubernetesOptions(), - ServiceMeshOptions: servicemesh.NewServiceMeshOptions(), - LdapOptions: ldap.NewOptions(), - RedisOptions: cache.NewRedisOptions(), - S3Options: s3.NewS3Options(), - OpenPitrixOptions: openpitrix.NewOptions(), - MonitoringOptions: prometheus.NewPrometheusOptions(), - KubeSphereOptions: kubesphere.NewKubeSphereOptions(), - AlertingOptions: alerting.NewAlertingOptions(), - NotificationOptions: notification.NewNotificationOptions(), - LoggingOptions: elasticsearch.NewElasticSearchOptions(), - AuthenticateOptions: auth.NewAuthenticateOptions(), + MySQLOptions: mysql.NewMySQLOptions(), + DevopsOptions: jenkins.NewDevopsOptions(), + SonarQubeOptions: sonarqube.NewSonarQubeOptions(), + KubernetesOptions: k8s.NewKubernetesOptions(), + ServiceMeshOptions: servicemesh.NewServiceMeshOptions(), + LdapOptions: ldap.NewOptions(), + RedisOptions: cache.NewRedisOptions(), + S3Options: s3.NewS3Options(), + OpenPitrixOptions: openpitrix.NewOptions(), + MonitoringOptions: prometheus.NewPrometheusOptions(), + AlertingOptions: alerting.NewAlertingOptions(), + NotificationOptions: notification.NewNotificationOptions(), + LoggingOptions: elasticsearch.NewElasticSearchOptions(), + AuthenticationOptions: authoptions.NewAuthenticateOptions(), } } @@ -125,17 +118,9 @@ func TryLoadFromDisk() (*Config, error) { } conf := New() + if err := viper.Unmarshal(conf); err != nil { return nil, err - } else { - // make sure kubesphere options always exists - if conf.KubeSphereOptions == nil { - conf.KubeSphereOptions = kubesphere.NewKubeSphereOptions() - } else { - ksOptions := kubesphere.NewKubeSphereOptions() - conf.KubeSphereOptions.ApplyTo(ksOptions) - conf.KubeSphereOptions = ksOptions - } } return conf, nil @@ -151,7 +136,7 @@ func (conf *Config) InstallAPI(c *restful.Container) { ws.Route(ws.GET("/configz"). To(func(request *restful.Request, response *restful.Response) { conf.stripEmptyOptions() - response.WriteAsJson(conf.toMap()) + response.WriteAsJson(conf.ToMap()) }). Doc("Get system components configuration"). Produces(restful.MIME_JSON). @@ -163,7 +148,8 @@ func (conf *Config) InstallAPI(c *restful.Container) { // convertToMap simply converts config to map[string]bool // to hide sensitive information -func (conf *Config) toMap() map[string]bool { +func (conf *Config) ToMap() map[string]bool { + conf.stripEmptyOptions() result := make(map[string]bool, 0) if conf == nil { diff --git a/pkg/apiserver/config/config_test.go b/pkg/apiserver/config/config_test.go index 66d9521416692a95da9ee3c6b794a0e94342e118..df31e2ae8a219ccdc59da3ca0757e5bf9a2419f4 100644 --- a/pkg/apiserver/config/config_test.go +++ b/pkg/apiserver/config/config_test.go @@ -2,14 +2,15 @@ package config import ( "fmt" + "github.com/google/go-cmp/cmp" "gopkg.in/yaml.v2" "io/ioutil" - "kubesphere.io/kubesphere/pkg/api/auth" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" + authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" "kubesphere.io/kubesphere/pkg/simple/client/alerting" "kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins" "kubesphere.io/kubesphere/pkg/simple/client/k8s" - "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "kubesphere.io/kubesphere/pkg/simple/client/ldap" "kubesphere.io/kubesphere/pkg/simple/client/logging/elasticsearch" "kubesphere.io/kubesphere/pkg/simple/client/monitoring/prometheus" @@ -19,14 +20,14 @@ import ( "kubesphere.io/kubesphere/pkg/simple/client/s3" "kubesphere.io/kubesphere/pkg/simple/client/servicemesh" "kubesphere.io/kubesphere/pkg/simple/client/sonarqube" - "kubesphere.io/kubesphere/pkg/utils/reflectutils" "os" "testing" "time" ) -func newTestConfig() *Config { - conf := &Config{ +func newTestConfig() (*Config, error) { + + var conf = &Config{ MySQLOptions: &mysql.Options{ Host: "10.68.96.5:3306", Username: "root", @@ -96,25 +97,34 @@ func newTestConfig() *Config { IndexPrefix: "elk", Version: "6", }, - KubeSphereOptions: &kubesphere.Options{ - APIServer: "http://ks-apiserver.kubesphere-system.svc", - AccountServer: "http://ks-account.kubesphere-system.svc", - }, AlertingOptions: &alerting.Options{ Endpoint: "http://alerting.kubesphere-alerting-system.svc:9200", }, NotificationOptions: ¬ification.Options{ Endpoint: "http://notification.kubesphere-alerting-system.svc:9200", }, - AuthenticateOptions: &auth.AuthenticationOptions{ + AuthenticationOptions: &authoptions.AuthenticationOptions{ AuthenticateRateLimiterMaxTries: 5, AuthenticateRateLimiterDuration: 30 * time.Minute, MaxAuthenticateRetries: 6, - TokenExpiration: 30 * time.Minute, + JwtSecret: "xxxxxx", MultipleLogin: false, + OAuthOptions: &oauth.Options{ + IdentityProviders: []oauth.IdentityProviderOptions{}, + Clients: []oauth.Client{{ + Name: "kubesphere-console-client", + Secret: "xxxxxx-xxxxxx-xxxxxx", + RespondWithChallenges: true, + RedirectURIs: []string{"http://ks-console.kubesphere-system.svc/oauth/token/implicit"}, + GrantMethod: oauth.GrantHandlerAuto, + AccessTokenInactivityTimeout: nil, + }}, + AccessTokenMaxAge: time.Hour * 24, + AccessTokenInactivityTimeout: 0, + }, }, } - return conf + return conf, nil } func saveTestConfig(t *testing.T, conf *Config) { @@ -122,7 +132,6 @@ func saveTestConfig(t *testing.T, conf *Config) { if err != nil { t.Fatalf("error marshal config. %v", err) } - err = ioutil.WriteFile(fmt.Sprintf("%s.yaml", defaultConfigurationName), content, 0640) if err != nil { t.Fatalf("error write configuration file, %v", err) @@ -144,7 +153,10 @@ func cleanTestConfig(t *testing.T) { } func TestGet(t *testing.T) { - conf := newTestConfig() + conf, err := newTestConfig() + if err != nil { + t.Fatal(err) + } saveTestConfig(t, conf) defer cleanTestConfig(t) @@ -152,48 +164,7 @@ func TestGet(t *testing.T) { if err != nil { t.Fatal(err) } - - if diff := reflectutils.Equal(conf, conf2); diff != nil { + if diff := cmp.Diff(conf, conf2); diff != "" { t.Fatal(diff) } } - -func TestKubeSphereOptions(t *testing.T) { - conf := newTestConfig() - - t.Run("save nil kubesphere options", func(t *testing.T) { - savedConf := *conf - savedConf.KubeSphereOptions = nil - saveTestConfig(t, &savedConf) - defer cleanTestConfig(t) - - loadedConf, err := TryLoadFromDisk() - if err != nil { - t.Fatal(err) - } - - if diff := reflectutils.Equal(conf, loadedConf); diff != nil { - t.Fatal(diff) - } - }) - - t.Run("save partially kubesphere options", func(t *testing.T) { - savedConf := *conf - savedConf.KubeSphereOptions.APIServer = "http://example.com" - savedConf.KubeSphereOptions.AccountServer = "" - - saveTestConfig(t, &savedConf) - defer cleanTestConfig(t) - - loadedConf, err := TryLoadFromDisk() - if err != nil { - t.Fatal(err) - } - - savedConf.KubeSphereOptions.AccountServer = "http://ks-account.kubesphere-system.svc" - - if diff := reflectutils.Equal(&savedConf, loadedConf); diff != nil { - t.Fatal(diff) - } - }) -} diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 1b569b7d7d9d0c6584949f425f395d2b82a83fbd..1edd6dc240b38d751368a966643704a4f33b826a 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -25,6 +25,7 @@ import ( rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" devopsv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/devops/v1alpha1" + iamv1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/iam/v1alpha2" networkv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/network/v1alpha1" servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/servicemesh/v1alpha2" tenantv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tenant/v1alpha1" @@ -34,6 +35,7 @@ import ( type Interface interface { Discovery() discovery.DiscoveryInterface DevopsV1alpha1() devopsv1alpha1.DevopsV1alpha1Interface + IamV1alpha2() iamv1alpha2.IamV1alpha2Interface NetworkV1alpha1() networkv1alpha1.NetworkV1alpha1Interface ServicemeshV1alpha2() servicemeshv1alpha2.ServicemeshV1alpha2Interface TenantV1alpha1() tenantv1alpha1.TenantV1alpha1Interface @@ -45,6 +47,7 @@ type Interface interface { type Clientset struct { *discovery.DiscoveryClient devopsV1alpha1 *devopsv1alpha1.DevopsV1alpha1Client + iamV1alpha2 *iamv1alpha2.IamV1alpha2Client networkV1alpha1 *networkv1alpha1.NetworkV1alpha1Client servicemeshV1alpha2 *servicemeshv1alpha2.ServicemeshV1alpha2Client tenantV1alpha1 *tenantv1alpha1.TenantV1alpha1Client @@ -56,6 +59,11 @@ func (c *Clientset) DevopsV1alpha1() devopsv1alpha1.DevopsV1alpha1Interface { return c.devopsV1alpha1 } +// IamV1alpha2 retrieves the IamV1alpha2Client +func (c *Clientset) IamV1alpha2() iamv1alpha2.IamV1alpha2Interface { + return c.iamV1alpha2 +} + // NetworkV1alpha1 retrieves the NetworkV1alpha1Client func (c *Clientset) NetworkV1alpha1() networkv1alpha1.NetworkV1alpha1Interface { return c.networkV1alpha1 @@ -101,6 +109,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { if err != nil { return nil, err } + cs.iamV1alpha2, err = iamv1alpha2.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.networkV1alpha1, err = networkv1alpha1.NewForConfig(&configShallowCopy) if err != nil { return nil, err @@ -130,6 +142,7 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { func NewForConfigOrDie(c *rest.Config) *Clientset { var cs Clientset cs.devopsV1alpha1 = devopsv1alpha1.NewForConfigOrDie(c) + cs.iamV1alpha2 = iamv1alpha2.NewForConfigOrDie(c) cs.networkV1alpha1 = networkv1alpha1.NewForConfigOrDie(c) cs.servicemeshV1alpha2 = servicemeshv1alpha2.NewForConfigOrDie(c) cs.tenantV1alpha1 = tenantv1alpha1.NewForConfigOrDie(c) @@ -143,6 +156,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { func New(c rest.Interface) *Clientset { var cs Clientset cs.devopsV1alpha1 = devopsv1alpha1.New(c) + cs.iamV1alpha2 = iamv1alpha2.New(c) cs.networkV1alpha1 = networkv1alpha1.New(c) cs.servicemeshV1alpha2 = servicemeshv1alpha2.New(c) cs.tenantV1alpha1 = tenantv1alpha1.New(c) diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index a92d2679b6d91554d26aa70167a3319bab7fb33f..81af882aba9f108a899ed5ab22a2355227b25257 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -27,6 +27,8 @@ import ( clientset "kubesphere.io/kubesphere/pkg/client/clientset/versioned" devopsv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/devops/v1alpha1" fakedevopsv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/devops/v1alpha1/fake" + iamv1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/iam/v1alpha2" + fakeiamv1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake" networkv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/network/v1alpha1" fakenetworkv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/network/v1alpha1/fake" servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/servicemesh/v1alpha2" @@ -89,6 +91,11 @@ func (c *Clientset) DevopsV1alpha1() devopsv1alpha1.DevopsV1alpha1Interface { return &fakedevopsv1alpha1.FakeDevopsV1alpha1{Fake: &c.Fake} } +// IamV1alpha2 retrieves the IamV1alpha2Client +func (c *Clientset) IamV1alpha2() iamv1alpha2.IamV1alpha2Interface { + return &fakeiamv1alpha2.FakeIamV1alpha2{Fake: &c.Fake} +} + // NetworkV1alpha1 retrieves the NetworkV1alpha1Client func (c *Clientset) NetworkV1alpha1() networkv1alpha1.NetworkV1alpha1Interface { return &fakenetworkv1alpha1.FakeNetworkV1alpha1{Fake: &c.Fake} diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index 004d160de47f5cf618dae40e07a00ed116b48cbb..8adecc48719734bf3c74397fb655864be8ef03ee 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" devopsv1alpha1 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha1" + iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2" tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" @@ -36,6 +37,7 @@ var codecs = serializer.NewCodecFactory(scheme) var parameterCodec = runtime.NewParameterCodec(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ devopsv1alpha1.AddToScheme, + iamv1alpha2.AddToScheme, networkv1alpha1.AddToScheme, servicemeshv1alpha2.AddToScheme, tenantv1alpha1.AddToScheme, diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go index 6c6997a2a058451ece0b4df2c28200361d60e304..f9a270a9a1a719052da19dbe8982e4a34d2d5900 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" devopsv1alpha1 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha1" + iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2" tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" @@ -36,6 +37,7 @@ var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ devopsv1alpha1.AddToScheme, + iamv1alpha2.AddToScheme, networkv1alpha1.AddToScheme, servicemeshv1alpha2.AddToScheme, tenantv1alpha1.AddToScheme, diff --git a/pkg/client/clientset/versioned/typed/iam/v1alpha2/doc.go b/pkg/client/clientset/versioned/typed/iam/v1alpha2/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..02d20203a436686ddc75fd913d8dec1a1e58635a --- /dev/null +++ b/pkg/client/clientset/versioned/typed/iam/v1alpha2/doc.go @@ -0,0 +1,20 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha2 diff --git a/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake/doc.go b/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..329c98fb5851ae844d6836e4aca84cdfbf130e5d --- /dev/null +++ b/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake/doc.go @@ -0,0 +1,20 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake/fake_iam_client.go b/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake/fake_iam_client.go new file mode 100644 index 0000000000000000000000000000000000000000..93b004ea79fb86a69214acc840b16a28ff1acbfe --- /dev/null +++ b/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake/fake_iam_client.go @@ -0,0 +1,40 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/iam/v1alpha2" +) + +type FakeIamV1alpha2 struct { + *testing.Fake +} + +func (c *FakeIamV1alpha2) Users() v1alpha2.UserInterface { + return &FakeUsers{c} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeIamV1alpha2) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake/fake_user.go b/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake/fake_user.go new file mode 100644 index 0000000000000000000000000000000000000000..f8a5077140dee12881c7d10cfa623b29c32f7ca4 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/iam/v1alpha2/fake/fake_user.go @@ -0,0 +1,131 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" +) + +// FakeUsers implements UserInterface +type FakeUsers struct { + Fake *FakeIamV1alpha2 +} + +var usersResource = schema.GroupVersionResource{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "users"} + +var usersKind = schema.GroupVersionKind{Group: "iam.kubesphere.io", Version: "v1alpha2", Kind: "User"} + +// Get takes name of the user, and returns the corresponding user object, and an error if there is any. +func (c *FakeUsers) Get(name string, options v1.GetOptions) (result *v1alpha2.User, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(usersResource, name), &v1alpha2.User{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.User), err +} + +// List takes label and field selectors, and returns the list of Users that match those selectors. +func (c *FakeUsers) List(opts v1.ListOptions) (result *v1alpha2.UserList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(usersResource, usersKind, opts), &v1alpha2.UserList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.UserList{ListMeta: obj.(*v1alpha2.UserList).ListMeta} + for _, item := range obj.(*v1alpha2.UserList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested users. +func (c *FakeUsers) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(usersResource, opts)) +} + +// Create takes the representation of a user and creates it. Returns the server's representation of the user, and an error, if there is any. +func (c *FakeUsers) Create(user *v1alpha2.User) (result *v1alpha2.User, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(usersResource, user), &v1alpha2.User{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.User), err +} + +// Update takes the representation of a user and updates it. Returns the server's representation of the user, and an error, if there is any. +func (c *FakeUsers) Update(user *v1alpha2.User) (result *v1alpha2.User, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(usersResource, user), &v1alpha2.User{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.User), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeUsers) UpdateStatus(user *v1alpha2.User) (*v1alpha2.User, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(usersResource, "status", user), &v1alpha2.User{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.User), err +} + +// Delete takes name of the user and deletes it. Returns an error if one occurs. +func (c *FakeUsers) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteAction(usersResource, name), &v1alpha2.User{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeUsers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(usersResource, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha2.UserList{}) + return err +} + +// Patch applies the patch and returns the patched user. +func (c *FakeUsers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.User, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(usersResource, name, pt, data, subresources...), &v1alpha2.User{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.User), err +} diff --git a/pkg/client/clientset/versioned/typed/iam/v1alpha2/generated_expansion.go b/pkg/client/clientset/versioned/typed/iam/v1alpha2/generated_expansion.go new file mode 100644 index 0000000000000000000000000000000000000000..6a63cac5af5156573a1a11d05e3d7c2b13d361c5 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/iam/v1alpha2/generated_expansion.go @@ -0,0 +1,21 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +type UserExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/iam/v1alpha2/iam_client.go b/pkg/client/clientset/versioned/typed/iam/v1alpha2/iam_client.go new file mode 100644 index 0000000000000000000000000000000000000000..a18e2b5a3388276369c6fdbb1355029e0864f03c --- /dev/null +++ b/pkg/client/clientset/versioned/typed/iam/v1alpha2/iam_client.go @@ -0,0 +1,89 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + rest "k8s.io/client-go/rest" + v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" + "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme" +) + +type IamV1alpha2Interface interface { + RESTClient() rest.Interface + UsersGetter +} + +// IamV1alpha2Client is used to interact with features provided by the iam.kubesphere.io group. +type IamV1alpha2Client struct { + restClient rest.Interface +} + +func (c *IamV1alpha2Client) Users() UserInterface { + return newUsers(c) +} + +// NewForConfig creates a new IamV1alpha2Client for the given config. +func NewForConfig(c *rest.Config) (*IamV1alpha2Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &IamV1alpha2Client{client}, nil +} + +// NewForConfigOrDie creates a new IamV1alpha2Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *IamV1alpha2Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new IamV1alpha2Client for the given RESTClient. +func New(c rest.Interface) *IamV1alpha2Client { + return &IamV1alpha2Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha2.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *IamV1alpha2Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset/versioned/typed/iam/v1alpha2/user.go b/pkg/client/clientset/versioned/typed/iam/v1alpha2/user.go new file mode 100644 index 0000000000000000000000000000000000000000..ea25d4ac25c24e8df1720d2e1bdfb993fbad7e1c --- /dev/null +++ b/pkg/client/clientset/versioned/typed/iam/v1alpha2/user.go @@ -0,0 +1,180 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" + scheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme" +) + +// UsersGetter has a method to return a UserInterface. +// A group's client should implement this interface. +type UsersGetter interface { + Users() UserInterface +} + +// UserInterface has methods to work with User resources. +type UserInterface interface { + Create(*v1alpha2.User) (*v1alpha2.User, error) + Update(*v1alpha2.User) (*v1alpha2.User, error) + UpdateStatus(*v1alpha2.User) (*v1alpha2.User, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha2.User, error) + List(opts v1.ListOptions) (*v1alpha2.UserList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.User, err error) + UserExpansion +} + +// users implements UserInterface +type users struct { + client rest.Interface +} + +// newUsers returns a Users +func newUsers(c *IamV1alpha2Client) *users { + return &users{ + client: c.RESTClient(), + } +} + +// Get takes name of the user, and returns the corresponding user object, and an error if there is any. +func (c *users) Get(name string, options v1.GetOptions) (result *v1alpha2.User, err error) { + result = &v1alpha2.User{} + err = c.client.Get(). + Resource("users"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Users that match those selectors. +func (c *users) List(opts v1.ListOptions) (result *v1alpha2.UserList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.UserList{} + err = c.client.Get(). + Resource("users"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested users. +func (c *users) Watch(opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Resource("users"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch() +} + +// Create takes the representation of a user and creates it. Returns the server's representation of the user, and an error, if there is any. +func (c *users) Create(user *v1alpha2.User) (result *v1alpha2.User, err error) { + result = &v1alpha2.User{} + err = c.client.Post(). + Resource("users"). + Body(user). + Do(). + Into(result) + return +} + +// Update takes the representation of a user and updates it. Returns the server's representation of the user, and an error, if there is any. +func (c *users) Update(user *v1alpha2.User) (result *v1alpha2.User, err error) { + result = &v1alpha2.User{} + err = c.client.Put(). + Resource("users"). + Name(user.Name). + Body(user). + Do(). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *users) UpdateStatus(user *v1alpha2.User) (result *v1alpha2.User, err error) { + result = &v1alpha2.User{} + err = c.client.Put(). + Resource("users"). + Name(user.Name). + SubResource("status"). + Body(user). + Do(). + Into(result) + return +} + +// Delete takes name of the user and deletes it. Returns an error if one occurs. +func (c *users) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Resource("users"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *users) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + var timeout time.Duration + if listOptions.TimeoutSeconds != nil { + timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Resource("users"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Timeout(timeout). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched user. +func (c *users) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.User, err error) { + result = &v1alpha2.User{} + err = c.client.Patch(pt). + Resource("users"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index 069b0a336143962e7d19dcc400f4d744c8de61cb..df5bbb39fe3bc7e6b461f233eeba2e24f9d68cd7 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -29,6 +29,7 @@ import ( cache "k8s.io/client-go/tools/cache" versioned "kubesphere.io/kubesphere/pkg/client/clientset/versioned" devops "kubesphere.io/kubesphere/pkg/client/informers/externalversions/devops" + iam "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam" internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces" network "kubesphere.io/kubesphere/pkg/client/informers/externalversions/network" servicemesh "kubesphere.io/kubesphere/pkg/client/informers/externalversions/servicemesh" @@ -177,6 +178,7 @@ type SharedInformerFactory interface { WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool Devops() devops.Interface + Iam() iam.Interface Network() network.Interface Servicemesh() servicemesh.Interface Tenant() tenant.Interface @@ -187,6 +189,10 @@ func (f *sharedInformerFactory) Devops() devops.Interface { return devops.New(f, f.namespace, f.tweakListOptions) } +func (f *sharedInformerFactory) Iam() iam.Interface { + return iam.New(f, f.namespace, f.tweakListOptions) +} + func (f *sharedInformerFactory) Network() network.Interface { return network.New(f, f.namespace, f.tweakListOptions) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 1a81f88c07bc043c6e6c7414a34eaee2108e220f..027a8d3868a26fca0ae720e3d27f15e24ea24252 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -24,8 +24,9 @@ import ( schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" v1alpha1 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha1" + v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" - v1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2" + servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2" tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" towerv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1" ) @@ -66,6 +67,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v1alpha1.SchemeGroupVersion.WithResource("s2iruns"): return &genericInformer{resource: resource.GroupResource(), informer: f.Devops().V1alpha1().S2iRuns().Informer()}, nil + // Group=iam.kubesphere.io, Version=v1alpha2 + case v1alpha2.SchemeGroupVersion.WithResource("users"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Iam().V1alpha2().Users().Informer()}, nil + // Group=network.kubesphere.io, Version=v1alpha1 case networkv1alpha1.SchemeGroupVersion.WithResource("namespacenetworkpolicies"): return &genericInformer{resource: resource.GroupResource(), informer: f.Network().V1alpha1().NamespaceNetworkPolicies().Informer()}, nil @@ -73,9 +78,9 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Network().V1alpha1().WorkspaceNetworkPolicies().Informer()}, nil // Group=servicemesh.kubesphere.io, Version=v1alpha2 - case v1alpha2.SchemeGroupVersion.WithResource("servicepolicies"): + case servicemeshv1alpha2.SchemeGroupVersion.WithResource("servicepolicies"): return &genericInformer{resource: resource.GroupResource(), informer: f.Servicemesh().V1alpha2().ServicePolicies().Informer()}, nil - case v1alpha2.SchemeGroupVersion.WithResource("strategies"): + case servicemeshv1alpha2.SchemeGroupVersion.WithResource("strategies"): return &genericInformer{resource: resource.GroupResource(), informer: f.Servicemesh().V1alpha2().Strategies().Informer()}, nil // Group=tenant.kubesphere.io, Version=v1alpha1 diff --git a/pkg/client/informers/externalversions/iam/interface.go b/pkg/client/informers/externalversions/iam/interface.go new file mode 100644 index 0000000000000000000000000000000000000000..544e94cd488a700867b8c1c0bfbd1d6724b3306a --- /dev/null +++ b/pkg/client/informers/externalversions/iam/interface.go @@ -0,0 +1,46 @@ +/* +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package iam + +import ( + v1alpha2 "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2" + internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha2 provides access to shared informers for resources in V1alpha2. + V1alpha2() v1alpha2.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha2 returns a new v1alpha2.Interface. +func (g *group) V1alpha2() v1alpha2.Interface { + return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/iam/v1alpha2/interface.go b/pkg/client/informers/externalversions/iam/v1alpha2/interface.go new file mode 100644 index 0000000000000000000000000000000000000000..f75006b7759cf869dbdad0acafde8b512623e041 --- /dev/null +++ b/pkg/client/informers/externalversions/iam/v1alpha2/interface.go @@ -0,0 +1,45 @@ +/* +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // Users returns a UserInformer. + Users() UserInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// Users returns a UserInformer. +func (v *version) Users() UserInformer { + return &userInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/iam/v1alpha2/user.go b/pkg/client/informers/externalversions/iam/v1alpha2/user.go new file mode 100644 index 0000000000000000000000000000000000000000..c2cfb157f5126e1843010d71ba3bf385cef33452 --- /dev/null +++ b/pkg/client/informers/externalversions/iam/v1alpha2/user.go @@ -0,0 +1,88 @@ +/* +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" + versioned "kubesphere.io/kubesphere/pkg/client/clientset/versioned" + internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces" + v1alpha2 "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2" +) + +// UserInformer provides access to a shared informer and lister for +// Users. +type UserInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.UserLister +} + +type userInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewUserInformer constructs a new informer for User type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewUserInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredUserInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredUserInformer constructs a new informer for User type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredUserInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.IamV1alpha2().Users().List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.IamV1alpha2().Users().Watch(options) + }, + }, + &iamv1alpha2.User{}, + resyncPeriod, + indexers, + ) +} + +func (f *userInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredUserInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *userInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&iamv1alpha2.User{}, f.defaultInformer) +} + +func (f *userInformer) Lister() v1alpha2.UserLister { + return v1alpha2.NewUserLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/listers/iam/v1alpha2/expansion_generated.go b/pkg/client/listers/iam/v1alpha2/expansion_generated.go new file mode 100644 index 0000000000000000000000000000000000000000..b75d2871499ce4728d973cb518ab42f4da5877a7 --- /dev/null +++ b/pkg/client/listers/iam/v1alpha2/expansion_generated.go @@ -0,0 +1,23 @@ +/* +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +// UserListerExpansion allows custom methods to be added to +// UserLister. +type UserListerExpansion interface{} diff --git a/pkg/client/listers/iam/v1alpha2/user.go b/pkg/client/listers/iam/v1alpha2/user.go new file mode 100644 index 0000000000000000000000000000000000000000..99090df750a3c294b91ff57bc1f9cff50875a04f --- /dev/null +++ b/pkg/client/listers/iam/v1alpha2/user.go @@ -0,0 +1,65 @@ +/* +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" +) + +// UserLister helps list Users. +type UserLister interface { + // List lists all Users in the indexer. + List(selector labels.Selector) (ret []*v1alpha2.User, err error) + // Get retrieves the User from the index for a given name. + Get(name string) (*v1alpha2.User, error) + UserListerExpansion +} + +// userLister implements the UserLister interface. +type userLister struct { + indexer cache.Indexer +} + +// NewUserLister returns a new UserLister. +func NewUserLister(indexer cache.Indexer) UserLister { + return &userLister{indexer: indexer} +} + +// List lists all Users in the indexer. +func (s *userLister) List(selector labels.Selector) (ret []*v1alpha2.User, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.User)) + }) + return ret, err +} + +// Get retrieves the User from the index for a given name. +func (s *userLister) Get(name string) (*v1alpha2.User, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("user"), name) + } + return obj.(*v1alpha2.User), nil +} diff --git a/pkg/controller/namespace/namespace_controller.go b/pkg/controller/namespace/namespace_controller.go index 9a2f38228fcf7941c1070268cd34bd321ca08bc0..42af07656dd1a561b84aff489637f505549a836a 100644 --- a/pkg/controller/namespace/namespace_controller.go +++ b/pkg/controller/namespace/namespace_controller.go @@ -24,7 +24,6 @@ import ( "github.com/golang/protobuf/ptypes/wrappers" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -44,22 +43,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" ) -const ( - adminDescription = "Allows admin access to perform any action on any resource, it gives full control over every resource in the namespace." - operatorDescription = "The maintainer of the namespace who can manage resources other than users and roles in the namespace." - viewerDescription = "Allows viewer access to view all resources in the namespace." -) - -var ( - admin = rbac.Role{ObjectMeta: metav1.ObjectMeta{Name: "admin", Annotations: map[string]string{constants.DescriptionAnnotationKey: adminDescription, constants.CreatorAnnotationKey: constants.System}}, Rules: []rbac.PolicyRule{{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}} - operator = rbac.Role{ObjectMeta: metav1.ObjectMeta{Name: "operator", Annotations: map[string]string{constants.DescriptionAnnotationKey: operatorDescription, constants.CreatorAnnotationKey: constants.System}}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, - {Verbs: []string{"*"}, APIGroups: []string{"apps", "extensions", "batch", "logging.kubesphere.io", "monitoring.kubesphere.io", "iam.kubesphere.io", "autoscaling", "alerting.kubesphere.io", "openpitrix.io", "app.k8s.io", "servicemesh.kubesphere.io", "operations.kubesphere.io", "devops.kubesphere.io"}, Resources: []string{"*"}}, - {Verbs: []string{"*"}, APIGroups: []string{"", "resources.kubesphere.io"}, Resources: []string{"jobs", "cronjobs", "daemonsets", "deployments", "horizontalpodautoscalers", "ingresses", "endpoints", "configmaps", "events", "persistentvolumeclaims", "pods", "podtemplates", "pods", "secrets", "services"}}, - }} - viewer = rbac.Role{ObjectMeta: metav1.ObjectMeta{Name: "viewer", Annotations: map[string]string{constants.DescriptionAnnotationKey: viewerDescription, constants.CreatorAnnotationKey: constants.System}}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}} - defaultRoles = []rbac.Role{admin, operator, viewer} -) - /** * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller * business logic. Delete these comments after modifying this file.* @@ -164,19 +147,6 @@ func (r *ReconcileNamespace) Reconcile(request reconcile.Request) (reconcile.Res return reconcile.Result{}, nil } - controlledByWorkspace, err := r.isControlledByWorkspace(instance) - - if err != nil { - return reconcile.Result{}, err - } - - if !controlledByWorkspace { - - err = r.deleteRoleBindings(instance) - - return reconcile.Result{}, err - } - if err = r.checkAndBindWorkspace(instance); err != nil { return reconcile.Result{}, err } @@ -357,24 +327,3 @@ func (r *ReconcileNamespace) deleteRouter(namespace string) error { return nil } - -func (r *ReconcileNamespace) deleteRoleBindings(namespace *corev1.Namespace) error { - klog.V(4).Info("deleting role bindings namespace: ", namespace.Name) - adminBinding := &rbac.RoleBinding{} - adminBinding.Name = admin.Name - adminBinding.Namespace = namespace.Name - err := r.Delete(context.TODO(), adminBinding) - if err != nil && !errors.IsNotFound(err) { - klog.Errorf("deleting role binding namespace: %s, role binding: %s,error: %s", namespace.Name, adminBinding.Name, err) - return err - } - viewerBinding := &rbac.RoleBinding{} - viewerBinding.Name = viewer.Name - viewerBinding.Namespace = namespace.Name - err = r.Delete(context.TODO(), viewerBinding) - if err != nil && !errors.IsNotFound(err) { - klog.Errorf("deleting role binding namespace: %s,role binding: %s,error: %s", namespace.Name, viewerBinding.Name, err) - return err - } - return nil -} diff --git a/pkg/controller/user/user_controller.go b/pkg/controller/user/user_controller.go new file mode 100644 index 0000000000000000000000000000000000000000..7ab3557a6509ed317b8d1f92afacb6b0c81265b4 --- /dev/null +++ b/pkg/controller/user/user_controller.go @@ -0,0 +1,230 @@ +/* +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 user + +import ( + "fmt" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog" + iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" + kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned" + kubespherescheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme" + userinformer "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2" + userlister "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2" + "time" +) + +const ( + // SuccessSynced is used as part of the Event 'reason' when a Foo is synced + successSynced = "Synced" + // is synced successfully + messageResourceSynced = "User synced successfully" + controllerName = "user-controller" +) + +type Controller struct { + kubeClientset kubernetes.Interface + kubesphereClientset kubesphereclient.Interface + + userInformer userinformer.UserInformer + userLister userlister.UserLister + userSynced cache.InformerSynced + // workqueue is a rate limited work queue. This is used to queue work to be + // processed instead of performing it as soon as a change happens. This + // means we can ensure we only process a fixed amount of resources at a + // time, and makes it easy to ensure we are never processing the same item + // simultaneously in two different workers. + workqueue workqueue.RateLimitingInterface + // recorder is an event recorder for recording Event resources to the + // Kubernetes API. + recorder record.EventRecorder +} + +func NewController(kubeclientset kubernetes.Interface, + kubesphereklientset kubesphereclient.Interface, + userInformer userinformer.UserInformer) *Controller { + // Create event broadcaster + // Add sample-controller types to the default Kubernetes Scheme so Events can be + // logged for sample-controller types. + + utilruntime.Must(kubespherescheme.AddToScheme(scheme.Scheme)) + klog.V(4).Info("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(klog.Infof) + eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")}) + recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerName}) + ctl := &Controller{ + kubeClientset: kubeclientset, + kubesphereClientset: kubesphereklientset, + userInformer: userInformer, + userLister: userInformer.Lister(), + userSynced: userInformer.Informer().HasSynced, + workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Users"), + recorder: recorder, + } + klog.Info("Setting up event handlers") + userInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: ctl.enqueueUser, + UpdateFunc: func(old, new interface{}) { + ctl.enqueueUser(new) + }, + DeleteFunc: ctl.enqueueUser, + }) + return ctl +} + +func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { + defer utilruntime.HandleCrash() + defer c.workqueue.ShutDown() + + //init client + + // Start the informer factories to begin populating the informer caches + klog.Info("Starting User controller") + + // Wait for the caches to be synced before starting workers + klog.Info("Waiting for informer caches to sync") + if ok := cache.WaitForCacheSync(stopCh, c.userSynced); !ok { + return fmt.Errorf("failed to wait for caches to sync") + } + + klog.Info("Starting workers") + // Launch two workers to process Foo resources + for i := 0; i < threadiness; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + klog.Info("Started workers") + <-stopCh + klog.Info("Shutting down workers") + return nil +} + +func (c *Controller) enqueueUser(obj interface{}) { + var key string + var err error + if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { + utilruntime.HandleError(err) + return + } + c.workqueue.Add(key) +} + +func (c *Controller) runWorker() { + for c.processNextWorkItem() { + } +} + +func (c *Controller) processNextWorkItem() bool { + obj, shutdown := c.workqueue.Get() + + if shutdown { + return false + } + + // We wrap this block in a func so we can defer c.workqueue.Done. + err := func(obj interface{}) error { + // We call Done here so the workqueue knows we have finished + // processing this item. We also must remember to call Forget if we + // do not want this work item being re-queued. For example, we do + // not call Forget if a transient error occurs, instead the item is + // put back on the workqueue and attempted again after a back-off + // period. + defer c.workqueue.Done(obj) + var key string + var ok bool + // We expect strings to come off the workqueue. These are of the + // form namespace/name. We do this as the delayed nature of the + // workqueue means the items in the informer cache may actually be + // more up to date that when the item was initially put onto the + // workqueue. + if key, ok = obj.(string); !ok { + // As the item in the workqueue is actually invalid, we call + // Forget here else we'd go into a loop of attempting to + // process a work item that is invalid. + c.workqueue.Forget(obj) + utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj)) + return nil + } + // Run the reconcile, passing it the namespace/name string of the + // Foo resource to be synced. + if err := c.reconcile(key); err != nil { + // Put the item back on the workqueue to handle any transient errors. + c.workqueue.AddRateLimited(key) + return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error()) + } + // Finally, if no error occurs we Forget this item so it does not + // get queued again until another change happens. + c.workqueue.Forget(obj) + klog.Infof("Successfully synced %s:%s", "key", key) + return nil + }(obj) + + if err != nil { + utilruntime.HandleError(err) + return true + } + + return true +} + +// syncHandler compares the actual state with the desired, and attempts to +// converge the two. It then updates the Status block of the Foo resource +// with the current status of the resource. +func (c *Controller) reconcile(key string) error { + + // Get the user with this name + user, err := c.userLister.Get(key) + if err != nil { + // The user may no longer exist, in which case we stop + // processing. + if errors.IsNotFound(err) { + utilruntime.HandleError(fmt.Errorf("user '%s' in work queue no longer exists", key)) + return nil + } + return err + } + + err = c.updateUserStatus(user) + + if err != nil { + return err + } + + c.recorder.Event(user, corev1.EventTypeNormal, successSynced, messageResourceSynced) + return nil +} + +func (c *Controller) updateUserStatus(user *iamv1alpha2.User) error { + userCopy := user.DeepCopy() + userCopy.Status.State = iamv1alpha2.UserActive + _, err := c.kubesphereClientset.IamV1alpha2().Users().Update(userCopy) + return err +} + +func (c *Controller) Start(stopCh <-chan struct{}) error { + return c.Run(4, stopCh) +} diff --git a/pkg/controller/user/user_controller_test.go b/pkg/controller/user/user_controller_test.go new file mode 100644 index 0000000000000000000000000000000000000000..60b3afc065afc6700271faee95bdec0274b6929a --- /dev/null +++ b/pkg/controller/user/user_controller_test.go @@ -0,0 +1,245 @@ +/* +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 user + +import ( + "fmt" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/diff" + kubeinformers "k8s.io/client-go/informers" + k8sfake "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/testing" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" + "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake" + informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions" + "reflect" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + alwaysReady = func() bool { return true } + noResyncPeriodFunc = func() time.Duration { return 0 } +) + +type fixture struct { + t *testing.T + + client *fake.Clientset + kubeclient *k8sfake.Clientset + // Objects to put in the store. + userLister []*iamv1alpha2.User + // Actions expected to happen on the client. + kubeactions []core.Action + actions []core.Action + // Objects from here preloaded into NewSimpleFake. + kubeobjects []runtime.Object + objects []runtime.Object +} + +func newFixture(t *testing.T) *fixture { + f := &fixture{} + f.t = t + f.objects = []runtime.Object{} + f.kubeobjects = []runtime.Object{} + return f +} + +func newUser(name string) *iamv1alpha2.User { + return &iamv1alpha2.User{ + TypeMeta: metav1.TypeMeta{APIVersion: iamv1alpha2.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: iamv1alpha2.UserSpec{ + Email: fmt.Sprintf("%s@kubesphere.io", name), + Lang: "zh-CN", + Description: "fake user", + }, + } +} + +func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, kubeinformers.SharedInformerFactory) { + f.client = fake.NewSimpleClientset(f.objects...) + f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...) + + i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc()) + k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc()) + + for _, user := range f.userLister { + err := i.Iam().V1alpha2().Users().Informer().GetIndexer().Add(user) + if err != nil { + f.t.Errorf("add user:%s", err) + } + } + + c := NewController(f.kubeclient, f.client, i.Iam().V1alpha2().Users()) + c.userSynced = alwaysReady + c.recorder = &record.FakeRecorder{} + + return c, i, k8sI +} + +func (f *fixture) run(userName string) { + f.runController(userName, true, false) +} + +func (f *fixture) runExpectError(userName string) { + f.runController(userName, true, true) +} + +func (f *fixture) runController(user string, startInformers bool, expectError bool) { + c, i, k8sI := f.newController() + if startInformers { + stopCh := make(chan struct{}) + defer close(stopCh) + i.Start(stopCh) + k8sI.Start(stopCh) + } + + err := c.reconcile(user) + if !expectError && err != nil { + f.t.Errorf("error syncing user: %v", err) + } else if expectError && err == nil { + f.t.Error("expected error syncing user, got nil") + } + + actions := filterInformerActions(f.client.Actions()) + for i, action := range actions { + if len(f.actions) < i+1 { + f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:]) + break + } + + expectedAction := f.actions[i] + checkAction(expectedAction, action, f.t) + } + + if len(f.actions) > len(actions) { + f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):]) + } + + k8sActions := filterInformerActions(f.kubeclient.Actions()) + for i, action := range k8sActions { + if len(f.kubeactions) < i+1 { + f.t.Errorf("%d unexpected actions: %+v", len(k8sActions)-len(f.kubeactions), k8sActions[i:]) + break + } + + expectedAction := f.kubeactions[i] + checkAction(expectedAction, action, f.t) + } + + if len(f.kubeactions) > len(k8sActions) { + f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):]) + } +} + +// checkAction verifies that expected and actual actions are equal and both have +// same attached resources +func checkAction(expected, actual core.Action, t *testing.T) { + if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) { + t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual) + return + } + + if reflect.TypeOf(actual) != reflect.TypeOf(expected) { + t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual) + return + } + + switch a := actual.(type) { + case core.CreateActionImpl: + e, _ := expected.(core.CreateActionImpl) + expObject := e.GetObject() + object := a.GetObject() + + if !reflect.DeepEqual(expObject, object) { + t.Errorf("Action %s %s has wrong object\nDiff:\n %s", + a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object)) + } + case core.UpdateActionImpl: + e, _ := expected.(core.UpdateActionImpl) + expObject := e.GetObject() + object := a.GetObject() + + if !reflect.DeepEqual(expObject, object) { + t.Errorf("Action %s %s has wrong object\nDiff:\n %s", + a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object)) + } + case core.PatchActionImpl: + e, _ := expected.(core.PatchActionImpl) + expPatch := e.GetPatch() + patch := a.GetPatch() + + if !reflect.DeepEqual(expPatch, patch) { + t.Errorf("Action %s %s has wrong patch\nDiff:\n %s", + a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch)) + } + default: + t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it", + actual.GetVerb(), actual.GetResource().Resource) + } +} + +// filterInformerActions filters list and watch actions for testing resources. +// Since list and watch don't change resource state we can filter it to lower +// nose level in our tests. +func filterInformerActions(actions []core.Action) []core.Action { + var ret []core.Action + for _, action := range actions { + if action.Matches("list", "users") || + action.Matches("watch", "users") { + continue + } + ret = append(ret, action) + } + + return ret +} + +func (f *fixture) expectUpdateUserStatusAction(user *iamv1alpha2.User) { + expect := user.DeepCopy() + expect.Status.State = iamv1alpha2.UserActive + action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect) + f.actions = append(f.actions, action) +} + +func getKey(user *iamv1alpha2.User, t *testing.T) string { + key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(user) + if err != nil { + t.Errorf("Unexpected error getting key for user %v: %v", user.Name, err) + return "" + } + return key +} + +func TestDoNothing(t *testing.T) { + f := newFixture(t) + user := newUser("test") + + f.userLister = append(f.userLister, user) + f.objects = append(f.objects, user) + + f.expectUpdateUserStatusAction(user) + f.run(getKey(user, t)) +} diff --git a/pkg/controller/workspace/workspace_controller.go b/pkg/controller/workspace/workspace_controller.go index ebc2d613a1978761392a4e3030602ca2cf4e4b7d..5792d226eee7a6a6daf978ae5aa7deba2ff2f50f 100644 --- a/pkg/controller/workspace/workspace_controller.go +++ b/pkg/controller/workspace/workspace_controller.go @@ -20,30 +20,17 @@ package workspace import ( "context" - "fmt" - corev1 "k8s.io/api/core/v1" - rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" - log "k8s.io/klog" tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" - "kubesphere.io/kubesphere/pkg/constants" - "kubesphere.io/kubesphere/pkg/models" - "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "kubesphere.io/kubesphere/pkg/utils/sliceutil" - "reflect" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "sync" ) const ( @@ -54,14 +41,14 @@ const ( // Add creates a new Workspace Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller // and Start it when the Manager is Started. -func Add(mgr manager.Manager, kubesphereClient kubesphere.Interface) error { - return add(mgr, newReconciler(mgr, kubesphereClient)) +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) } // newReconciler returns a new reconcile.Reconciler -func newReconciler(mgr manager.Manager, kubesphereClient kubesphere.Interface) reconcile.Reconciler { +func newReconciler(mgr manager.Manager) reconcile.Reconciler { return &ReconcileWorkspace{Client: mgr.GetClient(), scheme: mgr.GetScheme(), - recorder: mgr.GetEventRecorderFor("workspace-controller"), ksclient: kubesphereClient} + recorder: mgr.GetEventRecorderFor("workspace-controller")} } // add adds a new Controller to mgr with r as the reconcile.Reconciler @@ -88,7 +75,6 @@ type ReconcileWorkspace struct { client.Client scheme *runtime.Scheme recorder record.EventRecorder - ksclient kubesphere.Interface } // Reconcile reads that state of the cluster for a Workspace object and makes changes based on the state read @@ -125,9 +111,6 @@ func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Res // The object is being deleted if sliceutil.HasString(instance.ObjectMeta.Finalizers, finalizer) { // our finalizer is present, so lets handle our external dependency - if err := r.deleteDevOpsProjects(instance); err != nil { - return reconcile.Result{}, err - } // remove our finalizer from the list and update it. instance.ObjectMeta.Finalizers = sliceutil.RemoveString(instance.ObjectMeta.Finalizers, func(item string) bool { @@ -142,497 +125,5 @@ func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Res return reconcile.Result{}, nil } - if err = r.createWorkspaceAdmin(instance); err != nil { - return reconcile.Result{}, err - } - - if err = r.createWorkspaceRegular(instance); err != nil { - return reconcile.Result{}, err - } - - if err = r.createWorkspaceViewer(instance); err != nil { - return reconcile.Result{}, err - } - - if err = r.createWorkspaceRoleBindings(instance); err != nil { - return reconcile.Result{}, err - } - - if err = r.bindNamespaces(instance); err != nil { - return reconcile.Result{}, err - } - return reconcile.Result{}, nil } - -func (r *ReconcileWorkspace) createWorkspaceAdmin(instance *tenantv1alpha1.Workspace) error { - found := &rbac.ClusterRole{} - - admin := getWorkspaceAdmin(instance.Name) - - if err := controllerutil.SetControllerReference(instance, admin, r.scheme); err != nil { - return err - } - - err := r.Get(context.TODO(), types.NamespacedName{Name: admin.Name}, found) - - if err != nil && errors.IsNotFound(err) { - log.Info("Creating workspace role", "workspace", instance.Name, "name", admin.Name) - err = r.Create(context.TODO(), admin) - if err != nil { - return err - } - found = admin - } else if err != nil { - // Error reading the object - requeue the request. - return err - } - - // Update the found object and write the result back if there are any changes - if !reflect.DeepEqual(admin.Rules, found.Rules) || !reflect.DeepEqual(admin.Labels, found.Labels) || !reflect.DeepEqual(admin.Annotations, found.Annotations) { - found.Rules = admin.Rules - found.Labels = admin.Labels - found.Annotations = admin.Annotations - log.Info("Updating workspace role", "workspace", instance.Name, "name", admin.Name) - err = r.Update(context.TODO(), found) - if err != nil { - return err - } - } - return nil -} - -func (r *ReconcileWorkspace) createWorkspaceRegular(instance *tenantv1alpha1.Workspace) error { - found := &rbac.ClusterRole{} - - regular := getWorkspaceRegular(instance.Name) - - if err := controllerutil.SetControllerReference(instance, regular, r.scheme); err != nil { - return err - } - - err := r.Get(context.TODO(), types.NamespacedName{Name: regular.Name}, found) - - if err != nil && errors.IsNotFound(err) { - - log.Info("Creating workspace role", "workspace", instance.Name, "name", regular.Name) - err = r.Create(context.TODO(), regular) - // Error reading the object - requeue the request. - if err != nil { - return err - } - found = regular - } else if err != nil { - // Error reading the object - requeue the request. - return err - } - - // Update the found object and write the result back if there are any changes - if !reflect.DeepEqual(regular.Rules, found.Rules) || !reflect.DeepEqual(regular.Labels, found.Labels) || !reflect.DeepEqual(regular.Annotations, found.Annotations) { - found.Rules = regular.Rules - found.Labels = regular.Labels - found.Annotations = regular.Annotations - log.Info("Updating workspace role", "workspace", instance.Name, "name", regular.Name) - err = r.Update(context.TODO(), found) - if err != nil { - return err - } - } - - return nil -} - -func (r *ReconcileWorkspace) createWorkspaceViewer(instance *tenantv1alpha1.Workspace) error { - found := &rbac.ClusterRole{} - - viewer := getWorkspaceViewer(instance.Name) - - if err := controllerutil.SetControllerReference(instance, viewer, r.scheme); err != nil { - return err - } - - err := r.Get(context.TODO(), types.NamespacedName{Name: viewer.Name}, found) - - if err != nil && errors.IsNotFound(err) { - log.Info("Creating workspace role", "workspace", instance.Name, "name", viewer.Name) - err = r.Create(context.TODO(), viewer) - // Error reading the object - requeue the request. - if err != nil { - return err - } - found = viewer - } else if err != nil { - // Error reading the object - requeue the request. - return err - } - - // Update the found object and write the result back if there are any changes - if !reflect.DeepEqual(viewer.Rules, found.Rules) || !reflect.DeepEqual(viewer.Labels, found.Labels) || !reflect.DeepEqual(viewer.Annotations, found.Annotations) { - found.Rules = viewer.Rules - found.Labels = viewer.Labels - found.Annotations = viewer.Annotations - log.Info("Updating workspace role", "workspace", instance.Name, "name", viewer.Name) - err = r.Update(context.TODO(), found) - if err != nil { - return err - } - } - - return nil -} - -func (r *ReconcileWorkspace) createGroup(instance *tenantv1alpha1.Workspace) error { - _, err := r.ksclient.DescribeGroup(instance.Name) - - group := &models.Group{ - Name: instance.Name, - } - - if err != nil && kubesphere.IsNotFound(err) { - log.Info("Creating group", "group name", instance.Name) - _, err = r.ksclient.CreateGroup(group) - if err != nil { - if kubesphere.IsExist(err) { - return nil - } - return err - } - } else if err != nil { - return err - } - - return nil -} - -func (r *ReconcileWorkspace) deleteGroup(instance *tenantv1alpha1.Workspace) error { - log.Info("Creating group", "group name", instance.Name) - if err := r.ksclient.DeleteGroup(instance.Name); err != nil { - if kubesphere.IsNotFound(err) { - return nil - } - return err - } - return nil -} - -func (r *ReconcileWorkspace) deleteDevOpsProjects(instance *tenantv1alpha1.Workspace) error { - var wg sync.WaitGroup - - log.Info("Delete DevOps Projects") - for { - projects, err := r.ksclient.ListWorkspaceDevOpsProjects(instance.Name) - if err != nil { - log.Error(err, "Failed to Get Workspace's DevOps Projects", "ws", instance.Name) - return err - } - if projects.TotalCount == 0 { - return nil - } - errChan := make(chan error, len(projects.Items)) - for _, project := range projects.Items { - wg.Add(1) - go func(workspace, devops string) { - err = r.ksclient.DeleteWorkspaceDevOpsProjects(workspace, devops) - errChan <- err - wg.Done() - }(instance.Name, project.ProjectId) - } - wg.Wait() - close(errChan) - for err := range errChan { - if err != nil { - log.Error(err, "delete devops project error") - return err - } - } - - } -} - -func (r *ReconcileWorkspace) createWorkspaceRoleBindings(instance *tenantv1alpha1.Workspace) error { - - adminRoleBinding := &rbac.ClusterRoleBinding{} - adminRoleBinding.Name = getWorkspaceAdminRoleBindingName(instance.Name) - adminRoleBinding.Labels = map[string]string{constants.WorkspaceLabelKey: instance.Name} - adminRoleBinding.RoleRef = rbac.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: getWorkspaceAdminRoleName(instance.Name)} - - workspaceManager := rbac.Subject{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: instance.Spec.Manager} - - if workspaceManager.Name != "" { - adminRoleBinding.Subjects = []rbac.Subject{workspaceManager} - } else { - adminRoleBinding.Subjects = []rbac.Subject{} - } - - if err := controllerutil.SetControllerReference(instance, adminRoleBinding, r.scheme); err != nil { - return err - } - - foundRoleBinding := &rbac.ClusterRoleBinding{} - - err := r.Get(context.TODO(), types.NamespacedName{Name: adminRoleBinding.Name}, foundRoleBinding) - - if err != nil && errors.IsNotFound(err) { - log.Info("Creating workspace role binding", "workspace", instance.Name, "name", adminRoleBinding.Name) - err = r.Create(context.TODO(), adminRoleBinding) - // Error reading the object - requeue the request. - if err != nil { - return err - } - foundRoleBinding = adminRoleBinding - } else if err != nil { - // Error reading the object - requeue the request. - return err - } - - // Update the found object and write the result back if there are any changes - if !reflect.DeepEqual(adminRoleBinding.RoleRef, foundRoleBinding.RoleRef) { - log.Info("Deleting conflict workspace role binding", "workspace", instance.Name, "name", adminRoleBinding.Name) - err = r.Delete(context.TODO(), foundRoleBinding) - if err != nil { - return err - } - return fmt.Errorf("conflict workspace role binding %s, waiting for recreate", foundRoleBinding.Name) - } - - if workspaceManager.Name != "" && !hasSubject(foundRoleBinding.Subjects, workspaceManager) { - foundRoleBinding.Subjects = append(foundRoleBinding.Subjects, workspaceManager) - log.Info("Updating workspace role binding", "workspace", instance.Name, "name", adminRoleBinding.Name) - err = r.Update(context.TODO(), foundRoleBinding) - if err != nil { - return err - } - } - - regularRoleBinding := &rbac.ClusterRoleBinding{} - regularRoleBinding.Name = getWorkspaceRegularRoleBindingName(instance.Name) - regularRoleBinding.Labels = map[string]string{constants.WorkspaceLabelKey: instance.Name} - regularRoleBinding.RoleRef = rbac.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: getWorkspaceRegularRoleName(instance.Name)} - regularRoleBinding.Subjects = []rbac.Subject{} - - if err = controllerutil.SetControllerReference(instance, regularRoleBinding, r.scheme); err != nil { - return err - } - - err = r.Get(context.TODO(), types.NamespacedName{Name: regularRoleBinding.Name}, foundRoleBinding) - - if err != nil && errors.IsNotFound(err) { - log.Info("Creating workspace role binding", "workspace", instance.Name, "name", regularRoleBinding.Name) - err = r.Create(context.TODO(), regularRoleBinding) - // Error reading the object - requeue the request. - if err != nil { - return err - } - foundRoleBinding = regularRoleBinding - } else if err != nil { - // Error reading the object - requeue the request. - return err - } - - // Update the found object and write the result back if there are any changes - if !reflect.DeepEqual(regularRoleBinding.RoleRef, foundRoleBinding.RoleRef) { - log.Info("Deleting conflict workspace role binding", "workspace", instance.Name, "name", regularRoleBinding.Name) - err = r.Delete(context.TODO(), foundRoleBinding) - if err != nil { - return err - } - return fmt.Errorf("conflict workspace role binding %s, waiting for recreate", foundRoleBinding.Name) - } - - viewerRoleBinding := &rbac.ClusterRoleBinding{} - viewerRoleBinding.Name = getWorkspaceViewerRoleBindingName(instance.Name) - viewerRoleBinding.Labels = map[string]string{constants.WorkspaceLabelKey: instance.Name} - viewerRoleBinding.RoleRef = rbac.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: getWorkspaceViewerRoleName(instance.Name)} - viewerRoleBinding.Subjects = []rbac.Subject{} - - if err = controllerutil.SetControllerReference(instance, viewerRoleBinding, r.scheme); err != nil { - return err - } - - err = r.Get(context.TODO(), types.NamespacedName{Name: viewerRoleBinding.Name}, foundRoleBinding) - - if err != nil && errors.IsNotFound(err) { - log.Info("Creating workspace role binding", "workspace", instance.Name, "name", viewerRoleBinding.Name) - err = r.Create(context.TODO(), viewerRoleBinding) - // Error reading the object - requeue the request. - if err != nil { - return err - } - foundRoleBinding = viewerRoleBinding - } else if err != nil { - // Error reading the object - requeue the request. - return err - } - - // Update the found object and write the result back if there are any changes - if !reflect.DeepEqual(viewerRoleBinding.RoleRef, foundRoleBinding.RoleRef) { - log.Info("Deleting conflict workspace role binding", "workspace", instance.Name, "name", viewerRoleBinding.Name) - err = r.Delete(context.TODO(), foundRoleBinding) - if err != nil { - return err - } - return fmt.Errorf("conflict workspace role binding %s, waiting for recreate", foundRoleBinding.Name) - } - - return nil -} - -func (r *ReconcileWorkspace) bindNamespaces(instance *tenantv1alpha1.Workspace) error { - - nsList := &corev1.NamespaceList{} - options := client.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{constants.WorkspaceLabelKey: instance.Name})} - err := r.List(context.TODO(), nsList, &options) - - if err != nil { - log.Error(err, fmt.Sprintf("list workspace %s namespace failed", instance.Name)) - return err - } - - for _, namespace := range nsList.Items { - if !metav1.IsControlledBy(&namespace, instance) { - if err := controllerutil.SetControllerReference(instance, &namespace, r.scheme); err != nil { - return err - } - log.Info("Bind workspace", "namespace", namespace.Name, "workspace", instance.Name) - err = r.Update(context.TODO(), &namespace) - if err != nil { - return err - } - } - } - - return nil -} - -func hasSubject(subjects []rbac.Subject, user rbac.Subject) bool { - for _, subject := range subjects { - if reflect.DeepEqual(subject, user) { - return true - } - } - return false -} - -func getWorkspaceAdminRoleName(workspaceName string) string { - return fmt.Sprintf("workspace:%s:admin", workspaceName) -} -func getWorkspaceRegularRoleName(workspaceName string) string { - return fmt.Sprintf("workspace:%s:regular", workspaceName) -} -func getWorkspaceViewerRoleName(workspaceName string) string { - return fmt.Sprintf("workspace:%s:viewer", workspaceName) -} - -func getWorkspaceAdminRoleBindingName(workspaceName string) string { - return fmt.Sprintf("workspace:%s:admin", workspaceName) -} - -func getWorkspaceRegularRoleBindingName(workspaceName string) string { - return fmt.Sprintf("workspace:%s:regular", workspaceName) -} - -func getWorkspaceViewerRoleBindingName(workspaceName string) string { - return fmt.Sprintf("workspace:%s:viewer", workspaceName) -} - -func getWorkspaceAdmin(workspaceName string) *rbac.ClusterRole { - admin := &rbac.ClusterRole{} - admin.Name = getWorkspaceAdminRoleName(workspaceName) - admin.Labels = map[string]string{constants.WorkspaceLabelKey: workspaceName} - admin.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceAdmin, constants.DescriptionAnnotationKey: workspaceAdminDescription, constants.CreatorAnnotationKey: constants.System} - admin.Rules = []rbac.PolicyRule{ - { - Verbs: []string{"*"}, - APIGroups: []string{"*"}, - ResourceNames: []string{workspaceName}, - Resources: []string{"workspaces", "workspaces/*"}, - }, - { - Verbs: []string{"watch"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{"iam.kubesphere.io"}, - Resources: []string{"users"}, - }, - { - Verbs: []string{"get", "list"}, - APIGroups: []string{"openpitrix.io"}, - Resources: []string{"categories"}, - }, - { - Verbs: []string{"*"}, - APIGroups: []string{"openpitrix.io"}, - Resources: []string{"applications", "apps", "apps/versions", "apps/events", "apps/action", "apps/audits", "repos", "repos/action", "attachments"}, - }, - } - - return admin -} - -func getWorkspaceRegular(workspaceName string) *rbac.ClusterRole { - regular := &rbac.ClusterRole{} - regular.Name = getWorkspaceRegularRoleName(workspaceName) - regular.Labels = map[string]string{constants.WorkspaceLabelKey: workspaceName} - regular.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceRegular, constants.DescriptionAnnotationKey: workspaceRegularDescription, constants.CreatorAnnotationKey: constants.System} - regular.Rules = []rbac.PolicyRule{ - { - Verbs: []string{"get"}, - APIGroups: []string{"*"}, - Resources: []string{"workspaces"}, - ResourceNames: []string{workspaceName}, - }, { - Verbs: []string{"create"}, - APIGroups: []string{"tenant.kubesphere.io"}, - Resources: []string{"workspaces/namespaces", "workspaces/devops"}, - ResourceNames: []string{workspaceName}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{"iam.kubesphere.io"}, - ResourceNames: []string{workspaceName}, - Resources: []string{"workspaces/members"}, - }, - { - Verbs: []string{"get", "list"}, - APIGroups: []string{"openpitrix.io"}, - Resources: []string{"apps/events", "apps/action", "apps/audits", "categories"}, - }, - - { - Verbs: []string{"*"}, - APIGroups: []string{"openpitrix.io"}, - Resources: []string{"applications", "apps", "apps/versions", "repos", "repos/action", "attachments"}, - }, - } - - return regular -} - -func getWorkspaceViewer(workspaceName string) *rbac.ClusterRole { - viewer := &rbac.ClusterRole{} - viewer.Name = getWorkspaceViewerRoleName(workspaceName) - viewer.Labels = map[string]string{constants.WorkspaceLabelKey: workspaceName} - viewer.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceViewer, constants.DescriptionAnnotationKey: workspaceViewerDescription, constants.CreatorAnnotationKey: constants.System} - viewer.Rules = []rbac.PolicyRule{ - { - Verbs: []string{"get", "list"}, - APIGroups: []string{"*"}, - ResourceNames: []string{workspaceName}, - Resources: []string{"workspaces", "workspaces/*"}, - }, - { - Verbs: []string{"watch"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"get", "list"}, - APIGroups: []string{"openpitrix.io"}, - Resources: []string{"applications", "apps", "apps/events", "apps/action", "apps/audits", "apps/versions", "repos", "categories", "attachments"}, - }, - } - return viewer -} diff --git a/pkg/informers/informers.go b/pkg/informers/informers.go index bbc6a83c60b92558a79e27fd45ef4bbe80541704..36a3bd6a7f61e92c08d7520a7c18cac4db156970 100644 --- a/pkg/informers/informers.go +++ b/pkg/informers/informers.go @@ -98,7 +98,7 @@ func (f *informerFactories) Start(stopCh <-chan struct{}) { f.ksInformerFactory.Start(stopCh) } - if f.informerFactory != nil { + if f.istioInformerFactory != nil { f.istioInformerFactory.Start(stopCh) } diff --git a/pkg/kapis/iam/v1alpha2/handler.go b/pkg/kapis/iam/v1alpha2/handler.go index 6f53f3070ce3598ce6cf1cc7269410db04070919..5c93193c3682ca28078cd0be6600afa26cde0895 100644 --- a/pkg/kapis/iam/v1alpha2/handler.go +++ b/pkg/kapis/iam/v1alpha2/handler.go @@ -2,7 +2,7 @@ package v1alpha2 import ( "github.com/emicklei/go-restful" - "kubesphere.io/kubesphere/pkg/api/auth" + authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/im" @@ -16,7 +16,7 @@ type iamHandler struct { imOperator im.IdentityManagementInterface } -func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) *iamHandler { +func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *authoptions.AuthenticationOptions) *iamHandler { return &iamHandler{ amOperator: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()), imOperator: im.NewLDAPOperator(ldapClient), diff --git a/pkg/kapis/iam/v1alpha2/register.go b/pkg/kapis/iam/v1alpha2/register.go index f41652a9ed865a4eb3dd7c3a887f2545fdc6a055..2a082d418bc31ccdc80127593916fe9a0334cbce 100644 --- a/pkg/kapis/iam/v1alpha2/register.go +++ b/pkg/kapis/iam/v1alpha2/register.go @@ -22,7 +22,7 @@ import ( "github.com/emicklei/go-restful-openapi" "k8s.io/apimachinery/pkg/runtime/schema" "kubesphere.io/kubesphere/pkg/api" - "kubesphere.io/kubesphere/pkg/api/auth" + authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" "kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/informers" @@ -38,7 +38,7 @@ const groupName = "iam.kubesphere.io" var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v1alpha2"} -func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) error { +func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *authoptions.AuthenticationOptions) error { ws := runtime.NewWebService(GroupVersion) handler := newIAMHandler(k8sClient, factory, ldapClient, cacheClient, options) diff --git a/pkg/kapis/oauth/handler.go b/pkg/kapis/oauth/handler.go index e78260978af57e7ff7da4bcb8a58d42fc8ba59c3..bab48cd0fa778df972c2265b0cb014a585eed3db 100644 --- a/pkg/kapis/oauth/handler.go +++ b/pkg/kapis/oauth/handler.go @@ -25,19 +25,21 @@ import ( "k8s.io/klog" "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api/auth" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider" "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" + authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" "kubesphere.io/kubesphere/pkg/apiserver/authentication/token" "kubesphere.io/kubesphere/pkg/apiserver/request" "net/http" ) type oauthHandler struct { - issuer token.Issuer - config oauth.Configuration + issuer token.Issuer + options *authoptions.AuthenticationOptions } -func newOAUTHHandler(issuer token.Issuer, config oauth.Configuration) *oauthHandler { - return &oauthHandler{issuer: issuer, config: config} +func newOAUTHHandler(issuer token.Issuer, options *authoptions.AuthenticationOptions) *oauthHandler { + return &oauthHandler{issuer: issuer, options: options} } // Implement webhook authentication interface @@ -59,7 +61,7 @@ func (h *oauthHandler) TokenReviewHandler(req *restful.Request, resp *restful.Re return } - user, _, err := h.issuer.Verify(tokenReview.Spec.Token) + user, err := h.issuer.Verify(tokenReview.Spec.Token) if err != nil { klog.Errorln(err) @@ -82,8 +84,9 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp user, ok := request.UserFrom(req.Request.Context()) clientId := req.QueryParameter("client_id") responseType := req.QueryParameter("response_type") + redirectURI := req.QueryParameter("redirect_uri") - conf, err := h.config.Load(clientId) + conf, err := h.options.OAuthOptions.OAuthClient(clientId) if err != nil { err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) @@ -103,7 +106,21 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp return } - accessToken, clm, err := h.issuer.IssueTo(user) + expiresIn := h.options.OAuthOptions.AccessTokenMaxAge + + if conf.AccessTokenMaxAge != nil { + expiresIn = *conf.AccessTokenMaxAge + } + + accessToken, err := h.issuer.IssueTo(user, expiresIn) + + if err != nil { + err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) + resp.WriteError(http.StatusUnauthorized, err) + return + } + + redirectURL, err := conf.ResolveRedirectURL(redirectURI) if err != nil { err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) @@ -111,11 +128,63 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp return } - redirectURL := fmt.Sprintf("%s?access_token=%s&token_type=Bearer", conf.RedirectURL, accessToken) - expiresIn := clm.ExpiresAt - clm.IssuedAt + redirectURL = fmt.Sprintf("%s#access_token=%s&token_type=Bearer", redirectURL, accessToken) + if expiresIn > 0 { - redirectURL = fmt.Sprintf("%s&expires_in=%v", redirectURL, expiresIn) + redirectURL = fmt.Sprintf("%s&expires_in=%v", redirectURL, expiresIn.Seconds()) } + resp.Header().Set("Content-Type", "text/plain") http.Redirect(resp, req.Request, redirectURL, http.StatusFound) } + +func (h *oauthHandler) OAuthCallBackHandler(req *restful.Request, resp *restful.Response) { + + code := req.QueryParameter("code") + name := req.PathParameter("callback") + + if code == "" { + err := apierrors.NewUnauthorized("Unauthorized: missing code") + resp.WriteError(http.StatusUnauthorized, err) + } + + idP, err := h.options.OAuthOptions.IdentityProviderOptions(name) + + if err != nil { + err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) + resp.WriteError(http.StatusUnauthorized, err) + } + + oauthIdentityProvider, err := identityprovider.ResolveOAuthProvider(idP.Type, idP.Provider) + + if err != nil { + err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) + resp.WriteError(http.StatusUnauthorized, err) + } + + user, err := oauthIdentityProvider.IdentityExchange(code) + + if err != nil { + err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) + resp.WriteError(http.StatusUnauthorized, err) + } + + expiresIn := h.options.OAuthOptions.AccessTokenMaxAge + + accessToken, err := h.issuer.IssueTo(user, expiresIn) + + if err != nil { + err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) + resp.WriteError(http.StatusUnauthorized, err) + return + } + + result := oauth.Token{ + AccessToken: accessToken, + TokenType: "Bearer", + ExpiresIn: int(expiresIn.Seconds()), + } + + resp.WriteEntity(result) + return +} diff --git a/pkg/kapis/oauth/register.go b/pkg/kapis/oauth/register.go index 846cb842000bc9d5ec2a82dcbd3669a7297e87e3..81d2bb42690324f732e9acb0645eed70a721f2a9 100644 --- a/pkg/kapis/oauth/register.go +++ b/pkg/kapis/oauth/register.go @@ -24,38 +24,70 @@ import ( "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api/auth" "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" + authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" "kubesphere.io/kubesphere/pkg/apiserver/authentication/token" "kubesphere.io/kubesphere/pkg/constants" "net/http" ) -func AddToContainer(c *restful.Container, issuer token.Issuer, configuration oauth.Configuration) error { +// ks-apiserver includes a built-in OAuth server. Users obtain OAuth access tokens to authenticate themselves to the API. +// The OAuth server supports standard authorization code grant and the implicit grant OAuth authorization flows. +// All requests for OAuth tokens involve a request to /oauth/authorize. +// Most authentication integrations place an authenticating proxy in front of this endpoint, or configure ks-apiserver +// to validate credentials against a backing identity provider. +// Requests to /oauth/authorize can come from user-agents that cannot display interactive login pages, such as the CLI. +func AddToContainer(c *restful.Container, issuer token.Issuer, options *authoptions.AuthenticationOptions) error { ws := &restful.WebService{} ws.Path("/oauth"). Consumes(restful.MIME_JSON). Produces(restful.MIME_JSON) - handler := newOAUTHHandler(issuer, configuration) + handler := newOAUTHHandler(issuer, options) // Implement webhook authentication interface // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication ws.Route(ws.POST("/authenticate"). - Doc("TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be cached by the webhook token authenticator plugin in the kube-apiserver."). + Doc("TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be "+ + "cached by the webhook token authenticator plugin in the kube-apiserver."). Reads(auth.TokenReview{}). To(handler.TokenReviewHandler). Returns(http.StatusOK, api.StatusOK, auth.TokenReview{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag})) - // TODO Built-in oauth2 server (provider) - // web console use 'Resource Owner Password Credentials Grant' or 'Client Credentials Grant' request for an OAuth token - // https://tools.ietf.org/html/rfc6749#section-4.3 - // https://tools.ietf.org/html/rfc6749#section-4.4 - - // curl -u admin:P@88w0rd 'http://ks-apiserver.kubesphere-system.svc/oauth/authorize?client_id=kubesphere-console-client&response_type=token' -v + // Only support implicit grant flow + // https://tools.ietf.org/html/rfc6749#section-4.2 ws.Route(ws.GET("/authorize"). + Doc("All requests for OAuth tokens involve a request to /oauth/authorize."). + Param(ws.QueryParameter("response_type", "The value MUST be one of \"code\" for requesting an "+ + "authorization code as described by [RFC6749] Section 4.1.1, \"token\" for requesting an access token (implicit grant)"+ + " as described by [RFC6749] Section 4.2.2.").Required(true)). + Param(ws.QueryParameter("client_id", "The client identifier issued to the client during the "+ + "registration process described by [RFC6749] Section 2.2.").Required(true)). + Param(ws.QueryParameter("redirect_uri", "After completing its interaction with the resource owner, "+ + "the authorization server directs the resource owner's user-agent back to the client.The redirection endpoint "+ + "URI MUST be an absolute URI as defined by [RFC3986] Section 4.3.").Required(false)). To(handler.AuthorizeHandler)) //ws.Route(ws.POST("/token")) - //ws.Route(ws.POST("/callback/{callback}")) + + // Authorization callback URL, where the end of the URL contains the identity provider name. + // The provider name is also used to build the callback URL. + ws.Route(ws.GET("/callback/{callback}"). + Doc("OAuth callback API, the path param callback is config by identity provider"). + Param(ws.QueryParameter("access_token", "The access token issued by the authorization server."). + Required(true)). + Param(ws.QueryParameter("token_type", "The type of the token issued as described in [RFC6479] Section 7.1. "+ + "Value is case insensitive.").Required(true)). + Param(ws.QueryParameter("expires_in", "The lifetime in seconds of the access token. For "+ + "example, the value \"3600\" denotes that the access token will "+ + "expire in one hour from the time the response was generated."+ + "If omitted, the authorization server SHOULD provide the "+ + "expiration time via other means or document the default value.")). + Param(ws.QueryParameter("scope", "if identical to the scope requested by the client;"+ + "otherwise, REQUIRED. The scope of the access token as described by [RFC6479] Section 3.3.").Required(false)). + Param(ws.QueryParameter("state", "if the \"state\" parameter was present in the client authorization request."+ + "The exact value received from the client.").Required(true)). + To(handler.OAuthCallBackHandler). + Returns(http.StatusOK, api.StatusOK, oauth.Token{})) c.Add(ws) diff --git a/pkg/kapis/serverconfig/v1alpha2/register.go b/pkg/kapis/serverconfig/v1alpha2/register.go new file mode 100644 index 0000000000000000000000000000000000000000..785d9c5a1a6ef65d306ce8f535124246bc91747f --- /dev/null +++ b/pkg/kapis/serverconfig/v1alpha2/register.go @@ -0,0 +1,51 @@ +/* + * + * Copyright 2020 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 v1alpha2 + +import ( + "github.com/emicklei/go-restful" + "k8s.io/apimachinery/pkg/runtime/schema" + apiserverconfig "kubesphere.io/kubesphere/pkg/apiserver/config" + "kubesphere.io/kubesphere/pkg/apiserver/runtime" +) + +const ( + GroupName = "config.kubesphere.io" +) + +var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} + +func AddToContainer(c *restful.Container, config *apiserverconfig.Config) error { + webservice := runtime.NewWebService(GroupVersion) + + webservice.Route(webservice.GET("/configs/oauth"). + Doc("Information about the authorization server are published."). + To(func(request *restful.Request, response *restful.Response) { + response.WriteEntity(config.AuthenticationOptions.OAuthOptions) + })) + + webservice.Route(webservice.GET("/configs/configz"). + Doc("Information about the server configuration"). + To(func(request *restful.Request, response *restful.Response) { + response.WriteAsJson(config.ToMap()) + })) + + c.Add(webservice) + return nil +} diff --git a/pkg/models/iam/am/fake_operator.go b/pkg/models/iam/am/fake_operator.go index 13a5c52533b97ac183cf716a6183a51eed903a2e..2d83d4133757551151dd8217cb33819261fb7845 100644 --- a/pkg/models/iam/am/fake_operator.go +++ b/pkg/models/iam/am/fake_operator.go @@ -133,7 +133,9 @@ func NewFakeAMOperator() *FakeOperator { }) operator.saveFakeRole(platformRoleCacheKey(user.Anonymous), FakeRole{ Name: "admin", - Rego: "package authz\ndefault allow = false", + Rego: `package authz +default allow = false +`, }) return operator } diff --git a/pkg/models/registries/image.go b/pkg/models/registries/image.go index 1d5f725be79810d539653101dd7f44642fb314bd..71caa444e01dc846ca31830ab252427174a6017b 100644 --- a/pkg/models/registries/image.go +++ b/pkg/models/registries/image.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/docker/distribution/reference" - digest "github.com/opencontainers/go-digest" + "github.com/opencontainers/go-digest" ) // Image holds information about an image. diff --git a/pkg/simple/client/cache/simple_cache.go b/pkg/simple/client/cache/simple_cache.go index be0a6d9d87ea5583a4463264dcb815d0f6aff42e..55dc5442843fe53c3627f5c4e122baa5e1593e86 100644 --- a/pkg/simple/client/cache/simple_cache.go +++ b/pkg/simple/client/cache/simple_cache.go @@ -34,7 +34,7 @@ func (s *simpleCache) Keys(pattern string) ([]string, error) { return nil, err } var keys []string - for k, _ := range s.store { + for k := range s.store { if re.MatchString(k) { keys = append(keys, k) } diff --git a/pkg/simple/client/devops/jenkins/build.go b/pkg/simple/client/devops/jenkins/build.go index 43f81a19c77ba58fadbdf72209df52c8b275689c..b2b4e9a6c79986479cb20652f6a2763c54371681 100644 --- a/pkg/simple/client/devops/jenkins/build.go +++ b/pkg/simple/client/devops/jenkins/build.go @@ -339,7 +339,7 @@ func (b *Build) GetRevisionBranch() string { } func (b *Build) IsGood() bool { - return (!b.IsRunning() && b.Raw.Result == STATUS_SUCCESS) + return !b.IsRunning() && b.Raw.Result == STATUS_SUCCESS } func (b *Build) IsRunning() bool { diff --git a/pkg/simple/client/k8s/kubernetes.go b/pkg/simple/client/k8s/kubernetes.go index 32bf5796c93ef4479411e6d37f323a0af1a2452c..c46e51b182e7455599c35bdac7e9e7ac55092937 100644 --- a/pkg/simple/client/k8s/kubernetes.go +++ b/pkg/simple/client/k8s/kubernetes.go @@ -98,6 +98,12 @@ func NewKubernetesClient(options *KubernetesOptions) (Client, error) { return nil, err } + k.istio, err = istioclient.NewForConfig(config) + + if err != nil { + return nil, err + } + k.master = options.Master k.config = config diff --git a/pkg/simple/client/kubesphere/kubesphere.go b/pkg/simple/client/kubesphere/kubesphere.go deleted file mode 100644 index c4a9c54f5ae1b17cec00a35e220678c0b8534f8b..0000000000000000000000000000000000000000 --- a/pkg/simple/client/kubesphere/kubesphere.go +++ /dev/null @@ -1,349 +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 kubesphere - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/api/devops/v1alpha2" - "kubesphere.io/kubesphere/pkg/constants" - "kubesphere.io/kubesphere/pkg/models" - "net/http" - "strings" -) - -type Interface interface { - CreateGroup(group *models.Group) (*models.Group, error) - UpdateGroup(group *models.Group) (*models.Group, error) - DescribeGroup(name string) (*models.Group, error) - DeleteGroup(name string) error - ListUsers() (*models.PageableResponse, error) - ListWorkspaceDevOpsProjects(workspace string) (*v1alpha2.PageableDevOpsProject, error) - DeleteWorkspaceDevOpsProjects(workspace, devops string) error -} - -type Client struct { - client *http.Client - - apiServer string - accountServer string -} - -func NewKubeSphereClient(options *Options) *Client { - return &Client{ - client: &http.Client{}, - apiServer: options.APIServer, - accountServer: options.AccountServer, - } -} - -type Error struct { - status int - message string -} - -func (e Error) Error() string { - return fmt.Sprintf("status: %d,message: %s", e.status, e.message) -} - -func (c *Client) CreateGroup(group *models.Group) (*models.Group, error) { - data, err := json.Marshal(group) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/groups", c.accountServer), bytes.NewReader(data)) - - if err != nil { - klog.Error(err) - return nil, err - } - req.Header.Add("Content-Type", "application/json") - - resp, err := c.client.Do(req) - - if err != nil { - klog.Error(err) - return nil, err - } - defer resp.Body.Close() - data, err = ioutil.ReadAll(resp.Body) - - if err != nil { - klog.Error(err) - return nil, err - } - - if resp.StatusCode > http.StatusOK { - return nil, Error{resp.StatusCode, string(data)} - } - - err = json.Unmarshal(data, group) - - if err != nil { - klog.Error(err) - return nil, err - } - - return group, nil -} - -func (c *Client) UpdateGroup(group *models.Group) (*models.Group, error) { - data, err := json.Marshal(group) - - if err != nil { - klog.Error(err) - return nil, err - } - - req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/groups/%s", c.accountServer, group.Name), bytes.NewReader(data)) - - if err != nil { - klog.Error(err) - return nil, err - } - - req.Header.Add("Content-Type", "application/json") - resp, err := c.client.Do(req) - - if err != nil { - return nil, err - } - defer resp.Body.Close() - data, err = ioutil.ReadAll(resp.Body) - - if err != nil { - klog.Error(err) - return nil, err - } - - if resp.StatusCode > http.StatusOK { - return nil, Error{resp.StatusCode, string(data)} - } - - err = json.Unmarshal(data, group) - - if err != nil { - klog.Error(err) - return nil, err - } - - return group, nil -} - -func (c *Client) DeleteGroup(name string) error { - req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/groups/%s", c.accountServer, name), nil) - - if err != nil { - klog.Error(err) - return err - } - - resp, err := c.client.Do(req) - - if err != nil { - klog.Error(err) - return err - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - klog.Error(err) - return err - } - - if resp.StatusCode > http.StatusOK { - return Error{resp.StatusCode, string(data)} - } - - return nil -} - -func (c *Client) DescribeGroup(name string) (*models.Group, error) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/groups/%s", c.accountServer, name), nil) - - if err != nil { - klog.Error(err) - return nil, err - } - resp, err := c.client.Do(req) - - if err != nil { - klog.Error(err) - return nil, err - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - klog.Error(err) - return nil, err - } - - if resp.StatusCode > http.StatusOK { - return nil, Error{resp.StatusCode, string(data)} - } - - var group models.Group - err = json.Unmarshal(data, &group) - - if err != nil { - klog.Error(err) - return nil, err - } - - return &group, nil -} - -func (c *Client) ListUsers() (*models.PageableResponse, error) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/users", c.accountServer), nil) - - if err != nil { - return nil, err - } - req.Header.Add("Authorization", c.accountServer) - resp, err := c.client.Do(req) - - if err != nil { - klog.Error(err) - return nil, err - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - klog.Error(err) - return nil, err - } - - if resp.StatusCode > http.StatusOK { - return nil, Error{resp.StatusCode, string(data)} - } - - var result models.PageableResponse - err = json.Unmarshal(data, &result) - - if err != nil { - klog.Error(err) - return nil, err - } - - return &result, nil -} - -func (c *Client) ListWorkspaceDevOpsProjects(workspace string) (*v1alpha2.PageableDevOpsProject, error) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/kapis/tenant.kubesphere.io/v1alpha2/workspaces/%s/devops", c.apiServer, workspace), nil) - - if err != nil { - klog.Error(err) - return nil, err - } - - req.Header.Add(constants.UserNameHeader, constants.AdminUserName) - - klog.Info(req.Method, req.URL) - resp, err := c.client.Do(req) - - if err != nil { - klog.Error(err) - return nil, err - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - klog.Error(err) - return nil, err - } - if resp.StatusCode > http.StatusOK { - klog.Error(req.Method, req.URL, resp.StatusCode, string(data)) - return nil, Error{resp.StatusCode, string(data)} - } - - var result v1alpha2.PageableDevOpsProject - err = json.Unmarshal(data, &result) - - if err != nil { - klog.Error(err) - return nil, err - } - return &result, nil - -} - -func (c *Client) DeleteWorkspaceDevOpsProjects(workspace, devops string) error { - req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/kapis/tenant.kubesphere.io/v1alpha2/workspaces/%s/devops/%s", c.apiServer, workspace, devops), nil) - - if err != nil { - klog.Error(err) - return err - } - req.Header.Add(constants.UserNameHeader, constants.AdminUserName) - - klog.Info(req.Method, req.URL) - resp, err := c.client.Do(req) - - if err != nil { - klog.Error(err) - return err - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - klog.Error(err) - return err - } - if resp.StatusCode > http.StatusOK { - klog.Error(req.Method, req.URL, resp.StatusCode, string(data)) - return Error{resp.StatusCode, string(data)} - } - - return nil -} - -func IsNotFound(err error) bool { - if e, ok := err.(Error); ok { - if e.status == http.StatusNotFound { - return true - } - if strings.Contains(e.message, "not exist") { - return true - } - if strings.Contains(e.message, "not found") { - return true - } - } - return false -} - -func IsExist(err error) bool { - if e, ok := err.(Error); ok { - if e.status == http.StatusConflict { - return true - } - if strings.Contains(e.message, "Already Exists") { - return true - } - } - return false -} diff --git a/pkg/simple/client/kubesphere/options.go b/pkg/simple/client/kubesphere/options.go deleted file mode 100644 index c4e134c7fa57ecd5ccbd4ed379571e520f899714..0000000000000000000000000000000000000000 --- a/pkg/simple/client/kubesphere/options.go +++ /dev/null @@ -1,40 +0,0 @@ -package kubesphere - -import "github.com/spf13/pflag" - -type Options struct { - APIServer string `json:"apiServer" yaml:"apiServer"` - AccountServer string `json:"accountServer" yaml:"accountServer"` -} - -// NewKubeSphereOptions create a default options -func NewKubeSphereOptions() *Options { - return &Options{ - APIServer: "http://ks-apiserver.kubesphere-system.svc", - AccountServer: "http://ks-account.kubesphere-system.svc", - } -} - -func (s *Options) ApplyTo(options *Options) { - if s.AccountServer != "" { - options.AccountServer = s.AccountServer - } - - if s.APIServer != "" { - options.APIServer = s.APIServer - } -} - -func (s *Options) Validate() []error { - errs := []error{} - - return errs -} - -func (s *Options) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&s.APIServer, "kubesphere-apiserver-host", s.APIServer, ""+ - "KubeSphere apiserver host address.") - - fs.StringVar(&s.AccountServer, "kubesphere-account-host", s.AccountServer, ""+ - "KubeSphere account server host address.") -} diff --git a/pkg/simple/client/mysql/options.go b/pkg/simple/client/mysql/options.go index dc0d545d62a899f578cd9e64a63f34c416840c46..e17b2730031a140b77f4b36f1ff16795d05e425e 100644 --- a/pkg/simple/client/mysql/options.go +++ b/pkg/simple/client/mysql/options.go @@ -2,7 +2,7 @@ package mysql import ( "github.com/spf13/pflag" - reflectutils "kubesphere.io/kubesphere/pkg/utils/reflectutils" + "kubesphere.io/kubesphere/pkg/utils/reflectutils" "time" )