Compare commits

...

7 Commits

Author SHA1 Message Date
Minio Trusted
c0bf9c5da8 update version to v0.3.8 2020-08-05 12:38:13 -07:00
Daniel Valdivia
16a6524b11 Pass Annotations to PVC (#233) 2020-08-05 12:35:41 -07:00
Minio Trusted
c1963c6122 update to v0.3.7 2020-08-05 11:06:25 -07:00
Cesar N
73154e8dd7 Add missing field on Tenant Creation (#232) 2020-08-05 01:21:35 -07:00
Daniel Valdivia
e2e8cbe46c Erasure Coding Parity (#231) 2020-08-04 22:32:41 -07:00
Cesar N
b9b776c278 Add ImageRegistry field to Tenant Create and Tenant Update (#230) 2020-08-04 20:54:59 -07:00
Cesar N
7710df62ee Add imagePullSecretsName field on Add Tenant request (#227) 2020-08-04 16:04:04 -07:00
10 changed files with 481 additions and 108 deletions

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.3.6
image: minio/console:v0.3.8
imagePullPolicy: "IfNotPresent"
args:
- server

View File

@@ -6,8 +6,18 @@ rules:
- apiGroups:
- ""
resources:
- namespaces
- secrets
verbs:
- get
- watch
- create
- list
- patch
- update
- apiGroups:
- ""
resources:
- namespaces
- pods
- services
- events

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.3.6
image: minio/console:v0.3.8
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_OPERATOR_MODE

View File

@@ -51,12 +51,18 @@ type CreateTenantRequest struct {
// encryption
Encryption *EncryptionConfiguration `json:"encryption,omitempty"`
// erasure coding parity
ErasureCodingParity int64 `json:"erasureCodingParity,omitempty"`
// idp
Idp *IdpConfiguration `json:"idp,omitempty"`
// image
Image string `json:"image,omitempty"`
// image registry
ImageRegistry *ImageRegistry `json:"image_registry,omitempty"`
// mounth path
MounthPath string `json:"mounth_path,omitempty"`
@@ -95,6 +101,10 @@ func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateImageRegistry(formats); err != nil {
res = append(res, err)
}
if err := m.validateName(formats); err != nil {
res = append(res, err)
}
@@ -153,6 +163,24 @@ func (m *CreateTenantRequest) validateIdp(formats strfmt.Registry) error {
return nil
}
func (m *CreateTenantRequest) validateImageRegistry(formats strfmt.Registry) error {
if swag.IsZero(m.ImageRegistry) { // not required
return nil
}
if m.ImageRegistry != nil {
if err := m.ImageRegistry.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("image_registry")
}
return err
}
}
return nil
}
func (m *CreateTenantRequest) validateName(formats strfmt.Registry) error {
if err := validate.Required("name", "body", m.Name); err != nil {

115
models/image_registry.go Normal file
View File

@@ -0,0 +1,115 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// ImageRegistry image registry
//
// swagger:model imageRegistry
type ImageRegistry struct {
// password
// Required: true
Password *string `json:"password"`
// registry
// Required: true
Registry *string `json:"registry"`
// username
// Required: true
Username *string `json:"username"`
}
// Validate validates this image registry
func (m *ImageRegistry) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePassword(formats); err != nil {
res = append(res, err)
}
if err := m.validateRegistry(formats); err != nil {
res = append(res, err)
}
if err := m.validateUsername(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ImageRegistry) validatePassword(formats strfmt.Registry) error {
if err := validate.Required("password", "body", m.Password); err != nil {
return err
}
return nil
}
func (m *ImageRegistry) validateRegistry(formats strfmt.Registry) error {
if err := validate.Required("registry", "body", m.Registry); err != nil {
return err
}
return nil
}
func (m *ImageRegistry) validateUsername(formats strfmt.Registry) error {
if err := validate.Required("username", "body", m.Username); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *ImageRegistry) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ImageRegistry) UnmarshalBinary(b []byte) error {
var res ImageRegistry
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -37,6 +37,9 @@ type UpdateTenantRequest struct {
// image
// Pattern: ^((.*?)/(.*?):(.+))$
Image string `json:"image,omitempty"`
// image registry
ImageRegistry *ImageRegistry `json:"image_registry,omitempty"`
}
// Validate validates this update tenant request
@@ -47,6 +50,10 @@ func (m *UpdateTenantRequest) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateImageRegistry(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
@@ -66,6 +73,24 @@ func (m *UpdateTenantRequest) validateImage(formats strfmt.Registry) error {
return nil
}
func (m *UpdateTenantRequest) validateImageRegistry(formats strfmt.Registry) error {
if swag.IsZero(m.ImageRegistry) { // not required
return nil
}
if m.ImageRegistry != nil {
if err := m.ImageRegistry.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("image_registry")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *UpdateTenantRequest) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -49,9 +49,25 @@ import (
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
operator "github.com/minio/operator/pkg/apis/minio.min.io/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
const (
minioRegCred = "minio-regcred-secret"
)
type imageRegistry struct {
Auths map[string]imageRegistryCredentials `json:"auths"`
}
type imageRegistryCredentials struct {
Username string `json:"username"`
Password string `json:"password"`
Auth string `json:"auth"`
}
func registerTenantHandlers(api *operations.ConsoleAPI) {
// Add Tenant
api.AdminAPICreateTenantHandler = admin_api.CreateTenantHandlerFunc(func(params admin_api.CreateTenantParams, session *models.Principal) middleware.Responder {
@@ -334,7 +350,9 @@ func getListTenantsResponse(session *models.Principal, params admin_api.ListTena
}
func getTenantCreatedResponse(session *models.Principal, params admin_api.CreateTenantParams) (*models.CreateTenantResponse, error) {
minioImage := params.Body.Image
tenantReq := params.Body
minioImage := tenantReq.Image
ctx := context.Background()
if minioImage == "" {
minImg, err := cluster.GetMinioImage()
@@ -349,20 +367,20 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
return nil, err
}
ns := *params.Body.Namespace
ns := *tenantReq.Namespace
// if access/secret are provided, use them, else create a random pair
accessKey := RandomCharString(16)
secretKey := RandomCharString(32)
if params.Body.AccessKey != "" {
accessKey = params.Body.AccessKey
if tenantReq.AccessKey != "" {
accessKey = tenantReq.AccessKey
}
if params.Body.SecretKey != "" {
secretKey = params.Body.SecretKey
if tenantReq.SecretKey != "" {
secretKey = tenantReq.SecretKey
}
secretName := fmt.Sprintf("%s-secret", *params.Body.Name)
secretName := fmt.Sprintf("%s-secret", *tenantReq.Name)
imm := true
instanceSecret := corev1.Secret{
@@ -376,15 +394,27 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
},
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &instanceSecret, metav1.CreateOptions{})
_, err = clientset.CoreV1().Secrets(ns).Create(ctx, &instanceSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
var envrionmentVariables []corev1.EnvVar
// 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, errors.New("invalid Erasure Coding Value")
}
envrionmentVariables = append(envrionmentVariables, corev1.EnvVar{
Name: "MINIO_STORAGE_CLASS_STANDARD",
Value: fmt.Sprintf("%d", tenantReq.ErasureCodingParity),
})
}
//Construct a MinIO Instance with everything we are getting from parameters
minInst := operator.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: *params.Body.Name,
Name: *tenantReq.Name,
},
Spec: operator.TenantSpec{
Image: minioImage,
@@ -392,20 +422,20 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
CredsSecret: &corev1.LocalObjectReference{
Name: secretName,
},
Env: []corev1.EnvVar{},
Env: envrionmentVariables,
},
}
idpEnabled := false
// Enable IDP (Active Directory) for MinIO
if params.Body.Idp != nil && params.Body.Idp.ActiveDirectory != nil {
url := *params.Body.Idp.ActiveDirectory.URL
userNameFormat := *params.Body.Idp.ActiveDirectory.UsernameFormat
userSearchFilter := *params.Body.Idp.ActiveDirectory.UserSearchFilter
tlsSkipVerify := params.Body.Idp.ActiveDirectory.SkipSslVerification
serverInsecure := params.Body.Idp.ActiveDirectory.ServerInsecure
groupSearchDN := params.Body.Idp.ActiveDirectory.GroupSearchBaseDn
groupSearchFilter := params.Body.Idp.ActiveDirectory.GroupSearchFilter
groupNameAttribute := params.Body.Idp.ActiveDirectory.GroupNameAttribute
if tenantReq.Idp != nil && tenantReq.Idp.ActiveDirectory != nil {
url := *tenantReq.Idp.ActiveDirectory.URL
userNameFormat := *tenantReq.Idp.ActiveDirectory.UsernameFormat
userSearchFilter := *tenantReq.Idp.ActiveDirectory.UserSearchFilter
tlsSkipVerify := tenantReq.Idp.ActiveDirectory.SkipSslVerification
serverInsecure := tenantReq.Idp.ActiveDirectory.ServerInsecure
groupSearchDN := tenantReq.Idp.ActiveDirectory.GroupSearchBaseDn
groupSearchFilter := tenantReq.Idp.ActiveDirectory.GroupSearchFilter
groupNameAttribute := tenantReq.Idp.ActiveDirectory.GroupNameAttribute
if url != "" && userNameFormat != "" && userSearchFilter != "" {
// CONSOLE_LDAP_ENABLED
idpEnabled = true
@@ -449,24 +479,24 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
// operator request AutoCert feature
encryption := false
if params.Body.EnableSsl != nil {
if tenantReq.EnableSsl != nil {
encryption = true
minInst.Spec.RequestAutoCert = *params.Body.EnableSsl
minInst.Spec.RequestAutoCert = *tenantReq.EnableSsl
}
// User provided TLS certificates (this will take priority over autoCert)
if params.Body.TLS != nil && params.Body.TLS.Crt != nil && params.Body.TLS.Key != nil {
if tenantReq.TLS != nil && tenantReq.TLS.Crt != nil && tenantReq.TLS.Key != nil {
encryption = true
externalTLSCertificateSecretName := fmt.Sprintf("%s-instance-external-certificates", secretName)
// disable autoCert
minInst.Spec.RequestAutoCert = false
tlsCrt, err := base64.StdEncoding.DecodeString(*params.Body.TLS.Crt)
tlsCrt, err := base64.StdEncoding.DecodeString(*tenantReq.TLS.Crt)
if err != nil {
return nil, err
}
tlsKey, err := base64.StdEncoding.DecodeString(*params.Body.TLS.Key)
tlsKey, err := base64.StdEncoding.DecodeString(*tenantReq.TLS.Key)
if err != nil {
return nil, err
}
@@ -482,7 +512,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
"tls.key": tlsKey,
},
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &externalTLSCertificateSecret, metav1.CreateOptions{})
_, err = clientset.CoreV1().Secrets(ns).Create(ctx, &externalTLSCertificateSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
@@ -493,19 +523,19 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
}
if params.Body.Encryption != nil && encryption {
if tenantReq.Encryption != nil && encryption {
// Enable auto encryption
minInst.Spec.Env = append(minInst.Spec.Env, corev1.EnvVar{
Name: "MINIO_KMS_AUTO_ENCRYPTION",
Value: "on",
})
if params.Body.Encryption.MasterKey != "" {
if tenantReq.Encryption.MasterKey != "" {
// Configure MinIO to use MINIO_KMS_MASTER_KEY legacy key
// https://docs.min.io/docs/minio-vault-legacy.html
minInst.Spec.Env = append(minInst.Spec.Env, corev1.EnvVar{
Name: "MINIO_KMS_MASTER_KEY",
Value: params.Body.Encryption.MasterKey,
Value: tenantReq.Encryption.MasterKey,
})
} else {
// KES configuration for Tenant instance
@@ -515,16 +545,16 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
Metadata: nil,
}
// Using custom image for KES
if params.Body.Encryption.Image != "" {
minInst.Spec.KES.Image = params.Body.Encryption.Image
if tenantReq.Encryption.Image != "" {
minInst.Spec.KES.Image = tenantReq.Encryption.Image
}
// Secret to store KES server TLS certificates
// TODO check if AutoCert it's already configured
serverTLSCrt, err := base64.StdEncoding.DecodeString(*params.Body.Encryption.Server.Crt)
serverTLSCrt, err := base64.StdEncoding.DecodeString(*tenantReq.Encryption.Server.Crt)
if err != nil {
return nil, err
}
serverTLSKey, err := base64.StdEncoding.DecodeString(*params.Body.Encryption.Server.Key)
serverTLSKey, err := base64.StdEncoding.DecodeString(*tenantReq.Encryption.Server.Key)
if err != nil {
return nil, err
}
@@ -540,7 +570,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
"tls.key": serverTLSKey,
},
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &kesExternalCertificateSecret, metav1.CreateOptions{})
_, err = clientset.CoreV1().Secrets(ns).Create(ctx, &kesExternalCertificateSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
@@ -551,11 +581,11 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
// Secret to store KES clients TLS certificates (mTLS authentication)
clientTLSCrt, err := base64.StdEncoding.DecodeString(*params.Body.Encryption.Client.Crt)
clientTLSCrt, err := base64.StdEncoding.DecodeString(*tenantReq.Encryption.Client.Crt)
if err != nil {
return nil, err
}
clientTLSKey, err := base64.StdEncoding.DecodeString(*params.Body.Encryption.Client.Key)
clientTLSKey, err := base64.StdEncoding.DecodeString(*tenantReq.Encryption.Client.Key)
if err != nil {
return nil, err
}
@@ -571,7 +601,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
"tls.key": clientTLSKey,
},
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &instanceExternalClientCertificateSecret, metav1.CreateOptions{})
_, err = clientset.CoreV1().Secrets(ns).Create(ctx, &instanceExternalClientCertificateSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
@@ -621,66 +651,66 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
Keys: kes.Keys{},
}
// if encryption is enabled and encryption is configured to use Vault
if params.Body.Encryption.Vault != nil {
if tenantReq.Encryption.Vault != nil {
// Initialize Vault Config
kesConfig.Keys.Vault = &kes.Vault{
Endpoint: *params.Body.Encryption.Vault.Endpoint,
EnginePath: params.Body.Encryption.Vault.Engine,
Namespace: params.Body.Encryption.Vault.Namespace,
Prefix: params.Body.Encryption.Vault.Prefix,
Endpoint: *tenantReq.Encryption.Vault.Endpoint,
EnginePath: tenantReq.Encryption.Vault.Engine,
Namespace: tenantReq.Encryption.Vault.Namespace,
Prefix: tenantReq.Encryption.Vault.Prefix,
Status: &kes.VaultStatus{
Ping: 10 * time.Second,
},
}
// Vault AppRole credentials
if params.Body.Encryption.Vault.Approle != nil {
if tenantReq.Encryption.Vault.Approle != nil {
kesConfig.Keys.Vault.AppRole = &kes.AppRole{
EnginePath: params.Body.Encryption.Vault.Approle.Engine,
ID: *params.Body.Encryption.Vault.Approle.ID,
Secret: *params.Body.Encryption.Vault.Approle.Secret,
EnginePath: tenantReq.Encryption.Vault.Approle.Engine,
ID: *tenantReq.Encryption.Vault.Approle.ID,
Secret: *tenantReq.Encryption.Vault.Approle.Secret,
Retry: 15 * time.Second,
}
} else {
return nil, errors.New("approle credentials missing for kes")
}
} else if params.Body.Encryption.Aws != nil {
} else if tenantReq.Encryption.Aws != nil {
// Initialize AWS
kesConfig.Keys.Aws = &kes.Aws{
SecretsManager: &kes.AwsSecretManager{},
}
// AWS basic configuration
if params.Body.Encryption.Aws.Secretsmanager != nil {
kesConfig.Keys.Aws.SecretsManager.Endpoint = *params.Body.Encryption.Aws.Secretsmanager.Endpoint
kesConfig.Keys.Aws.SecretsManager.Region = *params.Body.Encryption.Aws.Secretsmanager.Region
kesConfig.Keys.Aws.SecretsManager.KmsKey = params.Body.Encryption.Aws.Secretsmanager.Kmskey
if tenantReq.Encryption.Aws.Secretsmanager != nil {
kesConfig.Keys.Aws.SecretsManager.Endpoint = *tenantReq.Encryption.Aws.Secretsmanager.Endpoint
kesConfig.Keys.Aws.SecretsManager.Region = *tenantReq.Encryption.Aws.Secretsmanager.Region
kesConfig.Keys.Aws.SecretsManager.KmsKey = tenantReq.Encryption.Aws.Secretsmanager.Kmskey
// AWS credentials
if params.Body.Encryption.Aws.Secretsmanager.Credentials != nil {
if tenantReq.Encryption.Aws.Secretsmanager.Credentials != nil {
kesConfig.Keys.Aws.SecretsManager.Login = &kes.AwsSecretManagerLogin{
AccessKey: *params.Body.Encryption.Aws.Secretsmanager.Credentials.Accesskey,
SecretKey: *params.Body.Encryption.Aws.Secretsmanager.Credentials.Secretkey,
SessionToken: params.Body.Encryption.Aws.Secretsmanager.Credentials.Token,
AccessKey: *tenantReq.Encryption.Aws.Secretsmanager.Credentials.Accesskey,
SecretKey: *tenantReq.Encryption.Aws.Secretsmanager.Credentials.Secretkey,
SessionToken: tenantReq.Encryption.Aws.Secretsmanager.Credentials.Token,
}
}
}
} else if params.Body.Encryption.Gemalto != nil {
} else if tenantReq.Encryption.Gemalto != nil {
// Initialize Gemalto
kesConfig.Keys.Gemalto = &kes.Gemalto{
KeySecure: &kes.GemaltoKeySecure{},
}
// Gemalto Configuration
if params.Body.Encryption.Gemalto.Keysecure != nil {
kesConfig.Keys.Gemalto.KeySecure.Endpoint = *params.Body.Encryption.Gemalto.Keysecure.Endpoint
if tenantReq.Encryption.Gemalto.Keysecure != nil {
kesConfig.Keys.Gemalto.KeySecure.Endpoint = *tenantReq.Encryption.Gemalto.Keysecure.Endpoint
// Gemalto TLS configuration
if params.Body.Encryption.Gemalto.Keysecure.TLS != nil {
if tenantReq.Encryption.Gemalto.Keysecure.TLS != nil {
kesConfig.Keys.Gemalto.KeySecure.TLS = &kes.GemaltoTLS{
CAPath: *params.Body.Encryption.Gemalto.Keysecure.TLS.Ca,
CAPath: *tenantReq.Encryption.Gemalto.Keysecure.TLS.Ca,
}
}
// Gemalto Login
if params.Body.Encryption.Gemalto.Keysecure.Credentials != nil {
if tenantReq.Encryption.Gemalto.Keysecure.Credentials != nil {
kesConfig.Keys.Gemalto.KeySecure.Credentials = &kes.GemaltoCredentials{
Token: *params.Body.Encryption.Gemalto.Keysecure.Credentials.Token,
Domain: *params.Body.Encryption.Gemalto.Keysecure.Credentials.Domain,
Token: *tenantReq.Encryption.Gemalto.Keysecure.Credentials.Token,
Domain: *tenantReq.Encryption.Gemalto.Keysecure.Credentials.Domain,
Retry: 15 * time.Second,
}
}
@@ -702,7 +732,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
"server-config.yaml": serverConfigYaml,
},
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &kesConfigurationSecret, metav1.CreateOptions{})
_, err = clientset.CoreV1().Secrets(ns).Create(ctx, &kesConfigurationSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
@@ -718,12 +748,12 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
var consoleSecret string
enableConsole := true
if params.Body.EnableConsole != nil {
enableConsole = *params.Body.EnableConsole
if tenantReq.EnableConsole != nil {
enableConsole = *tenantReq.EnableConsole
}
if enableConsole {
consoleSelector := fmt.Sprintf("%s-console", *params.Body.Name)
consoleSelector := fmt.Sprintf("%s-console", *tenantReq.Name)
consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector)
consoleAccess = RandomCharString(16)
consoleSecret = RandomCharString(32)
@@ -743,10 +773,10 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
// Enable IDP (Open ID Connect) for console
if !idpEnabled && params.Body.Idp != nil && params.Body.Idp.Oidc != nil {
url := *params.Body.Idp.Oidc.URL
clientID := *params.Body.Idp.Oidc.ClientID
secretID := *params.Body.Idp.Oidc.SecretID
if !idpEnabled && tenantReq.Idp != nil && tenantReq.Idp.Oidc != nil {
url := *tenantReq.Idp.Oidc.URL
clientID := *tenantReq.Idp.Oidc.ClientID
secretID := *tenantReq.Idp.Oidc.SecretID
if url != "" && clientID != "" && secretID != "" {
instanceSecret.Data["CONSOLE_IDP_URL"] = []byte(url)
instanceSecret.Data["CONSOLE_IDP_CLIENT_ID"] = []byte(clientID)
@@ -763,12 +793,12 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &instanceSecret, metav1.CreateOptions{})
_, err = clientset.CoreV1().Secrets(ns).Create(ctx, &instanceSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
const consoleVersion = "minio/console:v0.3.6"
const consoleVersion = "minio/console:v0.3.8"
minInst.Spec.Console = &operator.ConsoleConfiguration{
Replicas: 2,
Image: consoleVersion,
@@ -782,12 +812,21 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
// set the service name if provided
if params.Body.ServiceName != "" {
minInst.Spec.ServiceName = params.Body.ServiceName
if tenantReq.ServiceName != "" {
minInst.Spec.ServiceName = tenantReq.ServiceName
}
// add annotations
var annotations map[string]string
if len(tenantReq.Annotations) > 0 {
if minInst.Spec.Metadata == nil {
minInst.Spec.Metadata = &metav1.ObjectMeta{}
}
annotations = tenantReq.Annotations
minInst.Spec.Metadata.Annotations = annotations
}
// set the zones if they are provided
for _, zone := range params.Body.Zones {
zone, err := parseTenantZoneRequest(zone)
for _, zone := range tenantReq.Zones {
zone, err := parseTenantZoneRequest(zone, annotations)
if err != nil {
return nil, err
}
@@ -795,15 +834,17 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
// Set Mount Path if provided
if params.Body.MounthPath != "" {
minInst.Spec.Mountpath = params.Body.MounthPath
if tenantReq.MounthPath != "" {
minInst.Spec.Mountpath = tenantReq.MounthPath
}
// add annotations
if len(params.Body.Annotations) > 0 {
if minInst.Spec.Metadata == nil {
minInst.Spec.Metadata = &metav1.ObjectMeta{}
}
minInst.Spec.Metadata.Annotations = params.Body.Annotations
if err := setImageRegistry(ctx, tenantReq.ImageRegistry, clientset.CoreV1(), ns); err != nil {
log.Println("error setting image registry secret:", err)
return nil, err
}
minInst.Spec.ImagePullSecret = corev1.LocalObjectReference{
Name: minioRegCred,
}
opClient, err := cluster.OperatorClient(session.SessionToken)
@@ -818,7 +859,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
// Integratrions
if os.Getenv("GKE_INTEGRATION") != "" {
err := gkeIntegration(clientset, *params.Body.Name, ns, session.SessionToken)
err := gkeIntegration(clientset, *tenantReq.Name, ns, session.SessionToken)
if err != nil {
return nil, err
}
@@ -829,24 +870,84 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
// Attach Console Credentials
if enableConsole {
response.Console = &models.CreateTenantResponseConsole{}
response.Console.AccessKey = consoleAccess
response.Console.SecretKey = consoleSecret
response.Console = &models.CreateTenantResponseConsole{
AccessKey: consoleAccess,
SecretKey: consoleSecret,
}
}
return response, nil
}
func setImageRegistry(ctx context.Context, req *models.ImageRegistry, clientset v1.CoreV1Interface, namespace string) error {
if req == nil || req.Registry == nil || req.Username == nil || req.Password == nil {
return nil
}
credentials := make(map[string]imageRegistryCredentials)
// username:password encoded
authData := []byte(fmt.Sprintf("%s:%s", *req.Username, *req.Password))
authStr := base64.StdEncoding.EncodeToString(authData)
credentials[*req.Registry] = imageRegistryCredentials{
Username: *req.Username,
Password: *req.Password,
Auth: authStr,
}
imRegistry := imageRegistry{
Auths: credentials,
}
imRegistryJSON, err := json.Marshal(imRegistry)
if err != nil {
return err
}
instanceSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: minioRegCred,
},
Data: map[string][]byte{
corev1.DockerConfigJsonKey: []byte(string(imRegistryJSON)),
},
Type: corev1.SecretTypeDockerConfigJson,
}
// Get or Create secret if it doesn't exist
_, err = clientset.Secrets(namespace).Get(ctx, minioRegCred, metav1.GetOptions{})
if err != nil {
if k8sErrors.IsNotFound(err) {
_, err = clientset.Secrets(namespace).Create(ctx, &instanceSecret, metav1.CreateOptions{})
if err != nil {
return err
}
return nil
}
return err
}
_, err = clientset.Secrets(namespace).Update(ctx, &instanceSecret, metav1.UpdateOptions{})
if err != nil {
return err
}
return nil
}
// updateTenantAction does an update on the minioTenant by patching the desired changes
func updateTenantAction(ctx context.Context, operatorClient OperatorClient, httpCl cluster.HTTPClientI, nameSpace string, params admin_api.UpdateTenantParams) error {
func updateTenantAction(ctx context.Context, operatorClient OperatorClient, clientset v1.CoreV1Interface, httpCl cluster.HTTPClientI, namespace string, params admin_api.UpdateTenantParams) error {
imageToUpdate := params.Body.Image
minInst, err := operatorClient.TenantGet(ctx, nameSpace, params.Tenant, metav1.GetOptions{})
imageRegistryReq := params.Body.ImageRegistry
if err := setImageRegistry(ctx, imageRegistryReq, clientset, namespace); err != nil {
log.Println("error setting image registry secret:", err)
return err
}
minInst, err := operatorClient.TenantGet(ctx, namespace, params.Tenant, metav1.GetOptions{})
if err != nil {
return err
}
// if image to update is empty we'll use the latest image by default
if strings.TrimSpace(imageToUpdate) != "" {
minInst.Spec.Image = params.Body.Image
minInst.Spec.Image = imageToUpdate
} else {
im, err := cluster.GetLatestMinioImage(httpCl)
if err != nil {
@@ -859,7 +960,7 @@ func updateTenantAction(ctx context.Context, operatorClient OperatorClient, http
if err != nil {
return err
}
_, err = operatorClient.TenantPatch(ctx, nameSpace, minInst.Name, types.MergePatchType, payloadBytes, metav1.PatchOptions{})
_, err = operatorClient.TenantPatch(ctx, namespace, minInst.Name, types.MergePatchType, payloadBytes, metav1.PatchOptions{})
if err != nil {
return err
}
@@ -873,6 +974,11 @@ func getUpdateTenantResponse(session *models.Principal, params admin_api.UpdateT
log.Println("error getting operator client:", err)
return err
}
// get Kubernetes Client
clientset, err := cluster.K8sClient(session.SessionToken)
if err != nil {
return err
}
opClient := &operatorClient{
client: opClientClientSet,
@@ -882,7 +988,8 @@ func getUpdateTenantResponse(session *models.Principal, params admin_api.UpdateT
Timeout: 4 * time.Second,
},
}
if err := updateTenantAction(ctx, opClient, httpC, params.Namespace, params); err != nil {
if err := updateTenantAction(ctx, opClient, clientset.CoreV1(), httpC, params.Namespace, params); err != nil {
log.Println("error patching Tenant:", err)
return err
}
@@ -897,7 +1004,7 @@ func addTenantZone(ctx context.Context, operatorClient OperatorClient, params ad
}
zoneParams := params.Body
zone, err := parseTenantZoneRequest(zoneParams)
zone, err := parseTenantZoneRequest(zoneParams, tenant.ObjectMeta.Annotations)
if err != nil {
return err
}
@@ -996,7 +1103,7 @@ func getTenantUsageResponse(session *models.Principal, params admin_api.GetTenan
// parseTenantZoneRequest parse zone request and returns the equivalent
// operator.Zone object
func parseTenantZoneRequest(zoneParams *models.Zone) (*operator.Zone, error) {
func parseTenantZoneRequest(zoneParams *models.Zone, annotations map[string]string) (*operator.Zone, error) {
if zoneParams.VolumeConfiguration == nil {
return nil, errors.New("a volume configuration must be specified")
}
@@ -1136,16 +1243,22 @@ func parseTenantZoneRequest(zoneParams *models.Zone) (*operator.Zone, error) {
tolerations = append(tolerations, toleration)
}
zone := &operator.Zone{
Name: zoneParams.Name,
Servers: int32(*zoneParams.Servers),
VolumesPerServer: *zoneParams.VolumesPerServer,
VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "data",
},
Spec: volTemp,
// Pass annotations to the volume
vct := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "data",
},
Spec: volTemp,
}
if len(annotations) > 0 {
vct.ObjectMeta.Annotations = annotations
}
zone := &operator.Zone{
Name: zoneParams.Name,
Servers: int32(*zoneParams.Servers),
VolumesPerServer: *zoneParams.VolumesPerServer,
VolumeClaimTemplate: vct,
Resources: corev1.ResourceRequirements{
Requests: resourcesRequests,
Limits: resourcesLimits,

View File

@@ -35,7 +35,9 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
types "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/fake"
)
var opClientTenantDeleteMock func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error
@@ -573,6 +575,7 @@ func Test_UpdateTenantAction(t *testing.T) {
tests := []struct {
name string
args args
objs []runtime.Object
wantErr bool
}{
{
@@ -708,8 +711,9 @@ func Test_UpdateTenantAction(t *testing.T) {
opClientTenantGetMock = tt.args.mockTenantGet
opClientTenantPatchMock = tt.args.mockTenantPatch
httpClientGetMock = tt.args.mockHTTPClientGet
cnsClient := fake.NewSimpleClientset(tt.objs...)
t.Run(tt.name, func(t *testing.T) {
if err := updateTenantAction(tt.args.ctx, tt.args.operatorClient, tt.args.httpCl, tt.args.nameSpace, tt.args.params); (err != nil) != tt.wantErr {
if err := updateTenantAction(tt.args.ctx, tt.args.operatorClient, cnsClient.CoreV1(), tt.args.httpCl, tt.args.nameSpace, tt.args.params); (err != nil) != tt.wantErr {
t.Errorf("deleteTenantAction() error = %v, wantErr %v", err, tt.wantErr)
}
})

View File

@@ -2036,6 +2036,9 @@ func init() {
"type": "object",
"$ref": "#/definitions/encryptionConfiguration"
},
"erasureCodingParity": {
"type": "integer"
},
"idp": {
"type": "object",
"$ref": "#/definitions/idpConfiguration"
@@ -2043,6 +2046,9 @@ func init() {
"image": {
"type": "string"
},
"image_registry": {
"$ref": "#/definitions/imageRegistry"
},
"mounth_path": {
"type": "string"
},
@@ -2289,6 +2295,25 @@ func init() {
}
}
},
"imageRegistry": {
"type": "object",
"required": [
"registry",
"username",
"password"
],
"properties": {
"password": {
"type": "string"
},
"registry": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"listBucketEventsResponse": {
"type": "object",
"properties": {
@@ -3049,6 +3074,9 @@ func init() {
"image": {
"type": "string",
"pattern": "^((.*?)/(.*?):(.+))$"
},
"image_registry": {
"$ref": "#/definitions/imageRegistry"
}
}
},
@@ -5927,6 +5955,9 @@ func init() {
"type": "object",
"$ref": "#/definitions/encryptionConfiguration"
},
"erasureCodingParity": {
"type": "integer"
},
"idp": {
"type": "object",
"$ref": "#/definitions/idpConfiguration"
@@ -5934,6 +5965,9 @@ func init() {
"image": {
"type": "string"
},
"image_registry": {
"$ref": "#/definitions/imageRegistry"
},
"mounth_path": {
"type": "string"
},
@@ -6180,6 +6214,25 @@ func init() {
}
}
},
"imageRegistry": {
"type": "object",
"required": [
"registry",
"username",
"password"
],
"properties": {
"password": {
"type": "string"
},
"registry": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"listBucketEventsResponse": {
"type": "object",
"properties": {
@@ -6874,6 +6927,9 @@ func init() {
"image": {
"type": "string",
"pattern": "^((.*?)/(.*?):(.+))$"
},
"image_registry": {
"$ref": "#/definitions/imageRegistry"
}
}
},

View File

@@ -1771,12 +1771,30 @@ definitions:
type: integer
format: int64
title: number of tenants accessible to tenant user
updateTenantRequest:
type: object
properties:
image:
type: string
pattern: "^((.*?)/(.*?):(.+))$"
image_registry:
$ref: "#/definitions/imageRegistry"
imageRegistry:
type: object
required:
- registry
- username
- password
properties:
registry:
type: string
username:
type: string
password:
type: string
createTenantRequest:
type: object
required:
@@ -1809,10 +1827,14 @@ definitions:
default: true
namespace:
type: string
erasureCodingParity:
type: integer
annotations:
type: object
additionalProperties:
type: string
image_registry:
$ref: "#/definitions/imageRegistry"
idp:
type: object
$ref: "#/definitions/idpConfiguration"