diff --git a/config/ks-core/templates/_helpers.tpl b/config/ks-core/templates/_helpers.tpl index a8a425a3a..d548bce4b 100644 --- a/config/ks-core/templates/_helpers.tpl +++ b/config/ks-core/templates/_helpers.tpl @@ -103,6 +103,19 @@ Create the name of the secret of sa token. {{- end }} {{- end }} +{{- define "telemetry.enabled" -}} +{{- $config := lookup "v1" "Secret" (printf "%s" .Release.Namespace) "io.kubesphere.config.platformconfig.telemetry" }} +{{- if $config }} +{{- with $config }} +{{- with (fromYaml ((index .data "configuration.yaml") | b64dec)) }} +{{- .enabled }} +{{- end }} +{{- end }} +{{- else }} +{{- true }} +{{- end }} +{{- end }} + {{- define "role" -}} {{- if eq .Values.role "" }} {{- with lookup "v1" "ConfigMap" (printf "%s" .Release.Namespace) "kubesphere-config" }} diff --git a/config/ks-core/templates/platformconfig-telemetry.yaml b/config/ks-core/templates/platformconfig-telemetry.yaml index 05833aae1..f4b10c67c 100644 --- a/config/ks-core/templates/platformconfig-telemetry.yaml +++ b/config/ks-core/templates/platformconfig-telemetry.yaml @@ -1,12 +1,18 @@ -{{ if eq (include "role" .) "host" }} +{{- if eq (include "role" .) "host" }} apiVersion: v1 kind: Secret metadata: name: io.kubesphere.config.platformconfig.telemetry namespace: {{ .Release.Namespace | quote }} -type: config.kubesphere.io/platformconfig +type: config.kubesphere.io/generic-platform-config stringData: configuration.yaml: | - enabled: true - ksCloudURL: "https://kubesphere.cloud" + enabled: {{ include "telemetry.enabled" . }} + {{- if eq .Values.cloud.env "clouddev.kubesphere.io" }} + endpoint: "https://clouddev.kubesphere.io" + {{- else if eq .Values.cloud.env "kubesphere.cloud" }} + endpoint: "https://kubesphere.cloud" + {{- else if and .Values.cloud.customEnv .Values.cloud.customEnv.url }} + endpoint: {{ $.Values.cloud.customEnv.url | quote }} + {{- end }} {{- end }} \ No newline at end of file diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index a12390df3..aece012f9 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -26,10 +26,11 @@ const ( KubeSphereManagedLabel = "kubesphere.io/managed" DeletionPropagationAnnotation = "kubesphere.io/deletion-propagation" CascadingDeletionFinalizer = "kubesphere.io/cascading-deletion" -) -const ( - SecretTypePlatformConfig corev1.SecretType = "config.kubesphere.io/platformconfig" + KubeSphereConfigGroup = "config.kubesphere.io" + SecretTypeGenericPlatformConfig corev1.SecretType = KubeSphereConfigGroup + "/generic-platform-config" + GenericPlatformConfigNameFmt = "io.kubesphere.config.platformconfig.%s" + GenericPlatformConfigFileName = "configuration.yaml" ) var ( diff --git a/pkg/controller/telemetry/options.go b/pkg/controller/telemetry/options.go index 0a7d767fc..ccbc41b38 100644 --- a/pkg/controller/telemetry/options.go +++ b/pkg/controller/telemetry/options.go @@ -8,6 +8,8 @@ package telemetry import ( "fmt" + "kubesphere.io/kubesphere/pkg/constants" + "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" ) @@ -21,8 +23,8 @@ const ( type TelemetryOptions struct { // should enable the telemetry. Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" mapstructure:"enabled"` - // KSCloudURL for kubesphere cloud - KSCloudURL string `json:"ksCloudURL,omitempty" yaml:"ksCloudURL,omitempty" mapstructure:"ksCloudURL"` + // Endpoint for kubesphere cloud + Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint"` // collect period // The schedule in telemetry clusterInfo format, see https://en.wikipedia.org/wiki/Cron. Schedule string `json:"schedule,omitempty" yaml:"schedule,omitempty" mapstructure:"schedule"` @@ -36,12 +38,12 @@ func NewTelemetryOptions() *TelemetryOptions { // LoadPlatformConfig from given ConfigMap. func LoadTelemetryConfig(secret *corev1.Secret) (*TelemetryOptions, error) { - value, ok := secret.Data[ConfigDataKey] + value, ok := secret.Data[constants.GenericPlatformConfigFileName] if !ok { return nil, fmt.Errorf("failed to get config %s from secret %s value", ConfigDataKey, ConfigName) } o := &TelemetryOptions{} - if err := yaml.Unmarshal([]byte(value), o); err != nil { + if err := yaml.Unmarshal(value, o); err != nil { return nil, fmt.Errorf("failed to unmarshal value from configmap. err: %s", err) } return o, nil diff --git a/pkg/controller/telemetry/runnable.go b/pkg/controller/telemetry/runnable.go index a626fa9d5..999959436 100644 --- a/pkg/controller/telemetry/runnable.go +++ b/pkg/controller/telemetry/runnable.go @@ -54,7 +54,7 @@ func (r *runnable) startTask() error { // Add the task to the cron scheduler id, err := r.cron.AddFunc(r.TelemetryOptions.Schedule, func() { var args = []string{ - "--url", r.TelemetryOptions.KSCloudURL, + "--url", r.TelemetryOptions.Endpoint, } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) diff --git a/pkg/controller/telemetry/telemetry_controller.go b/pkg/controller/telemetry/telemetry_controller.go index 25fee3d93..a4b09fc79 100644 --- a/pkg/controller/telemetry/telemetry_controller.go +++ b/pkg/controller/telemetry/telemetry_controller.go @@ -60,12 +60,12 @@ func (r *Reconciler) SetupWithManager(mgr *kscontroller.Manager) error { } return secret.Namespace == constants.KubeSphereNamespace && secret.Name == ConfigName && - secret.Type == constants.SecretTypePlatformConfig + secret.Type == constants.SecretTypeGenericPlatformConfig }))). Complete(r) } -func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { +func (r *Reconciler) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: ConfigName, @@ -73,7 +73,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) ( }, } if err := r.Client.Get(ctx, runtimeclient.ObjectKeyFromObject(secret), secret); err != nil { - if errors.IsNotFound(err) { // not found. telemetry is disabled. + if errors.IsNotFound(err) { + // not found. telemetry is disabled. if r.telemetryRunnable != nil { r.telemetryRunnable.Close() } @@ -95,9 +96,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) ( } // check value when telemetry is enabled. - if conf.Enabled && - (conf.KSCloudURL == "" || conf.Schedule == "") { - klog.V(9).ErrorS(nil, "ksCloudURL and schedule should not be empty when telemetry enabled is true.") + if conf.Enabled && (conf.Endpoint == "" || conf.Schedule == "") { + klog.V(9).ErrorS(nil, "endpoint and schedule should not be empty when telemetry enabled is true.") return reconcile.Result{}, nil } diff --git a/pkg/kapis/config/v1alpha2/handler.go b/pkg/kapis/config/v1alpha2/handler.go index f22676c1c..2442b06b8 100644 --- a/pkg/kapis/config/v1alpha2/handler.go +++ b/pkg/kapis/config/v1alpha2/handler.go @@ -6,15 +6,22 @@ package v1alpha2 import ( + "encoding/json" "fmt" + "strings" "github.com/emicklei/go-restful/v3" + jsonpatch "github.com/evanphx/json-patch/v5" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider" @@ -22,12 +29,21 @@ import ( "kubesphere.io/kubesphere/pkg/apiserver/options" "kubesphere.io/kubesphere/pkg/apiserver/rest" "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/server/errors" ) const ( - themeConfigurationName = "platform-configuration-theme" + themeConfigurationName = "platform-configuration-theme" + GenericPlatformConfigurationKind = "GenericPlatformConfiguration" ) +type GenericPlatformConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Data runtime.RawExtension `json:"data,omitempty"` +} + func NewHandler(config *options.Options, client client.Client) rest.Handler { return &handler{ config: config, @@ -106,3 +122,200 @@ func (h *handler) getOAuthConfiguration(req *restful.Request, resp *restful.Resp } _ = resp.WriteEntity(configuration) } + +func (h *handler) getConfigz(_ *restful.Request, response *restful.Response) { + _ = response.WriteAsJson(h.config) +} + +func (h *handler) getPlatformConfiguration(request *restful.Request, response *restful.Response) { + configName := request.PathParameter("config") + if len(validation.IsDNS1123Label(configName)) > 0 { + api.HandleNotFound(response, request, fmt.Errorf("platform config %s not found", configName)) + return + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.KubeSphereNamespace, + Name: fmt.Sprintf(constants.GenericPlatformConfigNameFmt, configName), + }, + } + if err := h.client.Get(request.Request.Context(), client.ObjectKeyFromObject(secret), secret); err != nil { + if apierrors.IsNotFound(err) { + api.HandleNotFound(response, request, fmt.Errorf("platform config %s not found", configName)) + return + } + api.HandleError(response, request, err) + return + } + + config := &GenericPlatformConfiguration{} + config.ConvertFromSecret(secret) + + _ = response.WriteEntity(config) +} + +func (h *handler) createPlatformConfiguration(request *restful.Request, response *restful.Response) { + config := &GenericPlatformConfiguration{} + if err := request.ReadEntity(config); err != nil { + api.HandleBadRequest(response, request, err) + return + } + + if config.Kind != GenericPlatformConfigurationKind { + api.HandleBadRequest(response, request, fmt.Errorf("invalid kind: %s", config.Kind)) + return + } + + if config.APIVersion != APIVersion { + api.HandleBadRequest(response, request, fmt.Errorf("invalid apiVersion: %s", config.APIVersion)) + return + } + + if len(validation.IsDNS1123Label(config.Name)) > 0 { + api.HandleBadRequest(response, request, fmt.Errorf("invalid config name: %s", config.Name)) + return + } + secret := config.ConvertToSecret() + if err := h.client.Create(request.Request.Context(), secret); err != nil { + api.HandleError(response, request, err) + return + } + + config.ConvertFromSecret(secret) + + _ = response.WriteEntity(config) +} + +func (h *handler) patchPlatformConfiguration(request *restful.Request, response *restful.Response) { + var raw map[string]json.RawMessage + if err := request.ReadEntity(&raw); err != nil { + api.HandleBadRequest(response, request, err) + return + } + + configName := request.PathParameter("config") + if len(validation.IsDNS1123Label(configName)) > 0 { + api.HandleNotFound(response, request, fmt.Errorf("platform config %s not found", configName)) + return + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.KubeSphereNamespace, + Name: fmt.Sprintf(constants.GenericPlatformConfigNameFmt, configName), + }, + } + + if err := h.client.Get(request.Request.Context(), client.ObjectKeyFromObject(secret), secret); err != nil { + if apierrors.IsNotFound(err) { + api.HandleNotFound(response, request, fmt.Errorf("platform config %s not found", configName)) + return + } + api.HandleError(response, request, err) + return + } + + config := &GenericPlatformConfiguration{} + config.ConvertFromSecret(secret) + + original, err := config.Data.MarshalJSON() + if err != nil { + api.HandleError(response, request, err) + return + } + + patchData := raw["data"] + if len(patchData) > 0 { + var modifiedData []byte + modifiedData, err = jsonpatch.MergePatch(original, patchData) + if err != nil { + api.HandleBadRequest(response, request, err) + return + } + + if err = json.Unmarshal(modifiedData, &config.Data); err != nil { + api.HandleBadRequest(response, request, err) + return + } + + secret = config.ConvertToSecret() + if err := h.client.Update(request.Request.Context(), secret); err != nil { + api.HandleError(response, request, err) + return + } + } + + config.ConvertFromSecret(secret) + _ = response.WriteEntity(config) +} + +func (h *handler) updatePlatformConfiguration(request *restful.Request, response *restful.Response) { + config := &GenericPlatformConfiguration{} + if err := request.ReadEntity(config); err != nil { + api.HandleBadRequest(response, request, err) + return + } + + secret := config.ConvertToSecret() + + if err := h.client.Update(request.Request.Context(), secret); err != nil { + api.HandleError(response, request, err) + return + } + + config.ConvertFromSecret(secret) + + _ = response.WriteEntity(config) +} + +func (h *handler) deletePlatformConfiguration(request *restful.Request, response *restful.Response) { + configName := request.PathParameter("config") + if len(validation.IsDNS1123Label(configName)) > 0 { + api.HandleNotFound(response, request, fmt.Errorf("platform config %s not found", configName)) + return + } + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.KubeSphereNamespace, + Name: fmt.Sprintf(constants.GenericPlatformConfigNameFmt, configName), + }, + } + + if err := h.client.Delete(request.Request.Context(), &secret); err != nil { + api.HandleError(response, request, err) + return + } + + _ = response.WriteEntity(errors.None) +} + +func (c *GenericPlatformConfiguration) ConvertToSecret() *corev1.Secret { + yamlData, err := yaml.Marshal(c.Data) + if err != nil { + klog.Warningf("Failed to marshal platform configuration: %v", err) + return nil + } + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.KubeSphereNamespace, + Name: fmt.Sprintf(constants.GenericPlatformConfigNameFmt, c.Name), + }, + Type: constants.SecretTypeGenericPlatformConfig, + Data: map[string][]byte{ + constants.GenericPlatformConfigFileName: yamlData, + }, + } + return secret +} + +func (c *GenericPlatformConfiguration) ConvertFromSecret(secret *corev1.Secret) { + c.Name = strings.TrimPrefix(secret.Name, fmt.Sprintf(constants.GenericPlatformConfigNameFmt, "")) + c.CreationTimestamp = secret.CreationTimestamp + c.ResourceVersion = secret.ResourceVersion + c.UID = secret.UID + c.Kind = GenericPlatformConfigurationKind + c.APIVersion = APIVersion + _ = yaml.Unmarshal(secret.Data[constants.GenericPlatformConfigFileName], &c.Data) +} diff --git a/pkg/kapis/config/v1alpha2/register.go b/pkg/kapis/config/v1alpha2/register.go index 2c3460ac1..157616c14 100644 --- a/pkg/kapis/config/v1alpha2/register.go +++ b/pkg/kapis/config/v1alpha2/register.go @@ -9,50 +9,85 @@ import ( restfulspec "github.com/emicklei/go-restful-openapi/v2" "github.com/emicklei/go-restful/v3" "k8s.io/apimachinery/pkg/runtime/schema" + clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1" "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/apiserver/runtime" ) const ( - GroupName = "config.kubesphere.io" + GroupName = "config.kubesphere.io" + Version = "v1alpha2" + APIVersion = GroupName + "/" + Version ) -var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} +var GroupVersion = schema.GroupVersion{Group: GroupName, Version: Version} func (h *handler) AddToContainer(c *restful.Container) error { webservice := runtime.NewWebService(GroupVersion) - - webservice.Route(webservice.GET("/configs/oauth"). - Doc("OAuth configurations"). - Metadata(restfulspec.KeyOpenAPITags, []string{api.TagPlatformConfigurations}). - Notes("Information about the authorization server are published."). - Operation("oauth-config"). - To(h.getOAuthConfiguration)) - webservice.Route(webservice.GET("/configs/configz"). Deprecate(). - Doc("Component configurations"). - Metadata(restfulspec.KeyOpenAPITags, []string{api.TagPlatformConfigurations}). - Notes("Information about the components configuration"). - Operation("component-config"). - To(func(request *restful.Request, response *restful.Response) { - _ = response.WriteAsJson(h.config) - })) + Doc("Retrieve multicluster configuration"). + Notes("Provides information about the multicluster configuration."). + Operation("getMulticlusterConfiguration"). + To(h.getConfigz)) - webservice.Route(webservice.GET("/configs/theme"). - Doc("Retrieve theme configurations"). - Metadata(restfulspec.KeyOpenAPITags, []string{api.TagPlatformConfigurations}). - Notes("Retrieve theme configuration settings."). - Operation("get-theme-config"). - To(h.getThemeConfiguration)) + if h.config.MultiClusterOptions.ClusterRole == string(clusterv1alpha1.ClusterRoleHost) { + webservice.Route(webservice.GET("/configs/oauth"). + Doc("Retrieve OAuth configuration"). + Notes("Provides information about the authorization server."). + Operation("getOAuthConfiguration"). + To(h.getOAuthConfiguration)) - webservice.Route(webservice.POST("/configs/theme"). - Doc("Update theme configurations"). - Metadata(restfulspec.KeyOpenAPITags, []string{api.TagPlatformConfigurations}). - Notes("Update theme configuration settings."). - Operation("update-theme-config"). - To(h.updateThemeConfiguration)) + webservice.Route(webservice.GET("/configs/theme"). + Doc("Retrieve the current theme configuration"). + Notes("Provides the current theme configuration details."). + Operation("getThemeConfiguration"). + To(h.getThemeConfiguration)) + + webservice.Route(webservice.PUT("/configs/theme"). + Doc("Update the theme configuration settings"). + Notes("Allows the user to update the theme configuration settings."). + Operation("updateThemeConfiguration"). + To(h.updateThemeConfiguration)) + + webservice.Route(webservice.POST("/platformconfigs"). + Doc("Create a new platform configuration"). + Notes("Allows the user to create a new configuration for the specified platform."). + Operation("createPlatformConfiguration"). + To(h.createPlatformConfiguration)) + + webservice.Route(webservice.DELETE("/platformconfigs/{config}"). + Doc("Delete the specified platform configuration"). + Notes("Allows the user to delete the configuration settings of the specified platform."). + Operation("deletePlatformConfiguration"). + To(h.deletePlatformConfiguration)) + + webservice.Route(webservice.PUT("/platformconfigs/{config}"). + Doc("Update the specified platform configuration settings"). + Notes("Allows the user to modify the configuration settings of the specified platform"). + Operation("updatePlatformConfiguration"). + To(h.updatePlatformConfiguration)) + + webservice.Route(webservice.PATCH("/platformconfigs/{config}"). + Doc("Patch the specified platform configuration settings"). + Consumes(runtime.MimeMergePatchJson). + Notes("Allows the user to apply partial modifications to the configuration settings of the specified platform"). + Operation("patchPlatformConfiguration"). + To(h.patchPlatformConfiguration)) + + webservice.Route(webservice.GET("/platformconfigs/{config}"). + Doc("Retrieve the specified platform configuration"). + Notes("Provides details of the specified platform configuration."). + Operation("getPlatformConfiguration"). + To(h.getPlatformConfiguration)) + } + + for _, route := range webservice.Routes() { + route.Metadata = map[string]interface{}{ + restfulspec.KeyOpenAPITags: []string{api.TagPlatformConfigurations}, + } + } c.Add(webservice) return nil