From 632c66539e594dda8966b7587b5a776c09746ef8 Mon Sep 17 00:00:00 2001 From: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> Date: Tue, 1 Mar 2022 10:32:50 -0800 Subject: [PATCH] Move Add Tenant Configure Audit Log and Monitoring to their own tabs (#1636) Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> --- operatorapi/consts.go | 10 +- operatorapi/operator_tenant_add.go | 588 +++++++++++++ operatorapi/operator_tenants.go | 545 ------------ .../Console/Tenants/AddTenant/AddTenant.tsx | 40 +- .../AddTenant/Steps/ConfigLogSearch.tsx | 556 ++++++++++++ .../AddTenant/Steps/ConfigPrometheus.tsx | 424 +++++++++ .../Tenants/AddTenant/Steps/Configure.tsx | 819 +----------------- .../Tenants/AddTenant/Steps/Images.tsx | 66 +- .../TenantDetails/UpdateTenantModal.tsx | 2 +- .../src/screens/Console/Tenants/reducer.ts | 8 +- .../src/screens/Console/Tenants/types.ts | 4 +- 11 files changed, 1639 insertions(+), 1423 deletions(-) create mode 100644 operatorapi/operator_tenant_add.go create mode 100644 portal-ui/src/screens/Console/Tenants/AddTenant/Steps/ConfigLogSearch.tsx create mode 100644 portal-ui/src/screens/Console/Tenants/AddTenant/Steps/ConfigPrometheus.tsx diff --git a/operatorapi/consts.go b/operatorapi/consts.go index b6d9182cb..0cccaa83f 100644 --- a/operatorapi/consts.go +++ b/operatorapi/consts.go @@ -18,9 +18,7 @@ package operatorapi // list of all console environment constants const ( - ConsoleSubnetLicense = "CONSOLE_SUBNET_LICENSE" ConsoleOperatorSAToken = "CONSOLE_OPERATOR_SA_TOKEN" - MinIOSubnetLicense = "MINIO_SUBNET_LICENSE" ConsoleMarketplace = "CONSOLE_OPERATOR_MARKETPLACE" // Constants for prometheus annotations @@ -31,11 +29,5 @@ const ( // Image versions const ( - KESImageVersion = "minio/kes:v0.16.1" -) - -// K8s - -const ( - OperatorSubnetLicenseSecretName = "subnet-license" + KESImageVersion = "minio/kes:v0.17.6" ) diff --git a/operatorapi/operator_tenant_add.go b/operatorapi/operator_tenant_add.go new file mode 100644 index 000000000..7d4100928 --- /dev/null +++ b/operatorapi/operator_tenant_add.go @@ -0,0 +1,588 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package operatorapi + +import ( + "context" + "encoding/base64" + "fmt" + "os" + + "github.com/dustin/go-humanize" + + "github.com/minio/console/restapi" + + "github.com/minio/console/operatorapi/operations/operator_api" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + "github.com/go-openapi/swag" + "github.com/minio/console/cluster" + "github.com/minio/console/models" + miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func getTenantCreatedResponse(session *models.Principal, params operator_api.CreateTenantParams) (response *models.CreateTenantResponse, mError *models.Error) { + tenantReq := params.Body + minioImage := tenantReq.Image + ctx := context.Background() + if minioImage == "" { + minImg, err := cluster.GetMinioImage() + // we can live without figuring out the latest version of MinIO, Operator will use a hardcoded value + if err == nil { + minioImage = *minImg + } + } + // get Kubernetes Client + clientSet, err := cluster.K8sClient(session.STSSessionToken) + k8sClient := k8sClient{ + client: clientSet, + } + if err != nil { + return nil, prepareError(err) + } + + ns := *tenantReq.Namespace + // if access/secret are provided, use them, else create a random pair + + accessKey := restapi.RandomCharString(16) + secretKey := restapi.RandomCharString(32) + + if tenantReq.AccessKey != "" { + accessKey = tenantReq.AccessKey + } + if tenantReq.SecretKey != "" { + secretKey = tenantReq.SecretKey + } + + tenantName := *tenantReq.Name + + imm := true + var instanceSecret corev1.Secret + var users []*corev1.LocalObjectReference + + tenantConfigurationENV := map[string]string{} + + // Create the secret for the root credentials (deprecated) + secretName := fmt.Sprintf("%s-secret", tenantName) + instanceSecret = corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Labels: map[string]string{ + miniov2.TenantLabel: tenantName, + }, + }, + Immutable: &imm, + Data: map[string][]byte{ + "accesskey": []byte(""), + "secretkey": []byte(""), + }, + } + + _, err = clientSet.CoreV1().Secrets(ns).Create(ctx, &instanceSecret, metav1.CreateOptions{}) + if err != nil { + return nil, prepareError(err) + } + + // Enable/Disable console object browser for MinIO tenant (default is on) + enabledConsole := "on" + if tenantReq.EnableConsole != nil && !*tenantReq.EnableConsole { + enabledConsole = "off" + } + tenantConfigurationENV["MINIO_BROWSER"] = enabledConsole + tenantConfigurationENV["MINIO_ROOT_USER"] = accessKey + tenantConfigurationENV["MINIO_ROOT_PASSWORD"] = secretKey + + // delete secrets created if an error occurred during tenant creation, + defer func() { + if mError != nil { + restapi.LogError("deleting secrets created for failed tenant: %s if any: %v", tenantName, mError) + opts := metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", miniov2.TenantLabel, tenantName), + } + err = clientSet.CoreV1().Secrets(ns).DeleteCollection(ctx, metav1.DeleteOptions{}, opts) + if err != nil { + restapi.LogError("error deleting tenant's secrets: %v", err) + } + } + }() + + // Check the Erasure Coding Parity for validity and pass it to Tenant + if tenantReq.ErasureCodingParity > 0 { + if tenantReq.ErasureCodingParity < 2 || tenantReq.ErasureCodingParity > 8 { + return nil, prepareError(errorInvalidErasureCodingValue) + } + tenantConfigurationENV["MINIO_STORAGE_CLASS_STANDARD"] = fmt.Sprintf("EC:%d", tenantReq.ErasureCodingParity) + } + + //Construct a MinIO Instance with everything we are getting from parameters + minInst := miniov2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: tenantName, + Labels: tenantReq.Labels, + }, + Spec: miniov2.TenantSpec{ + Image: minioImage, + Mountpath: "/export", + CredsSecret: &corev1.LocalObjectReference{ + Name: secretName, + }, + }, + } + var tenantExternalIDPConfigured bool + if tenantReq.Idp != nil { + // Enable IDP (Active Directory) for MinIO + if tenantReq.Idp.ActiveDirectory != nil { + tenantExternalIDPConfigured = true + serverAddress := *tenantReq.Idp.ActiveDirectory.URL + userNameFormat := tenantReq.Idp.ActiveDirectory.UsernameFormat + userNameSearchFilter := tenantReq.Idp.ActiveDirectory.UsernameSearchFilter + groupNameAttribute := tenantReq.Idp.ActiveDirectory.GroupNameAttribute + tlsSkipVerify := tenantReq.Idp.ActiveDirectory.SkipTLSVerification + serverInsecure := tenantReq.Idp.ActiveDirectory.ServerInsecure + lookupBindDN := tenantReq.Idp.ActiveDirectory.LookupBindDn + lookupBindPassword := tenantReq.Idp.ActiveDirectory.LookupBindPassword + userDNSearchBaseDN := tenantReq.Idp.ActiveDirectory.UserDnSearchBaseDn + userDNSearchFilter := tenantReq.Idp.ActiveDirectory.UserDnSearchFilter + groupSearchBaseDN := tenantReq.Idp.ActiveDirectory.GroupSearchBaseDn + groupSearchFilter := tenantReq.Idp.ActiveDirectory.GroupSearchFilter + serverStartTLS := tenantReq.Idp.ActiveDirectory.ServerStartTLS + + // LDAP Server + tenantConfigurationENV["MINIO_IDENTITY_LDAP_SERVER_ADDR"] = serverAddress + if tlsSkipVerify { + tenantConfigurationENV["MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY"] = "on" + } + if serverInsecure { + tenantConfigurationENV["MINIO_IDENTITY_LDAP_SERVER_INSECURE"] = "on" + } + if serverStartTLS { + tenantConfigurationENV["MINIO_IDENTITY_LDAP_SERVER_STARTTLS"] = "on" + } + + // LDAP Username + tenantConfigurationENV["MINIO_IDENTITY_LDAP_USERNAME_FORMAT"] = userNameFormat + tenantConfigurationENV["MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER"] = userNameSearchFilter + + // LDAP Lookup + tenantConfigurationENV["MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN"] = lookupBindDN + tenantConfigurationENV["MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD"] = lookupBindPassword + + // LDAP User DN + tenantConfigurationENV["MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN"] = userDNSearchBaseDN + tenantConfigurationENV["MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER"] = userDNSearchFilter + + // LDAP Group + tenantConfigurationENV["MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE"] = groupNameAttribute + tenantConfigurationENV["MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN"] = groupSearchBaseDN + tenantConfigurationENV["MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER"] = groupSearchFilter + + // Attach the list of LDAP user DNs that will be administrator for the Tenant + for i, userDN := range tenantReq.Idp.ActiveDirectory.UserDNS { + userSecretName := fmt.Sprintf("%s-user-%d", tenantName, i) + users = append(users, &corev1.LocalObjectReference{Name: userSecretName}) + userSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: userSecretName, + Labels: map[string]string{ + miniov2.TenantLabel: tenantName, + }, + }, + Immutable: &imm, + Data: map[string][]byte{ + "CONSOLE_ACCESS_KEY": []byte(userDN), + }, + } + _, err := clientSet.CoreV1().Secrets(ns).Create(ctx, &userSecret, metav1.CreateOptions{}) + if err != nil { + return nil, prepareError(err) + } + } + // attach the users to the tenant + minInst.Spec.Users = users + + } else if tenantReq.Idp.Oidc != nil { + tenantExternalIDPConfigured = true + // Enable IDP (OIDC) for MinIO + configurationURL := *tenantReq.Idp.Oidc.ConfigurationURL + clientID := *tenantReq.Idp.Oidc.ClientID + secretID := *tenantReq.Idp.Oidc.SecretID + claimName := *tenantReq.Idp.Oidc.ClaimName + scopes := tenantReq.Idp.Oidc.Scopes + callbackURL := tenantReq.Idp.Oidc.CallbackURL + tenantConfigurationENV["MINIO_IDENTITY_OPENID_CONFIG_URL"] = configurationURL + tenantConfigurationENV["MINIO_IDENTITY_OPENID_CLIENT_ID"] = clientID + tenantConfigurationENV["MINIO_IDENTITY_OPENID_CLIENT_SECRET"] = secretID + tenantConfigurationENV["MINIO_IDENTITY_OPENID_CLAIM_NAME"] = claimName + tenantConfigurationENV["MINIO_IDENTITY_OPENID_REDIRECT_URI"] = callbackURL + if scopes == "" { + scopes = "openid,profile,email" + } + tenantConfigurationENV["MINIO_IDENTITY_OPENID_SCOPES"] = scopes + } else if len(tenantReq.Idp.Keys) > 0 { + // Create the secret any built-in user passed if no external IDP was configured + for i := 0; i < len(tenantReq.Idp.Keys); i++ { + userSecretName := fmt.Sprintf("%s-user-%d", tenantName, i) + users = append(users, &corev1.LocalObjectReference{Name: userSecretName}) + userSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: userSecretName, + Labels: map[string]string{ + miniov2.TenantLabel: tenantName, + }, + }, + Immutable: &imm, + Data: map[string][]byte{ + "CONSOLE_ACCESS_KEY": []byte(*tenantReq.Idp.Keys[i].AccessKey), + "CONSOLE_SECRET_KEY": []byte(*tenantReq.Idp.Keys[i].SecretKey), + }, + } + _, err := clientSet.CoreV1().Secrets(ns).Create(ctx, &userSecret, metav1.CreateOptions{}) + if err != nil { + return nil, prepareError(err) + } + } + // attach the users to the tenant + minInst.Spec.Users = users + } + } + + isEncryptionEnabled := false + + if tenantReq.EnableTLS != nil { + // if enableTLS is defined in the create tenant request we assign the value + // to the RequestAutoCert attribute in the tenant spec + minInst.Spec.RequestAutoCert = tenantReq.EnableTLS + if *tenantReq.EnableTLS { + // requestAutoCert is enabled, MinIO will be deployed with TLS enabled and encryption can be enabled + isEncryptionEnabled = true + } + } + // External TLS certificates for MinIO + if tenantReq.TLS != nil && len(tenantReq.TLS.Minio) > 0 { + isEncryptionEnabled = true + // Certificates used by the MinIO instance + externalCertSecretName := fmt.Sprintf("%s-instance-external-certificates", secretName) + externalCertSecret, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, tenantReq.TLS.Minio, externalCertSecretName, tenantName) + if err != nil { + return nil, prepareError(err) + } + minInst.Spec.ExternalCertSecret = externalCertSecret + } + // If encryption configuration is present and TLS will be enabled (using AutoCert or External certificates) + if tenantReq.Encryption != nil && isEncryptionEnabled { + // KES client mTLSCertificates used by MinIO instance + if tenantReq.Encryption.Client != nil { + tenantExternalClientCertSecretName := fmt.Sprintf("%s-tenant-external-client-cert", secretName) + certificates := []*models.KeyPairConfiguration{tenantReq.Encryption.Client} + certificateSecrets, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, certificates, tenantExternalClientCertSecretName, tenantName) + if err != nil { + return nil, prepareError(restapi.ErrorGeneric) + } + if len(certificateSecrets) > 0 { + minInst.Spec.ExternalClientCertSecret = certificateSecrets[0] + } + } + + // KES configuration for Tenant instance + minInst.Spec.KES, err = getKESConfiguration(ctx, &k8sClient, ns, tenantReq.Encryption, secretName, tenantName) + if err != nil { + return nil, prepareError(restapi.ErrorGeneric) + } + // Set Labels, Annotations and Node Selector for KES + minInst.Spec.KES.Labels = tenantReq.Encryption.Labels + minInst.Spec.KES.Annotations = tenantReq.Encryption.Annotations + minInst.Spec.KES.NodeSelector = tenantReq.Encryption.NodeSelector + + if tenantReq.Encryption.SecurityContext != nil { + sc, err := convertModelSCToK8sSC(tenantReq.Encryption.SecurityContext) + if err != nil { + return nil, prepareError(err) + } + minInst.Spec.KES.SecurityContext = sc + } + } + // External TLS CA certificates for MinIO + if tenantReq.TLS != nil && len(tenantReq.TLS.CaCertificates) > 0 { + var caCertificates []tenantSecret + for i, caCertificate := range tenantReq.TLS.CaCertificates { + certificateContent, err := base64.StdEncoding.DecodeString(caCertificate) + if err != nil { + return nil, prepareError(restapi.ErrorGeneric, nil, err) + } + caCertificates = append(caCertificates, tenantSecret{ + Name: fmt.Sprintf("ca-certificate-%d", i), + Content: map[string][]byte{ + "public.crt": certificateContent, + }, + }) + } + if len(caCertificates) > 0 { + certificateSecrets, err := createOrReplaceSecrets(ctx, &k8sClient, ns, caCertificates, tenantName) + if err != nil { + return nil, prepareError(restapi.ErrorGeneric, nil, err) + } + minInst.Spec.ExternalCaCertSecret = certificateSecrets + } + } + + // add annotations + var annotations map[string]string + + if len(tenantReq.Annotations) > 0 { + annotations = tenantReq.Annotations + minInst.Annotations = annotations + } + // set the pools if they are provided + for _, pool := range tenantReq.Pools { + pool, err := parseTenantPoolRequest(pool) + if err != nil { + restapi.LogError("parseTenantPoolRequest failed: %v", err) + return nil, prepareError(err) + } + minInst.Spec.Pools = append(minInst.Spec.Pools, *pool) + } + + // Set Mount Path if provided + if tenantReq.MounthPath != "" { + minInst.Spec.Mountpath = tenantReq.MounthPath + } + + // We accept either `image_pull_secret` or the individual details of the `image_registry` but not both + var imagePullSecret string + + if tenantReq.ImagePullSecret != "" { + imagePullSecret = tenantReq.ImagePullSecret + } else if imagePullSecret, err = setImageRegistry(ctx, tenantReq.ImageRegistry, clientSet.CoreV1(), ns, tenantName); err != nil { + return nil, prepareError(err) + } + // pass the image pull secret to the Tenant + if imagePullSecret != "" { + minInst.Spec.ImagePullSecret = corev1.LocalObjectReference{ + Name: imagePullSecret, + } + } + + // prometheus annotations support + if tenantReq.EnablePrometheus != nil && *tenantReq.EnablePrometheus && minInst.Annotations != nil { + minInst.Annotations[prometheusPath] = "/minio/prometheus/metrics" + minInst.Annotations[prometheusPort] = fmt.Sprint(miniov2.MinIOPort) + minInst.Annotations[prometheusScrape] = "true" + } + + // Is Log Search enabled? (present in the parameters) if so configure + if tenantReq.LogSearchConfiguration != nil { + + //Default class name for Log search + diskSpaceFromAPI := int64(5) * humanize.GiByte // Default is 5Gi + logSearchImage := "" + logSearchPgImage := "" + logSearchPgInitImage := "" + var logSearchStorageClass *string // Nil means use default storage class + var logSearchSecurityContext *corev1.PodSecurityContext + var logSearchPgSecurityContext *corev1.PodSecurityContext + + if tenantReq.LogSearchConfiguration.StorageSize != nil { + diskSpaceFromAPI = int64(*tenantReq.LogSearchConfiguration.StorageSize) * humanize.GiByte + } + if tenantReq.LogSearchConfiguration.StorageClass != "" { + logSearchStorageClass = stringPtr(tenantReq.LogSearchConfiguration.StorageClass) + } + if tenantReq.LogSearchConfiguration.Image != "" { + logSearchImage = tenantReq.LogSearchConfiguration.Image + } + if tenantReq.LogSearchConfiguration.PostgresImage != "" { + logSearchPgImage = tenantReq.LogSearchConfiguration.PostgresImage + } + if tenantReq.LogSearchConfiguration.PostgresInitImage != "" { + logSearchPgInitImage = tenantReq.LogSearchConfiguration.PostgresInitImage + } + // if security context for logSearch is present, configure it. + if tenantReq.LogSearchConfiguration.SecurityContext != nil { + sc, err := convertModelSCToK8sSC(tenantReq.LogSearchConfiguration.SecurityContext) + if err != nil { + return nil, prepareError(err) + } + logSearchSecurityContext = sc + } + // if security context for logSearch is present, configure it. + if tenantReq.LogSearchConfiguration.PostgresSecurityContext != nil { + sc, err := convertModelSCToK8sSC(tenantReq.LogSearchConfiguration.PostgresSecurityContext) + if err != nil { + return nil, prepareError(err) + } + logSearchPgSecurityContext = sc + } + + logSearchDiskSpace := resource.NewQuantity(diskSpaceFromAPI, resource.DecimalExponent) + + // the audit max cap cannot be larger than disk size on the DB, else it won't trim the data + auditMaxCap := 10 + if (diskSpaceFromAPI / humanize.GiByte) < int64(auditMaxCap) { + auditMaxCap = int(diskSpaceFromAPI / humanize.GiByte) + } + // default activate lgo search and prometheus + minInst.Spec.Log = &miniov2.LogConfig{ + Audit: &miniov2.AuditConfig{DiskCapacityGB: swag.Int(auditMaxCap)}, + Db: &miniov2.LogDbConfig{ + VolumeClaimTemplate: &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: tenantName + "-log", + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: *logSearchDiskSpace, + }, + }, + StorageClassName: logSearchStorageClass, + }, + }, + }, + } + // set log search images if any + if logSearchImage != "" { + minInst.Spec.Log.Image = logSearchImage + } + if logSearchPgImage != "" { + minInst.Spec.Log.Db.Image = logSearchPgImage + } + if logSearchPgInitImage != "" { + minInst.Spec.Log.Db.InitImage = logSearchPgInitImage + } + if logSearchSecurityContext != nil { + minInst.Spec.Log.SecurityContext = logSearchSecurityContext + } + if logSearchPgSecurityContext != nil { + minInst.Spec.Log.Db.SecurityContext = logSearchPgSecurityContext + } + + } + + // Is Prometheus/Monitoring enabled? (config present in the parameters) if so configure + if tenantReq.PrometheusConfiguration != nil { + prometheusDiskSpace := 5 // Default is 5 by API + prometheusImage := "" // Default is "" + prometheusSidecardImage := "" // Default is "" + prometheusInitImage := "" // Default is "" + + var prometheusStorageClass *string // Nil means default storage class + + if tenantReq.PrometheusConfiguration.StorageSize != nil { + prometheusDiskSpace = int(*tenantReq.PrometheusConfiguration.StorageSize) + } + if tenantReq.PrometheusConfiguration.StorageClass != "" { + prometheusStorageClass = stringPtr(tenantReq.PrometheusConfiguration.StorageClass) + } + if tenantReq.PrometheusConfiguration.Image != "" { + prometheusImage = tenantReq.PrometheusConfiguration.Image + } + if tenantReq.PrometheusConfiguration.SidecarImage != "" { + prometheusSidecardImage = tenantReq.PrometheusConfiguration.SidecarImage + } + if tenantReq.PrometheusConfiguration.InitImage != "" { + prometheusInitImage = tenantReq.PrometheusConfiguration.InitImage + } + + minInst.Spec.Prometheus = &miniov2.PrometheusConfig{ + DiskCapacityDB: swag.Int(prometheusDiskSpace), + StorageClassName: prometheusStorageClass, + } + if prometheusImage != "" { + minInst.Spec.Prometheus.Image = prometheusImage + } + if prometheusSidecardImage != "" { + minInst.Spec.Prometheus.SideCarImage = prometheusSidecardImage + } + if prometheusInitImage != "" { + minInst.Spec.Prometheus.InitImage = prometheusInitImage + } + // if security context for prometheus is present, configure it. + if tenantReq.PrometheusConfiguration != nil && tenantReq.PrometheusConfiguration.SecurityContext != nil { + sc, err := convertModelSCToK8sSC(tenantReq.PrometheusConfiguration.SecurityContext) + if err != nil { + return nil, prepareError(err) + } + minInst.Spec.Prometheus.SecurityContext = sc + } + + } + + // expose services + minInst.Spec.ExposeServices = &miniov2.ExposeServices{ + MinIO: tenantReq.ExposeMinio, + Console: tenantReq.ExposeConsole, + } + + // write tenant configuration to secret that contains config.env + tenantConfigurationName := fmt.Sprintf("%s-env-configuration", tenantName) + _, err = createOrReplaceSecrets(ctx, &k8sClient, ns, []tenantSecret{ + { + Name: tenantConfigurationName, + Content: map[string][]byte{ + "config.env": []byte(GenerateTenantConfigurationFile(tenantConfigurationENV)), + }, + }, + }, tenantName) + if err != nil { + return nil, prepareError(restapi.ErrorGeneric, nil, err) + } + minInst.Spec.Configuration = &corev1.LocalObjectReference{Name: tenantConfigurationName} + + opClient, err := cluster.OperatorClient(session.STSSessionToken) + if err != nil { + return nil, prepareError(err) + } + + _, err = opClient.MinioV2().Tenants(ns).Create(context.Background(), &minInst, metav1.CreateOptions{}) + if err != nil { + restapi.LogError("Creating new tenant failed with: %v", err) + return nil, prepareError(err) + } + + // Integrations + if os.Getenv("GKE_INTEGRATION") != "" { + err := gkeIntegration(clientSet, tenantName, ns, session.STSSessionToken) + if err != nil { + return nil, prepareError(err) + } + } + response = &models.CreateTenantResponse{ + ExternalIDP: tenantExternalIDPConfigured, + } + thisClient := &operatorClient{ + client: opClient, + } + + minTenant, err := getTenant(ctx, thisClient, ns, tenantName) + + if tenantReq.Idp != nil && !tenantExternalIDPConfigured { + for _, credential := range tenantReq.Idp.Keys { + response.Console = append(response.Console, &models.TenantResponseItem{ + AccessKey: *credential.AccessKey, + SecretKey: *credential.SecretKey, + URL: GetTenantServiceURL(minTenant), + }) + } + } + return response, nil +} diff --git a/operatorapi/operator_tenants.go b/operatorapi/operator_tenants.go index 21f991f61..38f970c22 100644 --- a/operatorapi/operator_tenants.go +++ b/operatorapi/operator_tenants.go @@ -27,7 +27,6 @@ import ( "fmt" "net" "net/http" - "os" "reflect" "sort" "strconv" @@ -980,550 +979,6 @@ func getListTenantsResponse(session *models.Principal, params operator_api.ListT return listT, nil } -func getTenantCreatedResponse(session *models.Principal, params operator_api.CreateTenantParams) (response *models.CreateTenantResponse, mError *models.Error) { - tenantReq := params.Body - minioImage := tenantReq.Image - ctx := context.Background() - if minioImage == "" { - minImg, err := cluster.GetMinioImage() - // we can live without figuring out the latest version of MinIO, Operator will use a hardcoded value - if err == nil { - minioImage = *minImg - } - } - // get Kubernetes Client - clientSet, err := cluster.K8sClient(session.STSSessionToken) - k8sClient := k8sClient{ - client: clientSet, - } - if err != nil { - return nil, prepareError(err) - } - - ns := *tenantReq.Namespace - // if access/secret are provided, use them, else create a random pair - - accessKey := restapi.RandomCharString(16) - secretKey := restapi.RandomCharString(32) - - if tenantReq.AccessKey != "" { - accessKey = tenantReq.AccessKey - } - if tenantReq.SecretKey != "" { - secretKey = tenantReq.SecretKey - } - - tenantName := *tenantReq.Name - - imm := true - var instanceSecret corev1.Secret - var users []*corev1.LocalObjectReference - - tenantConfigurationENV := map[string]string{} - - // Create the secret for the root credentials (deprecated) - secretName := fmt.Sprintf("%s-secret", tenantName) - instanceSecret = corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Labels: map[string]string{ - miniov2.TenantLabel: tenantName, - }, - }, - Immutable: &imm, - Data: map[string][]byte{ - "accesskey": []byte(""), - "secretkey": []byte(""), - }, - } - - _, err = clientSet.CoreV1().Secrets(ns).Create(ctx, &instanceSecret, metav1.CreateOptions{}) - if err != nil { - return nil, prepareError(err) - } - - // Enable/Disable console object browser for MinIO tenant (default is on) - enabledConsole := "on" - if tenantReq.EnableConsole != nil && !*tenantReq.EnableConsole { - enabledConsole = "off" - } - tenantConfigurationENV["MINIO_BROWSER"] = enabledConsole - tenantConfigurationENV["MINIO_ROOT_USER"] = accessKey - tenantConfigurationENV["MINIO_ROOT_PASSWORD"] = secretKey - - // delete secrets created if an error occurred during tenant creation, - defer func() { - if mError != nil { - restapi.LogError("deleting secrets created for failed tenant: %s if any: %v", tenantName, mError) - opts := metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", miniov2.TenantLabel, tenantName), - } - err = clientSet.CoreV1().Secrets(ns).DeleteCollection(ctx, metav1.DeleteOptions{}, opts) - if err != nil { - restapi.LogError("error deleting tenant's secrets: %v", err) - } - } - }() - - // Check the Erasure Coding Parity for validity and pass it to Tenant - if tenantReq.ErasureCodingParity > 0 { - if tenantReq.ErasureCodingParity < 2 || tenantReq.ErasureCodingParity > 8 { - return nil, prepareError(errorInvalidErasureCodingValue) - } - tenantConfigurationENV["MINIO_STORAGE_CLASS_STANDARD"] = fmt.Sprintf("EC:%d", tenantReq.ErasureCodingParity) - } - - //Construct a MinIO Instance with everything we are getting from parameters - minInst := miniov2.Tenant{ - ObjectMeta: metav1.ObjectMeta{ - Name: tenantName, - Labels: tenantReq.Labels, - }, - Spec: miniov2.TenantSpec{ - Image: minioImage, - Mountpath: "/export", - CredsSecret: &corev1.LocalObjectReference{ - Name: secretName, - }, - }, - } - var tenantExternalIDPConfigured bool - if tenantReq.Idp != nil { - // Enable IDP (Active Directory) for MinIO - if tenantReq.Idp.ActiveDirectory != nil { - tenantExternalIDPConfigured = true - serverAddress := *tenantReq.Idp.ActiveDirectory.URL - userNameFormat := tenantReq.Idp.ActiveDirectory.UsernameFormat - userNameSearchFilter := tenantReq.Idp.ActiveDirectory.UsernameSearchFilter - groupNameAttribute := tenantReq.Idp.ActiveDirectory.GroupNameAttribute - tlsSkipVerify := tenantReq.Idp.ActiveDirectory.SkipTLSVerification - serverInsecure := tenantReq.Idp.ActiveDirectory.ServerInsecure - lookupBindDN := tenantReq.Idp.ActiveDirectory.LookupBindDn - lookupBindPassword := tenantReq.Idp.ActiveDirectory.LookupBindPassword - userDNSearchBaseDN := tenantReq.Idp.ActiveDirectory.UserDnSearchBaseDn - userDNSearchFilter := tenantReq.Idp.ActiveDirectory.UserDnSearchFilter - groupSearchBaseDN := tenantReq.Idp.ActiveDirectory.GroupSearchBaseDn - groupSearchFilter := tenantReq.Idp.ActiveDirectory.GroupSearchFilter - serverStartTLS := tenantReq.Idp.ActiveDirectory.ServerStartTLS - - // LDAP Server - tenantConfigurationENV["MINIO_IDENTITY_LDAP_SERVER_ADDR"] = serverAddress - if tlsSkipVerify { - tenantConfigurationENV["MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY"] = "on" - } - if serverInsecure { - tenantConfigurationENV["MINIO_IDENTITY_LDAP_SERVER_INSECURE"] = "on" - } - if serverStartTLS { - tenantConfigurationENV["MINIO_IDENTITY_LDAP_SERVER_STARTTLS"] = "on" - } - - // LDAP Username - tenantConfigurationENV["MINIO_IDENTITY_LDAP_USERNAME_FORMAT"] = userNameFormat - tenantConfigurationENV["MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER"] = userNameSearchFilter - - // LDAP Lookup - tenantConfigurationENV["MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN"] = lookupBindDN - tenantConfigurationENV["MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD"] = lookupBindPassword - - // LDAP User DN - tenantConfigurationENV["MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN"] = userDNSearchBaseDN - tenantConfigurationENV["MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER"] = userDNSearchFilter - - // LDAP Group - tenantConfigurationENV["MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE"] = groupNameAttribute - tenantConfigurationENV["MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN"] = groupSearchBaseDN - tenantConfigurationENV["MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER"] = groupSearchFilter - - // Attach the list of LDAP user DNs that will be administrator for the Tenant - for i, userDN := range tenantReq.Idp.ActiveDirectory.UserDNS { - userSecretName := fmt.Sprintf("%s-user-%d", tenantName, i) - users = append(users, &corev1.LocalObjectReference{Name: userSecretName}) - userSecret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: userSecretName, - Labels: map[string]string{ - miniov2.TenantLabel: tenantName, - }, - }, - Immutable: &imm, - Data: map[string][]byte{ - "CONSOLE_ACCESS_KEY": []byte(userDN), - }, - } - _, err := clientSet.CoreV1().Secrets(ns).Create(ctx, &userSecret, metav1.CreateOptions{}) - if err != nil { - return nil, prepareError(err) - } - } - // attach the users to the tenant - minInst.Spec.Users = users - - } else if tenantReq.Idp.Oidc != nil { - tenantExternalIDPConfigured = true - // Enable IDP (OIDC) for MinIO - configurationURL := *tenantReq.Idp.Oidc.ConfigurationURL - clientID := *tenantReq.Idp.Oidc.ClientID - secretID := *tenantReq.Idp.Oidc.SecretID - claimName := *tenantReq.Idp.Oidc.ClaimName - scopes := tenantReq.Idp.Oidc.Scopes - callbackURL := tenantReq.Idp.Oidc.CallbackURL - tenantConfigurationENV["MINIO_IDENTITY_OPENID_CONFIG_URL"] = configurationURL - tenantConfigurationENV["MINIO_IDENTITY_OPENID_CLIENT_ID"] = clientID - tenantConfigurationENV["MINIO_IDENTITY_OPENID_CLIENT_SECRET"] = secretID - tenantConfigurationENV["MINIO_IDENTITY_OPENID_CLAIM_NAME"] = claimName - tenantConfigurationENV["MINIO_IDENTITY_OPENID_REDIRECT_URI"] = callbackURL - if scopes == "" { - scopes = "openid,profile,email" - } - tenantConfigurationENV["MINIO_IDENTITY_OPENID_SCOPES"] = scopes - } else if len(tenantReq.Idp.Keys) > 0 { - // Create the secret any built-in user passed if no external IDP was configured - for i := 0; i < len(tenantReq.Idp.Keys); i++ { - userSecretName := fmt.Sprintf("%s-user-%d", tenantName, i) - users = append(users, &corev1.LocalObjectReference{Name: userSecretName}) - userSecret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: userSecretName, - Labels: map[string]string{ - miniov2.TenantLabel: tenantName, - }, - }, - Immutable: &imm, - Data: map[string][]byte{ - "CONSOLE_ACCESS_KEY": []byte(*tenantReq.Idp.Keys[i].AccessKey), - "CONSOLE_SECRET_KEY": []byte(*tenantReq.Idp.Keys[i].SecretKey), - }, - } - _, err := clientSet.CoreV1().Secrets(ns).Create(ctx, &userSecret, metav1.CreateOptions{}) - if err != nil { - return nil, prepareError(err) - } - } - // attach the users to the tenant - minInst.Spec.Users = users - } - } - - isEncryptionEnabled := false - - if tenantReq.EnableTLS != nil { - // if enableTLS is defined in the create tenant request we assign the value - // to the RequestAutoCert attribute in the tenant spec - minInst.Spec.RequestAutoCert = tenantReq.EnableTLS - if *tenantReq.EnableTLS { - // requestAutoCert is enabled, MinIO will be deployed with TLS enabled and encryption can be enabled - isEncryptionEnabled = true - } - } - // External TLS certificates for MinIO - if tenantReq.TLS != nil && len(tenantReq.TLS.Minio) > 0 { - isEncryptionEnabled = true - // Certificates used by the MinIO instance - externalCertSecretName := fmt.Sprintf("%s-instance-external-certificates", secretName) - externalCertSecret, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, tenantReq.TLS.Minio, externalCertSecretName, tenantName) - if err != nil { - return nil, prepareError(err) - } - minInst.Spec.ExternalCertSecret = externalCertSecret - } - // If encryption configuration is present and TLS will be enabled (using AutoCert or External certificates) - if tenantReq.Encryption != nil && isEncryptionEnabled { - // KES client mTLSCertificates used by MinIO instance - if tenantReq.Encryption.Client != nil { - tenantExternalClientCertSecretName := fmt.Sprintf("%s-tenant-external-client-cert", secretName) - certificates := []*models.KeyPairConfiguration{tenantReq.Encryption.Client} - certificateSecrets, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, certificates, tenantExternalClientCertSecretName, tenantName) - if err != nil { - return nil, prepareError(restapi.ErrorGeneric) - } - if len(certificateSecrets) > 0 { - minInst.Spec.ExternalClientCertSecret = certificateSecrets[0] - } - } - - // KES configuration for Tenant instance - minInst.Spec.KES, err = getKESConfiguration(ctx, &k8sClient, ns, tenantReq.Encryption, secretName, tenantName) - if err != nil { - return nil, prepareError(restapi.ErrorGeneric) - } - // Set Labels, Annotations and Node Selector for KES - minInst.Spec.KES.Labels = tenantReq.Encryption.Labels - minInst.Spec.KES.Annotations = tenantReq.Encryption.Annotations - minInst.Spec.KES.NodeSelector = tenantReq.Encryption.NodeSelector - - if tenantReq.Encryption.SecurityContext != nil { - sc, err := convertModelSCToK8sSC(tenantReq.Encryption.SecurityContext) - if err != nil { - return nil, prepareError(err) - } - minInst.Spec.KES.SecurityContext = sc - } - } - // External TLS CA certificates for MinIO - if tenantReq.TLS != nil && len(tenantReq.TLS.CaCertificates) > 0 { - var caCertificates []tenantSecret - for i, caCertificate := range tenantReq.TLS.CaCertificates { - certificateContent, err := base64.StdEncoding.DecodeString(caCertificate) - if err != nil { - return nil, prepareError(restapi.ErrorGeneric, nil, err) - } - caCertificates = append(caCertificates, tenantSecret{ - Name: fmt.Sprintf("ca-certificate-%d", i), - Content: map[string][]byte{ - "public.crt": certificateContent, - }, - }) - } - if len(caCertificates) > 0 { - certificateSecrets, err := createOrReplaceSecrets(ctx, &k8sClient, ns, caCertificates, tenantName) - if err != nil { - return nil, prepareError(restapi.ErrorGeneric, nil, err) - } - minInst.Spec.ExternalCaCertSecret = certificateSecrets - } - } - - // add annotations - var annotations map[string]string - - if len(tenantReq.Annotations) > 0 { - annotations = tenantReq.Annotations - minInst.Annotations = annotations - } - // set the pools if they are provided - for _, pool := range tenantReq.Pools { - pool, err := parseTenantPoolRequest(pool) - if err != nil { - restapi.LogError("parseTenantPoolRequest failed: %v", err) - return nil, prepareError(err) - } - minInst.Spec.Pools = append(minInst.Spec.Pools, *pool) - } - - // Set Mount Path if provided - if tenantReq.MounthPath != "" { - minInst.Spec.Mountpath = tenantReq.MounthPath - } - - // We accept either `image_pull_secret` or the individual details of the `image_registry` but not both - var imagePullSecret string - - if tenantReq.ImagePullSecret != "" { - imagePullSecret = tenantReq.ImagePullSecret - } else if imagePullSecret, err = setImageRegistry(ctx, tenantReq.ImageRegistry, clientSet.CoreV1(), ns, tenantName); err != nil { - return nil, prepareError(err) - } - // pass the image pull secret to the Tenant - if imagePullSecret != "" { - minInst.Spec.ImagePullSecret = corev1.LocalObjectReference{ - Name: imagePullSecret, - } - } - - // prometheus annotations support - if tenantReq.EnablePrometheus != nil && *tenantReq.EnablePrometheus && minInst.Annotations != nil { - minInst.Annotations[prometheusPath] = "/minio/prometheus/metrics" - minInst.Annotations[prometheusPort] = fmt.Sprint(miniov2.MinIOPort) - minInst.Annotations[prometheusScrape] = "true" - } - - //Default class name for Log search - diskSpaceFromAPI := int64(5) * humanize.GiByte // Default is 5Gi - logSearchImage := "" - logSearchPgImage := "" - logSearchPgInitImage := "" - var logSearchStorageClass *string // Nil means use default storage class - var logSearchSecurityContext *corev1.PodSecurityContext - var logSearchPgSecurityContext *corev1.PodSecurityContext - - if tenantReq.LogSearchConfiguration != nil { - if tenantReq.LogSearchConfiguration.StorageSize != nil { - diskSpaceFromAPI = int64(*tenantReq.LogSearchConfiguration.StorageSize) * humanize.GiByte - } - if tenantReq.LogSearchConfiguration.StorageClass != "" { - logSearchStorageClass = stringPtr(tenantReq.LogSearchConfiguration.StorageClass) - } - if tenantReq.LogSearchConfiguration.Image != "" { - logSearchImage = tenantReq.LogSearchConfiguration.Image - } - if tenantReq.LogSearchConfiguration.PostgresImage != "" { - logSearchPgImage = tenantReq.LogSearchConfiguration.PostgresImage - } - if tenantReq.LogSearchConfiguration.PostgresInitImage != "" { - logSearchPgInitImage = tenantReq.LogSearchConfiguration.PostgresInitImage - } - // if security context for logSearch is present, configure it. - if tenantReq.LogSearchConfiguration.SecurityContext != nil { - sc, err := convertModelSCToK8sSC(tenantReq.LogSearchConfiguration.SecurityContext) - if err != nil { - return nil, prepareError(err) - } - logSearchSecurityContext = sc - } - // if security context for logSearch is present, configure it. - if tenantReq.LogSearchConfiguration.PostgresSecurityContext != nil { - sc, err := convertModelSCToK8sSC(tenantReq.LogSearchConfiguration.PostgresSecurityContext) - if err != nil { - return nil, prepareError(err) - } - logSearchPgSecurityContext = sc - } - } - - logSearchDiskSpace := resource.NewQuantity(diskSpaceFromAPI, resource.DecimalExponent) - - // the audit max cap cannot be larger than disk size on the DB, else it won't trim the data - auditMaxCap := 10 - if (diskSpaceFromAPI / humanize.GiByte) < int64(auditMaxCap) { - auditMaxCap = int(diskSpaceFromAPI / humanize.GiByte) - } - // default activate lgo search and prometheus - minInst.Spec.Log = &miniov2.LogConfig{ - Audit: &miniov2.AuditConfig{DiskCapacityGB: swag.Int(auditMaxCap)}, - Db: &miniov2.LogDbConfig{ - VolumeClaimTemplate: &corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: tenantName + "-log", - }, - Spec: corev1.PersistentVolumeClaimSpec{ - AccessModes: []corev1.PersistentVolumeAccessMode{ - corev1.ReadWriteOnce, - }, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceStorage: *logSearchDiskSpace, - }, - }, - StorageClassName: logSearchStorageClass, - }, - }, - }, - } - // set log search images if any - if logSearchImage != "" { - minInst.Spec.Log.Image = logSearchImage - } - if logSearchPgImage != "" { - minInst.Spec.Log.Db.Image = logSearchPgImage - } - if logSearchPgInitImage != "" { - minInst.Spec.Log.Db.InitImage = logSearchPgInitImage - } - if logSearchSecurityContext != nil { - minInst.Spec.Log.SecurityContext = logSearchSecurityContext - } - if logSearchPgSecurityContext != nil { - minInst.Spec.Log.Db.SecurityContext = logSearchPgSecurityContext - } - - prometheusDiskSpace := 5 // Default is 5 by API - prometheusImage := "" // Default is "" - prometheusSidecardImage := "" // Default is "" - prometheusInitImage := "" // Default is "" - - var prometheusStorageClass *string // Nil means default storage class - - if tenantReq.PrometheusConfiguration != nil { - if tenantReq.PrometheusConfiguration.StorageSize != nil { - prometheusDiskSpace = int(*tenantReq.PrometheusConfiguration.StorageSize) - } - if tenantReq.PrometheusConfiguration.StorageClass != "" { - prometheusStorageClass = stringPtr(tenantReq.PrometheusConfiguration.StorageClass) - } - if tenantReq.PrometheusConfiguration.Image != "" { - prometheusImage = tenantReq.PrometheusConfiguration.Image - } - if tenantReq.PrometheusConfiguration.SidecarImage != "" { - prometheusSidecardImage = tenantReq.PrometheusConfiguration.SidecarImage - } - if tenantReq.PrometheusConfiguration.InitImage != "" { - prometheusInitImage = tenantReq.PrometheusConfiguration.InitImage - } - } - - minInst.Spec.Prometheus = &miniov2.PrometheusConfig{ - DiskCapacityDB: swag.Int(prometheusDiskSpace), - StorageClassName: prometheusStorageClass, - } - if prometheusImage != "" { - minInst.Spec.Prometheus.Image = prometheusImage - } - if prometheusSidecardImage != "" { - minInst.Spec.Prometheus.SideCarImage = prometheusSidecardImage - } - if prometheusInitImage != "" { - minInst.Spec.Prometheus.InitImage = prometheusInitImage - } - // if security context for prometheus is present, configure it. - if tenantReq.PrometheusConfiguration != nil && tenantReq.PrometheusConfiguration.SecurityContext != nil { - sc, err := convertModelSCToK8sSC(tenantReq.PrometheusConfiguration.SecurityContext) - if err != nil { - return nil, prepareError(err) - } - minInst.Spec.Prometheus.SecurityContext = sc - } - - // expose services - minInst.Spec.ExposeServices = &miniov2.ExposeServices{ - MinIO: tenantReq.ExposeMinio, - Console: tenantReq.ExposeConsole, - } - - // write tenant configuration to secret that contains config.env - tenantConfigurationName := fmt.Sprintf("%s-env-configuration", tenantName) - _, err = createOrReplaceSecrets(ctx, &k8sClient, ns, []tenantSecret{ - { - Name: tenantConfigurationName, - Content: map[string][]byte{ - "config.env": []byte(GenerateTenantConfigurationFile(tenantConfigurationENV)), - }, - }, - }, tenantName) - if err != nil { - return nil, prepareError(restapi.ErrorGeneric, nil, err) - } - minInst.Spec.Configuration = &corev1.LocalObjectReference{Name: tenantConfigurationName} - - opClient, err := cluster.OperatorClient(session.STSSessionToken) - if err != nil { - return nil, prepareError(err) - } - - _, err = opClient.MinioV2().Tenants(ns).Create(context.Background(), &minInst, metav1.CreateOptions{}) - if err != nil { - restapi.LogError("Creating new tenant failed with: %v", err) - return nil, prepareError(err) - } - - // Integrations - if os.Getenv("GKE_INTEGRATION") != "" { - err := gkeIntegration(clientSet, tenantName, ns, session.STSSessionToken) - if err != nil { - return nil, prepareError(err) - } - } - response = &models.CreateTenantResponse{ - ExternalIDP: tenantExternalIDPConfigured, - } - thisClient := &operatorClient{ - client: opClient, - } - - minTenant, err := getTenant(ctx, thisClient, ns, tenantName) - - if tenantReq.Idp != nil && !tenantExternalIDPConfigured { - for _, credential := range tenantReq.Idp.Keys { - response.Console = append(response.Console, &models.TenantResponseItem{ - AccessKey: *credential.AccessKey, - SecretKey: *credential.SecretKey, - URL: GetTenantServiceURL(minTenant), - }) - } - } - return response, nil -} - // setImageRegistry creates a secret to store the private registry credentials, if one exist it updates the existing one // returns the name of the secret created/updated func setImageRegistry(ctx context.Context, req *models.ImageRegistry, clientset v1.CoreV1Interface, namespace, tenantName string) (string, error) { diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx index a7a9a57d3..d6d8139c9 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx @@ -53,6 +53,8 @@ import Images from "./Steps/Images"; import PageLayout from "../../Common/Layout/PageLayout"; import BackLink from "../../../../common/BackLink"; import TenantResources from "./Steps/TenantResources/TenantResources"; +import ConfigLogSearch from "./Steps/ConfigLogSearch"; +import ConfigPrometheus from "./Steps/ConfigPrometheus"; interface IAddTenantProps { setErrorSnackMessage: typeof setErrorSnackMessage; @@ -175,8 +177,8 @@ const AddTenant = ({ const ecParity = fields.tenantSize.ecParity; const distribution = fields.tenantSize.distribution; const tenantCustom = fields.configure.tenantCustom; - const logSearchCustom = fields.configure.logSearchCustom; - const prometheusCustom = fields.configure.prometheusCustom; + const logSearchEnabled = fields.configure.logSearchEnabled; + const prometheusEnabled = fields.configure.prometheusEnabled; const logSearchVolumeSize = fields.configure.logSearchVolumeSize; const logSearchSelectedStorageClass = fields.configure.logSearchSelectedStorageClass; @@ -313,7 +315,7 @@ const AddTenant = ({ }; } - if (logSearchCustom) { + if (logSearchEnabled) { dataSend = { ...dataSend, logSearchConfiguration: { @@ -329,18 +331,9 @@ const AddTenant = ({ postgres_securityContext: logSearchPostgresSecurityContext, }, }; - } else { - dataSend = { - ...dataSend, - logSearchConfiguration: { - image: logSearchImage, - postgres_image: logSearchPostgresImage, - postgres_init_image: logSearchPostgresInitImage, - }, - }; } - if (prometheusCustom) { + if (prometheusEnabled) { dataSend = { ...dataSend, prometheusConfiguration: { @@ -355,15 +348,6 @@ const AddTenant = ({ securityContext: prometheusSecurityContext, }, }; - } else { - dataSend = { - ...dataSend, - prometheusConfiguration: { - image: prometheusImage, - sidecar_image: prometheusSidecarImage, - init_image: prometheusInitImage, - }, - }; } let tenantCerts: any = null; @@ -753,6 +737,18 @@ const AddTenant = ({ componentRender: , buttons: [cancelButton, createButton], }, + { + label: "Audit Log", + advancedOnly: true, + componentRender: , + buttons: [cancelButton, createButton], + }, + { + label: "Monitoring", + advancedOnly: true, + componentRender: , + buttons: [cancelButton, createButton], + }, ]; let filteredWizardSteps = wizardSteps; diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/ConfigLogSearch.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/ConfigLogSearch.tsx new file mode 100644 index 000000000..2ae9de589 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/ConfigLogSearch.tsx @@ -0,0 +1,556 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { useCallback, useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import { Grid, Paper, SelectChangeEvent } from "@mui/material"; +import { + createTenantCommon, + modalBasic, + wizardCommon, +} from "../../../Common/FormComponents/common/styleLibrary"; +import { isPageValid, updateAddField } from "../../actions"; +import { AppState } from "../../../../../store"; +import { clearValidationError } from "../../utils"; +import { + commonFormValidation, + IValidation, +} from "../../../../../utils/validationFunctions"; +import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; +import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper"; +import { ISecurityContext } from "../../types"; +import InputUnitMenu from "../../../Common/FormComponents/InputUnitMenu/InputUnitMenu"; + +interface IConfigureProps { + updateAddField: typeof updateAddField; + isPageValid: typeof isPageValid; + storageClasses: any; + classes: any; + logSearchEnabled: boolean; + logSearchVolumeSize: string; + logSearchSizeFactor: string; + logSearchSelectedStorageClass: string; + logSearchImage: string; + logSearchPostgresImage: string; + logSearchPostgresInitImage: string; + selectedStorageClass: string; + tenantSecurityContext: ISecurityContext; + logSearchSecurityContext: ISecurityContext; + logSearchPostgresSecurityContext: ISecurityContext; +} + +const styles = (theme: Theme) => + createStyles({ + configSectionItem: { + marginRight: 15, + + "& .multiContainer": { + border: "1px solid red", + }, + }, + containerItem: { + marginRight: 15, + }, + fieldGroup: { + ...createTenantCommon.fieldGroup, + paddingTop: 15, + marginBottom: 25, + }, + responsiveSectionItem: { + "@media (max-width: 900px)": { + flexFlow: "column", + alignItems: "flex-start", + + "& div > div": { + marginBottom: 5, + marginRight: 0, + }, + }, + }, + + logSearchEnabledFields: { + marginLeft: 20, // 2nd Level(15+15) + padding: 10, + width: "90%", + margin: "auto", + }, + fieldSpaceTop: { + marginTop: 15, + }, + ...modalBasic, + ...wizardCommon, + }); + +const ConfigLogSearch = ({ + classes, + storageClasses, + logSearchEnabled, + logSearchVolumeSize, + logSearchSizeFactor, + logSearchImage, + logSearchPostgresImage, + logSearchPostgresInitImage, + logSearchSelectedStorageClass, + updateAddField, + isPageValid, + selectedStorageClass, + tenantSecurityContext, + logSearchSecurityContext, + logSearchPostgresSecurityContext, +}: IConfigureProps) => { + const [validationErrors, setValidationErrors] = useState({}); + + const configureSTClasses = [ + { label: "Default", value: "default" }, + ...storageClasses, + ]; + + // Common + const updateField = useCallback( + (field: string, value: any) => { + updateAddField("configure", field, value); + }, + [updateAddField] + ); + + // Validation + useEffect(() => { + let customAccountValidation: IValidation[] = []; + + if (logSearchEnabled) { + customAccountValidation = [ + ...customAccountValidation, + { + fieldKey: "log_search_storage_class", + required: true, + value: logSearchSelectedStorageClass, + customValidation: logSearchSelectedStorageClass === "", + customValidationMessage: "Field cannot be empty", + }, + { + fieldKey: "log_search_volume_size", + required: true, + value: logSearchVolumeSize, + customValidation: + logSearchVolumeSize === "" || parseInt(logSearchVolumeSize) <= 0, + customValidationMessage: `Volume size must be present and be greatter than 0`, + }, + { + fieldKey: "logSearch_securityContext_runAsUser", + required: true, + value: logSearchSecurityContext.runAsUser, + customValidation: + logSearchSecurityContext.runAsUser === "" || + parseInt(logSearchSecurityContext.runAsUser) < 0, + customValidationMessage: `runAsUser must be present and be 0 or more`, + }, + { + fieldKey: "logSearch_securityContext_runAsGroup", + required: true, + value: logSearchSecurityContext.runAsGroup, + customValidation: + logSearchSecurityContext.runAsGroup === "" || + parseInt(logSearchSecurityContext.runAsGroup) < 0, + customValidationMessage: `runAsGroup must be present and be 0 or more`, + }, + { + fieldKey: "logSearch_securityContext_fsGroup", + required: true, + value: logSearchSecurityContext.fsGroup, + customValidation: + logSearchSecurityContext.fsGroup === "" || + parseInt(logSearchSecurityContext.fsGroup) < 0, + customValidationMessage: `fsGroup must be present and be 0 or more`, + }, + { + fieldKey: "postgres_securityContext_runAsUser", + required: true, + value: logSearchPostgresSecurityContext.runAsUser, + customValidation: + logSearchPostgresSecurityContext.runAsUser === "" || + parseInt(logSearchPostgresSecurityContext.runAsUser) < 0, + customValidationMessage: `runAsUser must be present and be 0 or more`, + }, + { + fieldKey: "postgres_securityContext_runAsGroup", + required: true, + value: logSearchSecurityContext.runAsGroup, + customValidation: + logSearchPostgresSecurityContext.runAsGroup === "" || + parseInt(logSearchPostgresSecurityContext.runAsGroup) < 0, + customValidationMessage: `runAsGroup must be present and be 0 or more`, + }, + { + fieldKey: "postgres_securityContext_fsGroup", + required: true, + value: logSearchPostgresSecurityContext.fsGroup, + customValidation: + logSearchPostgresSecurityContext.fsGroup === "" || + parseInt(logSearchPostgresSecurityContext.fsGroup) < 0, + customValidationMessage: `fsGroup must be present and be 0 or more`, + }, + ]; + } + + const commonVal = commonFormValidation(customAccountValidation); + + isPageValid("configure", Object.keys(commonVal).length === 0); + + setValidationErrors(commonVal); + }, [ + logSearchImage, + logSearchPostgresImage, + logSearchPostgresInitImage, + isPageValid, + logSearchEnabled, + logSearchSelectedStorageClass, + logSearchVolumeSize, + tenantSecurityContext, + logSearchSecurityContext, + logSearchPostgresSecurityContext, + ]); + + useEffect(() => { + // New default values in current selection is invalid + if (storageClasses.length > 0) { + const filterLogSearch = storageClasses.filter( + (item: any) => item.value === logSearchSelectedStorageClass + ); + if (filterLogSearch.length === 0) { + updateField("logSearchSelectedStorageClass", "default"); + } + } + }, [ + logSearchSelectedStorageClass, + selectedStorageClass, + storageClasses, + updateField, + ]); + + const cleanValidation = (fieldName: string) => { + setValidationErrors(clearValidationError(validationErrors, fieldName)); + }; + + return ( + +
+

Audit Log

+ + Audit log deploys a small PostgreSQL database and store access logs of + all calls into the tenant. + +
+ + { + const targetD = e.target; + const checked = targetD.checked; + + updateField("logSearchEnabled", checked); + }} + label={"Enabled"} + /> + + {logSearchEnabled && ( + + + ) => { + updateField( + "logSearchSelectedStorageClass", + e.target.value as string + ); + }} + label="Log Search Storage Class" + value={logSearchSelectedStorageClass} + options={configureSTClasses} + disabled={configureSTClasses.length < 1} + /> + + +
+ ) => { + updateField("logSearchVolumeSize", e.target.value); + cleanValidation("log_search_volume_size"); + }} + label="Storage Size" + overlayObject={ + {}} + unitSelected={"Gi"} + unitsList={[{ label: "Gi", value: "Gi" }]} + disabled={true} + /> + } + value={logSearchVolumeSize} + required + error={validationErrors["log_search_volume_size"] || ""} + min="0" + /> +
+
+ +
+ + SecurityContext for LogSearch + + + +
+
+ ) => { + updateField("logSearchSecurityContext", { + ...logSearchSecurityContext, + runAsUser: e.target.value, + }); + cleanValidation("logSearch_securityContext_runAsUser"); + }} + label="Run As User" + value={logSearchSecurityContext.runAsUser} + required + error={ + validationErrors["logSearch_securityContext_runAsUser"] || + "" + } + min="0" + /> +
+
+ ) => { + updateField("logSearchSecurityContext", { + ...logSearchSecurityContext, + runAsGroup: e.target.value, + }); + cleanValidation("logSearch_securityContext_runAsGroup"); + }} + label="Run As Group" + value={logSearchSecurityContext.runAsGroup} + required + error={ + validationErrors[ + "logSearch_securityContext_runAsGroup" + ] || "" + } + min="0" + /> +
+
+ ) => { + updateField("logSearchSecurityContext", { + ...logSearchSecurityContext, + fsGroup: e.target.value, + }); + cleanValidation("logSearch_securityContext_fsGroup"); + }} + label="FsGroup" + value={logSearchSecurityContext.fsGroup} + required + error={ + validationErrors["logSearch_securityContext_fsGroup"] || + "" + } + min="0" + /> +
+
+
+
+ +
+ { + const targetD = e.target; + const checked = targetD.checked; + updateField("logSearchSecurityContext", { + ...logSearchSecurityContext, + runAsNonRoot: checked, + }); + }} + label={"Do not run as Root"} + /> +
+
+
+
+ + SecurityContext for PostgreSQL + + + +
+
+ ) => { + updateField("logSearchPostgresSecurityContext", { + ...logSearchPostgresSecurityContext, + runAsUser: e.target.value, + }); + cleanValidation("postgres_securityContext_runAsUser"); + }} + label="Run As User" + value={logSearchPostgresSecurityContext.runAsUser} + required + error={ + validationErrors["postgres_securityContext_runAsUser"] || + "" + } + min="0" + /> +
+
+ ) => { + updateField("logSearchPostgresSecurityContext", { + ...logSearchPostgresSecurityContext, + runAsGroup: e.target.value, + }); + cleanValidation("postgres_securityContext_runAsGroup"); + }} + label="Run As Group" + value={logSearchPostgresSecurityContext.runAsGroup} + required + error={ + validationErrors["postgres_securityContext_runAsGroup"] || + "" + } + min="0" + /> +
+
+ ) => { + updateField("logSearchPostgresSecurityContext", { + ...logSearchPostgresSecurityContext, + fsGroup: e.target.value, + }); + cleanValidation("postgres_securityContext_fsGroup"); + }} + label="FsGroup" + value={logSearchPostgresSecurityContext.fsGroup} + required + error={ + validationErrors["postgres_securityContext_fsGroup"] || "" + } + min="0" + /> +
+
+
+
+ +
+ { + const targetD = e.target; + const checked = targetD.checked; + updateField("logSearchPostgresSecurityContext", { + ...logSearchPostgresSecurityContext, + runAsNonRoot: checked, + }); + }} + label={"Do not run as Root"} + /> +
+
+
+
+ )} +
+ ); +}; + +const mapState = (state: AppState) => ({ + storageClasses: state.tenants.createTenant.storageClasses, + logSearchEnabled: + state.tenants.createTenant.fields.configure.logSearchEnabled, + logSearchVolumeSize: + state.tenants.createTenant.fields.configure.logSearchVolumeSize, + logSearchSizeFactor: + state.tenants.createTenant.fields.configure.logSearchSizeFactor, + logSearchSelectedStorageClass: + state.tenants.createTenant.fields.configure.logSearchSelectedStorageClass, + logSearchImage: state.tenants.createTenant.fields.configure.logSearchImage, + logSearchPostgresImage: + state.tenants.createTenant.fields.configure.logSearchPostgresImage, + logSearchPostgresInitImage: + state.tenants.createTenant.fields.configure.logSearchPostgresInitImage, + selectedStorageClass: + state.tenants.createTenant.fields.nameTenant.selectedStorageClass, + tenantSecurityContext: + state.tenants.createTenant.fields.configure.tenantSecurityContext, + logSearchSecurityContext: + state.tenants.createTenant.fields.configure.logSearchSecurityContext, + logSearchPostgresSecurityContext: + state.tenants.createTenant.fields.configure + .logSearchPostgresSecurityContext, +}); + +const connector = connect(mapState, { + updateAddField, + isPageValid, +}); + +export default withStyles(styles)(connector(ConfigLogSearch)); diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/ConfigPrometheus.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/ConfigPrometheus.tsx new file mode 100644 index 000000000..98847b1d2 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/ConfigPrometheus.tsx @@ -0,0 +1,424 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { useCallback, useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import { Grid, Paper, SelectChangeEvent } from "@mui/material"; +import { + createTenantCommon, + modalBasic, + wizardCommon, +} from "../../../Common/FormComponents/common/styleLibrary"; +import { isPageValid, updateAddField } from "../../actions"; +import { AppState } from "../../../../../store"; +import { clearValidationError } from "../../utils"; +import { + commonFormValidation, + IValidation, +} from "../../../../../utils/validationFunctions"; +import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; +import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper"; +import { ISecurityContext } from "../../types"; +import InputUnitMenu from "../../../Common/FormComponents/InputUnitMenu/InputUnitMenu"; + +interface IConfigureProps { + updateAddField: typeof updateAddField; + isPageValid: typeof isPageValid; + storageClasses: any; + classes: any; + prometheusEnabled: boolean; + prometheusVolumeSize: string; + prometheusSizeFactor: string; + prometheusSelectedStorageClass: string; + prometheusImage: string; + prometheusSidecarImage: string; + prometheusInitImage: string; + selectedStorageClass: string; + tenantSecurityContext: ISecurityContext; + prometheusSecurityContext: ISecurityContext; +} + +const styles = (theme: Theme) => + createStyles({ + configSectionItem: { + marginRight: 15, + + "& .multiContainer": { + border: "1px solid red", + }, + }, + containerItem: { + marginRight: 15, + }, + fieldGroup: { + ...createTenantCommon.fieldGroup, + paddingTop: 15, + marginBottom: 25, + }, + responsiveSectionItem: { + "@media (max-width: 900px)": { + flexFlow: "column", + alignItems: "flex-start", + + "& div > div": { + marginBottom: 5, + marginRight: 0, + }, + }, + }, + fieldSpaceTop: { + marginTop: 15, + }, + prometheusEnabledFields: { + marginLeft: 20, // 2nd Level(15+15) + padding: 10, + width: "90%", + margin: "auto", + }, + ...modalBasic, + ...wizardCommon, + }); + +const ConfigPrometheus = ({ + classes, + storageClasses, + prometheusEnabled, + prometheusVolumeSize, + prometheusSizeFactor, + prometheusSelectedStorageClass, + prometheusImage, + prometheusSidecarImage, + prometheusInitImage, + updateAddField, + isPageValid, + selectedStorageClass, + tenantSecurityContext, + prometheusSecurityContext, +}: IConfigureProps) => { + const [validationErrors, setValidationErrors] = useState({}); + + const configureSTClasses = [ + { label: "Default", value: "default" }, + ...storageClasses, + ]; + + // Common + const updateField = useCallback( + (field: string, value: any) => { + updateAddField("configure", field, value); + }, + [updateAddField] + ); + + // Validation + useEffect(() => { + let customAccountValidation: IValidation[] = []; + + if (prometheusEnabled) { + customAccountValidation = [ + ...customAccountValidation, + { + fieldKey: "prometheus_storage_class", + required: true, + value: prometheusSelectedStorageClass, + customValidation: prometheusSelectedStorageClass === "", + customValidationMessage: "Field cannot be empty", + }, + { + fieldKey: "prometheus_volume_size", + required: true, + value: prometheusVolumeSize, + customValidation: + prometheusVolumeSize === "" || parseInt(prometheusVolumeSize) <= 0, + customValidationMessage: `Volume size must be present and be greater than 0`, + }, + { + fieldKey: "prometheus_securityContext_runAsUser", + required: true, + value: prometheusSecurityContext.runAsUser, + customValidation: + prometheusSecurityContext.runAsUser === "" || + parseInt(prometheusSecurityContext.runAsUser) < 0, + customValidationMessage: `runAsUser must be present and be 0 or more`, + }, + { + fieldKey: "prometheus_securityContext_runAsGroup", + required: true, + value: prometheusSecurityContext.runAsGroup, + customValidation: + prometheusSecurityContext.runAsGroup === "" || + parseInt(prometheusSecurityContext.runAsGroup) < 0, + customValidationMessage: `runAsGroup must be present and be 0 or more`, + }, + { + fieldKey: "prometheus_securityContext_fsGroup", + required: true, + value: prometheusSecurityContext.fsGroup, + customValidation: + prometheusSecurityContext.fsGroup === "" || + parseInt(prometheusSecurityContext.fsGroup) < 0, + customValidationMessage: `fsGroup must be present and be 0 or more`, + }, + ]; + } + + const commonVal = commonFormValidation(customAccountValidation); + + isPageValid("configure", Object.keys(commonVal).length === 0); + + setValidationErrors(commonVal); + }, [ + prometheusImage, + prometheusSidecarImage, + prometheusInitImage, + isPageValid, + prometheusEnabled, + prometheusSelectedStorageClass, + prometheusVolumeSize, + tenantSecurityContext, + prometheusSecurityContext, + ]); + + useEffect(() => { + // New default values in current selection is invalid + if (storageClasses.length > 0) { + const filterPrometheus = storageClasses.filter( + (item: any) => item.value === prometheusSelectedStorageClass + ); + if (filterPrometheus.length === 0) { + updateField("prometheusSelectedStorageClass", "default"); + } + } + }, [ + prometheusSelectedStorageClass, + selectedStorageClass, + storageClasses, + updateField, + ]); + + const cleanValidation = (fieldName: string) => { + setValidationErrors(clearValidationError(validationErrors, fieldName)); + }; + + return ( + +
+

Monitoring

+ + A small Prometheus will be deployed to keep metrics about the tenant. + +
+ + { + const targetD = e.target; + const checked = targetD.checked; + + updateField("prometheusEnabled", checked); + }} + label={"Enabled"} + /> + + {prometheusEnabled && ( + + + ) => { + updateField( + "prometheusSelectedStorageClass", + e.target.value as string + ); + }} + label="Prometheus Storage Class" + value={prometheusSelectedStorageClass} + options={configureSTClasses} + disabled={configureSTClasses.length < 1} + /> + + +
+ ) => { + updateField("prometheusVolumeSize", e.target.value); + cleanValidation("prometheus_volume_size"); + }} + label="Storage Size" + overlayObject={ + {}} + unitSelected={"Gi"} + unitsList={[{ label: "Gi", value: "Gi" }]} + disabled={true} + /> + } + value={prometheusVolumeSize} + required + error={validationErrors["prometheus_volume_size"] || ""} + min="0" + /> +
+
+
+ + SecurityContext for Prometheus + + +
+
+ ) => { + updateField("prometheusSecurityContext", { + ...prometheusSecurityContext, + runAsUser: e.target.value, + }); + cleanValidation("prometheus_securityContext_runAsUser"); + }} + label="Run As User" + value={prometheusSecurityContext.runAsUser} + required + error={ + validationErrors[ + "prometheus_securityContext_runAsUser" + ] || "" + } + min="0" + /> +
+
+ ) => { + updateField("prometheusSecurityContext", { + ...prometheusSecurityContext, + runAsGroup: e.target.value, + }); + cleanValidation("prometheus_securityContext_runAsGroup"); + }} + label="Run As Group" + value={prometheusSecurityContext.runAsGroup} + required + error={ + validationErrors[ + "prometheus_securityContext_runAsGroup" + ] || "" + } + min="0" + /> +
+
+ ) => { + updateField("prometheusSecurityContext", { + ...prometheusSecurityContext, + fsGroup: e.target.value, + }); + cleanValidation("prometheus_securityContext_fsGroup"); + }} + label="FsGroup" + value={prometheusSecurityContext.fsGroup} + required + error={ + validationErrors["prometheus_securityContext_fsGroup"] || + "" + } + min="0" + /> +
+
+
+ +
+ { + const targetD = e.target; + const checked = targetD.checked; + updateField("prometheusSecurityContext", { + ...prometheusSecurityContext, + runAsNonRoot: checked, + }); + }} + label={"Do not run as Root"} + /> +
+
+
+
+ )} +
+ ); +}; + +const mapState = (state: AppState) => ({ + storageClasses: state.tenants.createTenant.storageClasses, + prometheusEnabled: + state.tenants.createTenant.fields.configure.prometheusEnabled, + prometheusVolumeSize: + state.tenants.createTenant.fields.configure.prometheusVolumeSize, + prometheusSizeFactor: + state.tenants.createTenant.fields.configure.prometheusSizeFactor, + prometheusSelectedStorageClass: + state.tenants.createTenant.fields.configure.prometheusSelectedStorageClass, + prometheusImage: state.tenants.createTenant.fields.configure.prometheusImage, + prometheusSidecarImage: + state.tenants.createTenant.fields.configure.prometheusSidecarImage, + prometheusInitImage: + state.tenants.createTenant.fields.configure.prometheusInitImage, + selectedStorageClass: + state.tenants.createTenant.fields.nameTenant.selectedStorageClass, + tenantSecurityContext: + state.tenants.createTenant.fields.configure.tenantSecurityContext, + prometheusSecurityContext: + state.tenants.createTenant.fields.configure.prometheusSecurityContext, +}); + +const connector = connect(mapState, { + updateAddField, + isPageValid, +}); + +export default withStyles(styles)(connector(ConfigPrometheus)); diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx index 8a6a0fe70..c305b75c5 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx @@ -19,7 +19,7 @@ import { connect } from "react-redux"; import { Theme } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; -import { Grid, Paper, SelectChangeEvent } from "@mui/material"; +import { Grid, Paper } from "@mui/material"; import { createTenantCommon, modalBasic, @@ -34,44 +34,16 @@ import { } from "../../../../../utils/validationFunctions"; import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; -import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper"; import { ISecurityContext } from "../../types"; -import InputUnitMenu from "../../../Common/FormComponents/InputUnitMenu/InputUnitMenu"; interface IConfigureProps { updateAddField: typeof updateAddField; isPageValid: typeof isPageValid; - storageClasses: any; classes: any; - customImage: boolean; - imageName: string; - customDockerhub: boolean; - imageRegistry: string; - imageRegistryUsername: string; - imageRegistryPassword: string; exposeMinIO: boolean; exposeConsole: boolean; - prometheusCustom: boolean; tenantCustom: boolean; - logSearchCustom: boolean; - logSearchVolumeSize: string; - logSearchSizeFactor: string; - prometheusVolumeSize: string; - prometheusSizeFactor: string; - logSearchSelectedStorageClass: string; - logSearchImage: string; - kesImage: string; - logSearchPostgresImage: string; - logSearchPostgresInitImage: string; - prometheusSelectedStorageClass: string; - prometheusImage: string; - prometheusSidecarImage: string; - prometheusInitImage: string; - selectedStorageClass: string; tenantSecurityContext: ISecurityContext; - logSearchSecurityContext: ISecurityContext; - logSearchPostgresSecurityContext: ISecurityContext; - prometheusSecurityContext: ISecurityContext; } const styles = (theme: Theme) => @@ -108,67 +80,25 @@ const styles = (theme: Theme) => }, }, - logSearchCustomFields: { - marginLeft: 20, // 2nd Level(15+15) - padding: 10, - width: "90%", - margin: "auto", - }, fieldSpaceTop: { marginTop: 15, }, - prometheusCustomFields: { - marginLeft: 20, // 2nd Level(15+15) - padding: 10, - width: "90%", - margin: "auto", - }, + ...modalBasic, ...wizardCommon, }); const Configure = ({ classes, - storageClasses, - customImage, - imageName, - customDockerhub, - imageRegistry, - imageRegistryUsername, - imageRegistryPassword, exposeMinIO, exposeConsole, - prometheusCustom, tenantCustom, - logSearchCustom, - logSearchVolumeSize, - logSearchSizeFactor, - logSearchImage, - kesImage, - logSearchPostgresImage, - logSearchPostgresInitImage, - prometheusVolumeSize, - prometheusSizeFactor, - logSearchSelectedStorageClass, - prometheusSelectedStorageClass, - prometheusImage, - prometheusSidecarImage, - prometheusInitImage, updateAddField, isPageValid, - selectedStorageClass, tenantSecurityContext, - logSearchSecurityContext, - logSearchPostgresSecurityContext, - prometheusSecurityContext, }: IConfigureProps) => { const [validationErrors, setValidationErrors] = useState({}); - const configureSTClasses = [ - { label: "Default", value: "default" }, - ...storageClasses, - ]; - // Common const updateField = useCallback( (field: string, value: any) => { @@ -212,273 +142,13 @@ const Configure = ({ }, ]; } - if (prometheusCustom) { - customAccountValidation = [ - ...customAccountValidation, - { - fieldKey: "prometheus_storage_class", - required: true, - value: prometheusSelectedStorageClass, - customValidation: prometheusSelectedStorageClass === "", - customValidationMessage: "Field cannot be empty", - }, - { - fieldKey: "prometheus_volume_size", - required: true, - value: prometheusVolumeSize, - customValidation: - prometheusVolumeSize === "" || parseInt(prometheusVolumeSize) <= 0, - customValidationMessage: `Volume size must be present and be greater than 0`, - }, - { - fieldKey: "prometheus_securityContext_runAsUser", - required: true, - value: prometheusSecurityContext.runAsUser, - customValidation: - prometheusSecurityContext.runAsUser === "" || - parseInt(prometheusSecurityContext.runAsUser) < 0, - customValidationMessage: `runAsUser must be present and be 0 or more`, - }, - { - fieldKey: "prometheus_securityContext_runAsGroup", - required: true, - value: prometheusSecurityContext.runAsGroup, - customValidation: - prometheusSecurityContext.runAsGroup === "" || - parseInt(prometheusSecurityContext.runAsGroup) < 0, - customValidationMessage: `runAsGroup must be present and be 0 or more`, - }, - { - fieldKey: "prometheus_securityContext_fsGroup", - required: true, - value: prometheusSecurityContext.fsGroup, - customValidation: - prometheusSecurityContext.fsGroup === "" || - parseInt(prometheusSecurityContext.fsGroup) < 0, - customValidationMessage: `fsGroup must be present and be 0 or more`, - }, - ]; - } - if (logSearchCustom) { - customAccountValidation = [ - ...customAccountValidation, - { - fieldKey: "log_search_storage_class", - required: true, - value: logSearchSelectedStorageClass, - customValidation: logSearchSelectedStorageClass === "", - customValidationMessage: "Field cannot be empty", - }, - { - fieldKey: "log_search_volume_size", - required: true, - value: logSearchVolumeSize, - customValidation: - logSearchVolumeSize === "" || parseInt(logSearchVolumeSize) <= 0, - customValidationMessage: `Volume size must be present and be greatter than 0`, - }, - { - fieldKey: "logSearch_securityContext_runAsUser", - required: true, - value: logSearchSecurityContext.runAsUser, - customValidation: - logSearchSecurityContext.runAsUser === "" || - parseInt(logSearchSecurityContext.runAsUser) < 0, - customValidationMessage: `runAsUser must be present and be 0 or more`, - }, - { - fieldKey: "logSearch_securityContext_runAsGroup", - required: true, - value: logSearchSecurityContext.runAsGroup, - customValidation: - logSearchSecurityContext.runAsGroup === "" || - parseInt(logSearchSecurityContext.runAsGroup) < 0, - customValidationMessage: `runAsGroup must be present and be 0 or more`, - }, - { - fieldKey: "logSearch_securityContext_fsGroup", - required: true, - value: logSearchSecurityContext.fsGroup, - customValidation: - logSearchSecurityContext.fsGroup === "" || - parseInt(logSearchSecurityContext.fsGroup) < 0, - customValidationMessage: `fsGroup must be present and be 0 or more`, - }, - { - fieldKey: "postgres_securityContext_runAsUser", - required: true, - value: logSearchPostgresSecurityContext.runAsUser, - customValidation: - logSearchPostgresSecurityContext.runAsUser === "" || - parseInt(logSearchPostgresSecurityContext.runAsUser) < 0, - customValidationMessage: `runAsUser must be present and be 0 or more`, - }, - { - fieldKey: "postgres_securityContext_runAsGroup", - required: true, - value: prometheusSecurityContext.runAsGroup, - customValidation: - logSearchPostgresSecurityContext.runAsGroup === "" || - parseInt(logSearchPostgresSecurityContext.runAsGroup) < 0, - customValidationMessage: `runAsGroup must be present and be 0 or more`, - }, - { - fieldKey: "postgres_securityContext_fsGroup", - required: true, - value: logSearchPostgresSecurityContext.fsGroup, - customValidation: - logSearchPostgresSecurityContext.fsGroup === "" || - parseInt(logSearchPostgresSecurityContext.fsGroup) < 0, - customValidationMessage: `fsGroup must be present and be 0 or more`, - }, - ]; - } - - if (customImage) { - customAccountValidation = [ - ...customAccountValidation, - { - fieldKey: "image", - required: false, - value: imageName, - pattern: /^((.*?)\/(.*?):(.+))$/, - customPatternMessage: "Format must be of form: 'minio/minio:VERSION'", - }, - { - fieldKey: "logSearchImage", - required: false, - value: logSearchImage, - pattern: /^((.*?)\/(.*?):(.+))$/, - customPatternMessage: - "Format must be of form: 'minio/logsearchapi:VERSION'", - }, - { - fieldKey: "kesImage", - required: false, - value: kesImage, - pattern: /^((.*?)\/(.*?):(.+))$/, - customPatternMessage: "Format must be of form: 'minio/kes:VERSION'", - }, - { - fieldKey: "logSearchPostgresImage", - required: false, - value: logSearchPostgresImage, - pattern: /^((.*?)\/(.*?):(.+))$/, - customPatternMessage: - "Format must be of form: 'library/postgres:VERSION'", - }, - { - fieldKey: "logSearchPostgresInitImage", - required: false, - value: logSearchPostgresInitImage, - pattern: /^((.*?)\/(.*?):(.+))$/, - customPatternMessage: - "Format must be of form: 'library/busybox:VERSION'", - }, - { - fieldKey: "prometheusImage", - required: false, - value: prometheusImage, - pattern: /^((.*?)\/(.*?):(.+))$/, - customPatternMessage: - "Format must be of form: 'minio/prometheus:VERSION'", - }, - { - fieldKey: "prometheusSidecarImage", - required: false, - value: prometheusSidecarImage, - pattern: /^((.*?)\/(.*?):(.+))$/, - customPatternMessage: - "Format must be of form: 'project/container:VERSION'", - }, - { - fieldKey: "prometheusInitImage", - required: false, - value: prometheusInitImage, - pattern: /^((.*?)\/(.*?):(.+))$/, - customPatternMessage: - "Format must be of form: 'library/busybox:VERSION'", - }, - ]; - if (customDockerhub) { - customAccountValidation = [ - ...customAccountValidation, - { - fieldKey: "registry", - required: true, - value: imageRegistry, - }, - { - fieldKey: "registryUsername", - required: true, - value: imageRegistryUsername, - }, - { - fieldKey: "registryPassword", - required: true, - value: imageRegistryPassword, - }, - ]; - } - } const commonVal = commonFormValidation(customAccountValidation); isPageValid("configure", Object.keys(commonVal).length === 0); setValidationErrors(commonVal); - }, [ - customImage, - imageName, - logSearchImage, - kesImage, - logSearchPostgresImage, - logSearchPostgresInitImage, - prometheusImage, - prometheusSidecarImage, - prometheusInitImage, - customDockerhub, - imageRegistry, - imageRegistryUsername, - imageRegistryPassword, - isPageValid, - prometheusCustom, - tenantCustom, - logSearchCustom, - prometheusSelectedStorageClass, - prometheusVolumeSize, - logSearchSelectedStorageClass, - logSearchVolumeSize, - tenantSecurityContext, - logSearchSecurityContext, - logSearchPostgresSecurityContext, - prometheusSecurityContext, - ]); - - useEffect(() => { - // New default values in current selection is invalid - if (storageClasses.length > 0) { - const filterPrometheus = storageClasses.filter( - (item: any) => item.value === prometheusSelectedStorageClass - ); - if (filterPrometheus.length === 0) { - updateField("prometheusSelectedStorageClass", "default"); - } - - const filterLogSearch = storageClasses.filter( - (item: any) => item.value === logSearchSelectedStorageClass - ); - if (filterLogSearch.length === 0) { - updateField("logSearchSelectedStorageClass", "default"); - } - } - }, [ - logSearchSelectedStorageClass, - prometheusSelectedStorageClass, - selectedStorageClass, - storageClasses, - updateField, - ]); + }, [isPageValid, tenantCustom, tenantSecurityContext]); const cleanValidation = (fieldName: string) => { setValidationErrors(clearValidationError(validationErrors, fieldName)); @@ -493,9 +163,10 @@ const Configure = ({
-

Expose Services

+

Services

- Whether the tenant's services should request an external IP. + Whether the tenant's services should request an external IP via + LoadBalancer service type.
@@ -529,13 +200,6 @@ const Configure = ({ /> -
-

Additional Configurations

- - Configure SecurityContext, Storage Classes & Storage size for Log - Search, Prometheus add-ons and your Tenant - -
{tenantCustom && ( @@ -650,485 +314,16 @@ const Configure = ({ )} - - { - const targetD = e.target; - const checked = targetD.checked; - - updateField("logSearchCustom", checked); - }} - label={"Override Log Search defaults"} - /> - - {logSearchCustom && ( - - - ) => { - updateField( - "logSearchSelectedStorageClass", - e.target.value as string - ); - }} - label="Log Search Storage Class" - value={logSearchSelectedStorageClass} - options={configureSTClasses} - disabled={configureSTClasses.length < 1} - /> - - -
- ) => { - updateField("logSearchVolumeSize", e.target.value); - cleanValidation("log_search_volume_size"); - }} - label="Storage Size" - overlayObject={ - {}} - unitSelected={"Gi"} - unitsList={[{ label: "Gi", value: "Gi" }]} - disabled={true} - /> - } - value={logSearchVolumeSize} - required - error={validationErrors["log_search_volume_size"] || ""} - min="0" - /> -
-
- -
- - SecurityContext for LogSearch - - - -
-
- ) => { - updateField("logSearchSecurityContext", { - ...logSearchSecurityContext, - runAsUser: e.target.value, - }); - cleanValidation("logSearch_securityContext_runAsUser"); - }} - label="Run As User" - value={logSearchSecurityContext.runAsUser} - required - error={ - validationErrors["logSearch_securityContext_runAsUser"] || - "" - } - min="0" - /> -
-
- ) => { - updateField("logSearchSecurityContext", { - ...logSearchSecurityContext, - runAsGroup: e.target.value, - }); - cleanValidation("logSearch_securityContext_runAsGroup"); - }} - label="Run As Group" - value={logSearchSecurityContext.runAsGroup} - required - error={ - validationErrors[ - "logSearch_securityContext_runAsGroup" - ] || "" - } - min="0" - /> -
-
- ) => { - updateField("logSearchSecurityContext", { - ...logSearchSecurityContext, - fsGroup: e.target.value, - }); - cleanValidation("logSearch_securityContext_fsGroup"); - }} - label="FsGroup" - value={logSearchSecurityContext.fsGroup} - required - error={ - validationErrors["logSearch_securityContext_fsGroup"] || - "" - } - min="0" - /> -
-
-
-
- -
- { - const targetD = e.target; - const checked = targetD.checked; - updateField("logSearchSecurityContext", { - ...logSearchSecurityContext, - runAsNonRoot: checked, - }); - }} - label={"Do not run as Root"} - /> -
-
-
-
- - SecurityContext for PostgreSQL - - - -
-
- ) => { - updateField("logSearchPostgresSecurityContext", { - ...logSearchPostgresSecurityContext, - runAsUser: e.target.value, - }); - cleanValidation("postgres_securityContext_runAsUser"); - }} - label="Run As User" - value={logSearchPostgresSecurityContext.runAsUser} - required - error={ - validationErrors["postgres_securityContext_runAsUser"] || - "" - } - min="0" - /> -
-
- ) => { - updateField("logSearchPostgresSecurityContext", { - ...logSearchPostgresSecurityContext, - runAsGroup: e.target.value, - }); - cleanValidation("postgres_securityContext_runAsGroup"); - }} - label="Run As Group" - value={logSearchPostgresSecurityContext.runAsGroup} - required - error={ - validationErrors["postgres_securityContext_runAsGroup"] || - "" - } - min="0" - /> -
-
- ) => { - updateField("logSearchPostgresSecurityContext", { - ...logSearchPostgresSecurityContext, - fsGroup: e.target.value, - }); - cleanValidation("postgres_securityContext_fsGroup"); - }} - label="FsGroup" - value={logSearchPostgresSecurityContext.fsGroup} - required - error={ - validationErrors["postgres_securityContext_fsGroup"] || "" - } - min="0" - /> -
-
-
-
- -
- { - const targetD = e.target; - const checked = targetD.checked; - updateField("logSearchPostgresSecurityContext", { - ...logSearchPostgresSecurityContext, - runAsNonRoot: checked, - }); - }} - label={"Do not run as Root"} - /> -
-
-
-
- )} - - { - const targetD = e.target; - const checked = targetD.checked; - - updateField("prometheusCustom", checked); - }} - label={"Override Prometheus defaults"} - /> - - {prometheusCustom && ( - - - ) => { - updateField( - "prometheusSelectedStorageClass", - e.target.value as string - ); - }} - label="Prometheus Storage Class" - value={prometheusSelectedStorageClass} - options={configureSTClasses} - disabled={configureSTClasses.length < 1} - /> - - -
- ) => { - updateField("prometheusVolumeSize", e.target.value); - cleanValidation("prometheus_volume_size"); - }} - label="Storage Size" - overlayObject={ - {}} - unitSelected={"Gi"} - unitsList={[{ label: "Gi", value: "Gi" }]} - disabled={true} - /> - } - value={prometheusVolumeSize} - required - error={validationErrors["prometheus_volume_size"] || ""} - min="0" - /> -
-
-
- - SecurityContext for Prometheus - - -
-
- ) => { - updateField("prometheusSecurityContext", { - ...prometheusSecurityContext, - runAsUser: e.target.value, - }); - cleanValidation("prometheus_securityContext_runAsUser"); - }} - label="Run As User" - value={prometheusSecurityContext.runAsUser} - required - error={ - validationErrors[ - "prometheus_securityContext_runAsUser" - ] || "" - } - min="0" - /> -
-
- ) => { - updateField("prometheusSecurityContext", { - ...prometheusSecurityContext, - runAsGroup: e.target.value, - }); - cleanValidation("prometheus_securityContext_runAsGroup"); - }} - label="Run As Group" - value={prometheusSecurityContext.runAsGroup} - required - error={ - validationErrors[ - "prometheus_securityContext_runAsGroup" - ] || "" - } - min="0" - /> -
-
- ) => { - updateField("prometheusSecurityContext", { - ...prometheusSecurityContext, - fsGroup: e.target.value, - }); - cleanValidation("prometheus_securityContext_fsGroup"); - }} - label="FsGroup" - value={prometheusSecurityContext.fsGroup} - required - error={ - validationErrors["prometheus_securityContext_fsGroup"] || - "" - } - min="0" - /> -
-
-
- -
- { - const targetD = e.target; - const checked = targetD.checked; - updateField("prometheusSecurityContext", { - ...prometheusSecurityContext, - runAsNonRoot: checked, - }); - }} - label={"Do not run as Root"} - /> -
-
-
-
- )} ); }; const mapState = (state: AppState) => ({ - storageClasses: state.tenants.createTenant.storageClasses, - customImage: state.tenants.createTenant.fields.configure.customImage, - imageName: state.tenants.createTenant.fields.configure.imageName, - customDockerhub: state.tenants.createTenant.fields.configure.customDockerhub, - imageRegistry: state.tenants.createTenant.fields.configure.imageRegistry, - imageRegistryUsername: - state.tenants.createTenant.fields.configure.imageRegistryUsername, - imageRegistryPassword: - state.tenants.createTenant.fields.configure.imageRegistryPassword, exposeMinIO: state.tenants.createTenant.fields.configure.exposeMinIO, exposeConsole: state.tenants.createTenant.fields.configure.exposeConsole, - prometheusCustom: - state.tenants.createTenant.fields.configure.prometheusCustom, tenantCustom: state.tenants.createTenant.fields.configure.tenantCustom, - logSearchCustom: state.tenants.createTenant.fields.configure.logSearchCustom, - logSearchVolumeSize: - state.tenants.createTenant.fields.configure.logSearchVolumeSize, - logSearchSizeFactor: - state.tenants.createTenant.fields.configure.logSearchSizeFactor, - prometheusVolumeSize: - state.tenants.createTenant.fields.configure.prometheusVolumeSize, - prometheusSizeFactor: - state.tenants.createTenant.fields.configure.prometheusSizeFactor, - logSearchSelectedStorageClass: - state.tenants.createTenant.fields.configure.logSearchSelectedStorageClass, - logSearchImage: state.tenants.createTenant.fields.configure.logSearchImage, - kesImage: state.tenants.createTenant.fields.configure.kesImage, - logSearchPostgresImage: - state.tenants.createTenant.fields.configure.logSearchPostgresImage, - logSearchPostgresInitImage: - state.tenants.createTenant.fields.configure.logSearchPostgresInitImage, - prometheusSelectedStorageClass: - state.tenants.createTenant.fields.configure.prometheusSelectedStorageClass, - prometheusImage: state.tenants.createTenant.fields.configure.prometheusImage, - prometheusSidecarImage: - state.tenants.createTenant.fields.configure.prometheusSidecarImage, - prometheusInitImage: - state.tenants.createTenant.fields.configure.prometheusInitImage, - selectedStorageClass: - state.tenants.createTenant.fields.nameTenant.selectedStorageClass, tenantSecurityContext: state.tenants.createTenant.fields.configure.tenantSecurityContext, - logSearchSecurityContext: - state.tenants.createTenant.fields.configure.logSearchSecurityContext, - logSearchPostgresSecurityContext: - state.tenants.createTenant.fields.configure - .logSearchPostgresSecurityContext, - prometheusSecurityContext: - state.tenants.createTenant.fields.configure.prometheusSecurityContext, }); const connector = connect(mapState, { diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx index b92775bed..90d9ee6e5 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx @@ -176,7 +176,7 @@ const Images = ({ value: logSearchImage, pattern: /^((.*?)\/(.*?):(.+))$/, customPatternMessage: - "Format must be of form: 'minio/logsearchapi:VERSION'", + "Format must be of form: 'minio/operator:VERSION'", }, { fieldKey: "kesImage", @@ -286,7 +286,7 @@ const Images = ({

Container Images

- Images used by the Tenant Deployment + Specify the container images used by the Tenant and it's features.
@@ -299,12 +299,30 @@ const Images = ({ updateField("imageName", e.target.value); cleanValidation("image"); }} - label="MinIO's Image" + label="MinIO" value={imageName} error={validationErrors["image"] || ""} - placeholder="E.g. minio/minio:RELEASE.2022-01-08T03-11-54Z" + placeholder="E.g. minio/minio:RELEASE.2022-02-26T02-54-46Z" /> + + + ) => { + updateField("kesImage", e.target.value); + cleanValidation("kesImage"); + }} + label="KES" + value={kesImage} + error={validationErrors["kesImage"] || ""} + placeholder="E.g. minio/kes:v0.17.6" + /> + + +

Log Search

+
- - - ) => { - updateField("kesImage", e.target.value); - cleanValidation("kesImage"); - }} - label="KES Image" - value={kesImage} - error={validationErrors["kesImage"] || ""} - placeholder="E.g. minio/kes:v0.14.0" + placeholder="E.g. minio/operator:v4.4.10" /> @@ -341,7 +345,7 @@ const Images = ({ updateField("logSearchPostgresImage", e.target.value); cleanValidation("logSearchPostgresImage"); }} - label="Log Search Postgres's Image" + label="PostgreSQL" value={logSearchPostgresImage} error={validationErrors["logSearchPostgresImage"] || ""} placeholder="E.g. library/postgres:13" @@ -355,12 +359,15 @@ const Images = ({ updateField("logSearchPostgresInitImage", e.target.value); cleanValidation("logSearchPostgresInitImage"); }} - label="Log Search Postgres's Init Image" + label="PostgreSQL Init" value={logSearchPostgresInitImage} error={validationErrors["logSearchPostgresInitImage"] || ""} placeholder="E.g. library/busybox:1.33.1" /> + +

Monitoring

+
+ +

Custom Container Registry

+
@@ -485,9 +495,9 @@ const mapState = (state: AppState) => ({ exposeMinIO: state.tenants.createTenant.fields.configure.exposeMinIO, exposeConsole: state.tenants.createTenant.fields.configure.exposeConsole, prometheusCustom: - state.tenants.createTenant.fields.configure.prometheusCustom, + state.tenants.createTenant.fields.configure.prometheusEnabled, tenantCustom: state.tenants.createTenant.fields.configure.tenantCustom, - logSearchCustom: state.tenants.createTenant.fields.configure.logSearchCustom, + logSearchCustom: state.tenants.createTenant.fields.configure.logSearchEnabled, logSearchVolumeSize: state.tenants.createTenant.fields.configure.logSearchVolumeSize, logSearchSizeFactor: diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/UpdateTenantModal.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/UpdateTenantModal.tsx index 0e558b041..3eaf6c414 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/UpdateTenantModal.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/UpdateTenantModal.tsx @@ -158,7 +158,7 @@ const UpdateTenantModal = ({ label={"MinIO's Image"} id={"minioImage"} name={"minioImage"} - placeholder={"E.g. minio/minio:RELEASE.2022-01-08T03-11-54Z"} + placeholder={"E.g. minio/minio:RELEASE.2022-02-26T02-54-46Z"} onChange={(e) => { setMinioImage(e.target.value); }} diff --git a/portal-ui/src/screens/Console/Tenants/reducer.ts b/portal-ui/src/screens/Console/Tenants/reducer.ts index a60b4b964..1af0aeea1 100644 --- a/portal-ui/src/screens/Console/Tenants/reducer.ts +++ b/portal-ui/src/screens/Console/Tenants/reducer.ts @@ -80,8 +80,8 @@ const initialState: ITenantState = { exposeMinIO: true, exposeConsole: true, tenantCustom: false, - logSearchCustom: false, - prometheusCustom: false, + logSearchEnabled: true, + prometheusEnabled: true, logSearchVolumeSize: "5", logSearchSizeFactor: "Gi", logSearchImage: "", @@ -632,8 +632,8 @@ export function tenantsReducer( exposeMinIO: true, exposeConsole: true, tenantCustom: false, - logSearchCustom: false, - prometheusCustom: false, + logSearchEnabled: true, + prometheusEnabled: true, logSearchVolumeSize: "5", logSearchSizeFactor: "Gi", logSearchSelectedStorageClass: "default", diff --git a/portal-ui/src/screens/Console/Tenants/types.ts b/portal-ui/src/screens/Console/Tenants/types.ts index 62c8c77e0..37c89d327 100644 --- a/portal-ui/src/screens/Console/Tenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/types.ts @@ -205,9 +205,9 @@ export interface IConfigureFields { imageRegistryPassword: string; exposeMinIO: boolean; exposeConsole: boolean; - prometheusCustom: boolean; + prometheusEnabled: boolean; tenantCustom: boolean; - logSearchCustom: boolean; + logSearchEnabled: boolean; logSearchVolumeSize: string; logSearchSizeFactor: string; logSearchSelectedStorageClass: string;