diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/go/main.go b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/main.go index 09bf15f2937..f6a428a2f6d 100644 --- a/examples/kustomization/sts-example/sample-clients/minio-sdk/go/main.go +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/main.go @@ -89,7 +89,6 @@ func main() { Secure: tenantEndpointURL.Scheme == "https", Transport: httpsTransport, }) - if err != nil { log.Fatalf("Error initializing client: %v", err) panic(1) diff --git a/helm/operator/templates/minio.min.io_tenants.yaml b/helm/operator/templates/minio.min.io_tenants.yaml index be905ffacda..aab65ec83fb 100644 --- a/helm/operator/templates/minio.min.io_tenants.yaml +++ b/helm/operator/templates/minio.min.io_tenants.yaml @@ -3845,11 +3845,6 @@ spec: type: object requestAutoCert: type: boolean - s3: - properties: - bucketDNS: - type: boolean - type: object serviceAccountName: type: string serviceMetadata: diff --git a/pkg/apis/minio.min.io/v2/helper.go b/pkg/apis/minio.min.io/v2/helper.go index 05663f086ca..1f1e50c2a60 100644 --- a/pkg/apis/minio.min.io/v2/helper.go +++ b/pkg/apis/minio.min.io/v2/helper.go @@ -37,6 +37,8 @@ import ( "text/template" "time" + "github.com/minio/operator/pkg/common" + "github.com/miekg/dns" appsv1 "k8s.io/api/apps/v1" @@ -54,23 +56,10 @@ import ( // Webhook API constants const ( - WebhookAPIVersion = "/webhook/v1" - WebhookDefaultPort = "4222" - WebhookSecret = "operator-webhook-secret" - WebhookOperatorUsername = "webhookUsername" - WebhookOperatorPassword = "webhookPassword" - - // Webhook environment variable constants - WebhookMinIOArgs = "MINIO_ARGS" - WebhookMinIOBucket = "MINIO_DNS_WEBHOOK_ENDPOINT" - MinIOServerURL = "MINIO_SERVER_URL" MinIODomain = "MINIO_DOMAIN" MinIOBrowserRedirectURL = "MINIO_BROWSER_REDIRECT_URL" - MinIORootUser = "MINIO_ROOT_USER" - MinIORootPassword = "MINIO_ROOT_PASSWORD" - defaultPrometheusJWTExpiry = 100 * 365 * 24 * time.Hour ) @@ -87,16 +76,7 @@ func envGet(key, defaultValue string) string { // List of webhook APIs const ( - WebhookAPIGetenv = WebhookAPIVersion + "/getenv" - WebhookAPIBucketService = WebhookAPIVersion + "/bucketsrv" - WebhookAPIUpdate = WebhookAPIVersion + "/update" - WebhookCRDConversaion = WebhookAPIVersion + "/crd-conversion" -) - -// STS API constants -const ( - STSDefaultPort = "4223" - STSEndpoint = "/sts" + WebhookAPIUpdate = common.WebhookAPIVersion + "/update" ) type hostsTemplateValues struct { @@ -560,8 +540,7 @@ func (t *Tenant) KESServiceHost() string { // BucketDNS indicates if Bucket DNS feature is enabled. func (t *Tenant) BucketDNS() bool { - // we've deprecated .spec.s3 and will top working in future releases of operator - return (t.Spec.Features != nil && t.Spec.Features.BucketDNS) || (t.Spec.S3 != nil && t.Spec.S3.BucketDNS) + return (t.Spec.Features != nil && t.Spec.Features.BucketDNS) } // HasKESEnabled checks if kes configuration is provided by user diff --git a/pkg/apis/minio.min.io/v2/types.go b/pkg/apis/minio.min.io/v2/types.go index e2d08091c1e..f3d2873c8b9 100644 --- a/pkg/apis/minio.min.io/v2/types.go +++ b/pkg/apis/minio.min.io/v2/types.go @@ -72,16 +72,6 @@ type TenantDomains struct { Console string `json:"console,omitempty"` } -// S3Features (`s3`) - Object describing which MinIO features to enable/disable in the MinIO Tenant. + -// *Deprecated in Operator v4.3.2* + -type S3Features struct { - // *Optional* + - // - // Specify `true` to allow clients to access buckets using the DNS path `.minio.default.svc.cluster.local`. Defaults to `false`. - // - BucketDNS bool `json:"bucketDNS,omitempty"` -} - // Features (`features`) - Object describing which MinIO features to enable/disable in the MinIO Tenant. + type Features struct { // *Optional* + @@ -251,12 +241,6 @@ type TenantSpec struct { // +optional Startup *corev1.Probe `json:"startup,omitempty"` - // *Optional* + - // *Deprecated in Operator v4.3.2* + - // - // S3 related features can be disabled or enabled such as `bucketDNS` etc. - S3 *S3Features `json:"s3,omitempty"` - // S3 related features can be disabled or enabled such as `bucketDNS` etc. Features *Features `json:"features,omitempty"` // *Optional* + diff --git a/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go b/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go index c5b6e7af966..a06644f6bcb 100644 --- a/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go +++ b/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go @@ -663,22 +663,6 @@ func (in *PrometheusConfig) DeepCopy() *PrometheusConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *S3Features) DeepCopyInto(out *S3Features) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new S3Features. -func (in *S3Features) DeepCopy() *S3Features { - if in == nil { - return nil - } - out := new(S3Features) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceMetadata) DeepCopyInto(out *ServiceMetadata) { *out = *in @@ -940,11 +924,6 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { *out = new(v1.Probe) (*in).DeepCopyInto(*out) } - if in.S3 != nil { - in, out := &in.S3, &out.S3 - *out = new(S3Features) - **out = **in - } if in.Features != nil { in, out := &in.Features, &out.Features *out = new(Features) diff --git a/pkg/client/applyconfiguration/minio.min.io/v2/tenantspec.go b/pkg/client/applyconfiguration/minio.min.io/v2/tenantspec.go index 3a12b63fe72..b0f9fa404f6 100644 --- a/pkg/client/applyconfiguration/minio.min.io/v2/tenantspec.go +++ b/pkg/client/applyconfiguration/minio.min.io/v2/tenantspec.go @@ -43,7 +43,6 @@ type TenantSpecApplyConfiguration struct { Liveness *v1.Probe `json:"liveness,omitempty"` Readiness *v1.Probe `json:"readiness,omitempty"` Startup *v1.Probe `json:"startup,omitempty"` - S3 *S3FeaturesApplyConfiguration `json:"s3,omitempty"` Features *FeaturesApplyConfiguration `json:"features,omitempty"` CertConfig *CertificateConfigApplyConfiguration `json:"certConfig,omitempty"` KES *KESConfigApplyConfiguration `json:"kes,omitempty"` @@ -218,14 +217,6 @@ func (b *TenantSpecApplyConfiguration) WithStartup(value v1.Probe) *TenantSpecAp return b } -// WithS3 sets the S3 field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the S3 field is set to the value of the last call. -func (b *TenantSpecApplyConfiguration) WithS3(value *S3FeaturesApplyConfiguration) *TenantSpecApplyConfiguration { - b.S3 = value - return b -} - // WithFeatures sets the Features field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Features field is set to the value of the last call. diff --git a/pkg/client/applyconfiguration/utils.go b/pkg/client/applyconfiguration/utils.go index 28a6cf0d202..4b73f6c93eb 100644 --- a/pkg/client/applyconfiguration/utils.go +++ b/pkg/client/applyconfiguration/utils.go @@ -63,8 +63,6 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &miniominiov2.PoolStatusApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("PrometheusConfig"): return &miniominiov2.PrometheusConfigApplyConfiguration{} - case v2.SchemeGroupVersion.WithKind("S3Features"): - return &miniominiov2.S3FeaturesApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("ServiceMetadata"): return &miniominiov2.ServiceMetadataApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("SideCars"): diff --git a/pkg/common/const.go b/pkg/common/const.go new file mode 100644 index 00000000000..9793347685c --- /dev/null +++ b/pkg/common/const.go @@ -0,0 +1,24 @@ +// This file is part of MinIO Operator +// Copyright (c) 2023 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package common + +// Constants for the webhook endpoints +const ( + WebhookAPIVersion = "/webhook/v1" + WebhookDefaultPort = "4222" + WebhookAPIBucketService = WebhookAPIVersion + "/bucketsrv" +) diff --git a/pkg/controller/cluster/main-controller.go b/pkg/controller/cluster/main-controller.go index c5ff728ffa0..2e7144abaee 100644 --- a/pkg/controller/cluster/main-controller.go +++ b/pkg/controller/cluster/main-controller.go @@ -27,6 +27,8 @@ import ( "syscall" "time" + "github.com/minio/operator/pkg/common" + xcerts "github.com/minio/pkg/certs" "github.com/minio/operator/pkg/controller/cluster/certificates" @@ -48,10 +50,8 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -266,10 +266,10 @@ func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSe } // Initialize operator webhook handlers - controller.ws = configureWebhookServer(controller) + controller.ws = configureWebhookServer() // Initialize operator HTTP upgrade server handlers - controller.us = configureHTTPUpgradeServer(controller) + controller.us = configureHTTPUpgradeServer() // Initialize STS API server handlers controller.sts = configureSTSServer(controller) @@ -344,29 +344,6 @@ func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSe return controller } -func getSecretForTenant(tenant *miniov2.Tenant, accessKey, secretKey string) *v1.Secret { - secret := &corev1.Secret{ - Type: "Opaque", - ObjectMeta: metav1.ObjectMeta{ - Name: miniov2.WebhookSecret, - Namespace: tenant.Namespace, - OwnerReferences: []metav1.OwnerReference{ - *metav1.NewControllerRef(tenant, schema.GroupVersionKind{ - Group: miniov2.SchemeGroupVersion.Group, - Version: miniov2.SchemeGroupVersion.Version, - Kind: miniov2.MinIOCRDResourceKind, - }), - }, - }, - Data: map[string][]byte{ - miniov2.WebhookOperatorUsername: []byte(accessKey), - miniov2.WebhookOperatorPassword: []byte(secretKey), - miniov2.WebhookMinIOArgs: secretData(tenant, accessKey, secretKey), - }, - } - return secret -} - // Start will set up the event handlers for types we are interested in, as well // as syncing informer caches and starting workers. It will block until stopCh // is closed, at which point it will shutdown the workqueue and wait for @@ -814,11 +791,6 @@ func (c *Controller) syncHandler(key string) error { klog.V(2).Infof(err.Error()) } - secret, err := c.applyOperatorWebhookSecret(ctx, tenant) - if err != nil { - return err - } - // In case the operator certificate is removed or expired, re-create them if err := c.recreateOperatorCertsIfRequired(ctx); err != nil { return err @@ -994,7 +966,6 @@ func (c *Controller) syncHandler(key string) error { } ss = statefulsets.NewPool(&statefulsets.NewPoolArgs{ Tenant: tenant, - WsSecret: secret, SkipEnvVars: skipEnvVars, Pool: &pool, PoolStatus: &tenant.Status.Pools[i], @@ -1165,7 +1136,7 @@ func (c *Controller) syncHandler(key string) error { updateURL, err := tenant.UpdateURL(latest, fmt.Sprintf("%s://operator.%s.svc.%s:%s%s", protocol, miniov2.GetNSFromFile(), miniov2.GetClusterDomain(), - miniov2.WebhookDefaultPort, miniov2.WebhookAPIUpdate, + common.WebhookDefaultPort, miniov2.WebhookAPIUpdate, )) if err != nil { _ = c.removeArtifacts() @@ -1231,7 +1202,6 @@ func (c *Controller) syncHandler(key string) error { // Now proceed to make the yaml changes for the tenant statefulset. ss := statefulsets.NewPool(&statefulsets.NewPoolArgs{ Tenant: tenant, - WsSecret: secret, SkipEnvVars: skipEnvVars, Pool: &pool, PoolStatus: &tenant.Status.Pools[i], @@ -1283,7 +1253,6 @@ func (c *Controller) syncHandler(key string) error { // generated the expected StatefulSet based on the new tenant configuration expectedStatefulSet := statefulsets.NewPool(&statefulsets.NewPoolArgs{ Tenant: tenant, - WsSecret: secret, SkipEnvVars: skipEnvVars, Pool: &pool, PoolStatus: &tenant.Status.Pools[i], diff --git a/pkg/controller/cluster/service-account.go b/pkg/controller/cluster/service-account.go index e838f6f9b89..5431a66191e 100644 --- a/pkg/controller/cluster/service-account.go +++ b/pkg/controller/cluster/service-account.go @@ -121,6 +121,19 @@ func getTenantRole(tenant *miniov2.Tenant) *rbacv1.Role { "watch", }, }, + { + APIGroups: []string{ + "", + }, + Resources: []string{ + "services", + }, + Verbs: []string{ + "create", + "delete", + "get", + }, + }, { APIGroups: []string{ "minio.min.io", diff --git a/pkg/controller/cluster/status.go b/pkg/controller/cluster/status.go index ca0f8dc9f0e..46a13e7326b 100644 --- a/pkg/controller/cluster/status.go +++ b/pkg/controller/cluster/status.go @@ -63,39 +63,6 @@ func (c *Controller) updateTenantStatusWithRetry(ctx context.Context, tenant *mi return t, nil } -func (c *Controller) increaseTenantRevision(ctx context.Context, tenant *miniov2.Tenant) (*miniov2.Tenant, error) { - return c.increaseTenantRevisionWithRetry(ctx, tenant, true) -} - -func (c *Controller) increaseTenantRevisionWithRetry(ctx context.Context, tenant *miniov2.Tenant, retry bool) (*miniov2.Tenant, error) { - // NEVER modify objects from the store. It's a read-only, local cache. - // You can use DeepCopy() to make a deep copy of original object and modify this copy - // Or create a copy manually for better performance - tenantCopy := tenant.DeepCopy() - tenantCopy.Status = *tenant.Status.DeepCopy() - tenantCopy.Status.Revision = tenantCopy.Status.Revision + 1 - // If the CustomResourceSubresources feature gate is not enabled, - // we must use Update instead of UpdateStatus to update the Status block of the Tenant resource. - // UpdateStatus will not allow changes to the Spec of the resource, - // which is ideal for ensuring nothing other than resource status has been updated. - opts := metav1.UpdateOptions{} - t, err := c.minioClientSet.MinioV2().Tenants(tenant.Namespace).UpdateStatus(ctx, tenantCopy, opts) - t.EnsureDefaults() - if err != nil { - // if rejected due to conflict, get the latest tenant and retry once - if k8serrors.IsConflict(err) && retry { - klog.Info("Hit conflict issue, getting latest version of tenant") - tenant, err = c.minioClientSet.MinioV2().Tenants(tenant.Namespace).Get(ctx, tenant.Name, metav1.GetOptions{}) - if err != nil { - return tenant, err - } - return c.increaseTenantRevisionWithRetry(ctx, tenant, false) - } - return t, err - } - return t, nil -} - func (c *Controller) updatePoolStatus(ctx context.Context, tenant *miniov2.Tenant) (*miniov2.Tenant, error) { return c.updatePoolStatusWithRetry(ctx, tenant, true) } diff --git a/pkg/controller/cluster/sts.go b/pkg/controller/cluster/sts.go index 09b19e260dd..7a8afd7d8d4 100644 --- a/pkg/controller/cluster/sts.go +++ b/pkg/controller/cluster/sts.go @@ -32,6 +32,12 @@ const ( // stsRoleArn = "RoleArn" ) +// STS API constants +const ( + STSDefaultPort = "4223" + STSEndpoint = "/sts" +) + const ( // STSEnabled Env variable name to turn on and off the STS Service is enabled, disabled by default STSEnabled = "OPERATOR_STS_ENABLED" @@ -251,13 +257,13 @@ func configureSTSServer(c *Controller) *http.Server { router := mux.NewRouter().SkipClean(true).UseEncodedPath() router.Methods(http.MethodPost). - Path(miniov2.STSEndpoint + "/{tenantNamespace}"). + Path(STSEndpoint + "/{tenantNamespace}"). HandlerFunc(c.AssumeRoleWithWebIdentityHandler) router.NotFoundHandler = http.NotFoundHandler() s := &http.Server{ - Addr: ":" + miniov2.STSDefaultPort, + Addr: ":" + STSDefaultPort, Handler: router, ReadTimeout: time.Minute, WriteTimeout: time.Minute, diff --git a/pkg/controller/cluster/http_handlers.go b/pkg/controller/cluster/sts_handlers.go similarity index 76% rename from pkg/controller/cluster/http_handlers.go rename to pkg/controller/cluster/sts_handlers.go index 6762c2c8068..e6bc97467c6 100644 --- a/pkg/controller/cluster/http_handlers.go +++ b/pkg/controller/cluster/sts_handlers.go @@ -34,7 +34,6 @@ import ( "github.com/gorilla/mux" miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" xhttp "github.com/minio/operator/pkg/internal" - "github.com/minio/operator/pkg/resources/services" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -43,92 +42,11 @@ import ( // Supported remote envs const ( - envMinIOArgs = "MINIO_ARGS" - envMinIOServiceTarget = "MINIO_DNS_WEBHOOK_ENDPOINT" - updatePath = "/tmp" + miniov2.WebhookAPIUpdate + slashSeparator + updatePath = "/tmp" + miniov2.WebhookAPIUpdate + slashSeparator ) const contextLogKey = contextKeyType("operatorlog") -// BucketSrvHandler - POST /webhook/v1/bucketsrv/{namespace}/{name}?bucket={bucket} -func (c *Controller) BucketSrvHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - v := r.URL.Query() - - namespace := vars["namespace"] - bucket := vars["bucket"] - name := vars["name"] - deleteBucket := v.Get("delete") - - secret, err := c.kubeClientSet.CoreV1().Secrets(namespace).Get(r.Context(), - miniov2.WebhookSecret, metav1.GetOptions{}) - if err != nil { - http.Error(w, err.Error(), http.StatusForbidden) - return - } - if err = c.validateRequest(r, secret); err != nil { - http.Error(w, err.Error(), http.StatusForbidden) - return - } - - ok, err := strconv.ParseBool(deleteBucket) - if err != nil { - http.Error(w, err.Error(), http.StatusForbidden) - return - } - if ok { - if err = c.kubeClientSet.CoreV1().Services(namespace).Delete(r.Context(), bucket, metav1.DeleteOptions{}); err != nil { - klog.Errorf("failed to delete service:%s for tenant:%s/%s, err:%s", name, namespace, name, err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - return - } - - // Find the tenant - tenant, err := c.minioClientSet.MinioV2().Tenants(namespace).Get(r.Context(), name, metav1.GetOptions{}) - if err != nil { - klog.Errorf("Unable to lookup tenant:%s/%s for the bucket:%s request. err:%s", namespace, name, bucket, err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - tenant.EnsureDefaults() - - // Validate the MinIO Tenant - if err = tenant.Validate(); err != nil { - http.Error(w, err.Error(), http.StatusForbidden) - return - } - ok, error := validateBucketName(bucket) - if !ok { - http.Error(w, error.Error(), http.StatusBadRequest) - return - } - // Create the service for the bucket name - service := services.ServiceForBucket(tenant, bucket) - _, err = c.kubeClientSet.CoreV1().Services(namespace).Create(r.Context(), service, metav1.CreateOptions{}) - if err != nil && k8serrors.IsAlreadyExists(err) { - klog.Infof("Bucket:%s already exists for tenant:%s/%s err:%s ", bucket, namespace, name, err) - // This might be a previously failed bucket creation. The service is expected to the be the same as the one - // already in place so clear the error. - err = nil - } - if err != nil { - klog.Errorf("Unable to create service for tenant:%s/%s for the bucket:%s request. err:%s", namespace, name, bucket, err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } -} - -func validateBucketName(bucket string) (bool, error) { - // Additional check on top of existing checks done by minio due to limitation of service creation in k8s - if strings.Contains(bucket, ".") { - return false, fmt.Errorf("invalid bucket name: . in bucket name: %s", bucket) - } - return true, nil -} - // AssumeRoleWithWebIdentityHandler - POST /sts/{tenantNamespace} // AssumeRoleWithWebIdentity - implementation of AWS STS API. // Authenticates a Kubernetes Service accounts using a JWT Token diff --git a/pkg/controller/cluster/upgrades.go b/pkg/controller/cluster/upgrades.go index 04cd1dd5261..bd6172b7d4c 100644 --- a/pkg/controller/cluster/upgrades.go +++ b/pkg/controller/cluster/upgrades.go @@ -19,7 +19,6 @@ package cluster import ( "context" "fmt" - "regexp" corev1 "k8s.io/api/core/v1" @@ -37,12 +36,16 @@ import ( const ( version420 = "v4.2.0" version424 = "v4.2.4" - version428 = "v4.2.8" version429 = "v4.2.9" version430 = "v4.3.0" version45 = "v4.5" ) +// Legacy const +const ( + WebhookSecret = "operator-webhook-secret" +) + type upgradeFunction func(ctx context.Context, tenant *miniov2.Tenant) (*miniov2.Tenant, error) // checkForUpgrades verifies if the tenant definition needs any upgrades @@ -51,7 +54,6 @@ func (c *Controller) checkForUpgrades(ctx context.Context, tenant *miniov2.Tenan upgrades := map[string]upgradeFunction{ version420: c.upgrade420, version424: c.upgrade424, - version428: c.upgrade428, version429: c.upgrade429, version430: c.upgrade430, version45: c.upgrade45, @@ -61,7 +63,6 @@ func (c *Controller) checkForUpgrades(ctx context.Context, tenant *miniov2.Tenan if tenant.Status.SyncVersion == "" { upgradesToDo = append(upgradesToDo, version420) upgradesToDo = append(upgradesToDo, version424) - upgradesToDo = append(upgradesToDo, version428) upgradesToDo = append(upgradesToDo, version429) upgradesToDo = append(upgradesToDo, version430) upgradesToDo = append(upgradesToDo, version45) @@ -74,7 +75,6 @@ func (c *Controller) checkForUpgrades(ctx context.Context, tenant *miniov2.Tenan versionsThatNeedUpgrades := []string{ version420, version424, - version428, version429, version430, version45, @@ -133,7 +133,7 @@ func (c *Controller) upgrade420(ctx context.Context, tenant *miniov2.Tenant) (*m } // delete the previous operator secrets, they may be in a bad state err = c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Delete(ctx, - miniov2.WebhookSecret, metav1.DeleteOptions{}) + WebhookSecret, metav1.DeleteOptions{}) if err != nil { klog.Errorf("Error deleting operator webhook secret, manual deletion is needed: %v", err) } @@ -190,54 +190,6 @@ func versionCompare(version1 string, version2 string) int { return vs1.Compare(vs2) } -// Upgrades the sync version to v4.2.8 -// we needed to clean `operator-webhook-secrets` with non-alphanumerical characters -func (c *Controller) upgrade428(ctx context.Context, tenant *miniov2.Tenant) (*miniov2.Tenant, error) { - secret, err := c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Get(ctx, miniov2.WebhookSecret, metav1.GetOptions{}) - if err != nil && !k8serrors.IsNotFound(err) { - return tenant, err - } - // this secret not found, means it's a fresh tenant - if err == nil { - - unsupportedChars := false - re := regexp.MustCompile(`(?m)^[a-zA-Z0-9]+$`) - - // if any of the keys contains non alphanumerical characters, - accessKey := string(secret.Data[miniov2.WebhookOperatorUsername]) - if !re.MatchString(accessKey) { - unsupportedChars = true - } - secretKey := string(secret.Data[miniov2.WebhookOperatorUsername]) - if !re.MatchString(secretKey) { - unsupportedChars = true - } - - if unsupportedChars { - // delete the secret - err := c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Delete(ctx, miniov2.WebhookSecret, metav1.DeleteOptions{}) - if err != nil && !k8serrors.IsNotFound(err) { - return tenant, err - } - if err == nil { - // regen the secret - _, err = c.applyOperatorWebhookSecret(ctx, tenant) - if err != nil { - return tenant, err - } - // update the revision of the tenant to force a rolling restart across all statefulsets of the tenant - tenant, err = c.increaseTenantRevision(ctx, tenant) - if err != nil { - return tenant, err - } - } - - } - } - - return c.updateTenantSyncVersion(ctx, tenant, version428) -} - // Upgrades the sync version to v4.2.9 // we need to mark any pool with a security context = root as a .status.pools[*].legacySC, this is due to do a // reversal on the fix we previously did on v4.2.4 diff --git a/pkg/controller/cluster/webhook.go b/pkg/controller/cluster/webhook.go index 6977c4ce708..57603ee33e8 100644 --- a/pkg/controller/cluster/webhook.go +++ b/pkg/controller/cluster/webhook.go @@ -15,35 +15,16 @@ package cluster import ( - "context" - "fmt" - "math/rand" "net/http" - "strings" "time" - jwtreq "github.com/golang-jwt/jwt/request" + "github.com/minio/operator/pkg/common" - v1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/golang-jwt/jwt" "github.com/gorilla/mux" miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" ) -// Used for registering with rest handlers (have a look at registerStorageRESTHandlers for usage example) -// If it is passed ["aaaa", "bbbb"], it returns ["aaaa", "{aaaa:.*}", "bbbb", "{bbbb:.*}"] -func restQueries(keys ...string) []string { - var accumulator []string - for _, key := range keys { - accumulator = append(accumulator, key, "{"+key+":.*}") - } - return accumulator -} - -func configureHTTPUpgradeServer(c *Controller) *http.Server { +func configureHTTPUpgradeServer() *http.Server { router := mux.NewRouter().SkipClean(true).UseEncodedPath() router.Methods(http.MethodGet). @@ -63,14 +44,9 @@ func configureHTTPUpgradeServer(c *Controller) *http.Server { return s } -func configureWebhookServer(c *Controller) *http.Server { +func configureWebhookServer() *http.Server { router := mux.NewRouter().SkipClean(true).UseEncodedPath() - router.Methods(http.MethodPost). - Path(miniov2.WebhookAPIBucketService + "/{namespace}/{name:.+}"). - HandlerFunc(c.BucketSrvHandler). - Queries(restQueries("bucket")...) - router.Methods(http.MethodGet). PathPrefix(miniov2.WebhookAPIUpdate). Handler(http.StripPrefix(miniov2.WebhookAPIUpdate, http.FileServer(http.Dir(updatePath)))) @@ -78,7 +54,7 @@ func configureWebhookServer(c *Controller) *http.Server { router.NotFoundHandler = http.NotFoundHandler() s := &http.Server{ - Addr: ":" + miniov2.WebhookDefaultPort, + Addr: ":" + common.WebhookDefaultPort, Handler: router, ReadTimeout: time.Minute, WriteTimeout: time.Minute, @@ -87,88 +63,3 @@ func configureWebhookServer(c *Controller) *http.Server { return s } - -func (c *Controller) validateRequest(r *http.Request, secret *v1.Secret) error { - tokenStr, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(r) - if err != nil { - return err - } - - stdClaims := &jwt.StandardClaims{} - token, err := jwt.ParseWithClaims(tokenStr, stdClaims, func(token *jwt.Token) (interface{}, error) { - return secret.Data[miniov2.WebhookOperatorPassword], nil - }) - if err != nil { - return err - } - - if !token.Valid { - return fmt.Errorf(http.StatusText(http.StatusForbidden)) - } - if stdClaims.Issuer != string(secret.Data[miniov2.WebhookOperatorUsername]) { - return fmt.Errorf(http.StatusText(http.StatusForbidden)) - } - - return nil -} - -func generateRandomKey(length int) string { - rand.Seed(time.Now().UnixNano()) - chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "abcdefghijklmnopqrstuvwxyz" + - "0123456789") - var b strings.Builder - for i := 0; i < length; i++ { - b.WriteRune(chars[rand.Intn(len(chars))]) - } - return b.String() -} - -func (c *Controller) applyOperatorWebhookSecret(ctx context.Context, tenant *miniov2.Tenant) (*v1.Secret, error) { - secret, err := c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Get(ctx, - miniov2.WebhookSecret, metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - secret = getSecretForTenant(tenant, generateRandomKey(20), generateRandomKey(40)) - return c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Create(ctx, secret, metav1.CreateOptions{}) - } - return nil, err - } - // check the secret has the desired values - minioArgs := string(secret.Data[miniov2.WebhookMinIOArgs]) - if strings.Contains(minioArgs, "env://") && isOperatorTLS() { - // update the secret - minioArgs = strings.ReplaceAll(minioArgs, "env://", "env+tls://") - secret.Data[miniov2.WebhookMinIOArgs] = []byte(minioArgs) - secret, err = c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Update(ctx, secret, metav1.UpdateOptions{}) - if err != nil { - return nil, err - } - // update the revision of the tenant to force a rolling restart across all statefulsets - t2, err := c.increaseTenantRevision(ctx, tenant) - if err != nil { - return nil, err - } - *tenant = *t2 - } - - return secret, nil -} - -func secretData(tenant *miniov2.Tenant, accessKey, secretKey string) []byte { - scheme := "env" - if isOperatorTLS() { - scheme = "env+tls" - } - return []byte(fmt.Sprintf("%s://%s:%s@%s:%s%s/%s/%s", - scheme, - accessKey, - secretKey, - fmt.Sprintf("operator.%s.svc.%s", - miniov2.GetNSFromFile(), - miniov2.GetClusterDomain()), - miniov2.WebhookDefaultPort, - miniov2.WebhookAPIGetenv, - tenant.Namespace, - tenant.Name)) -} diff --git a/pkg/resources/statefulsets/minio-statefulset.go b/pkg/resources/statefulsets/minio-statefulset.go index 2749eb31586..8496efce139 100644 --- a/pkg/resources/statefulsets/minio-statefulset.go +++ b/pkg/resources/statefulsets/minio-statefulset.go @@ -20,6 +20,8 @@ import ( "strconv" "strings" + "github.com/minio/operator/pkg/common" + miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -29,6 +31,10 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) +const ( + bucketDNSEnv = "MINIO_DNS_WEBHOOK_ENDPOINT" +) + // Adds required Console environment variables func consoleEnvVars(t *miniov2.Tenant) []corev1.EnvVar { var envVars []corev1.EnvVar @@ -103,16 +109,14 @@ func minioEnvironmentVars(t *miniov2.Tenant, skipEnvVars map[string][]byte, opVe // Enable Bucket DNS only if asked for by default turned off if t.BucketDNS() { domains = append(domains, t.MinIOBucketBaseDomain()) - envVarsMap[miniov2.WebhookMinIOBucket] = corev1.EnvVar{ - Name: miniov2.WebhookMinIOBucket, - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: miniov2.WebhookSecret, - }, - Key: miniov2.WebhookMinIOArgs, - }, - }, + sidecarBucketURL := fmt.Sprintf("http://127.0.0.1:%s%s/%s/%s", + common.WebhookDefaultPort, + common.WebhookAPIBucketService, + t.Namespace, + t.Name) + envVarsMap[bucketDNSEnv] = corev1.EnvVar{ + Name: bucketDNSEnv, + Value: sidecarBucketURL, } } // Check if any domains are configured @@ -318,7 +322,7 @@ func volumeMounts(t *miniov2.Tenant, pool *miniov2.Pool, operatorTLS bool, certV } // Builds the MinIO container for a Tenant. -func poolMinioServerContainer(t *miniov2.Tenant, wsSecret *v1.Secret, skipEnvVars map[string][]byte, pool *miniov2.Pool, hostsTemplate string, opVersion string, operatorTLS bool, certVolumeSources []v1.VolumeProjection) v1.Container { +func poolMinioServerContainer(t *miniov2.Tenant, skipEnvVars map[string][]byte, pool *miniov2.Pool, hostsTemplate string, opVersion string, operatorTLS bool, certVolumeSources []v1.VolumeProjection) v1.Container { consolePort := miniov2.ConsolePort if t.TLS() { consolePort = miniov2.ConsoleTLSPort @@ -454,7 +458,6 @@ const CfgVol = "cfg-vol" // NewPoolArgs arguments used to create a new pool type NewPoolArgs struct { Tenant *miniov2.Tenant - WsSecret *v1.Secret SkipEnvVars map[string][]byte Pool *miniov2.Pool PoolStatus *miniov2.PoolStatus @@ -469,7 +472,6 @@ type NewPoolArgs struct { // NewPool creates a new StatefulSet for the given Cluster. func NewPool(args *NewPoolArgs) *appsv1.StatefulSet { t := args.Tenant - wsSecret := args.WsSecret skipEnvVars := args.SkipEnvVars pool := args.Pool poolStatus := args.PoolStatus @@ -825,7 +827,7 @@ func NewPool(args *NewPoolArgs) *appsv1.StatefulSet { } containers := []corev1.Container{ - poolMinioServerContainer(t, wsSecret, skipEnvVars, pool, hostsTemplate, operatorVersion, operatorTLS, certVolumeSources), + poolMinioServerContainer(t, skipEnvVars, pool, hostsTemplate, operatorVersion, operatorTLS, certVolumeSources), getSideCarContainer(t, operatorImage), } diff --git a/pkg/sidecar/http_handlers.go b/pkg/sidecar/http_handlers.go new file mode 100644 index 00000000000..9d2e498faa1 --- /dev/null +++ b/pkg/sidecar/http_handlers.go @@ -0,0 +1,101 @@ +// This file is part of MinIO Operator +// Copyright (c) 2023 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package sidecar + +//lint:file-ignore ST1005 Incorrectly formatted error string + +import ( + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/gorilla/mux" + "github.com/minio/operator/pkg/resources/services" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/klog/v2" +) + +// BucketSrvHandler - POST /webhook/v1/bucketsrv/{namespace}/{name}?bucket={bucket} +func (c *Controller) BucketSrvHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + v := r.URL.Query() + + namespace := vars["namespace"] + bucket := vars["bucket"] + name := vars["name"] + deleteBucket := v.Get("delete") + + ok, err := strconv.ParseBool(deleteBucket) + if err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + if ok { + if err = c.kubeClient.CoreV1().Services(namespace).Delete(r.Context(), bucket, metav1.DeleteOptions{}); err != nil { + klog.Errorf("failed to delete service:%s for tenant:%s/%s, err:%s", name, namespace, name, err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + return + } + + // Find the tenant + tenant, err := c.controllerClient.MinioV2().Tenants(namespace).Get(r.Context(), name, metav1.GetOptions{}) + if err != nil { + klog.Errorf("Unable to lookup tenant:%s/%s for the bucket:%s request. err:%s", namespace, name, bucket, err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + tenant.EnsureDefaults() + + // Validate the MinIO Tenant + if err = tenant.Validate(); err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + ok, error := validateBucketName(bucket) + if !ok { + http.Error(w, error.Error(), http.StatusBadRequest) + return + } + // Create the service for the bucket name + service := services.ServiceForBucket(tenant, bucket) + _, err = c.kubeClient.CoreV1().Services(namespace).Create(r.Context(), service, metav1.CreateOptions{}) + if err != nil && k8serrors.IsAlreadyExists(err) { + klog.Infof("Bucket:%s already exists for tenant:%s/%s err:%s ", bucket, namespace, name, err) + // This might be a previously failed bucket creation. The service is expected to be the same as the one + // already in place so clear the error. + err = nil + } + if err != nil { + klog.Errorf("Unable to create service for tenant:%s/%s for the bucket:%s request. err:%s", namespace, name, bucket, err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } +} + +func validateBucketName(bucket string) (bool, error) { + // Additional check on top of existing checks done by minio due to limitation of service creation in k8s + if strings.Contains(bucket, ".") { + return false, fmt.Errorf("invalid bucket name: . in bucket name: %s", bucket) + } + return true, nil +} diff --git a/pkg/sidecar/sidecar.go b/pkg/sidecar/sidecar.go index 66ef0c15c2f..2936ddd36ea 100644 --- a/pkg/sidecar/sidecar.go +++ b/pkg/sidecar/sidecar.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "log" + "net/http" "os" "strings" "time" @@ -42,7 +43,7 @@ func init() { log.SetFlags(log.LstdFlags | log.Lshortfile) } -// StartSideCar instantiates kube clients and starts the side car controller +// StartSideCar instantiates kube clients and starts the side-car controller func StartSideCar(tenantName string, secretName string) { log.Println("Starting Sidecar") cfg, err := rest.InClusterConfig() @@ -65,14 +66,25 @@ func StartSideCar(tenantName string, secretName string) { } controller := NewSideCarController(kubeClient, controllerClient, tenantName, secretName) + controller.ws = configureWebhookServer(controller) - stop := make(chan struct{}) - defer close(stop) - err = controller.Run(stop) + stopControllerCh := make(chan struct{}) + + defer close(stopControllerCh) + err = controller.Run(stopControllerCh) if err != nil { klog.Fatal(err) } - select {} + + go func() { + if err = controller.ws.ListenAndServe(); err != nil { + // if the web server exits, + klog.Error(err) + close(stopControllerCh) + } + }() + + <-stopControllerCh } // Controller is the controller holding the informers used to monitor args and tenant structure @@ -86,6 +98,7 @@ type Controller struct { tenantInformer v22.TenantInformer namespace string informerFactory informers.SharedInformerFactory + ws *http.Server } // NewSideCarController returns an instance of Controller with the provided clients diff --git a/pkg/sidecar/webhook_server.go b/pkg/sidecar/webhook_server.go new file mode 100644 index 00000000000..1b91219dee4 --- /dev/null +++ b/pkg/sidecar/webhook_server.go @@ -0,0 +1,55 @@ +// Copyright (C) 2020, MinIO, Inc. +// +// This code is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// 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, version 3, +// along with this program. If not, see + +package sidecar + +import ( + "net/http" + "time" + + "github.com/minio/operator/pkg/common" + + "github.com/gorilla/mux" +) + +// Used for registering with rest handlers (have a look at registerStorageRESTHandlers for usage example) +// If it is passed ["aaaa", "bbbb"], it returns ["aaaa", "{aaaa:.*}", "bbbb", "{bbbb:.*}"] +func restQueries(keys ...string) []string { + var accumulator []string + for _, key := range keys { + accumulator = append(accumulator, key, "{"+key+":.*}") + } + return accumulator +} + +func configureWebhookServer(c *Controller) *http.Server { + router := mux.NewRouter().SkipClean(true).UseEncodedPath() + + router.Methods(http.MethodPost). + Path(common.WebhookAPIBucketService + "/{namespace}/{name:.+}"). + HandlerFunc(c.BucketSrvHandler). + Queries(restQueries("bucket")...) + + router.NotFoundHandler = http.NotFoundHandler() + + s := &http.Server{ + Addr: "127.0.0.1:" + common.WebhookDefaultPort, + Handler: router, + ReadTimeout: time.Minute, + WriteTimeout: time.Minute, + MaxHeaderBytes: 1 << 20, + } + + return s +} diff --git a/resources/base/crds/minio.min.io_tenants.yaml b/resources/base/crds/minio.min.io_tenants.yaml index be905ffacda..aab65ec83fb 100644 --- a/resources/base/crds/minio.min.io_tenants.yaml +++ b/resources/base/crds/minio.min.io_tenants.yaml @@ -3845,11 +3845,6 @@ spec: type: object requestAutoCert: type: boolean - s3: - properties: - bucketDNS: - type: boolean - type: object serviceAccountName: type: string serviceMetadata: diff --git a/web-app/src/screens/Console/Support/Register.tsx.orig b/web-app/src/screens/Console/Support/Register.tsx.orig deleted file mode 100644 index 847a200312c..00000000000 --- a/web-app/src/screens/Console/Support/Register.tsx.orig +++ /dev/null @@ -1,363 +0,0 @@ -// This file is part of MinIO Operator -// Copyright (c) 2022 MinIO, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -import React, { Fragment, useEffect, useState } from "react"; -import { Theme } from "@mui/material/styles"; -import { PageHeader } from "mds"; -import createStyles from "@mui/styles/createStyles"; -import { spacingUtils } from "../Common/FormComponents/common/styleLibrary"; -import withStyles from "@mui/styles/withStyles"; -import { Box } from "@mui/material"; -import PageLayout from "../Common/Layout/PageLayout"; -import api from "../../../common/api"; - -import { SubnetRegTokenResponse } from "../License/types"; -import { ErrorResponseHandler } from "../../../common/types"; -import { useSelector } from "react-redux"; -import { setErrorSnackMessage } from "../../../systemSlice"; -import { AppState, useAppDispatch } from "../../../store"; -import Tabs from "@mui/material/Tabs"; -import Tab from "@mui/material/Tab"; -import { TabPanel } from "../../shared/tabs"; -import { ClusterRegistered, ProxyConfiguration } from "./utils"; -import ApiKeyRegister from "./ApiKeyRegister"; -import { fetchLicenseInfo } from "./registerThunks"; -import { - resetRegisterForm, - setCurTab, - setLoading, - setSubnetRegToken, -} from "./registerSlice"; -import OfflineRegistration from "./OfflineRegistration"; -import SubnetMFAToken from "./SubnetMFAToken"; -import ClusterRegistrationForm from "./ClusterRegistrationForm"; -import OnlineRegistration from "./OnlineRegistration"; - -interface IRegister { - classes: any; -} - -const styles = (theme: Theme) => - createStyles({ - sizedLabel: { - minWidth: "75px", - }, - ...spacingUtils, -<<<<<<< HEAD - ...containerForHeader, -======= ->>>>>>> 257e2bf98 (Move Register Component to Redux) - }); - -const Register = ({ classes }: IRegister) => { - const dispatch = useAppDispatch(); - - const subnetMFAToken = useSelector( - (state: AppState) => state.register.subnetMFAToken - ); - const subnetAccessToken = useSelector( - (state: AppState) => state.register.subnetAccessToken - ); - - const subnetRegToken = useSelector( - (state: AppState) => state.register.subnetRegToken - ); - const subnetOrganizations = useSelector( - (state: AppState) => state.register.subnetOrganizations - ); - - const loading = useSelector((state: AppState) => state.register.loading); - const loadingLicenseInfo = useSelector( - (state: AppState) => state.register.loadingLicenseInfo - ); - const clusterRegistered = useSelector( - (state: AppState) => state.register.clusterRegistered - ); - const licenseInfo = useSelector( - (state: AppState) => state.register.licenseInfo - ); - const curTab = useSelector((state: AppState) => state.register.curTab); - - const [initialLicenseLoading, setInitialLicenseLoading] = - useState(true); - - useEffect(() => { - // when unmounted, reset - return () => { - dispatch(resetRegisterForm()); - }; - }, [dispatch]); - - const fetchSubnetRegToken = () => { - if (loading || subnetRegToken) { - return; - } - dispatch(setLoading(true)); - api - .invoke("GET", "/api/v1/subnet/registration-token") - .then((resp: SubnetRegTokenResponse) => { - dispatch(setLoading(false)); - if (resp && resp.regToken) { - dispatch(setSubnetRegToken(resp.regToken)); - } - }) - .catch((err: ErrorResponseHandler) => { - console.error(err); - dispatch(setErrorSnackMessage(err)); - dispatch(setLoading(false)); - }); - }; - - useEffect(() => { - if (initialLicenseLoading) { - dispatch(fetchLicenseInfo()); - setInitialLicenseLoading(false); - } - }, [initialLicenseLoading, setInitialLicenseLoading, dispatch]); - - let clusterRegistrationForm: JSX.Element = ; - - if (subnetAccessToken && subnetOrganizations.length > 0) { - clusterRegistrationForm = ; - } else if (subnetMFAToken) { - clusterRegistrationForm = ; - } else { - clusterRegistrationForm = ; - } - - const apiKeyRegistration = ( - - - {clusterRegistered && licenseInfo ? ( - - ) : ( - { - dispatch(fetchLicenseInfo()); - }} - registerEndpoint={"/api/v1/subnet/login"} - /> - )} - - - - ); - -<<<<<<< HEAD - const offlineRegUrl = `https://subnet.min.io/cluster/register?token=${subnetRegToken}`; - const offlineRegistration = ( - - - {clusterRegistered && licenseInfo ? ( - - ) : null} - - } - title={`Register cluster in an Airgap environment`} - /> - - - - - - -
- Click on the link to register this cluster in SUBNET -
-
- - - - https://subnet.min.io/cluster/register - - - - -