Move Add Tenant Configure Audit Log and Monitoring to their own tabs (#1636)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
@@ -18,9 +18,7 @@ package operatorapi
|
|||||||
|
|
||||||
// list of all console environment constants
|
// list of all console environment constants
|
||||||
const (
|
const (
|
||||||
ConsoleSubnetLicense = "CONSOLE_SUBNET_LICENSE"
|
|
||||||
ConsoleOperatorSAToken = "CONSOLE_OPERATOR_SA_TOKEN"
|
ConsoleOperatorSAToken = "CONSOLE_OPERATOR_SA_TOKEN"
|
||||||
MinIOSubnetLicense = "MINIO_SUBNET_LICENSE"
|
|
||||||
ConsoleMarketplace = "CONSOLE_OPERATOR_MARKETPLACE"
|
ConsoleMarketplace = "CONSOLE_OPERATOR_MARKETPLACE"
|
||||||
|
|
||||||
// Constants for prometheus annotations
|
// Constants for prometheus annotations
|
||||||
@@ -31,11 +29,5 @@ const (
|
|||||||
|
|
||||||
// Image versions
|
// Image versions
|
||||||
const (
|
const (
|
||||||
KESImageVersion = "minio/kes:v0.16.1"
|
KESImageVersion = "minio/kes:v0.17.6"
|
||||||
)
|
|
||||||
|
|
||||||
// K8s
|
|
||||||
|
|
||||||
const (
|
|
||||||
OperatorSubnetLicenseSecretName = "subnet-license"
|
|
||||||
)
|
)
|
||||||
|
|||||||
588
operatorapi/operator_tenant_add.go
Normal file
588
operatorapi/operator_tenant_add.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -27,7 +27,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -980,550 +979,6 @@ func getListTenantsResponse(session *models.Principal, params operator_api.ListT
|
|||||||
return listT, nil
|
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
|
// 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
|
// returns the name of the secret created/updated
|
||||||
func setImageRegistry(ctx context.Context, req *models.ImageRegistry, clientset v1.CoreV1Interface, namespace, tenantName string) (string, error) {
|
func setImageRegistry(ctx context.Context, req *models.ImageRegistry, clientset v1.CoreV1Interface, namespace, tenantName string) (string, error) {
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ import Images from "./Steps/Images";
|
|||||||
import PageLayout from "../../Common/Layout/PageLayout";
|
import PageLayout from "../../Common/Layout/PageLayout";
|
||||||
import BackLink from "../../../../common/BackLink";
|
import BackLink from "../../../../common/BackLink";
|
||||||
import TenantResources from "./Steps/TenantResources/TenantResources";
|
import TenantResources from "./Steps/TenantResources/TenantResources";
|
||||||
|
import ConfigLogSearch from "./Steps/ConfigLogSearch";
|
||||||
|
import ConfigPrometheus from "./Steps/ConfigPrometheus";
|
||||||
|
|
||||||
interface IAddTenantProps {
|
interface IAddTenantProps {
|
||||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||||
@@ -175,8 +177,8 @@ const AddTenant = ({
|
|||||||
const ecParity = fields.tenantSize.ecParity;
|
const ecParity = fields.tenantSize.ecParity;
|
||||||
const distribution = fields.tenantSize.distribution;
|
const distribution = fields.tenantSize.distribution;
|
||||||
const tenantCustom = fields.configure.tenantCustom;
|
const tenantCustom = fields.configure.tenantCustom;
|
||||||
const logSearchCustom = fields.configure.logSearchCustom;
|
const logSearchEnabled = fields.configure.logSearchEnabled;
|
||||||
const prometheusCustom = fields.configure.prometheusCustom;
|
const prometheusEnabled = fields.configure.prometheusEnabled;
|
||||||
const logSearchVolumeSize = fields.configure.logSearchVolumeSize;
|
const logSearchVolumeSize = fields.configure.logSearchVolumeSize;
|
||||||
const logSearchSelectedStorageClass =
|
const logSearchSelectedStorageClass =
|
||||||
fields.configure.logSearchSelectedStorageClass;
|
fields.configure.logSearchSelectedStorageClass;
|
||||||
@@ -313,7 +315,7 @@ const AddTenant = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logSearchCustom) {
|
if (logSearchEnabled) {
|
||||||
dataSend = {
|
dataSend = {
|
||||||
...dataSend,
|
...dataSend,
|
||||||
logSearchConfiguration: {
|
logSearchConfiguration: {
|
||||||
@@ -329,18 +331,9 @@ const AddTenant = ({
|
|||||||
postgres_securityContext: logSearchPostgresSecurityContext,
|
postgres_securityContext: logSearchPostgresSecurityContext,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
dataSend = {
|
|
||||||
...dataSend,
|
|
||||||
logSearchConfiguration: {
|
|
||||||
image: logSearchImage,
|
|
||||||
postgres_image: logSearchPostgresImage,
|
|
||||||
postgres_init_image: logSearchPostgresInitImage,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prometheusCustom) {
|
if (prometheusEnabled) {
|
||||||
dataSend = {
|
dataSend = {
|
||||||
...dataSend,
|
...dataSend,
|
||||||
prometheusConfiguration: {
|
prometheusConfiguration: {
|
||||||
@@ -355,15 +348,6 @@ const AddTenant = ({
|
|||||||
securityContext: prometheusSecurityContext,
|
securityContext: prometheusSecurityContext,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
dataSend = {
|
|
||||||
...dataSend,
|
|
||||||
prometheusConfiguration: {
|
|
||||||
image: prometheusImage,
|
|
||||||
sidecar_image: prometheusSidecarImage,
|
|
||||||
init_image: prometheusInitImage,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let tenantCerts: any = null;
|
let tenantCerts: any = null;
|
||||||
@@ -753,6 +737,18 @@ const AddTenant = ({
|
|||||||
componentRender: <Encryption />,
|
componentRender: <Encryption />,
|
||||||
buttons: [cancelButton, createButton],
|
buttons: [cancelButton, createButton],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Audit Log",
|
||||||
|
advancedOnly: true,
|
||||||
|
componentRender: <ConfigLogSearch />,
|
||||||
|
buttons: [cancelButton, createButton],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Monitoring",
|
||||||
|
advancedOnly: true,
|
||||||
|
componentRender: <ConfigPrometheus />,
|
||||||
|
buttons: [cancelButton, createButton],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let filteredWizardSteps = wizardSteps;
|
let filteredWizardSteps = wizardSteps;
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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<any>({});
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Paper className={classes.paperWrapper}>
|
||||||
|
<div className={classes.headerElement}>
|
||||||
|
<h3 className={classes.h3Section}>Audit Log</h3>
|
||||||
|
<span className={classes.descriptionText}>
|
||||||
|
Audit log deploys a small PostgreSQL database and store access logs of
|
||||||
|
all calls into the tenant.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Grid item xs={12} className={classes.configSectionItem}>
|
||||||
|
<FormSwitchWrapper
|
||||||
|
value="logSearchConfig"
|
||||||
|
id="log_search_configuration"
|
||||||
|
name="log_search_configuration"
|
||||||
|
checked={logSearchEnabled}
|
||||||
|
onChange={(e) => {
|
||||||
|
const targetD = e.target;
|
||||||
|
const checked = targetD.checked;
|
||||||
|
|
||||||
|
updateField("logSearchEnabled", checked);
|
||||||
|
}}
|
||||||
|
label={"Enabled"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
{logSearchEnabled && (
|
||||||
|
<Grid xs={12} className={classes.logSearchEnabledFields}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<SelectWrapper
|
||||||
|
id="log_search_storage_class"
|
||||||
|
name="log_search_storage_class"
|
||||||
|
onChange={(e: SelectChangeEvent<string>) => {
|
||||||
|
updateField(
|
||||||
|
"logSearchSelectedStorageClass",
|
||||||
|
e.target.value as string
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
label="Log Search Storage Class"
|
||||||
|
value={logSearchSelectedStorageClass}
|
||||||
|
options={configureSTClasses}
|
||||||
|
disabled={configureSTClasses.length < 1}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div className={classes.multiContainer}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="log_search_volume_size"
|
||||||
|
name="log_search_volume_size"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateField("logSearchVolumeSize", e.target.value);
|
||||||
|
cleanValidation("log_search_volume_size");
|
||||||
|
}}
|
||||||
|
label="Storage Size"
|
||||||
|
overlayObject={
|
||||||
|
<InputUnitMenu
|
||||||
|
id={"size-unit"}
|
||||||
|
onUnitChange={() => {}}
|
||||||
|
unitSelected={"Gi"}
|
||||||
|
unitsList={[{ label: "Gi", value: "Gi" }]}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={logSearchVolumeSize}
|
||||||
|
required
|
||||||
|
error={validationErrors["log_search_volume_size"] || ""}
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<fieldset
|
||||||
|
className={`${classes.fieldGroup} ${classes.fieldSpaceTop}`}
|
||||||
|
>
|
||||||
|
<legend className={classes.descriptionText}>
|
||||||
|
SecurityContext for LogSearch
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div
|
||||||
|
className={`${classes.multiContainer} ${classes.responsiveSectionItem}`}
|
||||||
|
>
|
||||||
|
<div className={classes.configSectionItem}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="logSearch_securityContext_runAsUser"
|
||||||
|
name="logSearch_securityContext_runAsUser"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.configSectionItem}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="logSearch_securityContext_runAsGroup"
|
||||||
|
name="logSearch_securityContext_runAsGroup"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.configSectionItem}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="logSearch_securityContext_fsGroup"
|
||||||
|
name="logSearch_securityContext_fsGroup"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateField("logSearchSecurityContext", {
|
||||||
|
...logSearchSecurityContext,
|
||||||
|
fsGroup: e.target.value,
|
||||||
|
});
|
||||||
|
cleanValidation("logSearch_securityContext_fsGroup");
|
||||||
|
}}
|
||||||
|
label="FsGroup"
|
||||||
|
value={logSearchSecurityContext.fsGroup}
|
||||||
|
required
|
||||||
|
error={
|
||||||
|
validationErrors["logSearch_securityContext_fsGroup"] ||
|
||||||
|
""
|
||||||
|
}
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<br />
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div className={classes.multiContainer}>
|
||||||
|
<FormSwitchWrapper
|
||||||
|
value="logSearchSecurityContextRunAsNonRoot"
|
||||||
|
id="logSearch_securityContext_runAsNonRoot"
|
||||||
|
name="logSearch_securityContext_runAsNonRoot"
|
||||||
|
checked={logSearchSecurityContext.runAsNonRoot}
|
||||||
|
onChange={(e) => {
|
||||||
|
const targetD = e.target;
|
||||||
|
const checked = targetD.checked;
|
||||||
|
updateField("logSearchSecurityContext", {
|
||||||
|
...logSearchSecurityContext,
|
||||||
|
runAsNonRoot: checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
label={"Do not run as Root"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset className={classes.fieldGroup}>
|
||||||
|
<legend className={classes.descriptionText}>
|
||||||
|
SecurityContext for PostgreSQL
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div
|
||||||
|
className={`${classes.multiContainer} ${classes.responsiveSectionItem}`}
|
||||||
|
>
|
||||||
|
<div className={classes.configSectionItem}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="postgres_securityContext_runAsUser"
|
||||||
|
name="postgres_securityContext_runAsUser"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.configSectionItem}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="postgres_securityContext_runAsGroup"
|
||||||
|
name="postgres_securityContext_runAsGroup"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.configSectionItem}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="postgres_securityContext_fsGroup"
|
||||||
|
name="postgres_securityContext_fsGroup"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateField("logSearchPostgresSecurityContext", {
|
||||||
|
...logSearchPostgresSecurityContext,
|
||||||
|
fsGroup: e.target.value,
|
||||||
|
});
|
||||||
|
cleanValidation("postgres_securityContext_fsGroup");
|
||||||
|
}}
|
||||||
|
label="FsGroup"
|
||||||
|
value={logSearchPostgresSecurityContext.fsGroup}
|
||||||
|
required
|
||||||
|
error={
|
||||||
|
validationErrors["postgres_securityContext_fsGroup"] || ""
|
||||||
|
}
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<br />
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div className={classes.multiContainer}>
|
||||||
|
<FormSwitchWrapper
|
||||||
|
value="postgresSecurityContextRunAsNonRoot"
|
||||||
|
id="postgres_securityContext_runAsNonRoot"
|
||||||
|
name="postgres_securityContext_runAsNonRoot"
|
||||||
|
checked={logSearchPostgresSecurityContext.runAsNonRoot}
|
||||||
|
onChange={(e) => {
|
||||||
|
const targetD = e.target;
|
||||||
|
const checked = targetD.checked;
|
||||||
|
updateField("logSearchPostgresSecurityContext", {
|
||||||
|
...logSearchPostgresSecurityContext,
|
||||||
|
runAsNonRoot: checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
label={"Do not run as Root"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</fieldset>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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<any>({});
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Paper className={classes.paperWrapper}>
|
||||||
|
<div className={classes.headerElement}>
|
||||||
|
<h3 className={classes.h3Section}>Monitoring</h3>
|
||||||
|
<span className={classes.descriptionText}>
|
||||||
|
A small Prometheus will be deployed to keep metrics about the tenant.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Grid item xs={12} className={classes.configSectionItem}>
|
||||||
|
<FormSwitchWrapper
|
||||||
|
value="prometheusConfig"
|
||||||
|
id="prometheus_configuration"
|
||||||
|
name="prometheus_configuration"
|
||||||
|
checked={prometheusEnabled}
|
||||||
|
onChange={(e) => {
|
||||||
|
const targetD = e.target;
|
||||||
|
const checked = targetD.checked;
|
||||||
|
|
||||||
|
updateField("prometheusEnabled", checked);
|
||||||
|
}}
|
||||||
|
label={"Enabled"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
{prometheusEnabled && (
|
||||||
|
<Grid xs={12} className={classes.prometheusEnabledFields}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<SelectWrapper
|
||||||
|
id="prometheus_storage_class"
|
||||||
|
name="prometheus_storage_class"
|
||||||
|
onChange={(e: SelectChangeEvent<string>) => {
|
||||||
|
updateField(
|
||||||
|
"prometheusSelectedStorageClass",
|
||||||
|
e.target.value as string
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
label="Prometheus Storage Class"
|
||||||
|
value={prometheusSelectedStorageClass}
|
||||||
|
options={configureSTClasses}
|
||||||
|
disabled={configureSTClasses.length < 1}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div className={classes.multiContainer}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="prometheus_volume_size"
|
||||||
|
name="prometheus_volume_size"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateField("prometheusVolumeSize", e.target.value);
|
||||||
|
cleanValidation("prometheus_volume_size");
|
||||||
|
}}
|
||||||
|
label="Storage Size"
|
||||||
|
overlayObject={
|
||||||
|
<InputUnitMenu
|
||||||
|
id={"size-unit"}
|
||||||
|
onUnitChange={() => {}}
|
||||||
|
unitSelected={"Gi"}
|
||||||
|
unitsList={[{ label: "Gi", value: "Gi" }]}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={prometheusVolumeSize}
|
||||||
|
required
|
||||||
|
error={validationErrors["prometheus_volume_size"] || ""}
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<fieldset
|
||||||
|
className={`${classes.fieldGroup} ${classes.fieldSpaceTop}`}
|
||||||
|
>
|
||||||
|
<legend className={classes.descriptionText}>
|
||||||
|
SecurityContext for Prometheus
|
||||||
|
</legend>
|
||||||
|
<Grid item xs={12} className={classes.configSectionItem}>
|
||||||
|
<div
|
||||||
|
className={`${classes.multiContainer} ${classes.responsiveSectionItem}`}
|
||||||
|
>
|
||||||
|
<div className={classes.configSectionItem}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="prometheus_securityContext_runAsUser"
|
||||||
|
name="prometheus_securityContext_runAsUser"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.configSectionItem}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="prometheus_securityContext_runAsGroup"
|
||||||
|
name="prometheus_securityContext_runAsGroup"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.configSectionItem}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
type="number"
|
||||||
|
id="prometheus_securityContext_fsGroup"
|
||||||
|
name="prometheus_securityContext_fsGroup"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateField("prometheusSecurityContext", {
|
||||||
|
...prometheusSecurityContext,
|
||||||
|
fsGroup: e.target.value,
|
||||||
|
});
|
||||||
|
cleanValidation("prometheus_securityContext_fsGroup");
|
||||||
|
}}
|
||||||
|
label="FsGroup"
|
||||||
|
value={prometheusSecurityContext.fsGroup}
|
||||||
|
required
|
||||||
|
error={
|
||||||
|
validationErrors["prometheus_securityContext_fsGroup"] ||
|
||||||
|
""
|
||||||
|
}
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} className={classes.configSectionItem}>
|
||||||
|
<div
|
||||||
|
className={`${classes.multiContainer} ${classes.fieldSpaceTop}`}
|
||||||
|
>
|
||||||
|
<FormSwitchWrapper
|
||||||
|
value="prometheusSecurityContextRunAsNonRoot"
|
||||||
|
id="prometheus_securityContext_runAsNonRoot"
|
||||||
|
name="prometheus_securityContext_runAsNonRoot"
|
||||||
|
checked={prometheusSecurityContext.runAsNonRoot}
|
||||||
|
onChange={(e) => {
|
||||||
|
const targetD = e.target;
|
||||||
|
const checked = targetD.checked;
|
||||||
|
updateField("prometheusSecurityContext", {
|
||||||
|
...prometheusSecurityContext,
|
||||||
|
runAsNonRoot: checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
label={"Do not run as Root"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</fieldset>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
@@ -19,7 +19,7 @@ import { connect } from "react-redux";
|
|||||||
import { Theme } from "@mui/material/styles";
|
import { Theme } from "@mui/material/styles";
|
||||||
import createStyles from "@mui/styles/createStyles";
|
import createStyles from "@mui/styles/createStyles";
|
||||||
import withStyles from "@mui/styles/withStyles";
|
import withStyles from "@mui/styles/withStyles";
|
||||||
import { Grid, Paper, SelectChangeEvent } from "@mui/material";
|
import { Grid, Paper } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
createTenantCommon,
|
createTenantCommon,
|
||||||
modalBasic,
|
modalBasic,
|
||||||
@@ -34,44 +34,16 @@ import {
|
|||||||
} from "../../../../../utils/validationFunctions";
|
} from "../../../../../utils/validationFunctions";
|
||||||
import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||||
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||||
import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
|
||||||
import { ISecurityContext } from "../../types";
|
import { ISecurityContext } from "../../types";
|
||||||
import InputUnitMenu from "../../../Common/FormComponents/InputUnitMenu/InputUnitMenu";
|
|
||||||
|
|
||||||
interface IConfigureProps {
|
interface IConfigureProps {
|
||||||
updateAddField: typeof updateAddField;
|
updateAddField: typeof updateAddField;
|
||||||
isPageValid: typeof isPageValid;
|
isPageValid: typeof isPageValid;
|
||||||
storageClasses: any;
|
|
||||||
classes: any;
|
classes: any;
|
||||||
customImage: boolean;
|
|
||||||
imageName: string;
|
|
||||||
customDockerhub: boolean;
|
|
||||||
imageRegistry: string;
|
|
||||||
imageRegistryUsername: string;
|
|
||||||
imageRegistryPassword: string;
|
|
||||||
exposeMinIO: boolean;
|
exposeMinIO: boolean;
|
||||||
exposeConsole: boolean;
|
exposeConsole: boolean;
|
||||||
prometheusCustom: boolean;
|
|
||||||
tenantCustom: 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;
|
tenantSecurityContext: ISecurityContext;
|
||||||
logSearchSecurityContext: ISecurityContext;
|
|
||||||
logSearchPostgresSecurityContext: ISecurityContext;
|
|
||||||
prometheusSecurityContext: ISecurityContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
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: {
|
fieldSpaceTop: {
|
||||||
marginTop: 15,
|
marginTop: 15,
|
||||||
},
|
},
|
||||||
prometheusCustomFields: {
|
|
||||||
marginLeft: 20, // 2nd Level(15+15)
|
|
||||||
padding: 10,
|
|
||||||
width: "90%",
|
|
||||||
margin: "auto",
|
|
||||||
},
|
|
||||||
...modalBasic,
|
...modalBasic,
|
||||||
...wizardCommon,
|
...wizardCommon,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Configure = ({
|
const Configure = ({
|
||||||
classes,
|
classes,
|
||||||
storageClasses,
|
|
||||||
customImage,
|
|
||||||
imageName,
|
|
||||||
customDockerhub,
|
|
||||||
imageRegistry,
|
|
||||||
imageRegistryUsername,
|
|
||||||
imageRegistryPassword,
|
|
||||||
exposeMinIO,
|
exposeMinIO,
|
||||||
exposeConsole,
|
exposeConsole,
|
||||||
prometheusCustom,
|
|
||||||
tenantCustom,
|
tenantCustom,
|
||||||
logSearchCustom,
|
|
||||||
logSearchVolumeSize,
|
|
||||||
logSearchSizeFactor,
|
|
||||||
logSearchImage,
|
|
||||||
kesImage,
|
|
||||||
logSearchPostgresImage,
|
|
||||||
logSearchPostgresInitImage,
|
|
||||||
prometheusVolumeSize,
|
|
||||||
prometheusSizeFactor,
|
|
||||||
logSearchSelectedStorageClass,
|
|
||||||
prometheusSelectedStorageClass,
|
|
||||||
prometheusImage,
|
|
||||||
prometheusSidecarImage,
|
|
||||||
prometheusInitImage,
|
|
||||||
updateAddField,
|
updateAddField,
|
||||||
isPageValid,
|
isPageValid,
|
||||||
selectedStorageClass,
|
|
||||||
tenantSecurityContext,
|
tenantSecurityContext,
|
||||||
logSearchSecurityContext,
|
|
||||||
logSearchPostgresSecurityContext,
|
|
||||||
prometheusSecurityContext,
|
|
||||||
}: IConfigureProps) => {
|
}: IConfigureProps) => {
|
||||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||||
|
|
||||||
const configureSTClasses = [
|
|
||||||
{ label: "Default", value: "default" },
|
|
||||||
...storageClasses,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Common
|
// Common
|
||||||
const updateField = useCallback(
|
const updateField = useCallback(
|
||||||
(field: string, value: any) => {
|
(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);
|
const commonVal = commonFormValidation(customAccountValidation);
|
||||||
|
|
||||||
isPageValid("configure", Object.keys(commonVal).length === 0);
|
isPageValid("configure", Object.keys(commonVal).length === 0);
|
||||||
|
|
||||||
setValidationErrors(commonVal);
|
setValidationErrors(commonVal);
|
||||||
}, [
|
}, [isPageValid, tenantCustom, tenantSecurityContext]);
|
||||||
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,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const cleanValidation = (fieldName: string) => {
|
const cleanValidation = (fieldName: string) => {
|
||||||
setValidationErrors(clearValidationError(validationErrors, fieldName));
|
setValidationErrors(clearValidationError(validationErrors, fieldName));
|
||||||
@@ -493,9 +163,10 @@ const Configure = ({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.headerElement}>
|
<div className={classes.headerElement}>
|
||||||
<h3 className={classes.h3Section}>Expose Services</h3>
|
<h3 className={classes.h3Section}>Services</h3>
|
||||||
<span className={classes.descriptionText}>
|
<span className={classes.descriptionText}>
|
||||||
Whether the tenant's services should request an external IP.
|
Whether the tenant's services should request an external IP via
|
||||||
|
LoadBalancer service type.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Grid item xs={12} className={classes.configSectionItem}>
|
<Grid item xs={12} className={classes.configSectionItem}>
|
||||||
@@ -529,13 +200,6 @@ const Configure = ({
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<div className={classes.headerElement}>
|
|
||||||
<h3 className={classes.h3Section}>Additional Configurations</h3>
|
|
||||||
<span className={classes.descriptionText}>
|
|
||||||
Configure SecurityContext, Storage Classes & Storage size for Log
|
|
||||||
Search, Prometheus add-ons and your Tenant
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Grid item xs={12} className={classes.configSectionItem}>
|
<Grid item xs={12} className={classes.configSectionItem}>
|
||||||
<FormSwitchWrapper
|
<FormSwitchWrapper
|
||||||
value="tenantConfig"
|
value="tenantConfig"
|
||||||
@@ -548,7 +212,7 @@ const Configure = ({
|
|||||||
|
|
||||||
updateField("tenantCustom", checked);
|
updateField("tenantCustom", checked);
|
||||||
}}
|
}}
|
||||||
label={"Override Tenant defaults"}
|
label={"Security Context"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
{tenantCustom && (
|
{tenantCustom && (
|
||||||
@@ -650,485 +314,16 @@ const Configure = ({
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
<Grid item xs={12} className={classes.configSectionItem}>
|
|
||||||
<FormSwitchWrapper
|
|
||||||
value="logSearchConfig"
|
|
||||||
id="log_search_configuration"
|
|
||||||
name="log_search_configuration"
|
|
||||||
checked={logSearchCustom}
|
|
||||||
onChange={(e) => {
|
|
||||||
const targetD = e.target;
|
|
||||||
const checked = targetD.checked;
|
|
||||||
|
|
||||||
updateField("logSearchCustom", checked);
|
|
||||||
}}
|
|
||||||
label={"Override Log Search defaults"}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
{logSearchCustom && (
|
|
||||||
<Grid xs={12} className={classes.logSearchCustomFields}>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<SelectWrapper
|
|
||||||
id="log_search_storage_class"
|
|
||||||
name="log_search_storage_class"
|
|
||||||
onChange={(e: SelectChangeEvent<string>) => {
|
|
||||||
updateField(
|
|
||||||
"logSearchSelectedStorageClass",
|
|
||||||
e.target.value as string
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
label="Log Search Storage Class"
|
|
||||||
value={logSearchSelectedStorageClass}
|
|
||||||
options={configureSTClasses}
|
|
||||||
disabled={configureSTClasses.length < 1}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<div className={classes.multiContainer}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="log_search_volume_size"
|
|
||||||
name="log_search_volume_size"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateField("logSearchVolumeSize", e.target.value);
|
|
||||||
cleanValidation("log_search_volume_size");
|
|
||||||
}}
|
|
||||||
label="Storage Size"
|
|
||||||
overlayObject={
|
|
||||||
<InputUnitMenu
|
|
||||||
id={"size-unit"}
|
|
||||||
onUnitChange={() => {}}
|
|
||||||
unitSelected={"Gi"}
|
|
||||||
unitsList={[{ label: "Gi", value: "Gi" }]}
|
|
||||||
disabled={true}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
value={logSearchVolumeSize}
|
|
||||||
required
|
|
||||||
error={validationErrors["log_search_volume_size"] || ""}
|
|
||||||
min="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<fieldset
|
|
||||||
className={`${classes.fieldGroup} ${classes.fieldSpaceTop}`}
|
|
||||||
>
|
|
||||||
<legend className={classes.descriptionText}>
|
|
||||||
SecurityContext for LogSearch
|
|
||||||
</legend>
|
|
||||||
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<div
|
|
||||||
className={`${classes.multiContainer} ${classes.responsiveSectionItem}`}
|
|
||||||
>
|
|
||||||
<div className={classes.configSectionItem}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="logSearch_securityContext_runAsUser"
|
|
||||||
name="logSearch_securityContext_runAsUser"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={classes.configSectionItem}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="logSearch_securityContext_runAsGroup"
|
|
||||||
name="logSearch_securityContext_runAsGroup"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={classes.configSectionItem}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="logSearch_securityContext_fsGroup"
|
|
||||||
name="logSearch_securityContext_fsGroup"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateField("logSearchSecurityContext", {
|
|
||||||
...logSearchSecurityContext,
|
|
||||||
fsGroup: e.target.value,
|
|
||||||
});
|
|
||||||
cleanValidation("logSearch_securityContext_fsGroup");
|
|
||||||
}}
|
|
||||||
label="FsGroup"
|
|
||||||
value={logSearchSecurityContext.fsGroup}
|
|
||||||
required
|
|
||||||
error={
|
|
||||||
validationErrors["logSearch_securityContext_fsGroup"] ||
|
|
||||||
""
|
|
||||||
}
|
|
||||||
min="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
<br />
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<div className={classes.multiContainer}>
|
|
||||||
<FormSwitchWrapper
|
|
||||||
value="logSearchSecurityContextRunAsNonRoot"
|
|
||||||
id="logSearch_securityContext_runAsNonRoot"
|
|
||||||
name="logSearch_securityContext_runAsNonRoot"
|
|
||||||
checked={logSearchSecurityContext.runAsNonRoot}
|
|
||||||
onChange={(e) => {
|
|
||||||
const targetD = e.target;
|
|
||||||
const checked = targetD.checked;
|
|
||||||
updateField("logSearchSecurityContext", {
|
|
||||||
...logSearchSecurityContext,
|
|
||||||
runAsNonRoot: checked,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
label={"Do not run as Root"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset className={classes.fieldGroup}>
|
|
||||||
<legend className={classes.descriptionText}>
|
|
||||||
SecurityContext for PostgreSQL
|
|
||||||
</legend>
|
|
||||||
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<div
|
|
||||||
className={`${classes.multiContainer} ${classes.responsiveSectionItem}`}
|
|
||||||
>
|
|
||||||
<div className={classes.configSectionItem}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="postgres_securityContext_runAsUser"
|
|
||||||
name="postgres_securityContext_runAsUser"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={classes.configSectionItem}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="postgres_securityContext_runAsGroup"
|
|
||||||
name="postgres_securityContext_runAsGroup"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={classes.configSectionItem}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="postgres_securityContext_fsGroup"
|
|
||||||
name="postgres_securityContext_fsGroup"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateField("logSearchPostgresSecurityContext", {
|
|
||||||
...logSearchPostgresSecurityContext,
|
|
||||||
fsGroup: e.target.value,
|
|
||||||
});
|
|
||||||
cleanValidation("postgres_securityContext_fsGroup");
|
|
||||||
}}
|
|
||||||
label="FsGroup"
|
|
||||||
value={logSearchPostgresSecurityContext.fsGroup}
|
|
||||||
required
|
|
||||||
error={
|
|
||||||
validationErrors["postgres_securityContext_fsGroup"] || ""
|
|
||||||
}
|
|
||||||
min="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
<br />
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<div className={classes.multiContainer}>
|
|
||||||
<FormSwitchWrapper
|
|
||||||
value="postgresSecurityContextRunAsNonRoot"
|
|
||||||
id="postgres_securityContext_runAsNonRoot"
|
|
||||||
name="postgres_securityContext_runAsNonRoot"
|
|
||||||
checked={logSearchPostgresSecurityContext.runAsNonRoot}
|
|
||||||
onChange={(e) => {
|
|
||||||
const targetD = e.target;
|
|
||||||
const checked = targetD.checked;
|
|
||||||
updateField("logSearchPostgresSecurityContext", {
|
|
||||||
...logSearchPostgresSecurityContext,
|
|
||||||
runAsNonRoot: checked,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
label={"Do not run as Root"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
</fieldset>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
<Grid item xs={12} className={classes.configSectionItem}>
|
|
||||||
<FormSwitchWrapper
|
|
||||||
value="prometheusConfig"
|
|
||||||
id="prometheus_configuration"
|
|
||||||
name="prometheus_configuration"
|
|
||||||
checked={prometheusCustom}
|
|
||||||
onChange={(e) => {
|
|
||||||
const targetD = e.target;
|
|
||||||
const checked = targetD.checked;
|
|
||||||
|
|
||||||
updateField("prometheusCustom", checked);
|
|
||||||
}}
|
|
||||||
label={"Override Prometheus defaults"}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
{prometheusCustom && (
|
|
||||||
<Grid xs={12} className={classes.prometheusCustomFields}>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<SelectWrapper
|
|
||||||
id="prometheus_storage_class"
|
|
||||||
name="prometheus_storage_class"
|
|
||||||
onChange={(e: SelectChangeEvent<string>) => {
|
|
||||||
updateField(
|
|
||||||
"prometheusSelectedStorageClass",
|
|
||||||
e.target.value as string
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
label="Prometheus Storage Class"
|
|
||||||
value={prometheusSelectedStorageClass}
|
|
||||||
options={configureSTClasses}
|
|
||||||
disabled={configureSTClasses.length < 1}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<div className={classes.multiContainer}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="prometheus_volume_size"
|
|
||||||
name="prometheus_volume_size"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateField("prometheusVolumeSize", e.target.value);
|
|
||||||
cleanValidation("prometheus_volume_size");
|
|
||||||
}}
|
|
||||||
label="Storage Size"
|
|
||||||
overlayObject={
|
|
||||||
<InputUnitMenu
|
|
||||||
id={"size-unit"}
|
|
||||||
onUnitChange={() => {}}
|
|
||||||
unitSelected={"Gi"}
|
|
||||||
unitsList={[{ label: "Gi", value: "Gi" }]}
|
|
||||||
disabled={true}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
value={prometheusVolumeSize}
|
|
||||||
required
|
|
||||||
error={validationErrors["prometheus_volume_size"] || ""}
|
|
||||||
min="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
<fieldset
|
|
||||||
className={`${classes.fieldGroup} ${classes.fieldSpaceTop}`}
|
|
||||||
>
|
|
||||||
<legend className={classes.descriptionText}>
|
|
||||||
SecurityContext for Prometheus
|
|
||||||
</legend>
|
|
||||||
<Grid item xs={12} className={classes.configSectionItem}>
|
|
||||||
<div
|
|
||||||
className={`${classes.multiContainer} ${classes.responsiveSectionItem}`}
|
|
||||||
>
|
|
||||||
<div className={classes.configSectionItem}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="prometheus_securityContext_runAsUser"
|
|
||||||
name="prometheus_securityContext_runAsUser"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={classes.configSectionItem}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="prometheus_securityContext_runAsGroup"
|
|
||||||
name="prometheus_securityContext_runAsGroup"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={classes.configSectionItem}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
type="number"
|
|
||||||
id="prometheus_securityContext_fsGroup"
|
|
||||||
name="prometheus_securityContext_fsGroup"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateField("prometheusSecurityContext", {
|
|
||||||
...prometheusSecurityContext,
|
|
||||||
fsGroup: e.target.value,
|
|
||||||
});
|
|
||||||
cleanValidation("prometheus_securityContext_fsGroup");
|
|
||||||
}}
|
|
||||||
label="FsGroup"
|
|
||||||
value={prometheusSecurityContext.fsGroup}
|
|
||||||
required
|
|
||||||
error={
|
|
||||||
validationErrors["prometheus_securityContext_fsGroup"] ||
|
|
||||||
""
|
|
||||||
}
|
|
||||||
min="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} className={classes.configSectionItem}>
|
|
||||||
<div
|
|
||||||
className={`${classes.multiContainer} ${classes.fieldSpaceTop}`}
|
|
||||||
>
|
|
||||||
<FormSwitchWrapper
|
|
||||||
value="prometheusSecurityContextRunAsNonRoot"
|
|
||||||
id="prometheus_securityContext_runAsNonRoot"
|
|
||||||
name="prometheus_securityContext_runAsNonRoot"
|
|
||||||
checked={prometheusSecurityContext.runAsNonRoot}
|
|
||||||
onChange={(e) => {
|
|
||||||
const targetD = e.target;
|
|
||||||
const checked = targetD.checked;
|
|
||||||
updateField("prometheusSecurityContext", {
|
|
||||||
...prometheusSecurityContext,
|
|
||||||
runAsNonRoot: checked,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
label={"Do not run as Root"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
</fieldset>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapState = (state: AppState) => ({
|
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,
|
exposeMinIO: state.tenants.createTenant.fields.configure.exposeMinIO,
|
||||||
exposeConsole: state.tenants.createTenant.fields.configure.exposeConsole,
|
exposeConsole: state.tenants.createTenant.fields.configure.exposeConsole,
|
||||||
prometheusCustom:
|
|
||||||
state.tenants.createTenant.fields.configure.prometheusCustom,
|
|
||||||
tenantCustom: state.tenants.createTenant.fields.configure.tenantCustom,
|
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:
|
tenantSecurityContext:
|
||||||
state.tenants.createTenant.fields.configure.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, {
|
const connector = connect(mapState, {
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ const Images = ({
|
|||||||
value: logSearchImage,
|
value: logSearchImage,
|
||||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||||
customPatternMessage:
|
customPatternMessage:
|
||||||
"Format must be of form: 'minio/logsearchapi:VERSION'",
|
"Format must be of form: 'minio/operator:VERSION'",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldKey: "kesImage",
|
fieldKey: "kesImage",
|
||||||
@@ -286,7 +286,7 @@ const Images = ({
|
|||||||
<div className={classes.headerElement}>
|
<div className={classes.headerElement}>
|
||||||
<h3 className={classes.h3Section}>Container Images</h3>
|
<h3 className={classes.h3Section}>Container Images</h3>
|
||||||
<span className={classes.descriptionText}>
|
<span className={classes.descriptionText}>
|
||||||
Images used by the Tenant Deployment
|
Specify the container images used by the Tenant and it's features.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -299,12 +299,30 @@ const Images = ({
|
|||||||
updateField("imageName", e.target.value);
|
updateField("imageName", e.target.value);
|
||||||
cleanValidation("image");
|
cleanValidation("image");
|
||||||
}}
|
}}
|
||||||
label="MinIO's Image"
|
label="MinIO"
|
||||||
value={imageName}
|
value={imageName}
|
||||||
error={validationErrors["image"] || ""}
|
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"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12} className={classes.formFieldRow}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
id="kesImage"
|
||||||
|
name="kesImage"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateField("kesImage", e.target.value);
|
||||||
|
cleanValidation("kesImage");
|
||||||
|
}}
|
||||||
|
label="KES"
|
||||||
|
value={kesImage}
|
||||||
|
error={validationErrors["kesImage"] || ""}
|
||||||
|
placeholder="E.g. minio/kes:v0.17.6"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} className={classes.formFieldRow}>
|
||||||
|
<h4>Log Search</h4>
|
||||||
|
</Grid>
|
||||||
<Grid item xs={12} className={classes.formFieldRow}>
|
<Grid item xs={12} className={classes.formFieldRow}>
|
||||||
<InputBoxWrapper
|
<InputBoxWrapper
|
||||||
id="logSearchImage"
|
id="logSearchImage"
|
||||||
@@ -313,24 +331,10 @@ const Images = ({
|
|||||||
updateField("logSearchImage", e.target.value);
|
updateField("logSearchImage", e.target.value);
|
||||||
cleanValidation("logSearchImage");
|
cleanValidation("logSearchImage");
|
||||||
}}
|
}}
|
||||||
label="Log Search API's Image"
|
label="API"
|
||||||
value={logSearchImage}
|
value={logSearchImage}
|
||||||
error={validationErrors["logSearchImage"] || ""}
|
error={validationErrors["logSearchImage"] || ""}
|
||||||
placeholder="E.g. minio/logsearchapi:v4.1.1"
|
placeholder="E.g. minio/operator:v4.4.10"
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} className={classes.formFieldRow}>
|
|
||||||
<InputBoxWrapper
|
|
||||||
id="kesImage"
|
|
||||||
name="kesImage"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateField("kesImage", e.target.value);
|
|
||||||
cleanValidation("kesImage");
|
|
||||||
}}
|
|
||||||
label="KES Image"
|
|
||||||
value={kesImage}
|
|
||||||
error={validationErrors["kesImage"] || ""}
|
|
||||||
placeholder="E.g. minio/kes:v0.14.0"
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} className={classes.formFieldRow}>
|
<Grid item xs={12} className={classes.formFieldRow}>
|
||||||
@@ -341,7 +345,7 @@ const Images = ({
|
|||||||
updateField("logSearchPostgresImage", e.target.value);
|
updateField("logSearchPostgresImage", e.target.value);
|
||||||
cleanValidation("logSearchPostgresImage");
|
cleanValidation("logSearchPostgresImage");
|
||||||
}}
|
}}
|
||||||
label="Log Search Postgres's Image"
|
label="PostgreSQL"
|
||||||
value={logSearchPostgresImage}
|
value={logSearchPostgresImage}
|
||||||
error={validationErrors["logSearchPostgresImage"] || ""}
|
error={validationErrors["logSearchPostgresImage"] || ""}
|
||||||
placeholder="E.g. library/postgres:13"
|
placeholder="E.g. library/postgres:13"
|
||||||
@@ -355,12 +359,15 @@ const Images = ({
|
|||||||
updateField("logSearchPostgresInitImage", e.target.value);
|
updateField("logSearchPostgresInitImage", e.target.value);
|
||||||
cleanValidation("logSearchPostgresInitImage");
|
cleanValidation("logSearchPostgresInitImage");
|
||||||
}}
|
}}
|
||||||
label="Log Search Postgres's Init Image"
|
label="PostgreSQL Init"
|
||||||
value={logSearchPostgresInitImage}
|
value={logSearchPostgresInitImage}
|
||||||
error={validationErrors["logSearchPostgresInitImage"] || ""}
|
error={validationErrors["logSearchPostgresInitImage"] || ""}
|
||||||
placeholder="E.g. library/busybox:1.33.1"
|
placeholder="E.g. library/busybox:1.33.1"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={12} className={classes.formFieldRow}>
|
||||||
|
<h4>Monitoring</h4>
|
||||||
|
</Grid>
|
||||||
<Grid item xs={12} className={classes.formFieldRow}>
|
<Grid item xs={12} className={classes.formFieldRow}>
|
||||||
<InputBoxWrapper
|
<InputBoxWrapper
|
||||||
id="prometheusImage"
|
id="prometheusImage"
|
||||||
@@ -369,7 +376,7 @@ const Images = ({
|
|||||||
updateField("prometheusImage", e.target.value);
|
updateField("prometheusImage", e.target.value);
|
||||||
cleanValidation("prometheusImage");
|
cleanValidation("prometheusImage");
|
||||||
}}
|
}}
|
||||||
label="Prometheus Image"
|
label="Prometheus"
|
||||||
value={prometheusImage}
|
value={prometheusImage}
|
||||||
error={validationErrors["prometheusImage"] || ""}
|
error={validationErrors["prometheusImage"] || ""}
|
||||||
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
||||||
@@ -383,7 +390,7 @@ const Images = ({
|
|||||||
updateField("prometheusSidecarImage", e.target.value);
|
updateField("prometheusSidecarImage", e.target.value);
|
||||||
cleanValidation("prometheusSidecarImage");
|
cleanValidation("prometheusSidecarImage");
|
||||||
}}
|
}}
|
||||||
label="Prometheus Sidecar Image"
|
label="Prometheus Sidecar"
|
||||||
value={prometheusSidecarImage}
|
value={prometheusSidecarImage}
|
||||||
error={validationErrors["prometheusSidecarImage"] || ""}
|
error={validationErrors["prometheusSidecarImage"] || ""}
|
||||||
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
||||||
@@ -397,7 +404,7 @@ const Images = ({
|
|||||||
updateField("prometheusInitImage", e.target.value);
|
updateField("prometheusInitImage", e.target.value);
|
||||||
cleanValidation("prometheusInitImage");
|
cleanValidation("prometheusInitImage");
|
||||||
}}
|
}}
|
||||||
label="Prometheus Init Image"
|
label="Prometheus Init"
|
||||||
value={prometheusInitImage}
|
value={prometheusInitImage}
|
||||||
error={validationErrors["prometheusInitImage"] || ""}
|
error={validationErrors["prometheusInitImage"] || ""}
|
||||||
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
||||||
@@ -407,6 +414,9 @@ const Images = ({
|
|||||||
|
|
||||||
{customImage && (
|
{customImage && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
<Grid item xs={12} className={classes.formFieldRow}>
|
||||||
|
<h4>Custom Container Registry</h4>
|
||||||
|
</Grid>
|
||||||
<Grid item xs={12} className={classes.formFieldRow}>
|
<Grid item xs={12} className={classes.formFieldRow}>
|
||||||
<FormSwitchWrapper
|
<FormSwitchWrapper
|
||||||
value="custom_docker_hub"
|
value="custom_docker_hub"
|
||||||
@@ -419,7 +429,7 @@ const Images = ({
|
|||||||
|
|
||||||
updateField("customDockerhub", checked);
|
updateField("customDockerhub", checked);
|
||||||
}}
|
}}
|
||||||
label={"Set/Update Image Registry"}
|
label={"Use a private container registry"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@@ -485,9 +495,9 @@ const mapState = (state: AppState) => ({
|
|||||||
exposeMinIO: state.tenants.createTenant.fields.configure.exposeMinIO,
|
exposeMinIO: state.tenants.createTenant.fields.configure.exposeMinIO,
|
||||||
exposeConsole: state.tenants.createTenant.fields.configure.exposeConsole,
|
exposeConsole: state.tenants.createTenant.fields.configure.exposeConsole,
|
||||||
prometheusCustom:
|
prometheusCustom:
|
||||||
state.tenants.createTenant.fields.configure.prometheusCustom,
|
state.tenants.createTenant.fields.configure.prometheusEnabled,
|
||||||
tenantCustom: state.tenants.createTenant.fields.configure.tenantCustom,
|
tenantCustom: state.tenants.createTenant.fields.configure.tenantCustom,
|
||||||
logSearchCustom: state.tenants.createTenant.fields.configure.logSearchCustom,
|
logSearchCustom: state.tenants.createTenant.fields.configure.logSearchEnabled,
|
||||||
logSearchVolumeSize:
|
logSearchVolumeSize:
|
||||||
state.tenants.createTenant.fields.configure.logSearchVolumeSize,
|
state.tenants.createTenant.fields.configure.logSearchVolumeSize,
|
||||||
logSearchSizeFactor:
|
logSearchSizeFactor:
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ const UpdateTenantModal = ({
|
|||||||
label={"MinIO's Image"}
|
label={"MinIO's Image"}
|
||||||
id={"minioImage"}
|
id={"minioImage"}
|
||||||
name={"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) => {
|
onChange={(e) => {
|
||||||
setMinioImage(e.target.value);
|
setMinioImage(e.target.value);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ const initialState: ITenantState = {
|
|||||||
exposeMinIO: true,
|
exposeMinIO: true,
|
||||||
exposeConsole: true,
|
exposeConsole: true,
|
||||||
tenantCustom: false,
|
tenantCustom: false,
|
||||||
logSearchCustom: false,
|
logSearchEnabled: true,
|
||||||
prometheusCustom: false,
|
prometheusEnabled: true,
|
||||||
logSearchVolumeSize: "5",
|
logSearchVolumeSize: "5",
|
||||||
logSearchSizeFactor: "Gi",
|
logSearchSizeFactor: "Gi",
|
||||||
logSearchImage: "",
|
logSearchImage: "",
|
||||||
@@ -632,8 +632,8 @@ export function tenantsReducer(
|
|||||||
exposeMinIO: true,
|
exposeMinIO: true,
|
||||||
exposeConsole: true,
|
exposeConsole: true,
|
||||||
tenantCustom: false,
|
tenantCustom: false,
|
||||||
logSearchCustom: false,
|
logSearchEnabled: true,
|
||||||
prometheusCustom: false,
|
prometheusEnabled: true,
|
||||||
logSearchVolumeSize: "5",
|
logSearchVolumeSize: "5",
|
||||||
logSearchSizeFactor: "Gi",
|
logSearchSizeFactor: "Gi",
|
||||||
logSearchSelectedStorageClass: "default",
|
logSearchSelectedStorageClass: "default",
|
||||||
|
|||||||
@@ -205,9 +205,9 @@ export interface IConfigureFields {
|
|||||||
imageRegistryPassword: string;
|
imageRegistryPassword: string;
|
||||||
exposeMinIO: boolean;
|
exposeMinIO: boolean;
|
||||||
exposeConsole: boolean;
|
exposeConsole: boolean;
|
||||||
prometheusCustom: boolean;
|
prometheusEnabled: boolean;
|
||||||
tenantCustom: boolean;
|
tenantCustom: boolean;
|
||||||
logSearchCustom: boolean;
|
logSearchEnabled: boolean;
|
||||||
logSearchVolumeSize: string;
|
logSearchVolumeSize: string;
|
||||||
logSearchSizeFactor: string;
|
logSearchSizeFactor: string;
|
||||||
logSearchSelectedStorageClass: string;
|
logSearchSelectedStorageClass: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user