Compare commits

..

46 Commits

Author SHA1 Message Date
Minio Trusted
858d363e97 update to version v0.3.26 2020-09-23 08:52:43 -07:00
Lenin Alevski
47704189d1 fix kes empty configuration (#286)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-09-22 20:49:25 -07:00
Daniel Valdivia
b72d424ec9 UI: Tweaks to multiple elements (#284) 2020-09-22 20:31:00 -07:00
Lenin Alevski
86426e95f7 Added Annotations, Labels and NodeSelector fields (#285)
For Console/Encryption objects in the  CreateTenant Api
2020-09-22 15:50:37 -07:00
Daniel Valdivia
e5f7870f5e Parity API (#280) 2020-09-22 11:15:21 -07:00
Lenin Alevski
c0ee739624 IV generation for ChaCha20 poly auth scheme (#283)
Generate 16 bytes IV instead of an IV of 32 bytes (and then use half of it) when using ChaCha20 to
encrypt tokens, this is to prevent tokens to become malleable.
2020-09-22 10:49:34 -07:00
Minio Trusted
1dc99498d9 update v0.3.25 2020-09-21 22:07:11 -07:00
Cesar N
319d96c725 Use operator port variables (#282) 2020-09-21 21:31:30 -07:00
Minio Trusted
6d58290a89 update to v0.3.24 2020-09-17 18:30:56 -07:00
Lenin Alevski
666904f902 fix regression when calculating token using chacha20 (#281) 2020-09-17 18:21:54 -07:00
Cesar N
064533d8aa Set annotations and labels at Tenant level (#279)
on Tenant Creation request api
2020-09-17 06:44:16 -07:00
Lenin Alevski
1768af9026 Fix tenant details screen (#277) 2020-09-16 23:01:28 -07:00
Alex
cb7513e9f0 Replaces create tenant functionality (#278)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-09-16 21:47:38 -07:00
Lenin Alevski
645b45cf35 fix tenant details screen (#276) 2020-09-15 14:00:28 -07:00
Cesar N
9f6d965ba2 Add missing validations on tenant info test (#273)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-09-10 15:43:43 -07:00
Cesar N
5348400665 Delete secrets created if it fails on tenant creation (#274)
Also a fix on a parity condition has been fixed.
2020-09-09 17:08:34 -07:00
Minio Trusted
812fd5f253 update to v0.3.23 2020-09-08 12:28:44 -07:00
Lenin Alevski
da9b393e1b fix regression on update update cert and encryption config endpoint (#272) 2020-09-08 12:20:38 -07:00
Minio Trusted
aeaa1a23ce update to v0.3.22 2020-09-07 17:14:21 -07:00
Lenin Alevski
a8d403a216 fix bug for tenant image pull credentials (#271)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-09-06 23:20:27 -07:00
Minio Trusted
7bd898b2c7 update to v0.3.21 2020-09-05 23:50:46 -07:00
Lenin Alevski
dad66db49a Support for adding prometheus annotations on update minio tenant (#269) 2020-09-05 23:48:51 -07:00
Daniel Valdivia
adf3f929a4 Add Tenant Deletion Date to tenant responses (#270) 2020-09-05 23:37:01 -07:00
Lenin Alevski
3b23e877b5 delete unnecessary logs (#268) 2020-09-05 17:39:21 -07:00
Minio Trusted
af4bebb6eb fix go mod tidy 2020-09-04 20:42:30 -07:00
Minio Trusted
8530eb5368 update to v0.3.20 2020-09-04 20:41:46 -07:00
Lenin Alevski
0ba1e76400 centralize errors on a single error function (#266)
prepareError receives an array of errors and return *model.Error object
with a message and error code, we can extend this function to add more
error types/code
2020-09-04 20:32:57 -07:00
Daniel Valdivia
94096ee657 Fix Bug Creating Tenant Pull Secret. (#267) 2020-09-04 17:09:17 -07:00
Minio Trusted
c59387c2b4 update v0.3.19 2020-09-04 11:45:13 -07:00
Lenin Alevski
c5a3eff745 Added endpoint for update tenant certificates (minio/console) (#258) 2020-09-03 10:20:58 -07:00
Cesar N
624891ae1f Replace resources api to return the max allocatable memory (#264) 2020-09-02 17:06:02 -07:00
Minio Trusted
83435e1ab9 update v0.3.18 2020-09-02 12:04:02 -07:00
Lenin Alevski
2b4606e773 fix tls certPool client regression (#263) 2020-08-31 21:40:33 -07:00
Cesar N
30f5943f8a Add api to get cluster nodes' resources (#260) 2020-08-28 21:06:45 -07:00
Cesar N
412ac0a603 Add Tenant Update Zones api (#257)
Since the Tenant's zones is an array, a PUT operation was done where
all zone elements on the Tenant are replaced by the defined ones on the request.
2020-08-26 17:12:59 -07:00
Minio Trusted
b2aa1349f8 update to v0.3.17 2020-08-24 15:27:12 -07:00
Lenin Alevski
8b62aec7fb Added support for prometheus addnotations #293 (#256) 2020-08-24 15:07:36 -07:00
Minio Trusted
83fe33b499 update to v0.3.16 2020-08-20 23:09:02 -07:00
Daniel Valdivia
54d0a1d342 Support for labels at pvc level (#254) 2020-08-20 22:46:07 -07:00
Minio Trusted
c59737a71d update v0.3.15 2020-08-20 21:02:34 -07:00
Lenin Alevski
7c2ba707eb add labels to tenant secrets for easy deletion (#252)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-08-20 19:09:13 -07:00
Cesar N
545a890c45 Delete secrets on tenant deletion (#253) 2020-08-20 18:57:34 -07:00
Minio Trusted
4b42308484 update console update to v0.3.14 2020-08-19 20:36:45 -07:00
Cesar N
5a95fed35b Add option to delete tenant's pvcs on tenant deletion (#251) 2020-08-19 20:34:43 -07:00
Lenin Alevski
f880e3976f encrypt token session using aes-gcm if cpu support it or ChaCha20 (#248)
Harsha's improvement to use binary encoding instead of json encoding
2020-08-18 12:42:13 -07:00
Daniel Valdivia
25fa2f3275 YARN upograde Dependencies (#247) 2020-08-15 21:52:36 -07:00
104 changed files with 11871 additions and 6907 deletions

5
go.mod
View File

@@ -19,13 +19,14 @@ require (
github.com/minio/mc v0.0.0-20200808005614-7e52c104bee1
github.com/minio/minio v0.0.0-20200808024306-2a9819aff876
github.com/minio/minio-go/v7 v7.0.5-0.20200807085956-d7db33ea7618
github.com/minio/operator v0.0.0-20200806194125-c2ff646f4af1
github.com/minio/operator v0.0.0-20200922064400-af3315add727
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/secure-io/sio-go v0.3.1
github.com/stretchr/testify v1.6.1
github.com/unrolled/secure v1.0.7
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
gopkg.in/yaml.v2 v2.3.0
k8s.io/api v0.18.6
k8s.io/apimachinery v0.18.6

685
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@ rules:
- ""
resources:
- namespaces
- secrets
- pods
- services
- events
@@ -18,6 +17,18 @@ rules:
- create
- list
- patch
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- watch
- create
- list
- patch
- deletecollection
- delete
- apiGroups:
- "storage.k8s.io"
resources:

View File

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

View File

@@ -8,4 +8,4 @@ resources:
- console-configmap.yaml
- console-service.yaml
- console-deployment.yaml
- minio-operator.yaml
- https://github.com/minio/operator/?ref=v3.0.10

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@ rules:
- list
- patch
- update
- deletecollection
- apiGroups:
- ""
resources:
@@ -22,12 +23,21 @@ rules:
- services
- events
- resourcequotas
- nodes
verbs:
- get
- watch
- create
- list
- patch
- apiGroups:
- ""
resources:
- persistentvolumeclaims
verbs:
- deletecollection
- list
- get
- apiGroups:
- "storage.k8s.io"
resources:

View File

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

View File

@@ -8,4 +8,4 @@ resources:
- console-configmap.yaml
- console-service.yaml
- console-deployment.yaml
- minio-operator.yaml
- https://github.com/minio/operator/?ref=v3.0.19

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
// 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"
)
// ConsoleConfiguration console configuration
//
// swagger:model consoleConfiguration
type ConsoleConfiguration struct {
MetadataFields
// image
Image string `json:"image,omitempty"`
}
// UnmarshalJSON unmarshals this object from a JSON structure
func (m *ConsoleConfiguration) UnmarshalJSON(raw []byte) error {
// AO0
var aO0 MetadataFields
if err := swag.ReadJSON(raw, &aO0); err != nil {
return err
}
m.MetadataFields = aO0
// AO1
var dataAO1 struct {
Image string `json:"image,omitempty"`
}
if err := swag.ReadJSON(raw, &dataAO1); err != nil {
return err
}
m.Image = dataAO1.Image
return nil
}
// MarshalJSON marshals this object to a JSON structure
func (m ConsoleConfiguration) MarshalJSON() ([]byte, error) {
_parts := make([][]byte, 0, 2)
aO0, err := swag.WriteJSON(m.MetadataFields)
if err != nil {
return nil, err
}
_parts = append(_parts, aO0)
var dataAO1 struct {
Image string `json:"image,omitempty"`
}
dataAO1.Image = m.Image
jsonDataAO1, errAO1 := swag.WriteJSON(dataAO1)
if errAO1 != nil {
return nil, errAO1
}
_parts = append(_parts, jsonDataAO1)
return swag.ConcatJSON(_parts...), nil
}
// Validate validates this console configuration
func (m *ConsoleConfiguration) Validate(formats strfmt.Registry) error {
var res []error
// validation for a type composition with MetadataFields
if err := m.MetadataFields.Validate(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// MarshalBinary interface implementation
func (m *ConsoleConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ConsoleConfiguration) UnmarshalBinary(b []byte) error {
var res ConsoleConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -42,12 +42,18 @@ type CreateTenantRequest struct {
// annotations
Annotations map[string]string `json:"annotations,omitempty"`
// console
Console *ConsoleConfiguration `json:"console,omitempty"`
// console image
ConsoleImage string `json:"console_image,omitempty"`
// enable console
EnableConsole *bool `json:"enable_console,omitempty"`
// enable prometheus
EnablePrometheus *bool `json:"enable_prometheus,omitempty"`
// enable tls
EnableTLS *bool `json:"enable_tls,omitempty"`
@@ -69,6 +75,9 @@ type CreateTenantRequest struct {
// image registry
ImageRegistry *ImageRegistry `json:"image_registry,omitempty"`
// labels
Labels map[string]string `json:"labels,omitempty"`
// mounth path
MounthPath string `json:"mounth_path,omitempty"`
@@ -99,6 +108,10 @@ type CreateTenantRequest struct {
func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsole(formats); err != nil {
res = append(res, err)
}
if err := m.validateEncryption(formats); err != nil {
res = append(res, err)
}
@@ -133,6 +146,24 @@ func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
return nil
}
func (m *CreateTenantRequest) validateConsole(formats strfmt.Registry) error {
if swag.IsZero(m.Console) { // not required
return nil
}
if m.Console != nil {
if err := m.Console.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("console")
}
return err
}
}
return nil
}
func (m *CreateTenantRequest) validateEncryption(formats strfmt.Registry) error {
if swag.IsZero(m.Encryption) { // not required

View File

@@ -0,0 +1,60 @@
// 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/strfmt"
"github.com/go-openapi/swag"
)
// DeleteTenantRequest delete tenant request
//
// swagger:model deleteTenantRequest
type DeleteTenantRequest struct {
// delete pvcs
DeletePvcs bool `json:"delete_pvcs,omitempty"`
}
// Validate validates this delete tenant request
func (m *DeleteTenantRequest) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *DeleteTenantRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *DeleteTenantRequest) UnmarshalBinary(b []byte) error {
var res DeleteTenantRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -32,6 +32,7 @@ import (
//
// swagger:model encryptionConfiguration
type EncryptionConfiguration struct {
MetadataFields
// aws
Aws *AwsConfiguration `json:"aws,omitempty"`
@@ -52,10 +53,100 @@ type EncryptionConfiguration struct {
Vault *VaultConfiguration `json:"vault,omitempty"`
}
// UnmarshalJSON unmarshals this object from a JSON structure
func (m *EncryptionConfiguration) UnmarshalJSON(raw []byte) error {
// AO0
var aO0 MetadataFields
if err := swag.ReadJSON(raw, &aO0); err != nil {
return err
}
m.MetadataFields = aO0
// AO1
var dataAO1 struct {
Aws *AwsConfiguration `json:"aws,omitempty"`
Client *KeyPairConfiguration `json:"client,omitempty"`
Gemalto *GemaltoConfiguration `json:"gemalto,omitempty"`
Image string `json:"image,omitempty"`
Server *KeyPairConfiguration `json:"server,omitempty"`
Vault *VaultConfiguration `json:"vault,omitempty"`
}
if err := swag.ReadJSON(raw, &dataAO1); err != nil {
return err
}
m.Aws = dataAO1.Aws
m.Client = dataAO1.Client
m.Gemalto = dataAO1.Gemalto
m.Image = dataAO1.Image
m.Server = dataAO1.Server
m.Vault = dataAO1.Vault
return nil
}
// MarshalJSON marshals this object to a JSON structure
func (m EncryptionConfiguration) MarshalJSON() ([]byte, error) {
_parts := make([][]byte, 0, 2)
aO0, err := swag.WriteJSON(m.MetadataFields)
if err != nil {
return nil, err
}
_parts = append(_parts, aO0)
var dataAO1 struct {
Aws *AwsConfiguration `json:"aws,omitempty"`
Client *KeyPairConfiguration `json:"client,omitempty"`
Gemalto *GemaltoConfiguration `json:"gemalto,omitempty"`
Image string `json:"image,omitempty"`
Server *KeyPairConfiguration `json:"server,omitempty"`
Vault *VaultConfiguration `json:"vault,omitempty"`
}
dataAO1.Aws = m.Aws
dataAO1.Client = m.Client
dataAO1.Gemalto = m.Gemalto
dataAO1.Image = m.Image
dataAO1.Server = m.Server
dataAO1.Vault = m.Vault
jsonDataAO1, errAO1 := swag.WriteJSON(dataAO1)
if errAO1 != nil {
return nil, errAO1
}
_parts = append(_parts, jsonDataAO1)
return swag.ConcatJSON(_parts...), nil
}
// Validate validates this encryption configuration
func (m *EncryptionConfiguration) Validate(formats strfmt.Registry) error {
var res []error
// validation for a type composition with MetadataFields
if err := m.MetadataFields.Validate(formats); err != nil {
res = append(res, err)
}
if err := m.validateAws(formats); err != nil {
res = append(res, err)
}

View File

@@ -35,7 +35,7 @@ import (
type Error struct {
// code
Code int64 `json:"code,omitempty"`
Code int32 `json:"code,omitempty"`
// message
// Required: true

View File

@@ -0,0 +1,60 @@
// 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/strfmt"
"github.com/go-openapi/swag"
)
// MaxAllocatableMemResponse max allocatable mem response
//
// swagger:model maxAllocatableMemResponse
type MaxAllocatableMemResponse struct {
// max memory
MaxMemory int64 `json:"max_memory,omitempty"`
}
// Validate validates this max allocatable mem response
func (m *MaxAllocatableMemResponse) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *MaxAllocatableMemResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *MaxAllocatableMemResponse) UnmarshalBinary(b []byte) error {
var res MaxAllocatableMemResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,138 @@
// 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"
)
// MetadataConfiguration metadata configuration
//
// swagger:model metadataConfiguration
type MetadataConfiguration struct {
// console
Console *MetadataFields `json:"console,omitempty"`
// kes
Kes *MetadataFields `json:"kes,omitempty"`
// minio
Minio *MetadataFields `json:"minio,omitempty"`
}
// Validate validates this metadata configuration
func (m *MetadataConfiguration) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsole(formats); err != nil {
res = append(res, err)
}
if err := m.validateKes(formats); err != nil {
res = append(res, err)
}
if err := m.validateMinio(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *MetadataConfiguration) validateConsole(formats strfmt.Registry) error {
if swag.IsZero(m.Console) { // not required
return nil
}
if m.Console != nil {
if err := m.Console.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("console")
}
return err
}
}
return nil
}
func (m *MetadataConfiguration) validateKes(formats strfmt.Registry) error {
if swag.IsZero(m.Kes) { // not required
return nil
}
if m.Kes != nil {
if err := m.Kes.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("kes")
}
return err
}
}
return nil
}
func (m *MetadataConfiguration) validateMinio(formats strfmt.Registry) error {
if swag.IsZero(m.Minio) { // not required
return nil
}
if m.Minio != nil {
if err := m.Minio.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("minio")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *MetadataConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *MetadataConfiguration) UnmarshalBinary(b []byte) error {
var res MetadataConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

66
models/metadata_fields.go Normal file
View File

@@ -0,0 +1,66 @@
// 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/strfmt"
"github.com/go-openapi/swag"
)
// MetadataFields metadata fields
//
// swagger:model metadataFields
type MetadataFields struct {
// annotations
Annotations map[string]string `json:"annotations,omitempty"`
// labels
Labels map[string]string `json:"labels,omitempty"`
// node selector
NodeSelector map[string]string `json:"node_selector,omitempty"`
}
// Validate validates this metadata fields
func (m *MetadataFields) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *MetadataFields) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *MetadataFields) UnmarshalBinary(b []byte) error {
var res MetadataFields
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

37
models/parity_response.go Normal file
View File

@@ -0,0 +1,37 @@
// 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/strfmt"
)
// ParityResponse parity response
//
// swagger:model parityResponse
type ParityResponse []string
// Validate validates this parity response
func (m ParityResponse) Validate(formats strfmt.Registry) error {
return nil
}

View File

@@ -35,12 +35,21 @@ import (
// swagger:model tenant
type Tenant struct {
// console image
ConsoleImage string `json:"console_image,omitempty"`
// creation date
CreationDate string `json:"creation_date,omitempty"`
// current state
CurrentState string `json:"currentState,omitempty"`
// deletion date
DeletionDate string `json:"deletion_date,omitempty"`
// enable prometheus
EnablePrometheus bool `json:"enable_prometheus,omitempty"`
// image
Image string `json:"image,omitempty"`

View File

@@ -38,6 +38,9 @@ type TenantList struct {
// current state
CurrentState string `json:"currentState,omitempty"`
// deletion date
DeletionDate string `json:"deletion_date,omitempty"`
// instance count
InstanceCount int64 `json:"instance_count,omitempty"`

View File

@@ -38,6 +38,9 @@ type UpdateTenantRequest struct {
// Pattern: ^((.*?)/(.*?):(.+))$
ConsoleImage string `json:"console_image,omitempty"`
// enable prometheus
EnablePrometheus bool `json:"enable_prometheus,omitempty"`
// image
// Pattern: ^((.*?)/(.*?):(.+))$
Image string `json:"image,omitempty"`

View File

@@ -207,6 +207,12 @@ func (m *Zone) UnmarshalBinary(b []byte) error {
// swagger:model ZoneVolumeConfiguration
type ZoneVolumeConfiguration struct {
// annotations
Annotations map[string]string `json:"annotations,omitempty"`
// labels
Labels map[string]string `json:"labels,omitempty"`
// size
// Required: true
Size *int64 `json:"size"`

View File

@@ -0,0 +1,99 @@
// 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 (
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// ZoneUpdateRequest zone update request
//
// swagger:model zoneUpdateRequest
type ZoneUpdateRequest struct {
// zones
// Required: true
Zones []*Zone `json:"zones"`
}
// Validate validates this zone update request
func (m *ZoneUpdateRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateZones(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneUpdateRequest) validateZones(formats strfmt.Registry) error {
if err := validate.Required("zones", "body", m.Zones); err != nil {
return err
}
for i := 0; i < len(m.Zones); i++ {
if swag.IsZero(m.Zones[i]) { // not required
continue
}
if m.Zones[i] != nil {
if err := m.Zones[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("zones" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneUpdateRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneUpdateRequest) UnmarshalBinary(b []byte) error {
var res ZoneUpdateRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -36,7 +36,7 @@ var (
bucketsDetail = "/buckets/:bucketName"
serviceAccounts = "/service-accounts"
tenants = "/tenants"
tenantsDetail = "/tenants/:tenantName"
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
heal = "/heal"
)

View File

@@ -17,14 +17,17 @@
package auth
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
@@ -33,6 +36,9 @@ import (
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth/token"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/secure-io/sio-go/sioutil"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/pbkdf2"
)
@@ -40,6 +46,7 @@ var (
errNoAuthToken = errors.New("session token missing")
errReadingToken = errors.New("session token internal data is malformed")
errClaimsFormat = errors.New("encrypted session token claims not in the right format")
errorGeneric = errors.New("an error has occurred")
)
// derivedKey is the key used to encrypt the session token claims, its derived using pbkdf on CONSOLE_PBKDF_PASSPHRASE with CONSOLE_PBKDF_SALT
@@ -102,9 +109,10 @@ func NewEncryptedTokenForClient(credentials *credentials.Value, actions []string
// returns a base64 encoded ciphertext
func encryptClaims(accessKeyID, secretAccessKey, sessionToken string, actions []string) (string, error) {
payload := []byte(fmt.Sprintf("%s#%s#%s#%s", accessKeyID, secretAccessKey, sessionToken, strings.Join(actions, ",")))
ciphertext, err := encrypt(payload)
ciphertext, err := encrypt(payload, []byte{})
if err != nil {
return "", err
log.Println(err)
return "", errorGeneric
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
@@ -116,7 +124,7 @@ func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
log.Println(err)
return nil, errClaimsFormat
}
plaintext, err := decrypt(decoded)
plaintext, err := decrypt(decoded, []byte{})
if err != nil {
log.Println(err)
return nil, errClaimsFormat
@@ -136,37 +144,137 @@ func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
}, nil
}
// Encrypt a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
func encrypt(plaintext []byte) ([]byte, error) {
block, _ := aes.NewCipher(derivedKey)
gcm, err := cipher.NewGCM(block)
const (
aesGcm = 0x00
c20p1305 = 0x01
)
// Encrypt a blob of data using AEAD scheme, AES-GCM if the executing CPU
// provides AES hardware support, otherwise will use ChaCha20-Poly1305
// with a pbkdf2 derived key, this function should be used to encrypt a session
// or data key provided as plaintext.
//
// The returned ciphertext data consists of:
// AEAD ID | iv | nonce | encrypted data
// 1 16 12 ~ len(data)
func encrypt(plaintext, associatedData []byte) ([]byte, error) {
iv, err := sioutil.Random(16) // 16 bytes IV
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
var algorithm byte
if sioutil.NativeAES() {
algorithm = aesGcm
} else {
algorithm = c20p1305
}
var aead cipher.AEAD
switch algorithm {
case aesGcm:
mac := hmac.New(sha256.New, derivedKey)
mac.Write(iv)
sealingKey := mac.Sum(nil)
var block cipher.Block
block, err = aes.NewCipher(sealingKey)
if err != nil {
return nil, err
}
aead, err = cipher.NewGCM(block)
if err != nil {
return nil, err
}
case c20p1305:
var sealingKey []byte
sealingKey, err = chacha20.HChaCha20(derivedKey, iv) // HChaCha20 expects nonce of 16 bytes
if err != nil {
return nil, err
}
aead, err = chacha20poly1305.New(sealingKey)
if err != nil {
return nil, err
}
}
nonce, err := sioutil.Random(aead.NonceSize())
if err != nil {
return nil, err
}
cipherText := gcm.Seal(nonce, nonce, plaintext, nil)
return cipherText, nil
sealedBytes := aead.Seal(nil, nonce, plaintext, associatedData)
// ciphertext = AEAD ID | iv | nonce | sealed bytes
var buf bytes.Buffer
buf.WriteByte(algorithm)
buf.Write(iv)
buf.Write(nonce)
buf.Write(sealedBytes)
return buf.Bytes(), nil
}
// Decrypts a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
func decrypt(data []byte) ([]byte, error) {
block, err := aes.NewCipher(derivedKey)
// Decrypts a blob of data using AEAD scheme AES-GCM if the executing CPU
// provides AES hardware support, otherwise will use ChaCha20-Poly1305with
// and a pbkdf2 derived key
func decrypt(ciphertext []byte, associatedData []byte) ([]byte, error) {
var (
algorithm [1]byte
iv [16]byte
nonce [12]byte // This depends on the AEAD but both used ciphers have the same nonce length.
)
r := bytes.NewReader(ciphertext)
if _, err := io.ReadFull(r, algorithm[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, iv[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, nonce[:]); err != nil {
return nil, err
}
var aead cipher.AEAD
switch algorithm[0] {
case aesGcm:
mac := hmac.New(sha256.New, derivedKey)
mac.Write(iv[:])
sealingKey := mac.Sum(nil)
block, err := aes.NewCipher(sealingKey[:])
if err != nil {
return nil, err
}
aead, err = cipher.NewGCM(block)
if err != nil {
return nil, err
}
case c20p1305:
sealingKey, err := chacha20.HChaCha20(derivedKey, iv[:]) // HChaCha20 expects nonce of 16 bytes
if err != nil {
return nil, err
}
aead, err = chacha20poly1305.New(sealingKey)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("invalid algorithm: %v", algorithm)
}
if len(nonce) != aead.NonceSize() {
return nil, fmt.Errorf("invalid nonce size %d, expected %d", len(nonce), aead.NonceSize())
}
sealedBytes, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
nonce, cipherText := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, cipherText, nil)
plaintext, err := aead.Open(nil, nonce[:], sealedBytes, associatedData)
if err != nil {
return nil, err
}
return plaintext, nil
}

View File

@@ -36,12 +36,12 @@ func TestNewJWTWithClaimsForClient(t *testing.T) {
funcAssert := assert.New(t)
// Test-1 : NewEncryptedTokenForClient() is generated correctly without errors
function := "NewEncryptedTokenForClient()"
jwt, err := NewEncryptedTokenForClient(creds, []string{""})
if err != nil || jwt == "" {
token, err := NewEncryptedTokenForClient(creds, []string{""})
if err != nil || token == "" {
t.Errorf("Failed on %s:, error occurred: %s", function, err)
}
// saving jwt for future tests
goodToken = jwt
// saving token for future tests
goodToken = token
// Test-2 : NewEncryptedTokenForClient() throws error because of empty credentials
if _, err = NewEncryptedTokenForClient(nil, []string{""}); err != nil {
funcAssert.Equal("provided credentials are empty", err.Error())

219
pkg/utils/parity.go Normal file
View File

@@ -0,0 +1,219 @@
// 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 utils
import (
"errors"
"fmt"
"sort"
"github.com/minio/minio/pkg/ellipses"
)
// This file implements and supports ellipses pattern for
// `minio server` command line arguments.
// Supported set sizes this is used to find the optimal
// single set size.
var setSizes = []uint64{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
// getDivisibleSize - returns a greatest common divisor of
// all the ellipses sizes.
func getDivisibleSize(totalSizes []uint64) (result uint64) {
gcd := func(x, y uint64) uint64 {
for y != 0 {
x, y = y, x%y
}
return x
}
result = totalSizes[0]
for i := 1; i < len(totalSizes); i++ {
result = gcd(result, totalSizes[i])
}
return result
}
// isValidSetSize - checks whether given count is a valid set size for erasure coding.
var isValidSetSize = func(count uint64) bool {
return (count >= setSizes[0] && count <= setSizes[len(setSizes)-1])
}
// possibleSetCountsWithSymmetry returns symmetrical setCounts based on the
// input argument patterns, the symmetry calculation is to ensure that
// we also use uniform number of drives common across all ellipses patterns.
func possibleSetCountsWithSymmetry(setCounts []uint64, argPatterns []ellipses.ArgPattern) []uint64 {
var newSetCounts = make(map[uint64]struct{})
for _, ss := range setCounts {
var symmetry bool
for _, argPattern := range argPatterns {
for _, p := range argPattern {
if uint64(len(p.Seq)) > ss {
symmetry = uint64(len(p.Seq))%ss == 0
} else {
symmetry = ss%uint64(len(p.Seq)) == 0
}
}
}
// With no arg patterns, it is expected that user knows
// the right symmetry, so either ellipses patterns are
// provided (recommended) or no ellipses patterns.
if _, ok := newSetCounts[ss]; !ok && (symmetry || argPatterns == nil) {
newSetCounts[ss] = struct{}{}
}
}
setCounts = []uint64{}
for setCount := range newSetCounts {
setCounts = append(setCounts, setCount)
}
// Not necessarily needed but it ensures to the readers
// eyes that we prefer a sorted setCount slice for the
// subsequent function to figure out the right common
// divisor, it avoids loops.
sort.Slice(setCounts, func(i, j int) bool {
return setCounts[i] < setCounts[j]
})
return setCounts
}
func commonSetDriveCount(divisibleSize uint64, setCounts []uint64) (setSize uint64) {
// prefers setCounts to be sorted for optimal behavior.
if divisibleSize < setCounts[len(setCounts)-1] {
return divisibleSize
}
// Figure out largest value of total_drives_in_erasure_set which results
// in least number of total_drives/total_drives_erasure_set ratio.
prevD := divisibleSize / setCounts[0]
for _, cnt := range setCounts {
if divisibleSize%cnt == 0 {
d := divisibleSize / cnt
if d <= prevD {
prevD = d
setSize = cnt
}
}
}
return setSize
}
// getSetIndexes returns list of indexes which provides the set size
// on each index, this function also determines the final set size
// The final set size has the affinity towards choosing smaller
// indexes (total sets)
func getSetIndexes(args []string, totalSizes []uint64, argPatterns []ellipses.ArgPattern) (setIndexes [][]uint64, err error) {
if len(totalSizes) == 0 || len(args) == 0 {
return nil, errors.New("invalid argument")
}
setIndexes = make([][]uint64, len(totalSizes))
for _, totalSize := range totalSizes {
// Check if totalSize has minimum range upto setSize
if totalSize < setSizes[0] {
return nil, fmt.Errorf("incorrect number of endpoints provided %s", args)
}
}
commonSize := getDivisibleSize(totalSizes)
possibleSetCounts := func(setSize uint64) (ss []uint64) {
for _, s := range setSizes {
if setSize%s == 0 {
ss = append(ss, s)
}
}
return ss
}
setCounts := possibleSetCounts(commonSize)
if len(setCounts) == 0 {
err = fmt.Errorf("incorrect number of endpoints provided %s, number of disks %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
return nil, err
}
// Returns possible set counts with symmetry.
setCounts = possibleSetCountsWithSymmetry(setCounts, argPatterns)
if len(setCounts) == 0 {
err = fmt.Errorf("no symmetric distribution detected with input endpoints provided %s, disks %d cannot be spread symmetrically by any supported erasure set sizes %d", args, commonSize, setSizes)
return nil, err
}
// Final set size with all the symmetry accounted for.
setSize := commonSetDriveCount(commonSize, setCounts)
// Check whether setSize is with the supported range.
if !isValidSetSize(setSize) {
err = fmt.Errorf("incorrect number of endpoints provided %s, number of disks %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
return nil, err
}
for i := range totalSizes {
for j := uint64(0); j < totalSizes[i]/setSize; j++ {
setIndexes[i] = append(setIndexes[i], setSize)
}
}
return setIndexes, nil
}
// Return the total size for each argument patterns.
func getTotalSizes(argPatterns []ellipses.ArgPattern) []uint64 {
var totalSizes []uint64
for _, argPattern := range argPatterns {
var totalSize uint64 = 1
for _, p := range argPattern {
totalSize = totalSize * uint64(len(p.Seq))
}
totalSizes = append(totalSizes, totalSize)
}
return totalSizes
}
// PossibleParityValues returns possible parities for input args,
// parties are calculated in uniform manner for one zone or
// multiple zones, ensuring that parities returned are common
// and applicable across all zones.
func PossibleParityValues(args ...string) ([]string, error) {
setIndexes, err := parseEndpointSet(args...)
if err != nil {
return nil, err
}
maximumParity := setIndexes[0][0] / 2
var parities []string
for maximumParity >= 2 {
parities = append(parities, fmt.Sprintf("EC:%d", maximumParity))
maximumParity--
}
return parities, nil
}
// Parses all arguments and returns an endpointSet which is a collection
// of endpoints following the ellipses pattern, this is what is used
// by the object layer for initializing itself.
func parseEndpointSet(args ...string) (setIndexes [][]uint64, err error) {
var argPatterns = make([]ellipses.ArgPattern, len(args))
for i, arg := range args {
patterns, err := ellipses.FindEllipsesPatterns(arg)
if err != nil {
return nil, err
}
argPatterns[i] = patterns
}
return getSetIndexes(args, getTotalSizes(argPatterns), argPatterns)
}

281
pkg/utils/parity_test.go Normal file
View File

@@ -0,0 +1,281 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// All rights reserved
package utils
import (
"reflect"
"testing"
"github.com/minio/minio/pkg/ellipses"
)
func TestGetDivisibleSize(t *testing.T) {
testCases := []struct {
totalSizes []uint64
result uint64
}{{[]uint64{24, 32, 16}, 8},
{[]uint64{32, 8, 4}, 4},
{[]uint64{8, 8, 8}, 8},
{[]uint64{24}, 24},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
gotGCD := getDivisibleSize(testCase.totalSizes)
if testCase.result != gotGCD {
t.Errorf("Expected %v, got %v", testCase.result, gotGCD)
}
})
}
}
// Test tests calculating set indexes.
func TestGetSetIndexes(t *testing.T) {
testCases := []struct {
args []string
totalSizes []uint64
indexes [][]uint64
success bool
}{
// Invalid inputs.
{
[]string{"data{1...3}"},
[]uint64{3},
nil,
false,
},
{
[]string{"data/controller1/export{1...2}, data/controller2/export{1...4}, data/controller3/export{1...8}"},
[]uint64{2, 4, 8},
nil,
false,
},
{
[]string{"data{1...17}/export{1...52}"},
[]uint64{14144},
nil,
false,
},
// Valid inputs.
{
[]string{"data{1...27}"},
[]uint64{27},
[][]uint64{{9, 9, 9}},
true,
},
{
[]string{"http://host{1...3}/data{1...180}"},
[]uint64{540},
[][]uint64{{15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15}},
true,
},
{
[]string{"http://host{1...2}.rack{1...4}/data{1...180}"},
[]uint64{1440},
[][]uint64{{16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}},
true,
},
{
[]string{"http://host{1...2}/data{1...180}"},
[]uint64{360},
[][]uint64{{12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12}},
true,
},
{
[]string{"data/controller1/export{1...4}, data/controller2/export{1...8}, data/controller3/export{1...12}"},
[]uint64{4, 8, 12},
[][]uint64{{4}, {4, 4}, {4, 4, 4}},
true,
},
{
[]string{"data{1...64}"},
[]uint64{64},
[][]uint64{{16, 16, 16, 16}},
true,
},
{
[]string{"data{1...24}"},
[]uint64{24},
[][]uint64{{12, 12}},
true,
},
{
[]string{"data/controller{1...11}/export{1...8}"},
[]uint64{88},
[][]uint64{{11, 11, 11, 11, 11, 11, 11, 11}},
true,
},
{
[]string{"data{1...4}"},
[]uint64{4},
[][]uint64{{4}},
true,
},
{
[]string{"data/controller1/export{1...10}, data/controller2/export{1...10}, data/controller3/export{1...10}"},
[]uint64{10, 10, 10},
[][]uint64{{10}, {10}, {10}},
true,
},
{
[]string{"data{1...16}/export{1...52}"},
[]uint64{832},
[][]uint64{{16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}},
true,
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
var argPatterns = make([]ellipses.ArgPattern, len(testCase.args))
for i, arg := range testCase.args {
patterns, err := ellipses.FindEllipsesPatterns(arg)
if err != nil {
t.Fatalf("Unexpected failure %s", err)
}
argPatterns[i] = patterns
}
gotIndexes, err := getSetIndexes(testCase.args, testCase.totalSizes, argPatterns)
if err != nil && testCase.success {
t.Errorf("Expected success but failed instead %s", err)
}
if err == nil && !testCase.success {
t.Errorf("Expected failure but passed instead")
}
if !reflect.DeepEqual(testCase.indexes, gotIndexes) {
t.Errorf("Expected %v, got %v", testCase.indexes, gotIndexes)
}
})
}
}
// Test tests possible parities returned for any input args
func TestPossibleParities(t *testing.T) {
testCases := []struct {
arg string
parities []string
success bool
}{
// Tests invalid inputs.
{
"...",
nil,
false,
},
// No range specified.
{
"{...}",
nil,
false,
},
// Invalid range.
{
"http://minio{2...3}/export/set{1...0}",
nil,
false,
},
// Range cannot be smaller than 4 minimum.
{
"/export{1..2}",
nil,
false,
},
// Unsupported characters.
{
"/export/test{1...2O}",
nil,
false,
},
// Tests valid inputs.
{
"{1...27}",
[]string{"EC:4", "EC:3", "EC:2"},
true,
},
{
"/export/set{1...64}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// Valid input for distributed setup.
{
"http://minio{2...3}/export/set{1...64}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// Supporting some advanced cases.
{
"http://minio{1...64}.mydomain.net/data",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
{
"http://rack{1...4}.mydomain.minio{1...16}/data",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// Supporting kubernetes cases.
{
"http://minio{0...15}.mydomain.net/data{0...1}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// No host regex, just disks.
{
"http://server1/data{1...32}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// No host regex, just disks with two position numerics.
{
"http://server1/data{01...32}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// More than 2 ellipses are supported as well.
{
"http://minio{2...3}/export/set{1...64}/test{1...2}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// More than 1 ellipses per argument for standalone setup.
{
"/export{1...10}/disk{1...10}",
[]string{"EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// IPv6 ellipses with hexadecimal expansion
{
"http://[2001:3984:3989::{1...a}]/disk{1...10}",
[]string{"EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// IPv6 ellipses with hexadecimal expansion with 3 position numerics.
{
"http://[2001:3984:3989::{001...00a}]/disk{1...10}",
[]string{"EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
gotPs, err := PossibleParityValues(testCase.arg)
if err != nil && testCase.success {
t.Errorf("Expected success but failed instead %s", err)
}
if err == nil && !testCase.success {
t.Errorf("Expected failure but passed instead")
}
if !reflect.DeepEqual(testCase.parities, gotPs) {
t.Errorf("Expected %v, got %v", testCase.parities, gotPs)
}
})
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,333 @@
// 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/>.
/* Copyright (c) 2020 MinIO, Inc. All rights reserved. */
export interface ITenantsObject {
tenants: ITenant[];
}
export interface ITenant {
creation_date: string;
deletion_date: string;
currentState: string;
image: string;
console_image: string;
instance_count: string;
name: string;
namespace?: string;
total_size: string;
used_size: string;
volume_count: string;
volume_size: string;
volumes_per_server?: string;
zone_count: string;
zones?: IZoneModel[];
used_capacity?: string;
endpoint?: string;
storage_class?: string;
enable_prometheus: boolean;
}
export interface IVolumeConfiguration {
size: string;
storage_class_name: string;
labels?: any;
}
export interface ITenantCreator {
name: string;
service_name: string;
enable_console: boolean;
enable_prometheus: boolean;
enable_tls: boolean;
access_key: string;
secret_key: string;
image: string;
console_image: string;
zones: IZoneModel[];
namespace: string;
erasureCodingParity: number;
tls?: ITLSTenantConfiguration;
encryption?: IEncryptionConfiguration;
idp?: IIDPConfiguration;
annotations?: Object;
}
export interface ITenantUpdateObject {
image: string;
image_registry?: IRegistryObject;
}
export interface IRegistryObject {
registry: string;
username: string;
password: string;
}
export interface ITenantUsage {
used: string;
disk_used: string;
}
export interface IAffinityModel {
podAntiAffinity: IPodAntiAffinityModel;
}
export interface IPodAntiAffinityModel {
requiredDuringSchedulingIgnoredDuringExecution: IPodAffinityTerm[];
}
export interface IPodAffinityTerm {
labelSelector: IPodAffinityTermLabelSelector;
topologyKey: string;
}
export interface IPodAffinityTermLabelSelector {
matchExpressions: IMatchExpressionItem[];
}
export interface IMatchExpressionItem {
key: string;
operator: string;
values: string[];
}
export interface ITolerationModel {
effect: string;
key: string;
operator: string;
value?: string;
tolerationSeconds?: ITolerationSeconds;
}
export interface ITolerationSeconds {
seconds: number;
}
export interface IResourceModel {
requests: IResourceRequests;
limits?: IResourceLimits;
}
export interface IResourceRequests {
memory: number;
}
export interface IResourceLimits {
memory: number;
}
export interface ITLSTenantConfiguration {
minio: ITLSConfiguration;
console: ITLSConfiguration;
}
export interface ITLSConfiguration {
crt: string;
key: string;
}
export interface IEncryptionConfiguration {
server: ITLSConfiguration;
client: ITLSConfiguration;
master_key?: string;
gemalto?: IGemaltoConfig;
aws?: IAWSConfig;
vault?: IVaultConfig;
}
export interface IVaultConfig {
endpoint: string;
engine?: string;
namespace?: string;
prefix?: string;
approle: IApproleConfig;
tls: IVaultTLSConfig;
status: IVaultStatusConfig;
}
export interface IGemaltoConfig {
keysecure: IKeysecureConfig;
}
export interface IAWSConfig {
secretsmanager: ISecretsManagerConfig;
}
export interface IApproleConfig {
engine: string;
id: string;
secret: string;
retry: number;
}
export interface IVaultTLSConfig {
key: string;
crt: string;
ca: string;
}
export interface IVaultStatusConfig {
ping: number;
}
export interface IKeysecureConfig {
endpoint: string;
credentials: IGemaltoCredentials;
tls: IGemaltoTLS;
}
export interface IGemaltoCredentials {
token: string;
domain: string;
retry?: number;
}
export interface IGemaltoTLS {
ca: string;
}
export interface ISecretsManagerConfig {
endpoint: string;
region: string;
kmskey?: string;
credentials: IAWSCredentials;
}
export interface IAWSCredentials {
accesskey: string;
secretkey: string;
token?: string;
}
export interface IIDPConfiguration {
oidc?: IOpenIDConfiguration;
active_directory: IActiveDirectoryConfiguration;
}
export interface IOpenIDConfiguration {
url: string;
client_id: string;
secret_id: string;
}
export interface IActiveDirectoryConfiguration {
url: string;
skip_tls_verification: boolean;
server_insecure: boolean;
user_search_filter: string;
group_Search_base_dn: string;
group_search_filter: string;
group_name_attribute: string;
}
export interface IStorageDistribution {
error: number;
nodes: number;
persistentVolumes: number;
disks: number;
pvSize: number;
}
export interface IErasureCodeCalc {
error: number;
maxEC: string;
erasureCodeSet: number;
rawCapacity: string;
storageFactors: IStorageFactors[];
defaultEC: string;
}
export interface IStorageFactors {
erasureCode: string;
storageFactor: number;
maxCapacity: string;
}
export interface ITenantHealthInList {
name: string;
namespace: string;
status?: string;
message?: string;
}
export interface ITenantsListHealthRequest {
tenants: ITenantHealthInList[];
}
export interface IMaxAllocatableMemoryRequest {
num_nodes: number;
}
export interface IMaxAllocatableMemoryResponse {
max_memory: number;
}
export interface IEncryptionUpdateRequest {
encryption: IEncryptionConfiguration;
}
export interface IArchivedTenantsList {
tenants: IArchivedTenant[];
}
export interface IArchivedTenant {
namespace: string;
tenant: string;
number_volumes: number;
capacity: number;
}
export interface IZoneModel {
name?: string;
servers: number;
volumes_per_server: number;
volume_configuration: IVolumeConfiguration;
affinity?: IAffinityModel;
tolerations?: ITolerationModel[];
resources?: IResourceModel;
}
export interface IUpdateZone {
zones: IZoneModel[];
}
export interface INode {
name: string;
freeSpace: string;
totalSpace: string;
disks: IDisk[];
}
export interface IStorageType {
freeSpace: string;
totalSpace: string;
storageClasses: string[];
nodes: INode[];
schedulableNodes: INode[];
}
export interface IDisk {
name: string;
freeSpace: string;
totalSpace: string;
}
export interface ICapacity {
value: string;
unit: string;
}

View File

@@ -15,6 +15,10 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import storage from "local-storage-fallback";
import { ICapacity, IStorageType, IZoneModel } from "./types";
const minStReq = 1073741824; // Minimal Space required for MinIO
const minMemReq = 2147483648; // Minimal Memory required for MinIO in bytes
export const units = [
"B",
@@ -28,6 +32,8 @@ export const units = [
"YiB",
];
export const k8sUnits = ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei"];
export const k8sCalcUnits = ["B", ...k8sUnits];
export const niceBytes = (x: string) => {
let l = 0,
n = parseInt(x, 10) || 0;
@@ -90,12 +96,19 @@ export const k8sfactorForDropdown = () => {
};
//getBytes, converts from a value and a unit from units array to bytes
export const getBytes = (value: string, unit: string) => {
export const getBytes = (
value: string,
unit: string,
fork8s: boolean = false
) => {
const vl: number = parseFloat(value);
const powFactor = units.findIndex((element) => element === unit);
if (powFactor == -1) {
return 0;
const unitsTake = fork8s ? k8sCalcUnits : units;
const powFactor = unitsTake.findIndex((element) => element === unit);
if (powFactor === -1) {
return "0";
}
const factor = Math.pow(1024, powFactor);
const total = vl * factor;
@@ -105,6 +118,220 @@ export const getBytes = (value: string, unit: string) => {
//getTotalSize gets the total size of a value & unit
export const getTotalSize = (value: string, unit: string) => {
const bytes = getBytes(value, unit).toString(10);
const bytes = getBytes(value, unit, true).toString();
return niceBytes(bytes);
};
export const setMemoryResource = (
memorySize: number,
capacitySize: string,
maxMemorySize: number
) => {
// value always comes as Gi
const requestedSizeBytes = getBytes(memorySize.toString(10), "Gi", true);
const memReqSize = parseInt(requestedSizeBytes, 10);
if (maxMemorySize === 0) {
return {
error: "There is no memory available for the selected number of nodes",
request: 0,
limit: 0,
};
}
if (maxMemorySize < minMemReq) {
return {
error: "There are not enough memory resources available",
request: 0,
limit: 0,
};
}
if (memReqSize < minMemReq) {
return {
error: "The requested memory size must be greater than 2Gi",
request: 0,
limit: 0,
};
}
if (memReqSize > maxMemorySize) {
return {
error:
"The requested memory is greater than the max available memory for the selected number of nodes",
request: 0,
limit: 0,
};
}
const capSize = parseInt(capacitySize, 10);
let memLimitSize = memReqSize;
// set memory limit based on the capacitySize
// if capacity size is lower than 1TiB we use the limit equal to request
if (capSize >= parseInt(getBytes("1", "Pi", true), 10)) {
memLimitSize = Math.max(
memReqSize,
parseInt(getBytes("64", "Gi", true), 10)
);
} else if (capSize >= parseInt(getBytes("100", "Ti"), 10)) {
memLimitSize = Math.max(
memReqSize,
parseInt(getBytes("32", "Gi", true), 10)
);
} else if (capSize >= parseInt(getBytes("10", "Ti"), 10)) {
memLimitSize = Math.max(
memReqSize,
parseInt(getBytes("16", "Gi", true), 10)
);
} else if (capSize >= parseInt(getBytes("1", "Ti"), 10)) {
memLimitSize = Math.max(
memReqSize,
parseInt(getBytes("8", "Gi", true), 10)
);
}
return {
error: "",
request: memReqSize,
limit: memLimitSize,
};
};
export const calculateDistribution = (
capacityToUse: ICapacity,
forcedNodes: number = 0,
limitSize: number = 0
) => {
let numberOfNodes = {};
const requestedSizeBytes = getBytes(
capacityToUse.value,
capacityToUse.unit,
true
);
if (parseInt(requestedSizeBytes, 10) < minStReq) {
return {
error: "The zone size must be greater than 1Gi",
nodes: 0,
persistentVolumes: 0,
disks: 0,
pvSize: 0,
};
}
if (forcedNodes < 4) {
return {
error: "Number of nodes cannot be less than 4",
nodes: 0,
persistentVolumes: 0,
disks: 0,
pvSize: 0,
};
}
numberOfNodes = calculateStorage(requestedSizeBytes, forcedNodes, limitSize);
return numberOfNodes;
};
const calculateStorage = (
requestedBytes: string,
forcedNodes: number,
limitSize: number
) => {
// Size validation
const intReqBytes = parseInt(requestedBytes, 10);
const maxDiskSize = minStReq * 256; // 256 GiB
// We get the distribution
return structureCalc(forcedNodes, intReqBytes, maxDiskSize, limitSize);
};
const structureCalc = (
nodes: number,
desiredCapacity: number,
maxDiskSize: number,
maxClusterSize: number,
disksPerNode: number = 0
) => {
if (
isNaN(nodes) ||
isNaN(desiredCapacity) ||
isNaN(maxDiskSize) ||
isNaN(maxClusterSize)
) {
return {
error: "Some provided data is invalid, please try again.",
nodes: 0,
persistentVolumes: 0,
disks: 0,
volumePerDisk: 0,
}; // Invalid Data
}
let persistentVolumeSize = 0;
let numberPersistentVolumes = 0;
let volumesPerServer = 0;
if (disksPerNode === 0) {
persistentVolumeSize = Math.floor(
Math.min(desiredCapacity / Math.max(4, nodes), maxDiskSize)
); // pVS = min((desiredCapacity / max(4 | nodes)) | maxDiskSize)
numberPersistentVolumes = desiredCapacity / persistentVolumeSize; // nPV = dC / pVS
volumesPerServer = numberPersistentVolumes / nodes; // vPS = nPV / n
}
if (disksPerNode) {
volumesPerServer = disksPerNode;
numberPersistentVolumes = volumesPerServer * nodes;
persistentVolumeSize = Math.floor(
desiredCapacity / numberPersistentVolumes
);
}
// Volumes are not exact, we force the volumes number & minimize the volume size
if (volumesPerServer % 1 > 0) {
volumesPerServer = Math.ceil(volumesPerServer); // Increment of volumes per server
numberPersistentVolumes = volumesPerServer * nodes; // nPV = vPS * n
persistentVolumeSize = Math.floor(
desiredCapacity / numberPersistentVolumes
); // pVS = dC / nPV
const limitSize = persistentVolumeSize * volumesPerServer * nodes; // lS = pVS * vPS * n
if (limitSize > maxClusterSize) {
return {
error: "We were not able to allocate this server.",
nodes: 0,
persistentVolumes: 0,
disks: 0,
volumePerDisk: 0,
}; // Cannot allocate this server
}
}
if (persistentVolumeSize < minStReq) {
return {
error:
"Disk Size with this combination would be less than 1Gi, please try another combination",
nodes: 0,
persistentVolumes: 0,
disks: 0,
volumePerDisk: 0,
}; // Cannot allocate this volume size
}
return {
error: "",
nodes,
persistentVolumes: numberPersistentVolumes,
disks: volumesPerServer,
pvSize: persistentVolumeSize,
};
};
// Zone Name Generator
export const generateZoneName = (zones: IZoneModel[]) => {
const zoneCounter = zones.length;
return `zone-${zoneCounter}`;
};

View File

@@ -23,7 +23,7 @@ import {
DialogContent,
DialogContentText,
DialogTitle,
LinearProgress
LinearProgress,
} from "@material-ui/core";
import api from "../../../../common/api";
import { BucketList } from "../types";
@@ -32,8 +32,8 @@ import Typography from "@material-ui/core/Typography";
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
}
color: "red",
},
});
interface IDeleteBucketProps {
@@ -54,7 +54,7 @@ class DeleteBucket extends React.Component<
> {
state: IDeleteBucketState = {
deleteLoading: false,
deleteError: ""
deleteError: "",
};
removeRecord() {
@@ -66,23 +66,23 @@ class DeleteBucket extends React.Component<
this.setState({ deleteLoading: true }, () => {
api
.invoke("DELETE", `/api/v1/buckets/${selectedBucket}`, {
name: selectedBucket
name: selectedBucket,
})
.then((res: BucketList) => {
this.setState(
{
deleteLoading: false,
deleteError: ""
deleteError: "",
},
() => {
this.props.closeDeleteModalAndRefresh(true);
}
);
})
.catch(err => {
.catch((err) => {
this.setState({
deleteLoading: false,
deleteError: err
deleteError: err,
});
});
});

View File

@@ -339,7 +339,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">
Bucket > {match.params["bucketName"]}
{`Bucket > ${match.params["bucketName"]}`}
</Typography>
</Grid>
<Grid item xs={12}>

View File

@@ -0,0 +1,113 @@
// 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/>.
import React from "react";
import { Grid, InputLabel, Tooltip } from "@material-ui/core";
import HelpIcon from "@material-ui/icons/Help";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
import { fileProcess } from "./utils";
interface InputBoxProps {
label: string;
classes: any;
onChange: (e: string) => void;
id: string;
name: string;
disabled?: boolean;
tooltip?: string;
required?: boolean;
error?: string;
accept?: string;
}
const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...tooltipHelper,
textBoxContainer: {
flexGrow: 1,
position: "relative",
},
errorState: {
color: "#b53b4b",
fontSize: 14,
position: "absolute",
top: 7,
right: 7,
},
});
const FileSelector = ({
label,
classes,
onChange,
id,
name,
disabled = false,
tooltip = "",
required,
error = "",
accept = "",
}: InputBoxProps) => {
return (
<React.Fragment>
<Grid
item
xs={12}
className={`${classes.fieldContainer} ${
error !== "" ? classes.errorInField : ""
}`}
>
{label !== "" && (
<InputLabel
htmlFor={id}
className={`${error !== "" ? classes.fieldLabelError : ""} ${
classes.inputLabel
}`}
>
<span>
{label}
{required ? "*" : ""}
</span>
{tooltip !== "" && (
<div className={classes.tooltipContainer}>
<Tooltip title={tooltip} placement="top-start">
<HelpIcon className={classes.tooltip} />
</Tooltip>
</div>
)}
</InputLabel>
)}
<div className={classes.textBoxContainer}>
<input
type="file"
name={name}
onChange={(e) => {
fileProcess(e, (data: any) => {
onChange(data);
});
}}
accept={accept}
required
/>
</div>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(FileSelector);

View File

@@ -0,0 +1,34 @@
// 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/>.
export const fileProcess = (evt: any, callback: any) => {
const file = evt.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
// reader.readAsDataURL(file) output will be something like: data:application/x-x509-ca-cert;base64,LS0tLS1CRUdJTiBDRVJUSU
// we care only about the actual base64 part (everything after "data:application/x-x509-ca-cert;base64,")
const fileBase64 = reader.result;
if (fileBase64) {
const fileArray = fileBase64.toString().split("base64,");
if (fileArray.length === 2) {
callback(fileArray[1]);
}
}
};
};

View File

@@ -42,6 +42,7 @@ interface SelectProps {
onChange: (
e: React.ChangeEvent<{ name?: string | undefined; value: unknown }>
) => void;
disabled?: boolean;
classes: any;
}
@@ -51,7 +52,7 @@ const styles = (theme: Theme) =>
...tooltipHelper,
inputLabel: {
...fieldBasic.inputLabel,
width: 116,
width: 215,
},
});
@@ -88,6 +89,7 @@ const SelectWrapper = ({
label,
tooltip = "",
value,
disabled = false,
}: SelectProps) => {
return (
<React.Fragment>
@@ -111,6 +113,7 @@ const SelectWrapper = ({
value={value}
onChange={onChange}
input={<SelectStyled />}
disabled={disabled}
>
{options.map((option) => (
<MenuItem

View File

@@ -265,7 +265,7 @@ const TableWrapper = ({
</Grid>
</Grid>
)}
{records && records.length > 0 ? (
{records && !isLoading && records.length > 0 ? (
<Table size="small" stickyHeader={stickyHeader}>
<TableHead className={classes.minTableHeader}>
<TableRow>

View File

@@ -311,7 +311,7 @@ const Console = ({
},
{
component: TenantDetails,
path: "/tenants/:tenantName",
path: "/namespaces/:tenantNamespace/tenants/:tenantName",
},
];
const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]);

File diff suppressed because it is too large Load Diff

View File

@@ -27,11 +27,12 @@ import {
import Typography from "@material-ui/core/Typography";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import api from "../../../../common/api";
import { ITenant } from "./types";
interface IDeleteTenant {
classes: any;
deleteOpen: boolean;
selectedTenant: string;
selectedTenant: ITenant;
closeDeleteModalAndRefresh: (refreshList: boolean) => any;
}
@@ -54,7 +55,10 @@ const DeleteTenant = ({
useEffect(() => {
if (deleteLoading) {
api
.invoke("DELETE", `/api/v1/tenants/${selectedTenant}`)
.invoke(
"DELETE",
`/api/v1/namespaces/${selectedTenant.namespace}/tenants/${selectedTenant.name}`
)
.then(() => {
setDeleteLoading(false);
setDeleteError("");
@@ -85,7 +89,7 @@ const DeleteTenant = ({
<DialogContent>
{deleteLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">
Are you sure you want to delete tenant <b>{selectedTenant}</b>?
Are you sure you want to delete tenant <b>{selectedTenant.name}</b>?
{deleteError !== "" && (
<React.Fragment>
<br />

View File

@@ -20,7 +20,7 @@ import Typography from "@material-ui/core/Typography";
import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import { Button } from "@material-ui/core";
import { Button, IconButton } from "@material-ui/core";
import { CreateIcon } from "../../../../icons";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
@@ -32,6 +32,10 @@ import DeleteTenant from "./DeleteTenant";
import AddTenant from "./AddTenant";
import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
import history from "../../../../history";
import RefreshIcon from "@material-ui/icons/Refresh";
import TenantCredentialsPrompt from "./TenantCredentialsPrompt/TenantCredentialsPrompt";
import { NewTenantCredential } from "./TenantCredentialsPrompt/types";
interface ITenantsList {
classes: any;
@@ -96,11 +100,11 @@ const ListTenants = ({ classes }: ITenantsList) => {
const [
createdAccount,
setCreatedAccount,
] = useState<NewServiceAccount | null>(null);
] = useState<NewTenantCredential | null>(null);
const closeAddModalAndRefresh = (
reloadData: boolean,
res: NewServiceAccount | null
res: NewTenantCredential | null
) => {
setCreateTenantOpen(false);
@@ -122,11 +126,16 @@ const ListTenants = ({ classes }: ITenantsList) => {
}
};
const confirmDeleteTenant = (tenant: string) => {
const confirmDeleteTenant = (tenant: ITenant) => {
setSelectedTenant(tenant);
setDeleteOpen(true);
};
const redirectToTenantDetails = (tenant: ITenant) => {
history.push(`/namespaces/${tenant.namespace}/tenants/${tenant.name}`);
return;
};
const closeCredentialsModal = () => {
setShowNewCredentials(false);
setCreatedAccount(null);
@@ -149,8 +158,8 @@ const ListTenants = ({ classes }: ITenantsList) => {
};
const tableActions = [
{ type: "view", to: `/tenants`, sendOnlyId: true },
{ type: "delete", onClick: confirmDeleteTenant, sendOnlyId: true },
{ type: "view", onClick: redirectToTenantDetails },
{ type: "delete", onClick: confirmDeleteTenant },
];
const filteredRecords = records
@@ -187,9 +196,7 @@ const ListTenants = ({ classes }: ITenantsList) => {
}
for (let i = 0; i < resTenants.length; i++) {
const total =
resTenants[i].volume_count * resTenants[i].volume_size;
resTenants[i].capacity = niceBytes(total + "");
resTenants[i].capacity = niceBytes(resTenants[i].total_size + "");
}
setRecords(resTenants);
@@ -231,7 +238,7 @@ const ListTenants = ({ classes }: ITenantsList) => {
/>
)}
{showNewCredentials && (
<CredentialsPrompt
<TenantCredentialsPrompt
newServiceAccount={createdAccount}
open={showNewCredentials}
closeModal={() => {
@@ -248,6 +255,17 @@ const ListTenants = ({ classes }: ITenantsList) => {
<br />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<IconButton
color="primary"
aria-label="Refresh Tenant List"
component="span"
onClick={() => {
setIsLoading(true);
}}
>
<RefreshIcon />
</IconButton>
<TextField
placeholder="Search Tenants"
className={classes.searchField}

View File

@@ -86,7 +86,14 @@ const ZonesMultiSelector = ({
onChange,
classes,
}: IZonesMultiSelector) => {
const defaultZone: IZone = { name: "", servers: 0, capacity: "", volumes: 0 };
const defaultZone: IZone = {
name: "",
servers: 0,
capacity: "",
volumes: 0,
volumes_per_server: 0,
volume_configuration: { size: 0, storage_class: "", labels: null },
};
const [currentElements, setCurrentElements] = useState<IZone[]>([]);
const [internalCounter, setInternalCounter] = useState<number>(1);

View File

@@ -17,18 +17,32 @@
export interface IZone {
name: string;
servers: number;
volumes_per_server: number;
volume_configuration: IVolumeConfiguration;
// computed
capacity: string;
volumes: number;
}
export interface IAddZoneRequest {
name: string;
servers: number;
volumes_per_server: number;
volume_configuration: IVolumeConfiguration;
}
export interface IVolumeConfiguration {
size: number;
storage_class: string;
labels: { [key: string]: any } | null;
}
export interface ITenant {
total_size: number;
name: string;
namespace: string;
image: string;
console_image: string;
zone_count: number;
currentState: string;
instance_count: 4;

View File

@@ -11,13 +11,14 @@ import {
niceBytes,
} from "../../../../common/utils";
import { Button, LinearProgress } from "@material-ui/core";
import api from "../../../../common/api";
import { IAddZoneRequest, ITenant } from "../ListTenants/types";
interface IAddZoneProps {
tenant: ITenant;
classes: any;
open: boolean;
onCloseZoneAndReload: (shouldReload: boolean) => void;
volumesPerInstance: number;
volumeSize: number;
}
const styles = (theme: Theme) =>
@@ -63,18 +64,18 @@ const styles = (theme: Theme) =>
});
const AddZoneModal = ({
tenant,
classes,
open,
onCloseZoneAndReload,
volumesPerInstance,
volumeSize,
}: IAddZoneProps) => {
const [addSending, setAddSending] = useState<boolean>(false);
const [zoneName, setZoneName] = useState<string>("");
const [numberOfInstances, setNumberOfInstances] = useState<number>(0);
const [numberOfNodes, setNumberOfNodes] = useState<number>(0);
const [volumeSize, setVolumeSize] = useState<number>(0);
const [volumesPerServer, setVolumesPerSever] = useState<number>(0);
const instanceCapacity: number = volumeSize * volumesPerInstance;
const totalCapacity: number = instanceCapacity * numberOfInstances;
const instanceCapacity: number = volumeSize * 1073741824 * volumesPerServer;
const totalCapacity: number = instanceCapacity * numberOfNodes;
return (
<ModalWrapper
@@ -88,30 +89,66 @@ const AddZoneModal = ({
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setAddSending(true);
const data: IAddZoneRequest = {
name: "",
servers: numberOfNodes,
volumes_per_server: volumesPerServer,
volume_configuration: {
size: volumeSize * 1073741824,
storage_class: "",
labels: null,
},
};
api
.invoke(
"POST",
`/api/v1/namespaces/${tenant.namespace}/tenants/${tenant.name}/zones`,
data
)
.then(() => {
setAddSending(false);
onCloseZoneAndReload(true);
})
.catch((err) => {
setAddSending(false);
// setDeleteError(err);
});
}}
>
<Grid item xs={12}>
<InputBoxWrapper
id="zone_name"
name="zone_name"
type="string"
id="number_of_nodes"
name="number_of_nodes"
type="number"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setZoneName(e.target.value);
setNumberOfNodes(parseInt(e.target.value));
}}
label="Name"
value={zoneName}
label="Number o Nodes"
value={numberOfNodes.toString(10)}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="number_instances"
name="number_instances"
id="zone_size"
name="zone_size"
type="number"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setNumberOfInstances(parseInt(e.target.value));
setVolumeSize(parseInt(e.target.value));
}}
label="Drives per Server"
value={numberOfInstances.toString(10)}
label="Volume Size (Gi)"
value={volumeSize.toString(10)}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="volumes_per_sever"
name="volumes_per_sever"
type="number"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setVolumesPerSever(parseInt(e.target.value));
}}
label="Volumes per Server"
value={volumesPerServer.toString(10)}
/>
</Grid>
<Grid item xs={12}>

View File

@@ -91,19 +91,6 @@ const styles = (theme: Theme) =>
...modalBasic,
});
const mainPagination = {
rowsPerPageOptions: [5, 10, 25],
colSpan: 3,
count: 0,
rowsPerPage: 0,
page: 0,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
ActionsComponent: MinTablePaginationActions,
};
const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
const [selectedTab, setSelectedTab] = useState<number>(0);
const [capacity, setCapacity] = useState<number>(0);
@@ -142,24 +129,38 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
const loadInfo = () => {
const tenantName = match.params["tenantName"];
const tenantNamespace = match.params["tenantNamespace"];
setLoading(true);
api
.invoke("GET", `/api/v1/tenants/${tenantName}`)
.invoke(
"GET",
`/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}`
)
.then((res: ITenant) => {
const total = res.volume_count * res.volume_size;
setCapacity(total);
setZoneCount(res.zone_count);
setVolumes(res.volume_count);
setInstances(res.instance_count);
const resZones = !res.zones ? [] : res.zones;
const total = res.volume_count * res.volume_size;
let totalInstances = 0;
let totalVolumes = 0;
let count = 1;
for (let zone of resZones) {
zone.volumes = res.volumes_per_server;
const cap = res.volumes_per_server * res.volume_size * zone.servers;
const cap =
zone.volumes_per_server *
zone.servers *
zone.volume_configuration.size;
zone.name = `zone-${count}`;
zone.capacity = niceBytes(cap + "");
zone.volumes = zone.servers * zone.volumes_per_server;
totalInstances += zone.servers;
totalVolumes += zone.volumes;
count += 1;
}
setCapacity(res.total_size);
setZoneCount(resZones.length);
setVolumes(totalVolumes);
setInstances(totalInstances);
setZones(resZones);
setTenant(res);
@@ -182,8 +183,7 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
<AddZoneModal
open={addZoneOpen}
onCloseZoneAndReload={onCloseZoneAndRefresh}
volumeSize={tenant.volume_size}
volumesPerInstance={tenant.volumes_per_server}
tenant={tenant}
/>
)}
{addBucketOpen && (
@@ -201,7 +201,7 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">
Tenant > {match.params["tenantName"]}
{`Tenant > ${match.params["tenantName"]}`}
</Typography>
</Grid>
<Grid item xs={12}>
@@ -212,40 +212,18 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
<div className={classes.infoGrid}>
<div>Capacity:</div>
<div>{niceBytes(capacity.toString(10))}</div>
<div>Minio:</div>
<div>{tenant ? tenant.image : ""}</div>
<div>Zones:</div>
<div>{zoneCount}</div>
<div>External IDP:</div>
<div>
{externalIDP ? "Yes" : "No"}&nbsp;&nbsp;
<Button
variant="contained"
color="primary"
size="small"
onClick={() => {}}
>
Edit
</Button>
</div>
<div>Console:</div>
<div>{tenant ? tenant.console_image : ""}</div>
<div>Instances:</div>
<div>{instances}</div>
<div>External KMS:</div>
<div>{externalKMS ? "Yes" : "No"}&nbsp;&nbsp;</div>
<div>Volumes:</div>
<div>{volumes}</div>
</div>
</Paper>
<div className={classes.masterActions}>
<div>
<Button
variant="contained"
color="primary"
fullWidth
onClick={() => {}}
>
Warp
</Button>
</div>
</div>
</Grid>
<Grid item xs={12}>
<br />
@@ -261,185 +239,59 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
aria-label="tenant-tabs"
>
<Tab label="Zones" />
<Tab label="Buckets" />
<Tab label="Replication" />
</Tabs>
</Grid>
<Grid item xs={6} className={classes.actionsTray}>
{selectedTab === 0 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddZone(true);
}}
>
Add Zone
</Button>
)}
{selectedTab === 1 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddBucketOpen(true);
}}
>
Create Bucket
</Button>
)}
{selectedTab === 2 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddReplicationOpen(true);
}}
>
Add Replication
</Button>
)}
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddZone(true);
}}
>
Add Zone
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
{selectedTab === 0 && (
<TableWrapper
itemActions={[
{
type: "view",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
<TableWrapper
itemActions={[
{
type: "delete",
onClick: (element) => {
console.log(element);
},
{
type: "delete",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
]}
columns={[
{ label: "Name", elementKey: "name" },
{ label: "Capacity", elementKey: "capacity" },
{ label: "# of Instances", elementKey: "servers" },
{ label: "# of Drives", elementKey: "volumes" },
]}
isLoading={false}
records={zones}
entityName="Zones"
idField="name"
paginatorConfig={{
...mainPagination,
onChangePage: () => {},
onChangeRowsPerPage: () => {},
}}
/>
)}
{selectedTab === 1 && (
<TableWrapper
itemActions={[
{
type: "view",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
{
type: "replicate",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
{
type: "mirror",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
{
type: "delete",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
]}
columns={[
{
label: "Status",
elementKey: "status",
},
{ label: "Name", elementKey: "name" },
{ label: "AccessPolicy", elementKey: "access_policy" },
]}
isLoading={false}
records={[]}
entityName="Buckets"
idField="name"
paginatorConfig={{
...mainPagination,
onChangePage: () => {},
onChangeRowsPerPage: () => {},
}}
/>
)}
{selectedTab === 2 && (
<TableWrapper
itemActions={[
{
type: "view",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
]}
columns={[
{
label: "Source",
elementKey: "source",
},
{ label: "Source Bucket", elementKey: "source_bucket" },
{ label: "Destination", elementKey: "destination" },
{
label: "Destination Bucket",
elementKey: "destination_bucket",
},
]}
isLoading={false}
records={[]}
entityName="Replication"
idField="id"
paginatorConfig={{
rowsPerPageOptions: [5, 10, 25],
colSpan: 3,
count: 0,
rowsPerPage: 0,
page: 0,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
onChangePage: () => {},
onChangeRowsPerPage: () => {},
ActionsComponent: MinTablePaginationActions,
}}
/>
)}
sendOnlyId: true,
},
]}
columns={[
{ label: "Name", elementKey: "name" },
{ label: "Capacity", elementKey: "capacity" },
{ label: "# of Instances", elementKey: "servers" },
{ label: "# of Drives", elementKey: "volumes" },
]}
isLoading={false}
records={zones}
entityName="Zones"
idField="name"
paginatorConfig={{
rowsPerPageOptions: [5, 10, 25],
colSpan: 3,
count: zoneCount,
rowsPerPage: 10,
page: 0,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
ActionsComponent: MinTablePaginationActions,
onChangePage: () => {},
onChangeRowsPerPage: () => {},
}}
/>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -19,6 +19,8 @@ export interface IValidation {
required: boolean;
pattern?: RegExp;
customPatternMessage?: string;
customValidation?: boolean;
customValidationMessage?: string;
value: string;
}
@@ -31,12 +33,18 @@ export const commonFormValidation = (fieldsValidate: IValidation[]) => {
return;
}
if (field.customValidation && field.customValidationMessage) {
returnErrors[field.fieldKey] = field.customValidationMessage;
return;
}
if (field.pattern && field.customPatternMessage) {
const rgx = new RegExp(field.pattern, "g");
if (!field.value.match(rgx)) {
returnErrors[field.fieldKey] = field.customPatternMessage;
}
return;
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -18,11 +18,9 @@ package restapi
import (
"context"
"log"
"time"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
@@ -33,7 +31,7 @@ func registerAdminArnsHandlers(api *operations.ConsoleAPI) {
api.AdminAPIArnListHandler = admin_api.ArnListHandlerFunc(func(params admin_api.ArnListParams, session *models.Principal) middleware.Responder {
arnsResp, err := getArnsResponse(session)
if err != nil {
return admin_api.NewArnListDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewArnListDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewArnListOK().WithPayload(arnsResp)
})
@@ -53,11 +51,10 @@ func getArns(ctx context.Context, client MinioAdmin) (*models.ArnsResponse, erro
}
// getArnsResponse returns a list of active arns in the instance
func getArnsResponse(session *models.Principal) (*models.ArnsResponse, error) {
func getArnsResponse(session *models.Principal) (*models.ArnsResponse, *models.Error) {
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -68,8 +65,7 @@ func getArnsResponse(session *models.Principal) (*models.ArnsResponse, error) {
// serialize output
arnsList, err := getArns(ctx, adminClient)
if err != nil {
log.Println("error getting arn list:", err)
return nil, err
return nil, prepareError(err)
}
return arnsList, nil
}

View File

@@ -19,7 +19,6 @@ package restapi
import (
"context"
"fmt"
"log"
"strings"
"github.com/go-openapi/runtime/middleware"
@@ -36,7 +35,7 @@ func registerConfigHandlers(api *operations.ConsoleAPI) {
api.AdminAPIListConfigHandler = admin_api.ListConfigHandlerFunc(func(params admin_api.ListConfigParams, session *models.Principal) middleware.Responder {
configListResp, err := getListConfigResponse(session)
if err != nil {
return admin_api.NewListConfigDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewListConfigDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewListConfigOK().WithPayload(configListResp)
})
@@ -44,14 +43,14 @@ func registerConfigHandlers(api *operations.ConsoleAPI) {
api.AdminAPIConfigInfoHandler = admin_api.ConfigInfoHandlerFunc(func(params admin_api.ConfigInfoParams, session *models.Principal) middleware.Responder {
config, err := getConfigResponse(session, params)
if err != nil {
return admin_api.NewConfigInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewConfigInfoDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewConfigInfoOK().WithPayload(config)
})
// Set Configuration
api.AdminAPISetConfigHandler = admin_api.SetConfigHandlerFunc(func(params admin_api.SetConfigParams, session *models.Principal) middleware.Responder {
if err := setConfigResponse(session, params.Name, params.Body); err != nil {
return admin_api.NewSetConfigDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewSetConfigDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewSetConfigNoContent()
})
@@ -76,11 +75,10 @@ func listConfig(client MinioAdmin) ([]*models.ConfigDescription, error) {
}
// getListConfigResponse performs listConfig() and serializes it to the handler's output
func getListConfigResponse(session *models.Principal) (*models.ListConfigResponse, error) {
func getListConfigResponse(session *models.Principal) (*models.ListConfigResponse, *models.Error) {
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -88,8 +86,7 @@ func getListConfigResponse(session *models.Principal) (*models.ListConfigRespons
configDescs, err := listConfig(adminClient)
if err != nil {
log.Println("error listing configurations:", err)
return nil, err
return nil, prepareError(err)
}
listGroupsResponse := &models.ListConfigResponse{
Configurations: configDescs,
@@ -127,11 +124,10 @@ func getConfig(client MinioAdmin, name string) ([]*models.ConfigurationKV, error
}
// getConfigResponse performs getConfig() and serializes it to the handler's output
func getConfigResponse(session *models.Principal, params admin_api.ConfigInfoParams) (*models.Configuration, error) {
func getConfigResponse(session *models.Principal, params admin_api.ConfigInfoParams) (*models.Configuration, *models.Error) {
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -139,8 +135,7 @@ func getConfigResponse(session *models.Principal, params admin_api.ConfigInfoPar
configkv, err := getConfig(adminClient, params.Name)
if err != nil {
log.Println("error getting configuration:", err)
return nil, err
return nil, prepareError(err)
}
configurationObj := &models.Configuration{
Name: params.Name,
@@ -180,11 +175,10 @@ func buildConfig(configName *string, kvs []*models.ConfigurationKV) *string {
}
// setConfigResponse implements setConfig() to be used by handler
func setConfigResponse(session *models.Principal, name string, configRequest *models.SetConfigRequest) error {
func setConfigResponse(session *models.Principal, name string, configRequest *models.SetConfigRequest) *models.Error {
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
return prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -194,8 +188,7 @@ func setConfigResponse(session *models.Principal, name string, configRequest *mo
ctx := context.Background()
if err := setConfigWithARNAccountID(ctx, adminClient, &configName, configRequest.KeyValues, configRequest.ArnResourceID); err != nil {
log.Println("error listing configurations:", err)
return err
return prepareError(err)
}
return nil
}

View File

@@ -22,7 +22,6 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/restapi/operations"
"github.com/minio/minio/pkg/madmin"
@@ -36,7 +35,7 @@ func registerGroupsHandlers(api *operations.ConsoleAPI) {
api.AdminAPIListGroupsHandler = admin_api.ListGroupsHandlerFunc(func(params admin_api.ListGroupsParams, session *models.Principal) middleware.Responder {
listGroupsResponse, err := getListGroupsResponse(session)
if err != nil {
return admin_api.NewListGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewListGroupsDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewListGroupsOK().WithPayload(listGroupsResponse)
})
@@ -44,21 +43,21 @@ func registerGroupsHandlers(api *operations.ConsoleAPI) {
api.AdminAPIGroupInfoHandler = admin_api.GroupInfoHandlerFunc(func(params admin_api.GroupInfoParams, session *models.Principal) middleware.Responder {
groupInfo, err := getGroupInfoResponse(session, params)
if err != nil {
return admin_api.NewGroupInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewGroupInfoDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewGroupInfoOK().WithPayload(groupInfo)
})
// Add Group
api.AdminAPIAddGroupHandler = admin_api.AddGroupHandlerFunc(func(params admin_api.AddGroupParams, session *models.Principal) middleware.Responder {
if err := getAddGroupResponse(session, params.Body); err != nil {
return admin_api.NewAddGroupDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewAddGroupDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewAddGroupCreated()
})
// Remove Group
api.AdminAPIRemoveGroupHandler = admin_api.RemoveGroupHandlerFunc(func(params admin_api.RemoveGroupParams, session *models.Principal) middleware.Responder {
if err := getRemoveGroupResponse(session, params); err != nil {
return admin_api.NewRemoveGroupDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewRemoveGroupDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewRemoveGroupNoContent()
})
@@ -66,7 +65,7 @@ func registerGroupsHandlers(api *operations.ConsoleAPI) {
api.AdminAPIUpdateGroupHandler = admin_api.UpdateGroupHandlerFunc(func(params admin_api.UpdateGroupParams, session *models.Principal) middleware.Responder {
groupUpdateResp, err := getUpdateGroupResponse(session, params)
if err != nil {
return admin_api.NewUpdateGroupDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewUpdateGroupDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewUpdateGroupOK().WithPayload(groupUpdateResp)
})
@@ -82,12 +81,11 @@ func listGroups(ctx context.Context, client MinioAdmin) (*[]string, error) {
}
// getListGroupsResponse performs listGroups() and serializes it to the handler's output
func getListGroupsResponse(session *models.Principal) (*models.ListGroupsResponse, error) {
func getListGroupsResponse(session *models.Principal) (*models.ListGroupsResponse, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -95,8 +93,7 @@ func getListGroupsResponse(session *models.Principal) (*models.ListGroupsRespons
groups, err := listGroups(ctx, adminClient)
if err != nil {
log.Println("error listing groups:", err)
return nil, err
return nil, prepareError(err)
}
// serialize output
listGroupsResponse := &models.ListGroupsResponse{
@@ -116,12 +113,11 @@ func groupInfo(ctx context.Context, client MinioAdmin, group string) (*madmin.Gr
}
// getGroupInfoResponse performs groupInfo() and serializes it to the handler's output
func getGroupInfoResponse(session *models.Principal, params admin_api.GroupInfoParams) (*models.Group, error) {
func getGroupInfoResponse(session *models.Principal, params admin_api.GroupInfoParams) (*models.Group, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -129,8 +125,7 @@ func getGroupInfoResponse(session *models.Principal, params admin_api.GroupInfoP
groupDesc, err := groupInfo(ctx, adminClient, params.Name)
if err != nil {
log.Println("error getting group info:", err)
return nil, err
return nil, prepareError(err)
}
groupResponse := &models.Group{
@@ -157,26 +152,23 @@ func addGroup(ctx context.Context, client MinioAdmin, group string, members []st
}
// getAddGroupResponse performs addGroup() and serializes it to the handler's output
func getAddGroupResponse(session *models.Principal, params *models.AddGroupRequest) error {
func getAddGroupResponse(session *models.Principal, params *models.AddGroupRequest) *models.Error {
ctx := context.Background()
// AddGroup request needed to proceed
if params == nil {
log.Println("error AddGroup body not in request")
return errors.New(500, "error AddGroup body not in request")
return prepareError(errGroupBodyNotInRequest)
}
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
return prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
if err := addGroup(ctx, adminClient, *params.Group, params.Members); err != nil {
log.Println("error adding group:", err)
return err
return prepareError(err)
}
return nil
}
@@ -196,25 +188,22 @@ func removeGroup(ctx context.Context, client MinioAdmin, group string) error {
}
// getRemoveGroupResponse performs removeGroup() and serializes it to the handler's output
func getRemoveGroupResponse(session *models.Principal, params admin_api.RemoveGroupParams) error {
func getRemoveGroupResponse(session *models.Principal, params admin_api.RemoveGroupParams) *models.Error {
ctx := context.Background()
if params.Name == "" {
log.Println("error group name not in request")
return errors.New(500, "error group name not in request")
return prepareError(errGroupNameNotInRequest)
}
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
return prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
if err := removeGroup(ctx, adminClient, params.Name); err != nil {
log.Println("error removing group:", err)
return err
return prepareError(err)
}
return nil
}
@@ -276,23 +265,21 @@ func setGroupStatus(ctx context.Context, client MinioAdmin, group, status string
// getUpdateGroupResponse updates a group by adding or removing it's members depending on the request,
// also sets the group's status if status in the request is different than the current one.
// Then serializes the output to be used by the handler.
func getUpdateGroupResponse(session *models.Principal, params admin_api.UpdateGroupParams) (*models.Group, error) {
func getUpdateGroupResponse(session *models.Principal, params admin_api.UpdateGroupParams) (*models.Group, *models.Error) {
ctx := context.Background()
if params.Name == "" {
log.Println("error group name not in request")
return nil, errors.New(500, "error group name not in request")
return nil, prepareError(errGroupNameNotInRequest)
}
if params.Body == nil {
log.Println("error body not in request")
return nil, errors.New(500, "error body not in request")
return nil, prepareError(errGroupBodyNotInRequest)
}
expectedGroupUpdate := params.Body
groupName := params.Name
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -300,8 +287,7 @@ func getUpdateGroupResponse(session *models.Principal, params admin_api.UpdateGr
groupUpdated, err := groupUpdate(ctx, adminClient, groupName, expectedGroupUpdate)
if err != nil {
log.Println("error updating group:", err)
return nil, err
return nil, prepareError(err)
}
groupResponse := &models.Group{
Name: groupUpdated.Name,

View File

@@ -18,11 +18,9 @@ package restapi
import (
"context"
"log"
"time"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
@@ -33,7 +31,7 @@ func registerAdminInfoHandlers(api *operations.ConsoleAPI) {
api.AdminAPIAdminInfoHandler = admin_api.AdminInfoHandlerFunc(func(params admin_api.AdminInfoParams, session *models.Principal) middleware.Responder {
infoResp, err := getAdminInfoResponse(session)
if err != nil {
return admin_api.NewAdminInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewAdminInfoDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewAdminInfoOK().WithPayload(infoResp)
})
@@ -72,11 +70,10 @@ func getAdminInfo(ctx context.Context, client MinioAdmin) (*usageInfo, error) {
}
// getAdminInfoResponse returns the response containing total buckets, objects and usage.
func getAdminInfoResponse(session *models.Principal) (*models.AdminInfoResponse, error) {
func getAdminInfoResponse(session *models.Principal) (*models.AdminInfoResponse, *models.Error) {
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -87,8 +84,7 @@ func getAdminInfoResponse(session *models.Principal) (*models.AdminInfoResponse,
// serialize output
usage, err := getAdminInfo(ctx, adminClient)
if err != nil {
log.Println("error getting information:", err)
return nil, err
return nil, prepareError(err)
}
sessionResp := &models.AdminInfoResponse{
Buckets: usage.Buckets,

136
restapi/admin_nodes.go Normal file
View File

@@ -0,0 +1,136 @@
// 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 restapi
import (
"context"
"sort"
"github.com/minio/console/cluster"
"errors"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
func registerNodesHandlers(api *operations.ConsoleAPI) {
api.AdminAPIGetMaxAllocatableMemHandler = admin_api.GetMaxAllocatableMemHandlerFunc(func(params admin_api.GetMaxAllocatableMemParams, principal *models.Principal) middleware.Responder {
resp, err := getMaxAllocatableMemoryResponse(principal, params.NumNodes)
if err != nil {
return admin_api.NewGetMaxAllocatableMemDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewGetMaxAllocatableMemOK().WithPayload(resp)
})
}
// getMaxAllocatableMemory get max allocatable memory given a desired number of nodes
func getMaxAllocatableMemory(ctx context.Context, clientset v1.CoreV1Interface, numNodes int32) (*models.MaxAllocatableMemResponse, error) {
if numNodes == 0 {
return nil, errors.New("error NumNodes must be greated than 0")
}
// get all nodes from cluster
nodes, err := clientset.Nodes().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
availableMemSizes := []int64{}
OUTER:
for _, n := range nodes.Items {
// Don't consider node if it has a NoSchedule or NoExecute Taint
for _, t := range n.Spec.Taints {
switch t.Effect {
case corev1.TaintEffectNoSchedule:
continue OUTER
case corev1.TaintEffectNoExecute:
continue OUTER
default:
continue
}
}
if quantity, ok := n.Status.Allocatable[corev1.ResourceMemory]; ok {
availableMemSizes = append(availableMemSizes, quantity.Value())
}
}
maxAllocatableMemory := getMaxClusterMemory(numNodes, availableMemSizes)
res := &models.MaxAllocatableMemResponse{
MaxMemory: maxAllocatableMemory,
}
return res, nil
}
// getMaxClusterMemory returns the maximum memory size that can be used
// across numNodes (number of nodes)
func getMaxClusterMemory(numNodes int32, nodesMemorySizes []int64) int64 {
if int32(len(nodesMemorySizes)) < numNodes || numNodes == 0 {
return 0
}
// sort nodesMemorySizes int64 array
sort.Slice(nodesMemorySizes, func(i, j int) bool { return nodesMemorySizes[i] < nodesMemorySizes[j] })
maxIndex := 0
maxAllocatableMemory := nodesMemorySizes[maxIndex]
for i, size := range nodesMemorySizes {
// maxAllocatableMemory is the minimum value of nodesMemorySizes array
// only within the size of numNodes, if more nodes are available
// then the maxAllocatableMemory is equal to the next minimum value
// on the sorted nodesMemorySizes array.
// e.g. with numNodes = 4;
// maxAllocatableMemory of [2,4,8,8] => 2
// maxAllocatableMemory of [2,4,8,8,16] => 4
if int32(i) < numNodes {
maxAllocatableMemory = min(maxAllocatableMemory, size)
} else {
maxIndex++
maxAllocatableMemory = nodesMemorySizes[maxIndex]
}
}
return maxAllocatableMemory
}
// min returns the smaller of x or y.
func min(x, y int64) int64 {
if x > y {
return y
}
return x
}
func getMaxAllocatableMemoryResponse(session *models.Principal, numNodes int32) (*models.MaxAllocatableMemResponse, *models.Error) {
ctx := context.Background()
client, err := cluster.K8sClient(session.SessionToken)
if err != nil {
return nil, prepareError(err)
}
clusterResources, err := getMaxAllocatableMemory(ctx, client.CoreV1(), numNodes)
if err != nil {
return nil, prepareError(err)
}
return clusterResources, nil
}

366
restapi/admin_nodes_test.go Normal file
View File

@@ -0,0 +1,366 @@
// 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 restapi
import (
"context"
"reflect"
"testing"
"github.com/minio/console/models"
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"
"k8s.io/client-go/kubernetes/fake"
)
func Test_MaxAllocatableMemory(t *testing.T) {
type args struct {
ctx context.Context
numNodes int32
objs []runtime.Object
}
tests := []struct {
name string
args args
expected *models.MaxAllocatableMemResponse
wantErr bool
}{
{
name: "Get Max Ram No Taints",
args: args{
ctx: context.Background(),
numNodes: 2,
objs: []runtime.Object{
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("2Ki"),
corev1.ResourceCPU: resource.MustParse("4Ki"),
},
},
},
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node2",
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512"),
corev1.ResourceCPU: resource.MustParse("1Ki"),
},
},
},
},
},
expected: &models.MaxAllocatableMemResponse{
MaxMemory: int64(512),
},
wantErr: false,
},
{
// Description: if there are more nodes than the amount
// of nodes we want to use, but one has taints of NoSchedule
// node should not be considered for max memory
name: "Get Max Ram on nodes with NoSchedule",
args: args{
ctx: context.Background(),
numNodes: 2,
objs: []runtime.Object{
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("2Ki"),
corev1.ResourceCPU: resource.MustParse("4Ki"),
},
},
},
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node2",
},
Spec: corev1.NodeSpec{
Taints: []corev1.Taint{
corev1.Taint{
Key: "node.kubernetes.io/unreachable",
Effect: corev1.TaintEffectNoSchedule,
},
},
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("6Ki"),
corev1.ResourceCPU: resource.MustParse("1Ki"),
},
},
},
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node3",
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("4Ki"),
corev1.ResourceCPU: resource.MustParse("1Ki"),
},
},
},
},
},
expected: &models.MaxAllocatableMemResponse{
MaxMemory: int64(2048),
},
wantErr: false,
},
{
// Description: if there are more nodes than the amount
// of nodes we want to use, but one has taints of NoExecute
// node should not be considered for max memory
// if one node has PreferNoSchedule that should be considered.
name: "Get Max Ram on nodes with NoExecute",
args: args{
ctx: context.Background(),
numNodes: 2,
objs: []runtime.Object{
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("2Ki"),
corev1.ResourceCPU: resource.MustParse("4Ki"),
},
},
},
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node2",
},
Spec: corev1.NodeSpec{
Taints: []corev1.Taint{
corev1.Taint{
Key: "node.kubernetes.io/unreachable",
Effect: corev1.TaintEffectNoExecute,
},
},
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("6Ki"),
corev1.ResourceCPU: resource.MustParse("1Ki"),
},
},
},
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node3",
},
Spec: corev1.NodeSpec{
Taints: []corev1.Taint{
corev1.Taint{
Key: "node.kubernetes.io/unreachable",
Effect: corev1.TaintEffectPreferNoSchedule,
},
},
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("4Ki"),
corev1.ResourceCPU: resource.MustParse("1Ki"),
},
},
},
},
},
expected: &models.MaxAllocatableMemResponse{
MaxMemory: int64(2048),
},
wantErr: false,
},
{
// Description: if there are more nodes than the amount
// of nodes we want to use, max allocatable memory should
// be the minimum ram on the n nodes requested
name: "Get Max Ram, several nodes available",
args: args{
ctx: context.Background(),
numNodes: 2,
objs: []runtime.Object{
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("2Ki"),
corev1.ResourceCPU: resource.MustParse("4Ki"),
},
},
},
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node2",
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("6Ki"),
corev1.ResourceCPU: resource.MustParse("1Ki"),
},
},
},
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node3",
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("4Ki"),
corev1.ResourceCPU: resource.MustParse("1Ki"),
},
},
},
},
},
expected: &models.MaxAllocatableMemResponse{
MaxMemory: int64(4096),
},
wantErr: false,
},
{
// Description: if request has nil as request, expect error
name: "Nil nodes should be greater than 0",
args: args{
ctx: context.Background(),
numNodes: 0,
},
wantErr: true,
},
}
for _, tt := range tests {
kubeClient := fake.NewSimpleClientset(tt.args.objs...)
t.Run(tt.name, func(t *testing.T) {
got, err := getMaxAllocatableMemory(tt.args.ctx, kubeClient.CoreV1(), tt.args.numNodes)
if (err != nil) != tt.wantErr {
t.Errorf("getMaxAllocatableMemory() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("\ngot: %d \nwant: %d", got, tt.expected)
}
})
}
}
func Test_MaxMemoryFunc(t *testing.T) {
type args struct {
numNodes int32
nodesMemorySizes []int64
}
tests := []struct {
name string
args args
expected int64
wantErr bool
}{
{
name: "Get Max memory",
args: args{
numNodes: int32(4),
nodesMemorySizes: []int64{4294967296, 8589934592, 8589934592, 17179869184, 17179869184, 17179869184, 25769803776, 25769803776, 68719476736},
},
expected: int64(17179869184),
wantErr: false,
},
{
// Description, if not enough nodes return 0
name: "Get Max memory Not enough nodes",
args: args{
numNodes: int32(4),
nodesMemorySizes: []int64{4294967296, 8589934592, 68719476736},
},
expected: int64(0),
wantErr: false,
},
{
// Description, if not enough nodes return 0
name: "Get Max memory no nodes",
args: args{
numNodes: int32(4),
nodesMemorySizes: []int64{},
},
expected: int64(0),
wantErr: false,
},
{
// Description, if not enough nodes return 0
name: "Get Max memory no nodes, no request",
args: args{
numNodes: int32(0),
nodesMemorySizes: []int64{},
},
expected: int64(0),
wantErr: false,
},
{
// Description, if there are multiple nodes
// and required nodes is only 1, max should be equal to max memory
name: "Get Max memory one node",
args: args{
numNodes: int32(1),
nodesMemorySizes: []int64{4294967296, 8589934592, 68719476736},
},
expected: int64(68719476736),
wantErr: false,
},
{
// Description: if more nodes max memory should be the minimum
// value across pairs of numNodes
name: "Get Max memory two nodes",
args: args{
numNodes: int32(2),
nodesMemorySizes: []int64{8589934592, 68719476736, 4294967296},
},
expected: int64(8589934592),
wantErr: false,
},
{
name: "Get Max Multiple Memory Sizes",
args: args{
numNodes: int32(4),
nodesMemorySizes: []int64{0, 0, 0, 0, 4294967296, 8589934592, 8589934592, 17179869184, 17179869184, 17179869184, 25769803776, 25769803776, 68719476736, 34359738368, 34359738368, 34359738368, 34359738368},
},
expected: int64(34359738368),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := getMaxClusterMemory(tt.args.numNodes, tt.args.nodesMemorySizes)
if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("\ngot: %d \nwant: %d", got, tt.expected)
}
})
}
}

View File

@@ -19,11 +19,9 @@ package restapi
import (
"context"
"errors"
"log"
"time"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
@@ -34,7 +32,7 @@ func registerAdminNotificationEndpointsHandlers(api *operations.ConsoleAPI) {
api.AdminAPINotificationEndpointListHandler = admin_api.NotificationEndpointListHandlerFunc(func(params admin_api.NotificationEndpointListParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getNotificationEndpointsResponse(session)
if err != nil {
return admin_api.NewNotificationEndpointListDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewNotificationEndpointListDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewNotificationEndpointListOK().WithPayload(notifEndpoints)
})
@@ -42,7 +40,7 @@ func registerAdminNotificationEndpointsHandlers(api *operations.ConsoleAPI) {
api.AdminAPIAddNotificationEndpointHandler = admin_api.AddNotificationEndpointHandlerFunc(func(params admin_api.AddNotificationEndpointParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getAddNotificationEndpointResponse(session, &params)
if err != nil {
return admin_api.NewAddNotificationEndpointDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewAddNotificationEndpointDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewAddNotificationEndpointCreated().WithPayload(notifEndpoints)
})
@@ -78,11 +76,10 @@ func getNotificationEndpoints(ctx context.Context, client MinioAdmin) (*models.N
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getNotificationEndpointsResponse(session *models.Principal) (*models.NotifEndpointResponse, error) {
func getNotificationEndpointsResponse(session *models.Principal) (*models.NotifEndpointResponse, *models.Error) {
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -93,8 +90,7 @@ func getNotificationEndpointsResponse(session *models.Principal) (*models.NotifE
// serialize output
notfEndpointResp, err := getNotificationEndpoints(ctx, adminClient)
if err != nil {
log.Println("error getting notification endpoint list:", err)
return nil, err
return nil, prepareError(err)
}
return notfEndpointResp, nil
}
@@ -150,11 +146,10 @@ func addNotificationEndpoint(ctx context.Context, client MinioAdmin, params *adm
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getAddNotificationEndpointResponse(session *models.Principal, params *admin_api.AddNotificationEndpointParams) (*models.NotificationEndpoint, error) {
func getAddNotificationEndpointResponse(session *models.Principal, params *admin_api.AddNotificationEndpointParams) (*models.NotificationEndpoint, *models.Error) {
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -165,8 +160,7 @@ func getAddNotificationEndpointResponse(session *models.Principal, params *admin
// serialize output
notfEndpointResp, err := addNotificationEndpoint(ctx, adminClient, params)
if err != nil {
log.Println("error getting notification endpoint list:", err)
return nil, err
return nil, prepareError(err)
}
return notfEndpointResp, nil
}

63
restapi/admin_parity.go Normal file
View File

@@ -0,0 +1,63 @@
// 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 restapi
import (
"fmt"
"log"
"github.com/minio/console/pkg/utils"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
)
func registerParityHandlers(api *operations.ConsoleAPI) {
api.AdminAPIGetParityHandler = admin_api.GetParityHandlerFunc(func(params admin_api.GetParityParams, principal *models.Principal) middleware.Responder {
resp, err := getParityResponse(params)
if err != nil {
return admin_api.NewGetParityDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewGetParityOK().WithPayload(resp)
})
}
func GetParityInfo(nodes int64, disksPerNode int64) (models.ParityResponse, error) {
parityVals, err := utils.PossibleParityValues(fmt.Sprintf(`http://minio{1...%d}/export/set{1...%d}`, nodes, disksPerNode))
if err != nil {
return nil, err
}
return parityVals, nil
}
func getParityResponse(params admin_api.GetParityParams) (models.ParityResponse, *models.Error) {
nodes := params.Nodes
disksPerNode := params.DisksPerNode
parityValues, err := GetParityInfo(nodes, disksPerNode)
if err != nil {
log.Println("error getting parity info:", err)
return nil, prepareError(err)
}
return parityValues, nil
}

View File

@@ -0,0 +1,79 @@
// 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 restapi
import (
"encoding/json"
"reflect"
"testing"
"github.com/minio/console/models"
)
func Test_getParityInfo(t *testing.T) {
tests := []struct {
description string
wantErr bool
nodes int64
disksPerNode int64
expectedResp models.ParityResponse
}{
{
description: "Incorrect Number of endpoints provided",
wantErr: true,
nodes: 1,
disksPerNode: 1,
expectedResp: nil,
},
{
description: "Number of endpoints is valid",
wantErr: false,
nodes: 4,
disksPerNode: 10,
expectedResp: models.ParityResponse{"EC:4", "EC:3", "EC:2"},
},
{
description: "More nodes than disks",
wantErr: false,
nodes: 4,
disksPerNode: 1,
expectedResp: models.ParityResponse{"EC:2"},
},
{
description: "More disks than nodes",
wantErr: false,
nodes: 2,
disksPerNode: 50,
expectedResp: models.ParityResponse{"EC:5", "EC:4", "EC:3", "EC:2"},
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
parity, err := GetParityInfo(tt.nodes, tt.disksPerNode)
if (err != nil) != tt.wantErr {
t.Errorf("GetParityInfo() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(parity, tt.expectedResp) {
ji, _ := json.Marshal(parity)
vi, _ := json.Marshal(tt.expectedResp)
t.Errorf("\ngot: %s \nwant: %s", ji, vi)
}
})
}
}

View File

@@ -22,10 +22,7 @@ import (
"encoding/json"
"log"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
@@ -37,7 +34,7 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
api.AdminAPIListPoliciesHandler = admin_api.ListPoliciesHandlerFunc(func(params admin_api.ListPoliciesParams, session *models.Principal) middleware.Responder {
listPoliciesResponse, err := getListPoliciesResponse(session)
if err != nil {
return admin_api.NewListPoliciesDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewListPoliciesDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewListPoliciesOK().WithPayload(listPoliciesResponse)
})
@@ -45,7 +42,7 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
api.AdminAPIPolicyInfoHandler = admin_api.PolicyInfoHandlerFunc(func(params admin_api.PolicyInfoParams, session *models.Principal) middleware.Responder {
policyInfo, err := getPolicyInfoResponse(session, params)
if err != nil {
return admin_api.NewPolicyInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewPolicyInfoDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewPolicyInfoOK().WithPayload(policyInfo)
})
@@ -53,24 +50,21 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
api.AdminAPIAddPolicyHandler = admin_api.AddPolicyHandlerFunc(func(params admin_api.AddPolicyParams, session *models.Principal) middleware.Responder {
policyResponse, err := getAddPolicyResponse(session, params.Body)
if err != nil {
return admin_api.NewAddPolicyDefault(500).WithPayload(&models.Error{
Code: 500,
Message: swag.String(err.Error()),
})
return admin_api.NewAddPolicyDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewAddPolicyCreated().WithPayload(policyResponse)
})
// Remove Policy
api.AdminAPIRemovePolicyHandler = admin_api.RemovePolicyHandlerFunc(func(params admin_api.RemovePolicyParams, session *models.Principal) middleware.Responder {
if err := getRemovePolicyResponse(session, params); err != nil {
return admin_api.NewRemovePolicyDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewRemovePolicyDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewRemovePolicyNoContent()
})
// Set Policy
api.AdminAPISetPolicyHandler = admin_api.SetPolicyHandlerFunc(func(params admin_api.SetPolicyParams, session *models.Principal) middleware.Responder {
if err := getSetPolicyResponse(session, params.Name, params.Body); err != nil {
return admin_api.NewSetPolicyDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewSetPolicyDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewSetPolicyNoContent()
})
@@ -97,12 +91,11 @@ func listPolicies(ctx context.Context, client MinioAdmin) ([]*models.Policy, err
}
// getListPoliciesResponse performs listPolicies() and serializes it to the handler's output
func getListPoliciesResponse(session *models.Principal) (*models.ListPoliciesResponse, error) {
func getListPoliciesResponse(session *models.Principal) (*models.ListPoliciesResponse, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -110,8 +103,7 @@ func getListPoliciesResponse(session *models.Principal) (*models.ListPoliciesRes
policies, err := listPolicies(ctx, adminClient)
if err != nil {
log.Println("error listing policies:", err)
return nil, err
return nil, prepareError(err)
}
// serialize output
listPoliciesResponse := &models.ListPoliciesResponse{
@@ -131,24 +123,21 @@ func removePolicy(ctx context.Context, client MinioAdmin, name string) error {
}
// getRemovePolicyResponse() performs removePolicy() and serializes it to the handler's output
func getRemovePolicyResponse(session *models.Principal, params admin_api.RemovePolicyParams) error {
func getRemovePolicyResponse(session *models.Principal, params admin_api.RemovePolicyParams) *models.Error {
ctx := context.Background()
if params.Name == "" {
log.Println("error policy name not in request")
return errors.New(500, "error policy name not in request")
return prepareError(errPolicyNameNotInRequest)
}
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
return prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
if err := removePolicy(ctx, adminClient, params.Name); err != nil {
log.Println("error removing policy:", err)
return err
return prepareError(err)
}
return nil
}
@@ -173,25 +162,23 @@ func addPolicy(ctx context.Context, client MinioAdmin, name, policy string) (*mo
}
// getAddPolicyResponse performs addPolicy() and serializes it to the handler's output
func getAddPolicyResponse(session *models.Principal, params *models.AddPolicyRequest) (*models.Policy, error) {
func getAddPolicyResponse(session *models.Principal, params *models.AddPolicyRequest) (*models.Policy, *models.Error) {
ctx := context.Background()
if params == nil {
log.Println("error AddPolicy body not in request")
return nil, errors.New(500, "error AddPolicy body not in request")
return nil, prepareError(errPolicyBodyNotInRequest)
}
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
policy, err := addPolicy(ctx, adminClient, *params.Name, *params.Policy)
if err != nil {
log.Println("error adding policy")
return nil, err
return nil, prepareError(err)
}
return policy, nil
}
@@ -213,20 +200,18 @@ func policyInfo(ctx context.Context, client MinioAdmin, name string) (*models.Po
}
// getPolicyInfoResponse performs policyInfo() and serializes it to the handler's output
func getPolicyInfoResponse(session *models.Principal, params admin_api.PolicyInfoParams) (*models.Policy, error) {
func getPolicyInfoResponse(session *models.Principal, params admin_api.PolicyInfoParams) (*models.Policy, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
policy, err := policyInfo(ctx, adminClient, params.Name)
if err != nil {
log.Println("error getting group info:", err)
return nil, err
return nil, prepareError(err)
}
return policy, nil
}
@@ -244,24 +229,21 @@ func setPolicy(ctx context.Context, client MinioAdmin, name, entityName string,
}
// getSetPolicyResponse() performs setPolicy() and serializes it to the handler's output
func getSetPolicyResponse(session *models.Principal, name string, params *models.SetPolicyRequest) error {
func getSetPolicyResponse(session *models.Principal, name string, params *models.SetPolicyRequest) *models.Error {
ctx := context.Background()
if name == "" {
log.Println("error policy name not in request")
return errors.New(500, "error policy name not in request")
return prepareError(errPolicyNameNotInRequest)
}
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
return prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
if err := setPolicy(ctx, adminClient, name, *params.EntityName, params.EntityType); err != nil {
log.Println("error setting policy:", err)
return err
return prepareError(err)
}
return nil
}

View File

@@ -22,10 +22,8 @@ import (
"log"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
@@ -37,7 +35,7 @@ func registerProfilingHandler(api *operations.ConsoleAPI) {
api.AdminAPIProfilingStartHandler = admin_api.ProfilingStartHandlerFunc(func(params admin_api.ProfilingStartParams, session *models.Principal) middleware.Responder {
profilingStartResponse, err := getProfilingStartResponse(session, params.Body)
if err != nil {
return admin_api.NewProfilingStartDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewProfilingStartDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewProfilingStartCreated().WithPayload(profilingStartResponse)
})
@@ -45,7 +43,7 @@ func registerProfilingHandler(api *operations.ConsoleAPI) {
api.AdminAPIProfilingStopHandler = admin_api.ProfilingStopHandlerFunc(func(params admin_api.ProfilingStopParams, session *models.Principal) middleware.Responder {
profilingStopResponse, err := getProfilingStopResponse(session)
if err != nil {
return admin_api.NewProfilingStopDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewProfilingStopDefault(int(err.Code)).WithPayload(err)
}
// Custom response writer to set the content-disposition header to tell the
// HTTP client the name and extension of the file we are returning
@@ -90,24 +88,21 @@ func startProfiling(ctx context.Context, client MinioAdmin, profilerType models.
}
// getProfilingStartResponse performs startProfiling() and serializes it to the handler's output
func getProfilingStartResponse(session *models.Principal, params *models.ProfilingStartRequest) (*models.StartProfilingList, error) {
func getProfilingStartResponse(session *models.Principal, params *models.ProfilingStartRequest) (*models.StartProfilingList, *models.Error) {
ctx := context.Background()
if params == nil {
log.Println("error profiling type not in body request")
return nil, errors.New(500, "error AddPolicy body not in request")
return nil, prepareError(errPolicyBodyNotInRequest)
}
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
profilingItems, err := startProfiling(ctx, adminClient, params.Type)
if err != nil {
log.Println("error starting profiling:", err)
return nil, err
return nil, prepareError(err)
}
profilingList := &models.StartProfilingList{
StartResults: profilingItems,
@@ -127,20 +122,18 @@ func stopProfiling(ctx context.Context, client MinioAdmin) (io.ReadCloser, error
}
// getProfilingStopResponse() performs setPolicy() and serializes it to the handler's output
func getProfilingStopResponse(session *models.Principal) (io.ReadCloser, error) {
func getProfilingStopResponse(session *models.Principal) (io.ReadCloser, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
profilingData, err := stopProfiling(ctx, adminClient)
if err != nil {
log.Println("error stopping profiling:", err)
return nil, err
return nil, prepareError(err)
}
return profilingData, nil
}

View File

@@ -18,11 +18,9 @@ package restapi
import (
"context"
"log"
"time"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
@@ -33,7 +31,7 @@ func registerServiceHandlers(api *operations.ConsoleAPI) {
// Restart Service
api.AdminAPIRestartServiceHandler = admin_api.RestartServiceHandlerFunc(func(params admin_api.RestartServiceParams, session *models.Principal) middleware.Responder {
if err := getRestartServiceResponse(session); err != nil {
return admin_api.NewRestartServiceDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewRestartServiceDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewRestartServiceNoContent()
})
@@ -61,20 +59,18 @@ func serviceRestart(ctx context.Context, client MinioAdmin) error {
}
// getRestartServiceResponse performs serviceRestart()
func getRestartServiceResponse(session *models.Principal) error {
func getRestartServiceResponse(session *models.Principal) *models.Error {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
return prepareError(err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
if err := serviceRestart(ctx, adminClient); err != nil {
log.Println("error restarting service:", err)
return err
return prepareError(err)
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,490 @@
// This file is part of MinIO Kubernetes Cloud
// 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 restapi
import (
"context"
"crypto"
"encoding/base64"
"encoding/hex"
"fmt"
"log"
"time"
"errors"
"github.com/minio/console/cluster"
"github.com/minio/console/models"
"github.com/minio/console/pkg/kes"
"github.com/minio/console/restapi/operations/admin_api"
operator "github.com/minio/operator/pkg/apis/minio.min.io/v1"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// tenantUpdateCertificates receives the keyPair certificates (public and private keys) for Minio and Console and will try
// to replace the existing kubernetes secrets with the new values, then will restart the affected pods so the new volumes can be mounted
func tenantUpdateCertificates(ctx context.Context, operatorClient OperatorClientI, clientSet K8sClientI, namespace string, params admin_api.TenantUpdateCertificateParams) error {
tenantName := params.Tenant
tenant, err := operatorClient.TenantGet(ctx, namespace, tenantName, metav1.GetOptions{})
if err != nil {
return err
}
secretName := fmt.Sprintf("%s-secret", tenantName)
body := params.Body
// check if MinIO is deployed with external certs and user provided new MinIO keypair
if tenant.ExternalCert() && body.Minio != nil {
minioCertSecretName := fmt.Sprintf("%s-instance-external-certificates", secretName)
// update certificates
if _, err := createOrReplaceExternalCertSecret(ctx, clientSet, namespace, body.Minio, minioCertSecretName, tenantName); err != nil {
return err
}
// restart MinIO pods
err := clientSet.deletePodCollection(ctx, namespace, metav1.DeleteOptions{}, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", operator.TenantLabel, tenantName),
})
if err != nil {
return err
}
}
// check if Console is deployed with external certs and user provided new Console keypair
if tenant.ConsoleExternalCert() && tenant.HasConsoleEnabled() && body.Console != nil {
consoleCertSecretName := fmt.Sprintf("%s-console-external-certificates", secretName)
// update certificates
if _, err := createOrReplaceExternalCertSecret(ctx, clientSet, namespace, body.Console, consoleCertSecretName, tenantName); err != nil {
return err
}
// restart Console pods
err := clientSet.deletePodCollection(ctx, namespace, metav1.DeleteOptions{}, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", operator.ConsoleTenantLabel, fmt.Sprintf("%s-console", tenantName)),
})
if err != nil {
return err
}
}
return nil
}
// getTenantUpdateCertificatesResponse wrapper of tenantUpdateCertificates
func getTenantUpdateCertificatesResponse(session *models.Principal, params admin_api.TenantUpdateCertificateParams) *models.Error {
ctx := context.Background()
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.SessionToken)
if err != nil {
return prepareError(err, errorUnableToUpdateTenantCertificates)
}
k8sClient := k8sClient{
client: clientSet,
}
opClientClientSet, err := cluster.OperatorClient(session.SessionToken)
if err != nil {
return prepareError(err, errorUnableToUpdateTenantCertificates)
}
opClient := operatorClient{
client: opClientClientSet,
}
if err := tenantUpdateCertificates(ctx, &opClient, &k8sClient, params.Namespace, params); err != nil {
return prepareError(err, errorUnableToUpdateTenantCertificates)
}
return nil
}
// tenantUpdateEncryption allow user to update KES server certificates, KES client certificates (used by MinIO for mTLS) and KES configuration (KMS configuration, credentials, etc)
func tenantUpdateEncryption(ctx context.Context, operatorClient OperatorClientI, clientSet K8sClientI, namespace string, params admin_api.TenantUpdateEncryptionParams) error {
tenantName := params.Tenant
secretName := fmt.Sprintf("%s-secret", tenantName)
tenant, err := operatorClient.TenantGet(ctx, namespace, tenantName, metav1.GetOptions{})
body := params.Body
if err != nil {
return err
}
// Check if encryption is enabled for MinIO via KES
if tenant.HasKESEnabled() {
// check if KES is deployed with external certificates and user provided new server keypair
if tenant.KESExternalCert() && body.Server != nil {
kesExternalCertSecretName := fmt.Sprintf("%s-kes-external-cert", secretName)
// update certificates
if _, err := createOrReplaceExternalCertSecret(ctx, clientSet, namespace, body.Server, kesExternalCertSecretName, tenantName); err != nil {
return err
}
}
// check if Tenant is deployed with external client certificates and user provided new client keypaiir
if tenant.ExternalClientCert() && body.Client != nil {
tenantExternalClientCertSecretName := fmt.Sprintf("%s-tenant-external-client-cert", secretName)
// Update certificates
if _, err := createOrReplaceExternalCertSecret(ctx, clientSet, namespace, body.Client, tenantExternalClientCertSecretName, tenantName); err != nil {
return err
}
// Restart MinIO pods to mount the new client secrets
err := clientSet.deletePodCollection(ctx, namespace, metav1.DeleteOptions{}, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", operator.TenantLabel, tenantName),
})
if err != nil {
return err
}
}
// update KES identities in kes-configuration.yaml secret
kesConfigurationSecretName := fmt.Sprintf("%s-kes-configuration", secretName)
kesClientCertSecretName := fmt.Sprintf("%s-kes-client-cert", secretName)
_, _, err := createOrReplaceKesConfigurationSecrets(ctx, clientSet, namespace, body, kesConfigurationSecretName, kesClientCertSecretName, tenantName)
if err != nil {
return err
}
// Restart KES pods to mount the new configuration
err = clientSet.deletePodCollection(ctx, namespace, metav1.DeleteOptions{}, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", operator.KESInstanceLabel, fmt.Sprintf("%s-kes", tenantName)),
})
if err != nil {
return err
}
}
return nil
}
// getTenantUpdateEncryptionResponse is a wrapper for tenantUpdateEncryption
func getTenantUpdateEncryptionResponse(session *models.Principal, params admin_api.TenantUpdateEncryptionParams) *models.Error {
ctx := context.Background()
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.SessionToken)
if err != nil {
return prepareError(err, errorUpdatingEncryptionConfig)
}
k8sClient := k8sClient{
client: clientSet,
}
opClientClientSet, err := cluster.OperatorClient(session.SessionToken)
if err != nil {
return prepareError(err, errorUpdatingEncryptionConfig)
}
opClient := operatorClient{
client: opClientClientSet,
}
if err := tenantUpdateEncryption(ctx, &opClient, &k8sClient, params.Namespace, params); err != nil {
return prepareError(err, errorUpdatingEncryptionConfig)
}
return nil
}
// getKESConfiguration will generate the KES server certificate secrets, the tenant client secrets for mTLS authentication between MinIO and KES and the
// kes-configuration.yaml file used by the KES service (how to connect to the external KMS, eg: Vault, AWS, Gemalto, etc)
func getKESConfiguration(ctx context.Context, clientSet K8sClientI, ns string, encryptionCfg *models.EncryptionConfiguration, secretName, tenantName string, autoCert bool) (kesConfiguration *operator.KESConfig, err error) {
// Secrets used by the KES service
//
// kesExternalCertSecretName is the name of the secret that will store the certificates for TLS in the KES server, eg: server.key and server.crt
kesExternalCertSecretName := fmt.Sprintf("%s-kes-external-cert", secretName)
// kesClientCertSecretName is the name of the secret that will store the certificates for mTLS between KES and the KMS, eg: mTLS with Vault or Gemalto KMS
kesClientCertSecretName := fmt.Sprintf("%s-kes-client-cert", secretName)
// kesConfigurationSecretName is the name of the secret that will store the configuration file, eg: kes-configuration.yaml
kesConfigurationSecretName := fmt.Sprintf("%s-kes-configuration", secretName)
kesConfiguration = &operator.KESConfig{
Image: "minio/kes:v0.11.0",
Replicas: 1,
}
// Using custom image for KES
if encryptionCfg.Image != "" {
kesConfiguration.Image = encryptionCfg.Image
}
// Generate server certificates for KES only if autoCert is disabled
if !autoCert {
kesExternalCertSecret, err := createOrReplaceExternalCertSecret(ctx, clientSet, ns, encryptionCfg.Server, kesExternalCertSecretName, tenantName)
if err != nil {
return nil, err
}
// External TLS certificates used by KES
kesConfiguration.ExternalCertSecret = kesExternalCertSecret
}
// Prepare kesConfiguration for KES
serverConfigSecret, clientCertSecret, err := createOrReplaceKesConfigurationSecrets(ctx, clientSet, ns, encryptionCfg, kesConfigurationSecretName, kesClientCertSecretName, tenantName)
if err != nil {
return nil, err
}
// Configuration used by KES
kesConfiguration.Configuration = serverConfigSecret
kesConfiguration.ClientCertSecret = clientCertSecret
return kesConfiguration, nil
}
// createOrReplaceExternalCertSecret receives a keypair, public and private key, encoded in base64, decode it and generate a new kubernetes secret
// to be used by the operator for TLS encryption
func createOrReplaceExternalCertSecret(ctx context.Context, clientSet K8sClientI, ns string, keyPair *models.KeyPairConfiguration, secretName, tenantName string) (*operator.LocalCertificateReference, error) {
if keyPair == nil || keyPair.Crt == nil || keyPair.Key == nil || *keyPair.Crt == "" || *keyPair.Key == "" {
return nil, errors.New("certificate files must not be empty")
}
// delete secret with same name if exists
err := clientSet.deleteSecret(ctx, ns, secretName, metav1.DeleteOptions{})
if err != nil {
// log the error if any and continue
log.Println(err)
}
imm := true
tlsCrt, err := base64.StdEncoding.DecodeString(*keyPair.Crt)
if err != nil {
return nil, err
}
tlsKey, err := base64.StdEncoding.DecodeString(*keyPair.Key)
if err != nil {
return nil, err
}
externalTLSCertificateSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
Data: map[string][]byte{
"tls.crt": tlsCrt,
"tls.key": tlsKey,
},
}
_, err = clientSet.createSecret(ctx, ns, externalTLSCertificateSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
// Certificates used by the minio instance
return &operator.LocalCertificateReference{
Name: secretName,
Type: "kubernetes.io/tls",
}, nil
}
func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sClientI, ns string, encryptionCfg *models.EncryptionConfiguration, kesConfigurationSecretName, kesClientCertSecretName, tenantName string) (*corev1.LocalObjectReference, *operator.LocalCertificateReference, error) {
// delete KES configuration secret if exists
if err := clientSet.deleteSecret(ctx, ns, kesConfigurationSecretName, metav1.DeleteOptions{}); err != nil {
// log the error if any and continue
log.Println(err)
}
// delete KES client cert secret if exists
if err := clientSet.deleteSecret(ctx, ns, kesClientCertSecretName, metav1.DeleteOptions{}); err != nil {
// log the error if any and continue
log.Println(err)
}
// if autoCert is enabled then Operator will generate the client certificates, calculate the client cert identity
// and pass it to KES via the $MINIO_KES_IDENTITY variable
clientCrtIdentity := "$MINIO_KES_IDENTITY"
// If a client certificate is provided proceed to calculate the identity
if encryptionCfg.Client != nil {
// Client certificate for KES used by Minio to mTLS
clientTLSCrt, err := base64.StdEncoding.DecodeString(*encryptionCfg.Client.Crt)
if err != nil {
return nil, nil, err
}
// Calculate the client cert identity based on the clientTLSCrt
h := crypto.SHA256.New()
certificate, err := kes.ParseCertificate(clientTLSCrt)
if err != nil {
return nil, nil, err
}
h.Write(certificate.RawSubjectPublicKeyInfo)
clientCrtIdentity = hex.EncodeToString(h.Sum(nil))
}
// Default kesConfiguration for KES
kesConfig := &kes.ServerConfig{
Addr: "0.0.0.0:7373",
Root: "disabled",
TLS: kes.TLS{
KeyPath: "/tmp/kes/server.key",
CertPath: "/tmp/kes/server.crt",
},
Policies: map[string]kes.Policy{
"default-policy": {
Paths: []string{
"/v1/key/create/my-minio-key",
"/v1/key/generate/my-minio-key",
"/v1/key/decrypt/my-minio-key",
},
Identities: []kes.Identity{
kes.Identity(clientCrtIdentity),
},
},
},
Cache: kes.Cache{
Expiry: &kes.Expiry{
Any: 5 * time.Minute,
Unused: 20 * time.Second,
},
},
Log: kes.Log{
Error: "on",
Audit: "off",
},
Keys: kes.Keys{},
}
// operator will mount the mTLSCertificates in the following paths
// therefore we set these values in the KES yaml kesConfiguration
var mTLSClientCrtPath = "/tmp/kes/client.crt"
var mTLSClientKeyPath = "/tmp/kes/client.key"
var mTLSClientCaPath = "/tmp/kes/ca.crt"
// map to hold mTLSCertificates for KES mTLS against Vault
mTLSCertificates := map[string][]byte{}
// if encryption is enabled and encryption is configured to use Vault
if encryptionCfg.Vault != nil {
// Initialize Vault Config
kesConfig.Keys.Vault = &kes.Vault{
Endpoint: *encryptionCfg.Vault.Endpoint,
EnginePath: encryptionCfg.Vault.Engine,
Namespace: encryptionCfg.Vault.Namespace,
Prefix: encryptionCfg.Vault.Prefix,
Status: &kes.VaultStatus{
Ping: 10 * time.Second,
},
}
// Vault AppRole credentials
if encryptionCfg.Vault.Approle != nil {
kesConfig.Keys.Vault.AppRole = &kes.AppRole{
EnginePath: encryptionCfg.Vault.Approle.Engine,
ID: *encryptionCfg.Vault.Approle.ID,
Secret: *encryptionCfg.Vault.Approle.Secret,
Retry: 15 * time.Second,
}
} else {
return nil, nil, errors.New("approle credentials missing for kes")
}
// Vault mTLS kesConfiguration
if encryptionCfg.Vault.TLS != nil {
vaultTLSConfig := encryptionCfg.Vault.TLS
kesConfig.Keys.Vault.TLS = &kes.VaultTLS{}
if vaultTLSConfig.Crt != "" {
clientCrt, err := base64.StdEncoding.DecodeString(vaultTLSConfig.Crt)
if err != nil {
return nil, nil, err
}
mTLSCertificates["client.crt"] = clientCrt
kesConfig.Keys.Vault.TLS.CertPath = mTLSClientCrtPath
}
if vaultTLSConfig.Key != "" {
clientKey, err := base64.StdEncoding.DecodeString(vaultTLSConfig.Key)
if err != nil {
return nil, nil, err
}
mTLSCertificates["client.key"] = clientKey
kesConfig.Keys.Vault.TLS.KeyPath = mTLSClientKeyPath
}
if vaultTLSConfig.Ca != "" {
caCrt, err := base64.StdEncoding.DecodeString(vaultTLSConfig.Ca)
if err != nil {
return nil, nil, err
}
mTLSCertificates["ca.crt"] = caCrt
kesConfig.Keys.Vault.TLS.CAPath = mTLSClientCaPath
}
}
} else if encryptionCfg.Aws != nil {
// Initialize AWS
kesConfig.Keys.Aws = &kes.Aws{
SecretsManager: &kes.AwsSecretManager{},
}
// AWS basic kesConfiguration
if encryptionCfg.Aws.Secretsmanager != nil {
kesConfig.Keys.Aws.SecretsManager.Endpoint = *encryptionCfg.Aws.Secretsmanager.Endpoint
kesConfig.Keys.Aws.SecretsManager.Region = *encryptionCfg.Aws.Secretsmanager.Region
kesConfig.Keys.Aws.SecretsManager.KmsKey = encryptionCfg.Aws.Secretsmanager.Kmskey
// AWS credentials
if encryptionCfg.Aws.Secretsmanager.Credentials != nil {
kesConfig.Keys.Aws.SecretsManager.Login = &kes.AwsSecretManagerLogin{
AccessKey: *encryptionCfg.Aws.Secretsmanager.Credentials.Accesskey,
SecretKey: *encryptionCfg.Aws.Secretsmanager.Credentials.Secretkey,
SessionToken: encryptionCfg.Aws.Secretsmanager.Credentials.Token,
}
}
}
} else if encryptionCfg.Gemalto != nil {
// Initialize Gemalto
kesConfig.Keys.Gemalto = &kes.Gemalto{
KeySecure: &kes.GemaltoKeySecure{},
}
// Gemalto Configuration
if encryptionCfg.Gemalto.Keysecure != nil {
kesConfig.Keys.Gemalto.KeySecure.Endpoint = *encryptionCfg.Gemalto.Keysecure.Endpoint
// Gemalto TLS kesConfiguration
if encryptionCfg.Gemalto.Keysecure.TLS != nil {
if encryptionCfg.Gemalto.Keysecure.TLS.Ca != nil {
caCrt, err := base64.StdEncoding.DecodeString(*encryptionCfg.Gemalto.Keysecure.TLS.Ca)
if err != nil {
return nil, nil, err
}
mTLSCertificates["ca.crt"] = caCrt
kesConfig.Keys.Gemalto.KeySecure.TLS = &kes.GemaltoTLS{
CAPath: mTLSClientCaPath,
}
}
}
// Gemalto Login
if encryptionCfg.Gemalto.Keysecure.Credentials != nil {
kesConfig.Keys.Gemalto.KeySecure.Credentials = &kes.GemaltoCredentials{
Token: *encryptionCfg.Gemalto.Keysecure.Credentials.Token,
Domain: *encryptionCfg.Gemalto.Keysecure.Credentials.Domain,
Retry: 15 * time.Second,
}
}
}
}
imm := true
// if mTLSCertificates contains elements we create the kubernetes secret
var clientCertSecretReference *operator.LocalCertificateReference
if len(mTLSCertificates) > 0 {
// Secret to store KES mTLS kesConfiguration to authenticate against a KMS
kesClientCertSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: kesClientCertSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: mTLSCertificates,
}
_, err := clientSet.createSecret(ctx, ns, &kesClientCertSecret, metav1.CreateOptions{})
if err != nil {
return nil, nil, err
}
// kubernetes generic secret
clientCertSecretReference = &operator.LocalCertificateReference{
Name: kesClientCertSecretName,
}
}
// Generate Yaml kesConfiguration for KES
serverConfigYaml, err := yaml.Marshal(kesConfig)
if err != nil {
return nil, nil, err
}
// Secret to store KES server kesConfiguration
kesConfigurationSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: kesConfigurationSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: map[string][]byte{
"server-config.yaml": serverConfigYaml,
},
}
_, err = clientSet.createSecret(ctx, ns, &kesConfigurationSecret, metav1.CreateOptions{})
if err != nil {
return nil, nil, err
}
return &corev1.LocalObjectReference{
Name: kesConfigurationSecretName,
}, clientCertSecretReference, nil
}

View File

@@ -0,0 +1,472 @@
// 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 restapi
import (
"context"
"errors"
"reflect"
"testing"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations/admin_api"
operator "github.com/minio/operator/pkg/apis/minio.min.io/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var DeletePodCollectionMock func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
var DeleteSecretMock func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error
var CreateSecretMock func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error)
func (c k8sClientMock) deletePodCollection(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return DeletePodCollectionMock(ctx, namespace, opts, listOpts)
}
func (c k8sClientMock) deleteSecret(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return DeleteSecretMock(ctx, namespace, name, opts)
}
func (c k8sClientMock) createSecret(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error) {
return CreateSecretMock(ctx, namespace, secret, opts)
}
func Test_tenantUpdateCertificates(t *testing.T) {
k8sClient := k8sClientMock{}
opClient := opClientMock{}
crt := "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUUzRENDQTBTZ0F3SUJBZ0lRS0pDalNyK0xaMnJrYVo5ODAvZnV5akFOQmdrcWhraUc5dzBCQVFzRkFEQ0IKdFRFZU1Cd0dBMVVFQ2hNVmJXdGpaWEowSUdSbGRtVnNiM0J0Wlc1MElFTkJNVVV3UXdZRFZRUUxERHhoYkdWMgpjMnRBVEdWdWFXNXpMVTFoWTBKdmIyc3RVSEp2TG14dlkyRnNJQ2hNWlc1cGJpQkJiR1YyYzJ0cElFaDFaWEowCllTQkJjbWxoY3lreFREQktCZ05WQkFNTVEyMXJZMlZ5ZENCaGJHVjJjMnRBVEdWdWFXNXpMVTFoWTBKdmIyc3QKVUhKdkxteHZZMkZzSUNoTVpXNXBiaUJCYkdWMmMydHBJRWgxWlhKMFlTQkJjbWxoY3lrd0hoY05NVGt3TmpBeApNREF3TURBd1doY05NekF3T0RBeU1ERTFNekEzV2pCaU1TY3dKUVlEVlFRS0V4NXRhMk5sY25RZ1pHVjJaV3h2CmNHMWxiblFnWTJWeWRHbG1hV05oZEdVeE56QTFCZ05WQkFzTUxtRnNaWFp6YTBCMGFXWmhMbXh2WTJGc0lDaE0KWlc1cGJpQkJiR1YyYzJ0cElFaDFaWEowWVNCQmNtbGhjeWt3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQgpEd0F3Z2dFS0FvSUJBUUM2dlhLVyswWmhJQUNycmpxTU9QZ3VSdGpSemk1L0VxK2JvZTJkQ1BpT3djdXo0WFlhCm1rVlcxSkhBS3VTc0I1UHE0QnFSRXFueUhYT0ZROWQ1bEFjRGNDYmhlTFRVb2h2ZkhOWW9SdWRrYkZ3RzAyOFQKdVMxTFNtcHU5VjhFU2Q0Q3BiOGRvUkcvUW8vRTF4RGk5STFhNVhoeUdHTTNsUFlYQU1HU1ZkWUNNNzh0M2ZLTwp0NFVlcEt6d2dFQ2NKRVg4MVFoem1ZT25ELysyazNxSVppZU9nOGFkbDNFUWV2eFBISXlXQ1JkaDJZTkZsRi9rCmEwTzQyRVl2NVNUT1N0dzI2TzMwTVRrcEg0dzRRZm9VV3BnZU93MDZyYTluRHdzV3VhMUZIaHlKZmxOanR1USsKU0NlaDdqTVlVUThYV1JkbXVHbzFRT0g5cjdDKy82L1JhMlZIQWdNQkFBR2pnYmt3Z2JZd0RnWURWUjBQQVFILwpCQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUJNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqCkJCZ3dGb0FVNHg4NUIyeGVlQzg0UEdvM1dZVWwxRGVvZlFNd1lBWURWUjBSQkZrd1Y0SWpLaTV0YVc1cGJ5MWgKYkdWMmMyc3Ribk11YzNaakxtTnNkWE4wWlhJdWJHOWpZV3lDTUNvdWFHOXVaWGwzWld4c0xXaHNMbTFwYm1sdgpMV0ZzWlhaemF5MXVjeTV6ZG1NdVkyeDFjM1JsY2k1c2IyTmhiREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBWUVBCnZnMFlIVGtzd3Z4NEE5ZXl0b1V3eTBOSFVjQXpuNy9BVCt3WEt6d3Fqa0RiS3hVYlFTMFNQVVI4SkVDMkpuUUgKU3pTNGFCNXRURVRYZUcrbHJZVU02b0RNcXlIalpUN1NJaW83OUNxOFloWWxORU9qMkhvaXdxalczZm10UU9kVApoVG1tS01lRnZleHo2cnRxbHp0cVdKa3kvOGd1MkMrMWw5UDFFUmhFNDZZY0puVmJ5REFTSGNvV2tiZVhFUGErCjNpNFd4bU1yMDlNZXFTNkFUb2NKanFBeEtYcURYWmlFZjRUVEp1ZTRBQ2s4WmhqaEtUUEV6RnQ3WllVaHY3dVoKdlZCOXhla2FqRzdMRU5kSHZncXVMRUxUV3czWkpBaXpSTU5KUUpzZU11LzdQN1NIeXRKTVJYdlVwd2R2dWhDOApKNm54aGRmUS91QVlQY1lHdlU5NUZPZ1NjWVZqNW1WQktXM0ZHbkl2YzZuamQ1OFhBSTE3dlk0K0ZZTnY4M2UxCm9mOGlxRFdWNTFyT1FlbG9FZFdmdHkvZTI2bXVWUUQzQlVjY2Z5SWpGYy9SeGNHdm5maUEwUm1uRDNkWFcyZ0oKUHFTd01ZZ3hxQndqMm1Qck5jdkFWY3BOeWRjTWJKOTFIOHRWY0ttRHMrY1NiV0tnblpmKzUvZm5yaTdmU3FLUQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
key := "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQzZ2WEtXKzBaaElBQ3IKcmpxTU9QZ3VSdGpSemk1L0VxK2JvZTJkQ1BpT3djdXo0WFlhbWtWVzFKSEFLdVNzQjVQcTRCcVJFcW55SFhPRgpROWQ1bEFjRGNDYmhlTFRVb2h2ZkhOWW9SdWRrYkZ3RzAyOFR1UzFMU21wdTlWOEVTZDRDcGI4ZG9SRy9Rby9FCjF4RGk5STFhNVhoeUdHTTNsUFlYQU1HU1ZkWUNNNzh0M2ZLT3Q0VWVwS3p3Z0VDY0pFWDgxUWh6bVlPbkQvKzIKazNxSVppZU9nOGFkbDNFUWV2eFBISXlXQ1JkaDJZTkZsRi9rYTBPNDJFWXY1U1RPU3R3MjZPMzBNVGtwSDR3NApRZm9VV3BnZU93MDZyYTluRHdzV3VhMUZIaHlKZmxOanR1UStTQ2VoN2pNWVVROFhXUmRtdUdvMVFPSDlyN0MrCi82L1JhMlZIQWdNQkFBRUNnZ0VCQUlyTlVFUnJSM2ZmK3IraGhJRS93ekZhbGNUMUZWaDh3aXpUWXJQcnZCMFkKYlZvcVJzZ2xUVTdxTjkvM3dmc2dzdEROZk5IQ1pySEJOR0drK0orMDZMV2tnakhycjdXeFBUaE16ZDRvUGN4RwpRdTBMOGE5ZVlBMXJwY3NOOVc5Um5JU3BRSEk4aTkxM0V6Z0RoOWk2WCt0bFQyNjNNK0JYaDhlM1Z5cDNSTmhpCjZZUTQwcWJsNlQ0TUlyLzRSMGJmcFExZWVMNVNnbHB6Z1d6ZGs4WGtmc0E5YnRiU1RoMjZKRlBPUU1tMm5adkcKbjBlZm85TDZtaktwRW9rRWpTY1hWYTRZNHJaREZFYTVIbkpUSDBHblByQzU1cDhBb3BFN1c1M2RDT3lxd29CcQo4SWtZb2grcm1qTUZrOGc5VlRVYlcwVE9YVTFSY1E1Z0gxWS9jam5uVTRrQ2dZRUE4amw5ZEQyN2JaQ2ZaTW9jCjJYRThiYkJTaFVXTjRvaklKc1lHY2xyTjJDUEtUNmExaVhvS3BrTXdGNUdsODEzQURGR1pTbWtUSUFVQXRXQU8KNzZCcEpGUlVCZ1hmUXhSU0gyS1RaRldxbE5yekZPQjNsT3h2bFJ1amw5eE9ueStyWUhJWC9BOWNqQlp3a2orSAo3MDZRTExvS1ZFL2lMYy9DMlI5VzRLRVI3R3NDZ1lFQXhWd3FyM1JnUXhHUmpueG1zbDZtUW5DQ3k2MXFoUzUxCkJicDJzWldraFNTV0w0YzFDY0NDMnZ2ektPSmJkZ2NZQU43L05sTHgzWjlCZUFjTVZMUEVoc2NPalB6YUlneEMKczJ2UkdwQUFtYnRjWi9MVlpKaERWTjIrSHowRHhnRUtCa1JzQU5XTG51cGRoUWJBdlZuTkVsY29YVlo1cC9qcwp3U1BCelFoSklaVUNnWUJqTUlHY0dTOW9SWUhRRHlmVEx4aVV2bEI4ZktnR2JRYXhRZ1FmemVsZktnRE5yekhGCnN6RXJOblk2SUkxNVpCbWhzY1I1QVNBd3kzdW55a2N6ZjFldTVjMW1qZjhJQkFsQkN1ZmFmVzRWK0xiMEJKdFQKWTZLcHg2Q3RMaTBQNk1CZ0JUaW5JazgrbW0zTXBiRnZvSmRQaVh0elhTYjhwWWhmeXdLVGg4SEVNd0tCZ1FDUwpvR0VPTFpYKy9pUjRDYkI2d0pzaExWbmZYSjJSQ096a0xwNVVYV3IzaURFVWFvMWJDMjJzcUJjRnZ2WllmL2l6ClhQbWJNSkNGS1BhSTZDT2ZJbGZXRWptYlFaZ0dSN21lZDNISkhFZDE3NTg5azBvN0RHeXB0bnl6MUs3akFvNmkKRFY5NFZ5NytCLzBuQWRkY1ZrVm5aTjJXU3RMam1xcTY2NGZtZmt0bTZRS0JnQzBsMk5OVlFWK2N0OFFRRFpINQpBRFIrSGM3Qk0wNDhURGhNTmhoR3JHc2tWNngwMCtMZTdISGdpT3h2NXJudkNlTlY2M001YUZHdFVWbllTN1VoCkE1NndaNVlZeFFnQ0xzNi9PRmZhK3NiTngrSjdnSjRjNXdMZVlJMXVPMTlzZHBHa2VHZ25vK3dXVmxDSzFCbW0KRGM0TXA2STRiUTVtdy93YVpLQnpjRTJLCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"
badCrt := "ASD"
badKey := "ASD"
type args struct {
ctx context.Context
opClient OperatorClientI
clientSet K8sClientI
namespace string
params admin_api.TenantUpdateCertificateParams
mockTenantGet func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error)
mockDeleteSecret func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error
mockCreateSecret func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error)
mockDeletePodCollection func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "error getting tenant information",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: admin_api.TenantUpdateCertificateParams{},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return nil, errors.New("invalid tenant")
},
},
wantErr: true,
},
{
name: "error replacing external certs for tenant because of missing keypair",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Minio: &models.KeyPairConfiguration{},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
},
},
}, nil
},
},
wantErr: true,
},
{
name: "error replacing external certs for tenant because of malformed encoded certs",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Minio: &models.KeyPairConfiguration{
Crt: &badCrt,
Key: &badKey,
},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
},
},
}, nil
},
mockDeleteSecret: func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
},
},
wantErr: true,
},
{
name: "error replacing external certs for tenant because of error during secret creation",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Minio: &models.KeyPairConfiguration{
Crt: &crt,
Key: &key,
},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
},
},
}, nil
},
mockDeleteSecret: func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
},
mockCreateSecret: func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error) {
return nil, errors.New("error creating secret")
},
},
wantErr: true,
},
{
name: "certificates replaced but error during deleting existing tenant pods",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Minio: &models.KeyPairConfiguration{
Crt: &crt,
Key: &key,
},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
},
},
}, nil
},
mockDeleteSecret: func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
},
mockCreateSecret: func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error) {
return &v1.Secret{}, nil
},
mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return errors.New("error deleting minio pods")
},
},
wantErr: true,
},
{
name: "error replacing external certs for console because of missing keypair",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Console: &models.KeyPairConfiguration{},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
Console: &operator.ConsoleConfiguration{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
},
},
},
}, nil
},
},
wantErr: true,
},
{
name: "certificates replaced but error during deleting existing tenant pods",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Console: &models.KeyPairConfiguration{
Crt: &crt,
Key: &key,
},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
Console: &operator.ConsoleConfiguration{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
},
},
},
}, nil
},
mockDeleteSecret: func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
},
mockCreateSecret: func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error) {
return &v1.Secret{}, nil
},
mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return errors.New("error deleting console pods")
},
},
wantErr: true,
},
}
for _, tt := range tests {
opClientTenantGetMock = tt.args.mockTenantGet
DeleteSecretMock = tt.args.mockDeleteSecret
CreateSecretMock = tt.args.mockCreateSecret
DeletePodCollectionMock = tt.args.mockDeletePodCollection
t.Run(tt.name, func(t *testing.T) {
if err := tenantUpdateCertificates(tt.args.ctx, tt.args.opClient, tt.args.clientSet, tt.args.namespace, tt.args.params); (err != nil) != tt.wantErr {
t.Errorf("tenantUpdateCertificates() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_tenantUpdateEncryption(t *testing.T) {
k8sClient := k8sClientMock{}
opClient := opClientMock{}
type args struct {
ctx context.Context
opClient OperatorClientI
clientSet K8sClientI
namespace string
params admin_api.TenantUpdateEncryptionParams
mockTenantGet func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error)
mockDeleteSecret func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error
mockCreateSecret func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error)
mockDeletePodCollection func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "error updating encryption configuration because of error getting tenant",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: admin_api.TenantUpdateEncryptionParams{},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return nil, errors.New("invalid tenant")
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opClientTenantGetMock = tt.args.mockTenantGet
DeleteSecretMock = tt.args.mockDeleteSecret
CreateSecretMock = tt.args.mockCreateSecret
DeletePodCollectionMock = tt.args.mockDeletePodCollection
if err := tenantUpdateEncryption(tt.args.ctx, tt.args.opClient, tt.args.clientSet, tt.args.namespace, tt.args.params); (err != nil) != tt.wantErr {
t.Errorf("tenantUpdateEncryption() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_createOrReplaceKesConfigurationSecrets(t *testing.T) {
k8sClient := k8sClientMock{}
crt := "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUUzRENDQTBTZ0F3SUJBZ0lRS0pDalNyK0xaMnJrYVo5ODAvZnV5akFOQmdrcWhraUc5dzBCQVFzRkFEQ0IKdFRFZU1Cd0dBMVVFQ2hNVmJXdGpaWEowSUdSbGRtVnNiM0J0Wlc1MElFTkJNVVV3UXdZRFZRUUxERHhoYkdWMgpjMnRBVEdWdWFXNXpMVTFoWTBKdmIyc3RVSEp2TG14dlkyRnNJQ2hNWlc1cGJpQkJiR1YyYzJ0cElFaDFaWEowCllTQkJjbWxoY3lreFREQktCZ05WQkFNTVEyMXJZMlZ5ZENCaGJHVjJjMnRBVEdWdWFXNXpMVTFoWTBKdmIyc3QKVUhKdkxteHZZMkZzSUNoTVpXNXBiaUJCYkdWMmMydHBJRWgxWlhKMFlTQkJjbWxoY3lrd0hoY05NVGt3TmpBeApNREF3TURBd1doY05NekF3T0RBeU1ERTFNekEzV2pCaU1TY3dKUVlEVlFRS0V4NXRhMk5sY25RZ1pHVjJaV3h2CmNHMWxiblFnWTJWeWRHbG1hV05oZEdVeE56QTFCZ05WQkFzTUxtRnNaWFp6YTBCMGFXWmhMbXh2WTJGc0lDaE0KWlc1cGJpQkJiR1YyYzJ0cElFaDFaWEowWVNCQmNtbGhjeWt3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQgpEd0F3Z2dFS0FvSUJBUUM2dlhLVyswWmhJQUNycmpxTU9QZ3VSdGpSemk1L0VxK2JvZTJkQ1BpT3djdXo0WFlhCm1rVlcxSkhBS3VTc0I1UHE0QnFSRXFueUhYT0ZROWQ1bEFjRGNDYmhlTFRVb2h2ZkhOWW9SdWRrYkZ3RzAyOFQKdVMxTFNtcHU5VjhFU2Q0Q3BiOGRvUkcvUW8vRTF4RGk5STFhNVhoeUdHTTNsUFlYQU1HU1ZkWUNNNzh0M2ZLTwp0NFVlcEt6d2dFQ2NKRVg4MVFoem1ZT25ELysyazNxSVppZU9nOGFkbDNFUWV2eFBISXlXQ1JkaDJZTkZsRi9rCmEwTzQyRVl2NVNUT1N0dzI2TzMwTVRrcEg0dzRRZm9VV3BnZU93MDZyYTluRHdzV3VhMUZIaHlKZmxOanR1USsKU0NlaDdqTVlVUThYV1JkbXVHbzFRT0g5cjdDKy82L1JhMlZIQWdNQkFBR2pnYmt3Z2JZd0RnWURWUjBQQVFILwpCQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUJNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqCkJCZ3dGb0FVNHg4NUIyeGVlQzg0UEdvM1dZVWwxRGVvZlFNd1lBWURWUjBSQkZrd1Y0SWpLaTV0YVc1cGJ5MWgKYkdWMmMyc3Ribk11YzNaakxtTnNkWE4wWlhJdWJHOWpZV3lDTUNvdWFHOXVaWGwzWld4c0xXaHNMbTFwYm1sdgpMV0ZzWlhaemF5MXVjeTV6ZG1NdVkyeDFjM1JsY2k1c2IyTmhiREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBWUVBCnZnMFlIVGtzd3Z4NEE5ZXl0b1V3eTBOSFVjQXpuNy9BVCt3WEt6d3Fqa0RiS3hVYlFTMFNQVVI4SkVDMkpuUUgKU3pTNGFCNXRURVRYZUcrbHJZVU02b0RNcXlIalpUN1NJaW83OUNxOFloWWxORU9qMkhvaXdxalczZm10UU9kVApoVG1tS01lRnZleHo2cnRxbHp0cVdKa3kvOGd1MkMrMWw5UDFFUmhFNDZZY0puVmJ5REFTSGNvV2tiZVhFUGErCjNpNFd4bU1yMDlNZXFTNkFUb2NKanFBeEtYcURYWmlFZjRUVEp1ZTRBQ2s4WmhqaEtUUEV6RnQ3WllVaHY3dVoKdlZCOXhla2FqRzdMRU5kSHZncXVMRUxUV3czWkpBaXpSTU5KUUpzZU11LzdQN1NIeXRKTVJYdlVwd2R2dWhDOApKNm54aGRmUS91QVlQY1lHdlU5NUZPZ1NjWVZqNW1WQktXM0ZHbkl2YzZuamQ1OFhBSTE3dlk0K0ZZTnY4M2UxCm9mOGlxRFdWNTFyT1FlbG9FZFdmdHkvZTI2bXVWUUQzQlVjY2Z5SWpGYy9SeGNHdm5maUEwUm1uRDNkWFcyZ0oKUHFTd01ZZ3hxQndqMm1Qck5jdkFWY3BOeWRjTWJKOTFIOHRWY0ttRHMrY1NiV0tnblpmKzUvZm5yaTdmU3FLUQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
key := "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQzZ2WEtXKzBaaElBQ3IKcmpxTU9QZ3VSdGpSemk1L0VxK2JvZTJkQ1BpT3djdXo0WFlhbWtWVzFKSEFLdVNzQjVQcTRCcVJFcW55SFhPRgpROWQ1bEFjRGNDYmhlTFRVb2h2ZkhOWW9SdWRrYkZ3RzAyOFR1UzFMU21wdTlWOEVTZDRDcGI4ZG9SRy9Rby9FCjF4RGk5STFhNVhoeUdHTTNsUFlYQU1HU1ZkWUNNNzh0M2ZLT3Q0VWVwS3p3Z0VDY0pFWDgxUWh6bVlPbkQvKzIKazNxSVppZU9nOGFkbDNFUWV2eFBISXlXQ1JkaDJZTkZsRi9rYTBPNDJFWXY1U1RPU3R3MjZPMzBNVGtwSDR3NApRZm9VV3BnZU93MDZyYTluRHdzV3VhMUZIaHlKZmxOanR1UStTQ2VoN2pNWVVROFhXUmRtdUdvMVFPSDlyN0MrCi82L1JhMlZIQWdNQkFBRUNnZ0VCQUlyTlVFUnJSM2ZmK3IraGhJRS93ekZhbGNUMUZWaDh3aXpUWXJQcnZCMFkKYlZvcVJzZ2xUVTdxTjkvM3dmc2dzdEROZk5IQ1pySEJOR0drK0orMDZMV2tnakhycjdXeFBUaE16ZDRvUGN4RwpRdTBMOGE5ZVlBMXJwY3NOOVc5Um5JU3BRSEk4aTkxM0V6Z0RoOWk2WCt0bFQyNjNNK0JYaDhlM1Z5cDNSTmhpCjZZUTQwcWJsNlQ0TUlyLzRSMGJmcFExZWVMNVNnbHB6Z1d6ZGs4WGtmc0E5YnRiU1RoMjZKRlBPUU1tMm5adkcKbjBlZm85TDZtaktwRW9rRWpTY1hWYTRZNHJaREZFYTVIbkpUSDBHblByQzU1cDhBb3BFN1c1M2RDT3lxd29CcQo4SWtZb2grcm1qTUZrOGc5VlRVYlcwVE9YVTFSY1E1Z0gxWS9jam5uVTRrQ2dZRUE4amw5ZEQyN2JaQ2ZaTW9jCjJYRThiYkJTaFVXTjRvaklKc1lHY2xyTjJDUEtUNmExaVhvS3BrTXdGNUdsODEzQURGR1pTbWtUSUFVQXRXQU8KNzZCcEpGUlVCZ1hmUXhSU0gyS1RaRldxbE5yekZPQjNsT3h2bFJ1amw5eE9ueStyWUhJWC9BOWNqQlp3a2orSAo3MDZRTExvS1ZFL2lMYy9DMlI5VzRLRVI3R3NDZ1lFQXhWd3FyM1JnUXhHUmpueG1zbDZtUW5DQ3k2MXFoUzUxCkJicDJzWldraFNTV0w0YzFDY0NDMnZ2ektPSmJkZ2NZQU43L05sTHgzWjlCZUFjTVZMUEVoc2NPalB6YUlneEMKczJ2UkdwQUFtYnRjWi9MVlpKaERWTjIrSHowRHhnRUtCa1JzQU5XTG51cGRoUWJBdlZuTkVsY29YVlo1cC9qcwp3U1BCelFoSklaVUNnWUJqTUlHY0dTOW9SWUhRRHlmVEx4aVV2bEI4ZktnR2JRYXhRZ1FmemVsZktnRE5yekhGCnN6RXJOblk2SUkxNVpCbWhzY1I1QVNBd3kzdW55a2N6ZjFldTVjMW1qZjhJQkFsQkN1ZmFmVzRWK0xiMEJKdFQKWTZLcHg2Q3RMaTBQNk1CZ0JUaW5JazgrbW0zTXBiRnZvSmRQaVh0elhTYjhwWWhmeXdLVGg4SEVNd0tCZ1FDUwpvR0VPTFpYKy9pUjRDYkI2d0pzaExWbmZYSjJSQ096a0xwNVVYV3IzaURFVWFvMWJDMjJzcUJjRnZ2WllmL2l6ClhQbWJNSkNGS1BhSTZDT2ZJbGZXRWptYlFaZ0dSN21lZDNISkhFZDE3NTg5azBvN0RHeXB0bnl6MUs3akFvNmkKRFY5NFZ5NytCLzBuQWRkY1ZrVm5aTjJXU3RMam1xcTY2NGZtZmt0bTZRS0JnQzBsMk5OVlFWK2N0OFFRRFpINQpBRFIrSGM3Qk0wNDhURGhNTmhoR3JHc2tWNngwMCtMZTdISGdpT3h2NXJudkNlTlY2M001YUZHdFVWbllTN1VoCkE1NndaNVlZeFFnQ0xzNi9PRmZhK3NiTngrSjdnSjRjNXdMZVlJMXVPMTlzZHBHa2VHZ25vK3dXVmxDSzFCbW0KRGM0TXA2STRiUTVtdy93YVpLQnpjRTJLCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"
badCrt := "ASD"
badKey := "ASD"
appRole := "ASD"
appSecret := "ASD"
endpoint := "ASD"
type args struct {
ctx context.Context
clientSet K8sClientI
ns string
encryptionCfg *models.EncryptionConfiguration
kesConfigurationSecretName string
kesClientCertSecretName string
tenantName string
mockDeleteSecret func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error
mockCreateSecret func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error)
}
tests := []struct {
name string
args args
want *v1.LocalObjectReference
want1 *operator.LocalCertificateReference
wantErr bool
}{
{
name: "error decoding the client certificate",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
encryptionCfg: &models.EncryptionConfiguration{
Client: &models.KeyPairConfiguration{
Crt: &badCrt,
Key: &badKey,
},
},
ns: "default",
kesConfigurationSecretName: "test-secret",
kesClientCertSecretName: "test-client-secret",
tenantName: "test",
mockDeleteSecret: func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
},
},
want: nil,
want1: nil,
wantErr: true,
},
{
name: "error because of malformed decoded certificate",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
encryptionCfg: &models.EncryptionConfiguration{
Client: &models.KeyPairConfiguration{
Crt: &key, // will cause an error because we are passing a private key as the public key
Key: &key,
},
},
ns: "default",
kesConfigurationSecretName: "test-secret",
kesClientCertSecretName: "test-client-secret",
tenantName: "test",
mockDeleteSecret: func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
},
},
want: nil,
want1: nil,
wantErr: true,
},
{
name: "error because of malformed decoded certificate",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
encryptionCfg: &models.EncryptionConfiguration{
Client: &models.KeyPairConfiguration{
Crt: &crt,
Key: &key,
},
Vault: &models.VaultConfiguration{
Approle: &models.VaultConfigurationApprole{
Engine: "",
ID: &appRole,
Retry: 0,
Secret: &appSecret,
},
Endpoint: &endpoint,
Engine: "",
Namespace: "",
Prefix: "",
Status: nil,
TLS: &models.VaultConfigurationTLS{
Ca: crt,
Crt: crt,
Key: key,
},
},
},
ns: "default",
kesConfigurationSecretName: "test-secret",
kesClientCertSecretName: "test-client-secret",
tenantName: "test",
mockDeleteSecret: func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
},
mockCreateSecret: func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error) {
return &v1.Secret{}, nil
},
},
want: &v1.LocalObjectReference{
Name: "test-secret",
},
want1: &operator.LocalCertificateReference{
Name: "test-client-secret",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
DeleteSecretMock = tt.args.mockDeleteSecret
CreateSecretMock = tt.args.mockCreateSecret
got, got1, err := createOrReplaceKesConfigurationSecrets(tt.args.ctx, tt.args.clientSet, tt.args.ns, tt.args.encryptionCfg, tt.args.kesConfigurationSecretName, tt.args.kesClientCertSecretName, tt.args.tenantName)
if (err != nil) != tt.wantErr {
t.Errorf("createOrReplaceKesConfigurationSecrets() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("createOrReplaceKesConfigurationSecrets() got = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got1, tt.want1) {
t.Errorf("createOrReplaceKesConfigurationSecrets() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

View File

@@ -33,9 +33,11 @@ import (
operator "github.com/minio/operator/pkg/apis/minio.min.io/v1"
v1 "github.com/minio/operator/pkg/apis/minio.min.io/v1"
corev1 "k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/fake"
)
@@ -85,13 +87,12 @@ func Test_TenantInfoTenantAdminClient(t *testing.T) {
ctx := context.Background()
kClient := k8sClientMock{}
type args struct {
ctx context.Context
client K8sClient
namespace string
tenantName string
serviceName string
scheme string
insecure bool
ctx context.Context
client K8sClientI
namespace string
tenantName string
serviceURL string
insecure bool
}
tests := []struct {
name string
@@ -103,12 +104,11 @@ func Test_TenantInfoTenantAdminClient(t *testing.T) {
{
name: "Return Tenant Admin, no errors",
args: args{
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceName: "service-1",
scheme: "http",
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceURL: "http://service-1.default.svc.cluster.local:80",
},
mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
vals := make(map[string][]byte)
@@ -132,12 +132,11 @@ func Test_TenantInfoTenantAdminClient(t *testing.T) {
{
name: "Access key not stored on secrets",
args: args{
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceName: "service-1",
scheme: "http",
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceURL: "http://service-1.default.svc.cluster.local:80",
},
mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
vals := make(map[string][]byte)
@@ -160,12 +159,11 @@ func Test_TenantInfoTenantAdminClient(t *testing.T) {
{
name: "Secret key not stored on secrets",
args: args{
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceName: "service-1",
scheme: "http",
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceURL: "http://service-1.default.svc.cluster.local:80",
},
mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
vals := make(map[string][]byte)
@@ -188,12 +186,11 @@ func Test_TenantInfoTenantAdminClient(t *testing.T) {
{
name: "Handle error on getService",
args: args{
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceName: "service-1",
scheme: "http",
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceURL: "http://service-1.default.svc.cluster.local:80",
},
mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
vals := make(map[string][]byte)
@@ -212,12 +209,11 @@ func Test_TenantInfoTenantAdminClient(t *testing.T) {
{
name: "Handle error on getSecret",
args: args{
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceName: "service-1",
scheme: "http",
ctx: ctx,
client: kClient,
namespace: "default",
tenantName: "tenant-1",
serviceURL: "http://service-1.default.svc.cluster.local:80",
},
mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return nil, errors.New("error")
@@ -237,7 +233,7 @@ func Test_TenantInfoTenantAdminClient(t *testing.T) {
k8sclientGetSecretMock = tt.mockGetSecret
k8sclientGetServiceMock = tt.mockGetService
t.Run(tt.name, func(t *testing.T) {
got, err := getTenantAdminClient(tt.args.ctx, tt.args.client, tt.args.namespace, tt.args.tenantName, tt.args.serviceName, tt.args.scheme, tt.args.insecure)
got, err := getTenantAdminClient(tt.args.ctx, tt.args.client, tt.args.namespace, tt.args.tenantName, tt.args.serviceURL, tt.args.insecure)
if err != nil {
if tt.wantErr {
return
@@ -255,7 +251,6 @@ func Test_TenantInfo(t *testing.T) {
testTimeStamp := metav1.Now()
type args struct {
minioTenant *operator.Tenant
tenantInfo *usageInfo
}
tests := []struct {
name string
@@ -296,9 +291,6 @@ func Test_TenantInfo(t *testing.T) {
CurrentState: "ready",
},
},
tenantInfo: &usageInfo{
DisksUsage: 1024,
},
},
want: &models.Tenant{
CreationDate: testTimeStamp.String(),
@@ -316,8 +308,143 @@ func Test_TenantInfo(t *testing.T) {
},
},
},
Namespace: "minio-ns",
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
Namespace: "minio-ns",
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
EnablePrometheus: false,
},
},
{
// Description if DeletionTimeStamp is present, value should be returned as string
// If Prometheus annotations are present, EnablePrometheus should be returned as true
// All three annotations should be defined to consider Prometheus enabled
name: "Get tenant Info w DeletionTimeStamp and Prometheus",
args: args{
minioTenant: &operator.Tenant{
ObjectMeta: metav1.ObjectMeta{
CreationTimestamp: testTimeStamp,
Name: "tenant1",
Namespace: "minio-ns",
DeletionTimestamp: &testTimeStamp,
Annotations: map[string]string{
prometheusPath: "some/path",
prometheusPort: "other/path",
prometheusScrape: "other/path",
},
},
Spec: operator.TenantSpec{
Zones: []operator.Zone{
{
Name: "zone1",
Servers: int32(2),
VolumesPerServer: 4,
VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
Spec: corev1.PersistentVolumeClaimSpec{
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceStorage: resource.MustParse("1Mi"),
},
},
StorageClassName: swag.String("standard"),
},
},
},
},
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
},
Status: operator.TenantStatus{
CurrentState: "ready",
},
},
},
want: &models.Tenant{
CreationDate: testTimeStamp.String(),
DeletionDate: testTimeStamp.String(),
Name: "tenant1",
TotalSize: int64(8388608),
CurrentState: "ready",
Zones: []*models.Zone{
{
Name: "zone1",
Servers: swag.Int64(int64(2)),
VolumesPerServer: swag.Int32(4),
VolumeConfiguration: &models.ZoneVolumeConfiguration{
StorageClassName: "standard",
Size: swag.Int64(1024 * 1024),
},
},
},
Namespace: "minio-ns",
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
EnablePrometheus: true,
},
},
{
// If Prometheus annotations are present, EnablePrometheus should be returned as true
// All three annotations should be defined to consider Prometheus enabled
name: "Get tenant Info, not all Prometheus annotations",
args: args{
minioTenant: &operator.Tenant{
ObjectMeta: metav1.ObjectMeta{
CreationTimestamp: testTimeStamp,
Name: "tenant1",
Namespace: "minio-ns",
Annotations: map[string]string{
prometheusPath: "some/path",
prometheusScrape: "other/path",
},
},
Spec: operator.TenantSpec{
Zones: []operator.Zone{},
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
},
Status: operator.TenantStatus{
CurrentState: "ready",
},
},
},
want: &models.Tenant{
CreationDate: testTimeStamp.String(),
Name: "tenant1",
CurrentState: "ready",
Namespace: "minio-ns",
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
EnablePrometheus: false,
},
},
{
// If console image is set, it should be returned on tenant info
name: "Get tenant Info, Console image set",
args: args{
minioTenant: &operator.Tenant{
ObjectMeta: metav1.ObjectMeta{
CreationTimestamp: testTimeStamp,
Name: "tenant1",
Namespace: "minio-ns",
Annotations: map[string]string{
prometheusPath: "some/path",
prometheusScrape: "other/path",
},
},
Spec: operator.TenantSpec{
Zones: []operator.Zone{},
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
Console: &operator.ConsoleConfiguration{
Image: "minio/console:master",
},
},
Status: operator.TenantStatus{
CurrentState: "ready",
},
},
},
want: &models.Tenant{
CreationDate: testTimeStamp.String(),
Name: "tenant1",
CurrentState: "ready",
Namespace: "minio-ns",
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
EnablePrometheus: false,
ConsoleImage: "minio/console:master",
},
},
}
@@ -335,12 +462,13 @@ func Test_TenantInfo(t *testing.T) {
func Test_deleteTenantAction(t *testing.T) {
opClient := opClientMock{}
type args struct {
ctx context.Context
operatorClient OperatorClient
operatorClient OperatorClientI
nameSpace string
tenantName string
deletePvcs bool
objs []runtime.Object
mockTenantDelete func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error
}
tests := []struct {
@@ -355,6 +483,7 @@ func Test_deleteTenantAction(t *testing.T) {
operatorClient: opClient,
nameSpace: "default",
tenantName: "minio-tenant",
deletePvcs: false,
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return nil
},
@@ -368,17 +497,155 @@ func Test_deleteTenantAction(t *testing.T) {
operatorClient: opClient,
nameSpace: "default",
tenantName: "minio-tenant",
deletePvcs: false,
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return errors.New("something happened")
},
},
wantErr: true,
},
{
// Delete only PVCs of the defined tenant on the specific namespace
name: "Delete PVCs on Tenant Deletion",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: true,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return nil
},
},
wantErr: false,
},
{
// Do not delete underlying pvcs
name: "Don't Delete PVCs on Tenant Deletion",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: false,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return nil
},
},
wantErr: false,
},
{
// If error is different than NotFound, PVC deletion should not continue
name: "Don't delete pvcs if error Deleting Tenant, return",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: true,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return errors.New("error returned")
},
},
wantErr: true,
},
{
// If error is NotFound while trying to Delete Tenant, PVC deletion should continue
name: "Delete pvcs if tenant not found",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: true,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return k8sErrors.NewNotFound(schema.GroupResource{}, "tenant1")
},
},
wantErr: false,
},
{
// If error is NotFound while trying to Delete Tenant and pvcdeletion=false,
// error should be returned
name: "Don't delete pvcs and return error if tenant not found",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: false,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return k8sErrors.NewNotFound(schema.GroupResource{}, "tenant1")
},
},
wantErr: true,
},
}
for _, tt := range tests {
opClientTenantDeleteMock = tt.args.mockTenantDelete
kubeClient := fake.NewSimpleClientset(tt.args.objs...)
t.Run(tt.name, func(t *testing.T) {
if err := deleteTenantAction(tt.args.ctx, tt.args.operatorClient, tt.args.nameSpace, tt.args.tenantName); (err != nil) != tt.wantErr {
if err := deleteTenantAction(tt.args.ctx, tt.args.operatorClient, kubeClient.CoreV1(), tt.args.nameSpace, tt.args.tenantName, tt.args.deletePvcs); (err != nil) != tt.wantErr {
t.Errorf("deleteTenantAction() error = %v, wantErr %v", err, tt.wantErr)
}
})
@@ -390,7 +657,7 @@ func Test_TenantAddZone(t *testing.T) {
type args struct {
ctx context.Context
operatorClient OperatorClient
operatorClient OperatorClientI
nameSpace string
mockTenantPatch func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*v1.Tenant, error)
mockTenantGet func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*v1.Tenant, error)
@@ -564,7 +831,7 @@ func Test_UpdateTenantAction(t *testing.T) {
type args struct {
ctx context.Context
operatorClient OperatorClient
operatorClient OperatorClientI
httpCl cluster.HTTPClientI
nameSpace string
tenantName string
@@ -729,7 +996,7 @@ func Test_UpdateTenantAction(t *testing.T) {
},
params: admin_api.UpdateTenantParams{
Body: &models.UpdateTenantRequest{
ConsoleImage: "minio/console:v0.3.13",
ConsoleImage: "minio/console:v0.3.26",
},
},
},
@@ -768,7 +1035,7 @@ func Test_UpdateTenantAction(t *testing.T) {
cnsClient := fake.NewSimpleClientset(tt.objs...)
t.Run(tt.name, func(t *testing.T) {
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)
t.Errorf("updateTenantAction() error = %v, wantErr %v", err, tt.wantErr)
}
})
}

View File

@@ -19,7 +19,6 @@ package restapi
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
@@ -36,7 +35,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.AdminAPIListUsersHandler = admin_api.ListUsersHandlerFunc(func(params admin_api.ListUsersParams, session *models.Principal) middleware.Responder {
listUsersResponse, err := getListUsersResponse(session)
if err != nil {
return admin_api.NewListUsersDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewListUsersDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewListUsersOK().WithPayload(listUsersResponse)
})
@@ -44,7 +43,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.AdminAPIAddUserHandler = admin_api.AddUserHandlerFunc(func(params admin_api.AddUserParams, session *models.Principal) middleware.Responder {
userResponse, err := getUserAddResponse(session, params)
if err != nil {
return admin_api.NewAddUserDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewAddUserDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewAddUserCreated().WithPayload(userResponse)
})
@@ -52,7 +51,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.AdminAPIRemoveUserHandler = admin_api.RemoveUserHandlerFunc(func(params admin_api.RemoveUserParams, session *models.Principal) middleware.Responder {
err := getRemoveUserResponse(session, params)
if err != nil {
return admin_api.NewRemoveUserDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewRemoveUserDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewRemoveUserNoContent()
})
@@ -60,7 +59,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.AdminAPIUpdateUserGroupsHandler = admin_api.UpdateUserGroupsHandlerFunc(func(params admin_api.UpdateUserGroupsParams, session *models.Principal) middleware.Responder {
userUpdateResponse, err := getUpdateUserGroupsResponse(session, params)
if err != nil {
return admin_api.NewUpdateUserGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewUpdateUserGroupsDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewUpdateUserGroupsOK().WithPayload(userUpdateResponse)
@@ -69,7 +68,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.AdminAPIGetUserInfoHandler = admin_api.GetUserInfoHandlerFunc(func(params admin_api.GetUserInfoParams, session *models.Principal) middleware.Responder {
userInfoResponse, err := getUserInfoResponse(session, params)
if err != nil {
return admin_api.NewGetUserInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewGetUserInfoDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewGetUserInfoOK().WithPayload(userInfoResponse)
@@ -78,7 +77,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.AdminAPIUpdateUserInfoHandler = admin_api.UpdateUserInfoHandlerFunc(func(params admin_api.UpdateUserInfoParams, session *models.Principal) middleware.Responder {
userUpdateResponse, err := getUpdateUserResponse(session, params)
if err != nil {
return admin_api.NewUpdateUserInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewUpdateUserInfoDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewUpdateUserInfoOK().WithPayload(userUpdateResponse)
@@ -87,7 +86,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.AdminAPIBulkUpdateUsersGroupsHandler = admin_api.BulkUpdateUsersGroupsHandlerFunc(func(params admin_api.BulkUpdateUsersGroupsParams, session *models.Principal) middleware.Responder {
err := getAddUsersListToGroupsResponse(session, params)
if err != nil {
return admin_api.NewBulkUpdateUsersGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewBulkUpdateUsersGroupsDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewBulkUpdateUsersGroupsOK()
@@ -119,12 +118,11 @@ func listUsers(ctx context.Context, client MinioAdmin) ([]*models.User, error) {
}
// getListUsersResponse performs listUsers() and serializes it to the handler's output
func getListUsersResponse(session *models.Principal) (*models.ListUsersResponse, error) {
func getListUsersResponse(session *models.Principal) (*models.ListUsersResponse, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -132,8 +130,7 @@ func getListUsersResponse(session *models.Principal) (*models.ListUsersResponse,
users, err := listUsers(ctx, adminClient)
if err != nil {
log.Println("error listing users:", err)
return nil, err
return nil, prepareError(err)
}
// serialize output
listUsersResponse := &models.ListUsersResponse{
@@ -167,12 +164,11 @@ func addUser(ctx context.Context, client MinioAdmin, accessKey, secretKey *strin
return userRet, nil
}
func getUserAddResponse(session *models.Principal, params admin_api.AddUserParams) (*models.User, error) {
func getUserAddResponse(session *models.Principal, params admin_api.AddUserParams) (*models.User, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -180,8 +176,7 @@ func getUserAddResponse(session *models.Principal, params admin_api.AddUserParam
user, err := addUser(ctx, adminClient, params.Body.AccessKey, params.Body.SecretKey, params.Body.Groups)
if err != nil {
log.Println("error adding user:", err)
return nil, err
return nil, prepareError(err)
}
return user, nil
}
@@ -194,13 +189,12 @@ func removeUser(ctx context.Context, client MinioAdmin, accessKey string) error
return nil
}
func getRemoveUserResponse(session *models.Principal, params admin_api.RemoveUserParams) error {
func getRemoveUserResponse(session *models.Principal, params admin_api.RemoveUserParams) *models.Error {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
return prepareError(err)
}
// create a minioClient interface implementation
@@ -208,8 +202,7 @@ func getRemoveUserResponse(session *models.Principal, params admin_api.RemoveUse
adminClient := adminClient{client: mAdmin}
if err := removeUser(ctx, adminClient, params.Name); err != nil {
log.Println("error removing user:", err)
return err
return prepareError(err)
}
log.Println("User removed successfully:", params.Name)
@@ -226,13 +219,12 @@ func getUserInfo(ctx context.Context, client MinioAdmin, accessKey string) (*mad
return &userInfo, nil
}
func getUserInfoResponse(session *models.Principal, params admin_api.GetUserInfoParams) (*models.User, error) {
func getUserInfoResponse(session *models.Principal, params admin_api.GetUserInfoParams) (*models.User, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
@@ -241,8 +233,7 @@ func getUserInfoResponse(session *models.Principal, params admin_api.GetUserInfo
user, err := getUserInfo(ctx, adminClient, params.Name)
if err != nil {
log.Println("error getting user:", err)
return nil, err
return nil, prepareError(err)
}
userInformation := &models.User{
@@ -341,13 +332,12 @@ func updateUserGroups(ctx context.Context, client MinioAdmin, user string, group
return userReturn, nil
}
func getUpdateUserGroupsResponse(session *models.Principal, params admin_api.UpdateUserGroupsParams) (*models.User, error) {
func getUpdateUserGroupsResponse(session *models.Principal, params admin_api.UpdateUserGroupsParams) (*models.User, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
@@ -357,8 +347,7 @@ func getUpdateUserGroupsResponse(session *models.Principal, params admin_api.Upd
user, err := updateUserGroups(ctx, adminClient, params.Name, params.Body.Groups)
if err != nil {
log.Println("error updating users's groups:", params.Body.Groups)
return nil, err
return nil, prepareError(err)
}
return user, nil
@@ -382,13 +371,12 @@ func setUserStatus(ctx context.Context, client MinioAdmin, user string, status s
return nil
}
func getUpdateUserResponse(session *models.Principal, params admin_api.UpdateUserInfoParams) (*models.User, error) {
func getUpdateUserResponse(session *models.Principal, params admin_api.UpdateUserInfoParams) (*models.User, *models.Error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
@@ -400,14 +388,13 @@ func getUpdateUserResponse(session *models.Principal, params admin_api.UpdateUse
groups := params.Body.Groups
if err := setUserStatus(ctx, adminClient, name, status); err != nil {
log.Println("error updating user status:", status)
return nil, err
return nil, prepareError(err)
}
userElem, errUG := updateUserGroups(ctx, adminClient, name, groups)
if errUG != nil {
return nil, errUG
return nil, prepareError(errUG)
}
return userElem, nil
}
@@ -455,13 +442,12 @@ func addUsersListToGroups(ctx context.Context, client MinioAdmin, usersToUpdate
return nil
}
func getAddUsersListToGroupsResponse(session *models.Principal, params admin_api.BulkUpdateUsersGroupsParams) error {
func getAddUsersListToGroupsResponse(session *models.Principal, params admin_api.BulkUpdateUsersGroupsParams) *models.Error {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
return prepareError(err)
}
// create a minioClient interface implementation
@@ -472,8 +458,7 @@ func getAddUsersListToGroupsResponse(session *models.Principal, params admin_api
groupsList := params.Body.Groups
if err := addUsersListToGroups(ctx, adminClient, usersList, groupsList); err != nil {
log.Println("error updating groups bulk users:", err.Error())
return err
return prepareError(err)
}
return nil

View File

@@ -112,6 +112,10 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
registerTenantHandlers(api)
// Register ResourceQuota handlers
registerResourceQuotaHandlers(api)
// Register Nodes' handlers
registerNodesHandlers(api)
// Register Parity' handlers
registerParityHandlers(api)
api.PreServerShutdown = func() {}

View File

@@ -50,3 +50,11 @@ const (
ConsoleSecureFeaturePolicy = "CONSOLE_SECURE_FEATURE_POLICY"
ConsoleSecureExpectCTHeader = "CONSOLE_SECURE_EXPECT_CT_HEADER"
)
// prometheus annotations
const (
prometheusPath = "prometheus.io/path"
prometheusPort = "prometheus.io/port"
prometheusScrape = "prometheus.io/scrape"
)

File diff suppressed because it is too large Load Diff

100
restapi/error.go Normal file
View File

@@ -0,0 +1,100 @@
package restapi
import (
"errors"
"log"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
)
var (
// Generic error messages
errorGeneric = errors.New("an error occurred, please try again")
errInvalidCredentials = errors.New("invalid Login")
errorGenericInvalidSession = errors.New("invalid session")
errorGenericUnauthorized = errors.New("unauthorized")
errorGenericForbidden = errors.New("forbidden")
errorGenericNotFound = errors.New("not found")
// Explicit error messages
errorInvalidErasureCodingValue = errors.New("invalid Erasure Coding Value")
errorUnableToGetTenantUsage = errors.New("unable to get tenant usage")
errorUnableToUpdateTenantCertificates = errors.New("unable to update tenant certificates")
errorUpdatingEncryptionConfig = errors.New("unable to update encryption configuration")
errBucketBodyNotInRequest = errors.New("error bucket body not in request")
errBucketNameNotInRequest = errors.New("error bucket name not in request")
errGroupBodyNotInRequest = errors.New("error group body not in request")
errGroupNameNotInRequest = errors.New("error group name not in request")
errPolicyNameNotInRequest = errors.New("error policy name not in request")
errPolicyBodyNotInRequest = errors.New("error policy body not in request")
)
// prepareError receives an error object and parse it against k8sErrors, returns the right error code paired with a generic error message
func prepareError(err ...error) *models.Error {
errorCode := int32(500)
errorMessage := errorGeneric.Error()
if len(err) > 0 {
log.Print("original error: ", err[0].Error())
if k8sErrors.IsUnauthorized(err[0]) {
errorCode = 401
errorMessage = errorGenericUnauthorized.Error()
}
if k8sErrors.IsForbidden(err[0]) {
errorCode = 403
errorMessage = errorGenericForbidden.Error()
}
if k8sErrors.IsNotFound(err[0]) {
errorCode = 404
errorMessage = errorGenericNotFound.Error()
}
if errors.Is(err[0], errInvalidCredentials) {
errorCode = 401
errorMessage = errInvalidCredentials.Error()
}
// console invalid erasure coding value
if errors.Is(err[0], errorInvalidErasureCodingValue) {
errorCode = 400
errorMessage = errorInvalidErasureCodingValue.Error()
}
if errors.Is(err[0], errBucketBodyNotInRequest) {
errorCode = 400
errorMessage = errBucketBodyNotInRequest.Error()
}
if errors.Is(err[0], errBucketNameNotInRequest) {
errorCode = 400
errorMessage = errBucketNameNotInRequest.Error()
}
if errors.Is(err[0], errGroupBodyNotInRequest) {
errorCode = 400
errorMessage = errGroupBodyNotInRequest.Error()
}
if errors.Is(err[0], errGroupNameNotInRequest) {
errorCode = 400
errorMessage = errGroupNameNotInRequest.Error()
}
if errors.Is(err[0], errPolicyNameNotInRequest) {
errorCode = 400
errorMessage = errPolicyNameNotInRequest.Error()
}
if errors.Is(err[0], errPolicyBodyNotInRequest) {
errorCode = 400
errorMessage = errPolicyBodyNotInRequest.Error()
}
// console invalid session error
if errors.Is(err[0], errorGenericInvalidSession) {
errorCode = 401
errorMessage = errorGenericInvalidSession.Error()
}
// if we received a second error take that as friendly message but dont override the code
if len(err) > 1 && err[1] != nil {
log.Print("friendly error: ", err[1].Error())
errorMessage = err[1].Error()
}
// if we receive third error we just print that as debugging
if len(err) > 2 && err[2] != nil {
log.Print("debugging error: ", err[2].Error())
}
}
return &models.Error{Code: errorCode, Message: swag.String(errorMessage)}
}

View File

@@ -24,13 +24,16 @@ import (
"k8s.io/client-go/kubernetes"
)
// K8sClient interface with all functions to be implemented
// by mock when testing, it should include all K8sClient respective api calls
// K8sClientI interface with all functions to be implemented
// by mock when testing, it should include all K8sClientI respective api calls
// that are used within this project.
type K8sClient interface {
type K8sClientI interface {
getResourceQuota(ctx context.Context, namespace, resource string, opts metav1.GetOptions) (*v1.ResourceQuota, error)
getSecret(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*v1.Secret, error)
getService(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*v1.Service, error)
deletePodCollection(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
deleteSecret(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error
createSecret(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error)
}
// Interface implementation
@@ -51,3 +54,15 @@ func (c *k8sClient) getSecret(ctx context.Context, namespace, secretName string,
func (c *k8sClient) getService(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*v1.Service, error) {
return c.client.CoreV1().Services(namespace).Get(ctx, serviceName, opts)
}
func (c *k8sClient) deletePodCollection(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return c.client.CoreV1().Pods(namespace).DeleteCollection(ctx, opts, listOpts)
}
func (c *k8sClient) deleteSecret(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return c.client.CoreV1().Secrets(namespace).Delete(ctx, name, opts)
}
func (c *k8sClient) createSecret(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error) {
return c.client.CoreV1().Secrets(namespace).Create(ctx, secret, opts)
}

View File

@@ -50,7 +50,7 @@ func NewDeleteTenant(ctx *middleware.Context, handler DeleteTenantHandler) *Dele
/*DeleteTenant swagger:route DELETE /namespaces/{namespace}/tenants/{tenant} AdminAPI deleteTenant
Delete Tenant
Delete tenant and underlying pvcs
*/
type DeleteTenant struct {

View File

@@ -26,8 +26,11 @@ import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/minio/console/models"
)
// NewDeleteTenantParams creates a new DeleteTenantParams object
@@ -46,6 +49,10 @@ type DeleteTenantParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
In: body
*/
Body *models.DeleteTenantRequest
/*
Required: true
In: path
@@ -67,6 +74,22 @@ func (o *DeleteTenantParams) BindRequest(r *http.Request, route *middleware.Matc
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.DeleteTenantRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
res = append(res, errors.NewParseError("body", "body", "", err))
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
}
rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace")
if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil {
res = append(res, err)

View File

@@ -0,0 +1,90 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// GetMaxAllocatableMemHandlerFunc turns a function with the right signature into a get max allocatable mem handler
type GetMaxAllocatableMemHandlerFunc func(GetMaxAllocatableMemParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn GetMaxAllocatableMemHandlerFunc) Handle(params GetMaxAllocatableMemParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// GetMaxAllocatableMemHandler interface for that can handle valid get max allocatable mem params
type GetMaxAllocatableMemHandler interface {
Handle(GetMaxAllocatableMemParams, *models.Principal) middleware.Responder
}
// NewGetMaxAllocatableMem creates a new http.Handler for the get max allocatable mem operation
func NewGetMaxAllocatableMem(ctx *middleware.Context, handler GetMaxAllocatableMemHandler) *GetMaxAllocatableMem {
return &GetMaxAllocatableMem{Context: ctx, Handler: handler}
}
/*GetMaxAllocatableMem swagger:route GET /cluster/max-allocatable-memory AdminAPI getMaxAllocatableMem
Get maximum allocatable memory for given number of nodes
*/
type GetMaxAllocatableMem struct {
Context *middleware.Context
Handler GetMaxAllocatableMemHandler
}
func (o *GetMaxAllocatableMem) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewGetMaxAllocatableMemParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
r = aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,119 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// NewGetMaxAllocatableMemParams creates a new GetMaxAllocatableMemParams object
// no default values defined in spec.
func NewGetMaxAllocatableMemParams() GetMaxAllocatableMemParams {
return GetMaxAllocatableMemParams{}
}
// GetMaxAllocatableMemParams contains all the bound params for the get max allocatable mem operation
// typically these are obtained from a http.Request
//
// swagger:parameters GetMaxAllocatableMem
type GetMaxAllocatableMemParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
Minimum: 1
In: query
*/
NumNodes int32
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewGetMaxAllocatableMemParams() beforehand.
func (o *GetMaxAllocatableMemParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
qNumNodes, qhkNumNodes, _ := qs.GetOK("num_nodes")
if err := o.bindNumNodes(qNumNodes, qhkNumNodes, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindNumNodes binds and validates parameter NumNodes from query.
func (o *GetMaxAllocatableMemParams) bindNumNodes(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("num_nodes", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("num_nodes", "query", raw); err != nil {
return err
}
value, err := swag.ConvertInt32(raw)
if err != nil {
return errors.InvalidType("num_nodes", "query", "int32", raw)
}
o.NumNodes = value
if err := o.validateNumNodes(formats); err != nil {
return err
}
return nil
}
// validateNumNodes carries on validations for parameter NumNodes
func (o *GetMaxAllocatableMemParams) validateNumNodes(formats strfmt.Registry) error {
if err := validate.MinimumInt("num_nodes", "query", int64(o.NumNodes), 1, false); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,133 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// GetMaxAllocatableMemOKCode is the HTTP code returned for type GetMaxAllocatableMemOK
const GetMaxAllocatableMemOKCode int = 200
/*GetMaxAllocatableMemOK A successful response.
swagger:response getMaxAllocatableMemOK
*/
type GetMaxAllocatableMemOK struct {
/*
In: Body
*/
Payload *models.MaxAllocatableMemResponse `json:"body,omitempty"`
}
// NewGetMaxAllocatableMemOK creates GetMaxAllocatableMemOK with default headers values
func NewGetMaxAllocatableMemOK() *GetMaxAllocatableMemOK {
return &GetMaxAllocatableMemOK{}
}
// WithPayload adds the payload to the get max allocatable mem o k response
func (o *GetMaxAllocatableMemOK) WithPayload(payload *models.MaxAllocatableMemResponse) *GetMaxAllocatableMemOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get max allocatable mem o k response
func (o *GetMaxAllocatableMemOK) SetPayload(payload *models.MaxAllocatableMemResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetMaxAllocatableMemOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*GetMaxAllocatableMemDefault Generic error response.
swagger:response getMaxAllocatableMemDefault
*/
type GetMaxAllocatableMemDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewGetMaxAllocatableMemDefault creates GetMaxAllocatableMemDefault with default headers values
func NewGetMaxAllocatableMemDefault(code int) *GetMaxAllocatableMemDefault {
if code <= 0 {
code = 500
}
return &GetMaxAllocatableMemDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get max allocatable mem default response
func (o *GetMaxAllocatableMemDefault) WithStatusCode(code int) *GetMaxAllocatableMemDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get max allocatable mem default response
func (o *GetMaxAllocatableMemDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get max allocatable mem default response
func (o *GetMaxAllocatableMemDefault) WithPayload(payload *models.Error) *GetMaxAllocatableMemDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get max allocatable mem default response
func (o *GetMaxAllocatableMemDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetMaxAllocatableMemDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,119 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"github.com/go-openapi/swag"
)
// GetMaxAllocatableMemURL generates an URL for the get max allocatable mem operation
type GetMaxAllocatableMemURL struct {
NumNodes int32
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetMaxAllocatableMemURL) WithBasePath(bp string) *GetMaxAllocatableMemURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetMaxAllocatableMemURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *GetMaxAllocatableMemURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/cluster/max-allocatable-memory"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
numNodesQ := swag.FormatInt32(o.NumNodes)
if numNodesQ != "" {
qs.Set("num_nodes", numNodesQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *GetMaxAllocatableMemURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *GetMaxAllocatableMemURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *GetMaxAllocatableMemURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on GetMaxAllocatableMemURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on GetMaxAllocatableMemURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *GetMaxAllocatableMemURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,90 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// GetParityHandlerFunc turns a function with the right signature into a get parity handler
type GetParityHandlerFunc func(GetParityParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn GetParityHandlerFunc) Handle(params GetParityParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// GetParityHandler interface for that can handle valid get parity params
type GetParityHandler interface {
Handle(GetParityParams, *models.Principal) middleware.Responder
}
// NewGetParity creates a new http.Handler for the get parity operation
func NewGetParity(ctx *middleware.Context, handler GetParityHandler) *GetParity {
return &GetParity{Context: ctx, Handler: handler}
}
/*GetParity swagger:route GET /get-parity/{nodes}/{disksPerNode} AdminAPI getParity
Gets parity by sending number of nodes & number of disks
*/
type GetParity struct {
Context *middleware.Context
Handler GetParityHandler
}
func (o *GetParity) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewGetParityParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
r = aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,154 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// NewGetParityParams creates a new GetParityParams object
// no default values defined in spec.
func NewGetParityParams() GetParityParams {
return GetParityParams{}
}
// GetParityParams contains all the bound params for the get parity operation
// typically these are obtained from a http.Request
//
// swagger:parameters GetParity
type GetParityParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
Minimum: 1
In: path
*/
DisksPerNode int64
/*
Required: true
Minimum: 2
In: path
*/
Nodes int64
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewGetParityParams() beforehand.
func (o *GetParityParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
rDisksPerNode, rhkDisksPerNode, _ := route.Params.GetOK("disksPerNode")
if err := o.bindDisksPerNode(rDisksPerNode, rhkDisksPerNode, route.Formats); err != nil {
res = append(res, err)
}
rNodes, rhkNodes, _ := route.Params.GetOK("nodes")
if err := o.bindNodes(rNodes, rhkNodes, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindDisksPerNode binds and validates parameter DisksPerNode from path.
func (o *GetParityParams) bindDisksPerNode(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
value, err := swag.ConvertInt64(raw)
if err != nil {
return errors.InvalidType("disksPerNode", "path", "int64", raw)
}
o.DisksPerNode = value
if err := o.validateDisksPerNode(formats); err != nil {
return err
}
return nil
}
// validateDisksPerNode carries on validations for parameter DisksPerNode
func (o *GetParityParams) validateDisksPerNode(formats strfmt.Registry) error {
if err := validate.MinimumInt("disksPerNode", "path", int64(o.DisksPerNode), 1, false); err != nil {
return err
}
return nil
}
// bindNodes binds and validates parameter Nodes from path.
func (o *GetParityParams) bindNodes(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
value, err := swag.ConvertInt64(raw)
if err != nil {
return errors.InvalidType("nodes", "path", "int64", raw)
}
o.Nodes = value
if err := o.validateNodes(formats); err != nil {
return err
}
return nil
}
// validateNodes carries on validations for parameter Nodes
func (o *GetParityParams) validateNodes(formats strfmt.Registry) error {
if err := validate.MinimumInt("nodes", "path", int64(o.Nodes), 2, false); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,136 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// GetParityOKCode is the HTTP code returned for type GetParityOK
const GetParityOKCode int = 200
/*GetParityOK A successful response.
swagger:response getParityOK
*/
type GetParityOK struct {
/*
In: Body
*/
Payload models.ParityResponse `json:"body,omitempty"`
}
// NewGetParityOK creates GetParityOK with default headers values
func NewGetParityOK() *GetParityOK {
return &GetParityOK{}
}
// WithPayload adds the payload to the get parity o k response
func (o *GetParityOK) WithPayload(payload models.ParityResponse) *GetParityOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get parity o k response
func (o *GetParityOK) SetPayload(payload models.ParityResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetParityOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if payload == nil {
// return empty array
payload = models.ParityResponse{}
}
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
/*GetParityDefault Generic error response.
swagger:response getParityDefault
*/
type GetParityDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewGetParityDefault creates GetParityDefault with default headers values
func NewGetParityDefault(code int) *GetParityDefault {
if code <= 0 {
code = 500
}
return &GetParityDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get parity default response
func (o *GetParityDefault) WithStatusCode(code int) *GetParityDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get parity default response
func (o *GetParityDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get parity default response
func (o *GetParityDefault) WithPayload(payload *models.Error) *GetParityDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get parity default response
func (o *GetParityDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetParityDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,126 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
"github.com/go-openapi/swag"
)
// GetParityURL generates an URL for the get parity operation
type GetParityURL struct {
DisksPerNode int64
Nodes int64
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetParityURL) WithBasePath(bp string) *GetParityURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetParityURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *GetParityURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/get-parity/{nodes}/{disksPerNode}"
disksPerNode := swag.FormatInt64(o.DisksPerNode)
if disksPerNode != "" {
_path = strings.Replace(_path, "{disksPerNode}", disksPerNode, -1)
} else {
return nil, errors.New("disksPerNode is required on GetParityURL")
}
nodes := swag.FormatInt64(o.Nodes)
if nodes != "" {
_path = strings.Replace(_path, "{nodes}", nodes, -1)
} else {
return nil, errors.New("nodes is required on GetParityURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *GetParityURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *GetParityURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *GetParityURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on GetParityURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on GetParityURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *GetParityURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,90 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// TenantUpdateCertificateHandlerFunc turns a function with the right signature into a tenant update certificate handler
type TenantUpdateCertificateHandlerFunc func(TenantUpdateCertificateParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn TenantUpdateCertificateHandlerFunc) Handle(params TenantUpdateCertificateParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// TenantUpdateCertificateHandler interface for that can handle valid tenant update certificate params
type TenantUpdateCertificateHandler interface {
Handle(TenantUpdateCertificateParams, *models.Principal) middleware.Responder
}
// NewTenantUpdateCertificate creates a new http.Handler for the tenant update certificate operation
func NewTenantUpdateCertificate(ctx *middleware.Context, handler TenantUpdateCertificateHandler) *TenantUpdateCertificate {
return &TenantUpdateCertificate{Context: ctx, Handler: handler}
}
/*TenantUpdateCertificate swagger:route PUT /namespaces/{namespace}/tenants/{tenant}/certificates AdminAPI tenantUpdateCertificate
Tenant Update Certificates
*/
type TenantUpdateCertificate struct {
Context *middleware.Context
Handler TenantUpdateCertificateHandler
}
func (o *TenantUpdateCertificate) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewTenantUpdateCertificateParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
r = aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,145 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/minio/console/models"
)
// NewTenantUpdateCertificateParams creates a new TenantUpdateCertificateParams object
// no default values defined in spec.
func NewTenantUpdateCertificateParams() TenantUpdateCertificateParams {
return TenantUpdateCertificateParams{}
}
// TenantUpdateCertificateParams contains all the bound params for the tenant update certificate operation
// typically these are obtained from a http.Request
//
// swagger:parameters TenantUpdateCertificate
type TenantUpdateCertificateParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.TLSConfiguration
/*
Required: true
In: path
*/
Namespace string
/*
Required: true
In: path
*/
Tenant string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewTenantUpdateCertificateParams() beforehand.
func (o *TenantUpdateCertificateParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.TLSConfiguration
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace")
if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil {
res = append(res, err)
}
rTenant, rhkTenant, _ := route.Params.GetOK("tenant")
if err := o.bindTenant(rTenant, rhkTenant, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindNamespace binds and validates parameter Namespace from path.
func (o *TenantUpdateCertificateParams) bindNamespace(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Namespace = raw
return nil
}
// bindTenant binds and validates parameter Tenant from path.
func (o *TenantUpdateCertificateParams) bindTenant(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Tenant = raw
return nil
}

View File

@@ -0,0 +1,113 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// TenantUpdateCertificateCreatedCode is the HTTP code returned for type TenantUpdateCertificateCreated
const TenantUpdateCertificateCreatedCode int = 201
/*TenantUpdateCertificateCreated A successful response.
swagger:response tenantUpdateCertificateCreated
*/
type TenantUpdateCertificateCreated struct {
}
// NewTenantUpdateCertificateCreated creates TenantUpdateCertificateCreated with default headers values
func NewTenantUpdateCertificateCreated() *TenantUpdateCertificateCreated {
return &TenantUpdateCertificateCreated{}
}
// WriteResponse to the client
func (o *TenantUpdateCertificateCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(201)
}
/*TenantUpdateCertificateDefault Generic error response.
swagger:response tenantUpdateCertificateDefault
*/
type TenantUpdateCertificateDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewTenantUpdateCertificateDefault creates TenantUpdateCertificateDefault with default headers values
func NewTenantUpdateCertificateDefault(code int) *TenantUpdateCertificateDefault {
if code <= 0 {
code = 500
}
return &TenantUpdateCertificateDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the tenant update certificate default response
func (o *TenantUpdateCertificateDefault) WithStatusCode(code int) *TenantUpdateCertificateDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the tenant update certificate default response
func (o *TenantUpdateCertificateDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the tenant update certificate default response
func (o *TenantUpdateCertificateDefault) WithPayload(payload *models.Error) *TenantUpdateCertificateDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the tenant update certificate default response
func (o *TenantUpdateCertificateDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *TenantUpdateCertificateDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,124 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// TenantUpdateCertificateURL generates an URL for the tenant update certificate operation
type TenantUpdateCertificateURL struct {
Namespace string
Tenant string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *TenantUpdateCertificateURL) WithBasePath(bp string) *TenantUpdateCertificateURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *TenantUpdateCertificateURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *TenantUpdateCertificateURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/namespaces/{namespace}/tenants/{tenant}/certificates"
namespace := o.Namespace
if namespace != "" {
_path = strings.Replace(_path, "{namespace}", namespace, -1)
} else {
return nil, errors.New("namespace is required on TenantUpdateCertificateURL")
}
tenant := o.Tenant
if tenant != "" {
_path = strings.Replace(_path, "{tenant}", tenant, -1)
} else {
return nil, errors.New("tenant is required on TenantUpdateCertificateURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *TenantUpdateCertificateURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *TenantUpdateCertificateURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *TenantUpdateCertificateURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on TenantUpdateCertificateURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on TenantUpdateCertificateURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *TenantUpdateCertificateURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,90 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// TenantUpdateEncryptionHandlerFunc turns a function with the right signature into a tenant update encryption handler
type TenantUpdateEncryptionHandlerFunc func(TenantUpdateEncryptionParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn TenantUpdateEncryptionHandlerFunc) Handle(params TenantUpdateEncryptionParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// TenantUpdateEncryptionHandler interface for that can handle valid tenant update encryption params
type TenantUpdateEncryptionHandler interface {
Handle(TenantUpdateEncryptionParams, *models.Principal) middleware.Responder
}
// NewTenantUpdateEncryption creates a new http.Handler for the tenant update encryption operation
func NewTenantUpdateEncryption(ctx *middleware.Context, handler TenantUpdateEncryptionHandler) *TenantUpdateEncryption {
return &TenantUpdateEncryption{Context: ctx, Handler: handler}
}
/*TenantUpdateEncryption swagger:route PUT /namespaces/{namespace}/tenants/{tenant}/encryption AdminAPI tenantUpdateEncryption
Tenant Update Encryption
*/
type TenantUpdateEncryption struct {
Context *middleware.Context
Handler TenantUpdateEncryptionHandler
}
func (o *TenantUpdateEncryption) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewTenantUpdateEncryptionParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
r = aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,145 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/minio/console/models"
)
// NewTenantUpdateEncryptionParams creates a new TenantUpdateEncryptionParams object
// no default values defined in spec.
func NewTenantUpdateEncryptionParams() TenantUpdateEncryptionParams {
return TenantUpdateEncryptionParams{}
}
// TenantUpdateEncryptionParams contains all the bound params for the tenant update encryption operation
// typically these are obtained from a http.Request
//
// swagger:parameters TenantUpdateEncryption
type TenantUpdateEncryptionParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.EncryptionConfiguration
/*
Required: true
In: path
*/
Namespace string
/*
Required: true
In: path
*/
Tenant string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewTenantUpdateEncryptionParams() beforehand.
func (o *TenantUpdateEncryptionParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.EncryptionConfiguration
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace")
if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil {
res = append(res, err)
}
rTenant, rhkTenant, _ := route.Params.GetOK("tenant")
if err := o.bindTenant(rTenant, rhkTenant, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindNamespace binds and validates parameter Namespace from path.
func (o *TenantUpdateEncryptionParams) bindNamespace(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Namespace = raw
return nil
}
// bindTenant binds and validates parameter Tenant from path.
func (o *TenantUpdateEncryptionParams) bindTenant(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Tenant = raw
return nil
}

View File

@@ -0,0 +1,113 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// TenantUpdateEncryptionCreatedCode is the HTTP code returned for type TenantUpdateEncryptionCreated
const TenantUpdateEncryptionCreatedCode int = 201
/*TenantUpdateEncryptionCreated A successful response.
swagger:response tenantUpdateEncryptionCreated
*/
type TenantUpdateEncryptionCreated struct {
}
// NewTenantUpdateEncryptionCreated creates TenantUpdateEncryptionCreated with default headers values
func NewTenantUpdateEncryptionCreated() *TenantUpdateEncryptionCreated {
return &TenantUpdateEncryptionCreated{}
}
// WriteResponse to the client
func (o *TenantUpdateEncryptionCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(201)
}
/*TenantUpdateEncryptionDefault Generic error response.
swagger:response tenantUpdateEncryptionDefault
*/
type TenantUpdateEncryptionDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewTenantUpdateEncryptionDefault creates TenantUpdateEncryptionDefault with default headers values
func NewTenantUpdateEncryptionDefault(code int) *TenantUpdateEncryptionDefault {
if code <= 0 {
code = 500
}
return &TenantUpdateEncryptionDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the tenant update encryption default response
func (o *TenantUpdateEncryptionDefault) WithStatusCode(code int) *TenantUpdateEncryptionDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the tenant update encryption default response
func (o *TenantUpdateEncryptionDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the tenant update encryption default response
func (o *TenantUpdateEncryptionDefault) WithPayload(payload *models.Error) *TenantUpdateEncryptionDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the tenant update encryption default response
func (o *TenantUpdateEncryptionDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *TenantUpdateEncryptionDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,124 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// TenantUpdateEncryptionURL generates an URL for the tenant update encryption operation
type TenantUpdateEncryptionURL struct {
Namespace string
Tenant string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *TenantUpdateEncryptionURL) WithBasePath(bp string) *TenantUpdateEncryptionURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *TenantUpdateEncryptionURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *TenantUpdateEncryptionURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/namespaces/{namespace}/tenants/{tenant}/encryption"
namespace := o.Namespace
if namespace != "" {
_path = strings.Replace(_path, "{namespace}", namespace, -1)
} else {
return nil, errors.New("namespace is required on TenantUpdateEncryptionURL")
}
tenant := o.Tenant
if tenant != "" {
_path = strings.Replace(_path, "{tenant}", tenant, -1)
} else {
return nil, errors.New("tenant is required on TenantUpdateEncryptionURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *TenantUpdateEncryptionURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *TenantUpdateEncryptionURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *TenantUpdateEncryptionURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on TenantUpdateEncryptionURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on TenantUpdateEncryptionURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *TenantUpdateEncryptionURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,90 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// TenantUpdateZonesHandlerFunc turns a function with the right signature into a tenant update zones handler
type TenantUpdateZonesHandlerFunc func(TenantUpdateZonesParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn TenantUpdateZonesHandlerFunc) Handle(params TenantUpdateZonesParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// TenantUpdateZonesHandler interface for that can handle valid tenant update zones params
type TenantUpdateZonesHandler interface {
Handle(TenantUpdateZonesParams, *models.Principal) middleware.Responder
}
// NewTenantUpdateZones creates a new http.Handler for the tenant update zones operation
func NewTenantUpdateZones(ctx *middleware.Context, handler TenantUpdateZonesHandler) *TenantUpdateZones {
return &TenantUpdateZones{Context: ctx, Handler: handler}
}
/*TenantUpdateZones swagger:route PUT /namespaces/{namespace}/tenants/{tenant}/zones AdminAPI tenantUpdateZones
Tenant Update Zones
*/
type TenantUpdateZones struct {
Context *middleware.Context
Handler TenantUpdateZonesHandler
}
func (o *TenantUpdateZones) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewTenantUpdateZonesParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
r = aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,145 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/minio/console/models"
)
// NewTenantUpdateZonesParams creates a new TenantUpdateZonesParams object
// no default values defined in spec.
func NewTenantUpdateZonesParams() TenantUpdateZonesParams {
return TenantUpdateZonesParams{}
}
// TenantUpdateZonesParams contains all the bound params for the tenant update zones operation
// typically these are obtained from a http.Request
//
// swagger:parameters TenantUpdateZones
type TenantUpdateZonesParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.ZoneUpdateRequest
/*
Required: true
In: path
*/
Namespace string
/*
Required: true
In: path
*/
Tenant string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewTenantUpdateZonesParams() beforehand.
func (o *TenantUpdateZonesParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.ZoneUpdateRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace")
if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil {
res = append(res, err)
}
rTenant, rhkTenant, _ := route.Params.GetOK("tenant")
if err := o.bindTenant(rTenant, rhkTenant, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindNamespace binds and validates parameter Namespace from path.
func (o *TenantUpdateZonesParams) bindNamespace(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Namespace = raw
return nil
}
// bindTenant binds and validates parameter Tenant from path.
func (o *TenantUpdateZonesParams) bindTenant(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Tenant = raw
return nil
}

View File

@@ -0,0 +1,133 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// TenantUpdateZonesOKCode is the HTTP code returned for type TenantUpdateZonesOK
const TenantUpdateZonesOKCode int = 200
/*TenantUpdateZonesOK A successful response.
swagger:response tenantUpdateZonesOK
*/
type TenantUpdateZonesOK struct {
/*
In: Body
*/
Payload *models.Tenant `json:"body,omitempty"`
}
// NewTenantUpdateZonesOK creates TenantUpdateZonesOK with default headers values
func NewTenantUpdateZonesOK() *TenantUpdateZonesOK {
return &TenantUpdateZonesOK{}
}
// WithPayload adds the payload to the tenant update zones o k response
func (o *TenantUpdateZonesOK) WithPayload(payload *models.Tenant) *TenantUpdateZonesOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the tenant update zones o k response
func (o *TenantUpdateZonesOK) SetPayload(payload *models.Tenant) {
o.Payload = payload
}
// WriteResponse to the client
func (o *TenantUpdateZonesOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*TenantUpdateZonesDefault Generic error response.
swagger:response tenantUpdateZonesDefault
*/
type TenantUpdateZonesDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewTenantUpdateZonesDefault creates TenantUpdateZonesDefault with default headers values
func NewTenantUpdateZonesDefault(code int) *TenantUpdateZonesDefault {
if code <= 0 {
code = 500
}
return &TenantUpdateZonesDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the tenant update zones default response
func (o *TenantUpdateZonesDefault) WithStatusCode(code int) *TenantUpdateZonesDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the tenant update zones default response
func (o *TenantUpdateZonesDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the tenant update zones default response
func (o *TenantUpdateZonesDefault) WithPayload(payload *models.Error) *TenantUpdateZonesDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the tenant update zones default response
func (o *TenantUpdateZonesDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *TenantUpdateZonesDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,124 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// TenantUpdateZonesURL generates an URL for the tenant update zones operation
type TenantUpdateZonesURL struct {
Namespace string
Tenant string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *TenantUpdateZonesURL) WithBasePath(bp string) *TenantUpdateZonesURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *TenantUpdateZonesURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *TenantUpdateZonesURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/namespaces/{namespace}/tenants/{tenant}/zones"
namespace := o.Namespace
if namespace != "" {
_path = strings.Replace(_path, "{namespace}", namespace, -1)
} else {
return nil, errors.New("namespace is required on TenantUpdateZonesURL")
}
tenant := o.Tenant
if tenant != "" {
_path = strings.Replace(_path, "{tenant}", tenant, -1)
} else {
return nil, errors.New("tenant is required on TenantUpdateZonesURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *TenantUpdateZonesURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *TenantUpdateZonesURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *TenantUpdateZonesURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on TenantUpdateZonesURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on TenantUpdateZonesURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *TenantUpdateZonesURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -114,6 +114,12 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPIDeleteTenantHandler: admin_api.DeleteTenantHandlerFunc(func(params admin_api.DeleteTenantParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.DeleteTenant has not yet been implemented")
}),
AdminAPIGetMaxAllocatableMemHandler: admin_api.GetMaxAllocatableMemHandlerFunc(func(params admin_api.GetMaxAllocatableMemParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.GetMaxAllocatableMem has not yet been implemented")
}),
AdminAPIGetParityHandler: admin_api.GetParityHandlerFunc(func(params admin_api.GetParityParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.GetParity has not yet been implemented")
}),
AdminAPIGetResourceQuotaHandler: admin_api.GetResourceQuotaHandlerFunc(func(params admin_api.GetResourceQuotaParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.GetResourceQuota has not yet been implemented")
}),
@@ -210,6 +216,15 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPITenantInfoHandler: admin_api.TenantInfoHandlerFunc(func(params admin_api.TenantInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.TenantInfo has not yet been implemented")
}),
AdminAPITenantUpdateCertificateHandler: admin_api.TenantUpdateCertificateHandlerFunc(func(params admin_api.TenantUpdateCertificateParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.TenantUpdateCertificate has not yet been implemented")
}),
AdminAPITenantUpdateEncryptionHandler: admin_api.TenantUpdateEncryptionHandlerFunc(func(params admin_api.TenantUpdateEncryptionParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.TenantUpdateEncryption has not yet been implemented")
}),
AdminAPITenantUpdateZonesHandler: admin_api.TenantUpdateZonesHandlerFunc(func(params admin_api.TenantUpdateZonesParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.TenantUpdateZones has not yet been implemented")
}),
AdminAPIUpdateGroupHandler: admin_api.UpdateGroupHandlerFunc(func(params admin_api.UpdateGroupParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.UpdateGroup has not yet been implemented")
}),
@@ -305,6 +320,10 @@ type ConsoleAPI struct {
UserAPIDeleteServiceAccountHandler user_api.DeleteServiceAccountHandler
// AdminAPIDeleteTenantHandler sets the operation handler for the delete tenant operation
AdminAPIDeleteTenantHandler admin_api.DeleteTenantHandler
// AdminAPIGetMaxAllocatableMemHandler sets the operation handler for the get max allocatable mem operation
AdminAPIGetMaxAllocatableMemHandler admin_api.GetMaxAllocatableMemHandler
// AdminAPIGetParityHandler sets the operation handler for the get parity operation
AdminAPIGetParityHandler admin_api.GetParityHandler
// AdminAPIGetResourceQuotaHandler sets the operation handler for the get resource quota operation
AdminAPIGetResourceQuotaHandler admin_api.GetResourceQuotaHandler
// AdminAPIGetTenantUsageHandler sets the operation handler for the get tenant usage operation
@@ -369,6 +388,12 @@ type ConsoleAPI struct {
AdminAPITenantAddZoneHandler admin_api.TenantAddZoneHandler
// AdminAPITenantInfoHandler sets the operation handler for the tenant info operation
AdminAPITenantInfoHandler admin_api.TenantInfoHandler
// AdminAPITenantUpdateCertificateHandler sets the operation handler for the tenant update certificate operation
AdminAPITenantUpdateCertificateHandler admin_api.TenantUpdateCertificateHandler
// AdminAPITenantUpdateEncryptionHandler sets the operation handler for the tenant update encryption operation
AdminAPITenantUpdateEncryptionHandler admin_api.TenantUpdateEncryptionHandler
// AdminAPITenantUpdateZonesHandler sets the operation handler for the tenant update zones operation
AdminAPITenantUpdateZonesHandler admin_api.TenantUpdateZonesHandler
// AdminAPIUpdateGroupHandler sets the operation handler for the update group operation
AdminAPIUpdateGroupHandler admin_api.UpdateGroupHandler
// AdminAPIUpdateTenantHandler sets the operation handler for the update tenant operation
@@ -501,6 +526,12 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPIDeleteTenantHandler == nil {
unregistered = append(unregistered, "admin_api.DeleteTenantHandler")
}
if o.AdminAPIGetMaxAllocatableMemHandler == nil {
unregistered = append(unregistered, "admin_api.GetMaxAllocatableMemHandler")
}
if o.AdminAPIGetParityHandler == nil {
unregistered = append(unregistered, "admin_api.GetParityHandler")
}
if o.AdminAPIGetResourceQuotaHandler == nil {
unregistered = append(unregistered, "admin_api.GetResourceQuotaHandler")
}
@@ -597,6 +628,15 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPITenantInfoHandler == nil {
unregistered = append(unregistered, "admin_api.TenantInfoHandler")
}
if o.AdminAPITenantUpdateCertificateHandler == nil {
unregistered = append(unregistered, "admin_api.TenantUpdateCertificateHandler")
}
if o.AdminAPITenantUpdateEncryptionHandler == nil {
unregistered = append(unregistered, "admin_api.TenantUpdateEncryptionHandler")
}
if o.AdminAPITenantUpdateZonesHandler == nil {
unregistered = append(unregistered, "admin_api.TenantUpdateZonesHandler")
}
if o.AdminAPIUpdateGroupHandler == nil {
unregistered = append(unregistered, "admin_api.UpdateGroupHandler")
}
@@ -780,6 +820,14 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/cluster/max-allocatable-memory"] = admin_api.NewGetMaxAllocatableMem(o.context, o.AdminAPIGetMaxAllocatableMemHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/get-parity/{nodes}/{disksPerNode}"] = admin_api.NewGetParity(o.context, o.AdminAPIGetParityHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/namespaces/{namespace}/resourcequotas/{resource-quota-name}"] = admin_api.NewGetResourceQuota(o.context, o.AdminAPIGetResourceQuotaHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
@@ -908,6 +956,18 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/namespaces/{namespace}/tenants/{tenant}/certificates"] = admin_api.NewTenantUpdateCertificate(o.context, o.AdminAPITenantUpdateCertificateHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/namespaces/{namespace}/tenants/{tenant}/encryption"] = admin_api.NewTenantUpdateEncryption(o.context, o.AdminAPITenantUpdateEncryptionHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/namespaces/{namespace}/tenants/{tenant}/zones"] = admin_api.NewTenantUpdateZones(o.context, o.AdminAPITenantUpdateZonesHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/groups/{name}"] = admin_api.NewUpdateGroup(o.context, o.AdminAPIUpdateGroupHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)

View File

@@ -25,10 +25,10 @@ import (
types "k8s.io/apimachinery/pkg/types"
)
// OperatorClient interface with all functions to be implemented
// by mock when testing, it should include all OperatorClient respective api calls
// OperatorClientI interface with all functions to be implemented
// by mock when testing, it should include all OperatorClientI respective api calls
// that are used within this project.
type OperatorClient interface {
type OperatorClientI interface {
TenantDelete(ctx context.Context, namespace string, instanceName string, options metav1.DeleteOptions) error
TenantGet(ctx context.Context, namespace string, instanceName string, options metav1.GetOptions) (*v1.Tenant, error)
TenantPatch(ctx context.Context, namespace string, instanceName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*v1.Tenant, error)

View File

@@ -18,12 +18,10 @@ package restapi
import (
"context"
"log"
"github.com/minio/console/cluster"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
@@ -35,14 +33,14 @@ func registerResourceQuotaHandlers(api *operations.ConsoleAPI) {
api.AdminAPIGetResourceQuotaHandler = admin_api.GetResourceQuotaHandlerFunc(func(params admin_api.GetResourceQuotaParams, session *models.Principal) middleware.Responder {
resp, err := getResourceQuotaResponse(session, params)
if err != nil {
return admin_api.NewGetResourceQuotaDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return admin_api.NewGetResourceQuotaDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewGetResourceQuotaOK().WithPayload(resp)
})
}
func getResourceQuota(ctx context.Context, client K8sClient, namespace, resourcequota string) (*models.ResourceQuota, error) {
func getResourceQuota(ctx context.Context, client K8sClientI, namespace, resourcequota string) (*models.ResourceQuota, error) {
resourceQuota, err := client.getResourceQuota(ctx, namespace, resourcequota, metav1.GetOptions{})
if err != nil {
return nil, err
@@ -68,20 +66,18 @@ func getResourceQuota(ctx context.Context, client K8sClient, namespace, resource
return &rq, nil
}
func getResourceQuotaResponse(session *models.Principal, params admin_api.GetResourceQuotaParams) (*models.ResourceQuota, error) {
func getResourceQuotaResponse(session *models.Principal, params admin_api.GetResourceQuotaParams) (*models.ResourceQuota, *models.Error) {
ctx := context.Background()
client, err := cluster.K8sClient(session.SessionToken)
if err != nil {
log.Println("error getting k8sClient:", err)
return nil, err
return nil, prepareError(err)
}
k8sClient := &k8sClient{
client: client,
}
resourceQuota, err := getResourceQuota(ctx, k8sClient, params.Namespace, params.ResourceQuotaName)
if err != nil {
log.Println("error getting resource quota:", err)
return nil, err
return nil, prepareError(err)
}
return resourceQuota, nil

View File

@@ -48,7 +48,7 @@ func Test_ResourceQuota(t *testing.T) {
kClient := k8sClientMock{}
type args struct {
ctx context.Context
client K8sClient
client K8sClientI
}
tests := []struct {
name string

View File

@@ -27,9 +27,14 @@ import (
)
func getCertPool() *x509.CertPool {
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
// In some systems (like Windows) system cert pool is
// not supported or no certificates are present on the
// system - so we create a new cert pool.
rootCAs = x509.NewCertPool()
}
caCertFileNames := getMinioServerTLSRootCAs()
// If CAs certificates are configured we save them to the http.Client RootCAs store
certs := x509.NewCertPool()
for _, caCert := range caCertFileNames {
pemData, err := ioutil.ReadFile(caCert)
if err != nil {
@@ -37,9 +42,9 @@ func getCertPool() *x509.CertPool {
log.Println(err)
continue
}
certs.AppendCertsFromPEM(pemData)
rootCAs.AppendCertsFromPEM(pemData)
}
return certs
return rootCAs
}
var certPool = getCertPool()

View File

@@ -20,11 +20,9 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
@@ -39,21 +37,21 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
api.UserAPIListBucketsHandler = user_api.ListBucketsHandlerFunc(func(params user_api.ListBucketsParams, session *models.Principal) middleware.Responder {
listBucketsResponse, err := getListBucketsResponse(session)
if err != nil {
return user_api.NewListBucketsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return user_api.NewListBucketsDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewListBucketsOK().WithPayload(listBucketsResponse)
})
// make bucket
api.UserAPIMakeBucketHandler = user_api.MakeBucketHandlerFunc(func(params user_api.MakeBucketParams, session *models.Principal) middleware.Responder {
if err := getMakeBucketResponse(session, params.Body); err != nil {
return user_api.NewMakeBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return user_api.NewMakeBucketDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewMakeBucketCreated()
})
// delete bucket
api.UserAPIDeleteBucketHandler = user_api.DeleteBucketHandlerFunc(func(params user_api.DeleteBucketParams, session *models.Principal) middleware.Responder {
if err := getDeleteBucketResponse(session, params); err != nil {
return user_api.NewMakeBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return user_api.NewMakeBucketDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewDeleteBucketNoContent()
@@ -62,7 +60,7 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
api.UserAPIBucketInfoHandler = user_api.BucketInfoHandlerFunc(func(params user_api.BucketInfoParams, session *models.Principal) middleware.Responder {
bucketInfoResp, err := getBucketInfoResponse(session, params)
if err != nil {
return user_api.NewBucketInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return user_api.NewBucketInfoDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewBucketInfoOK().WithPayload(bucketInfoResp)
@@ -71,7 +69,7 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
api.UserAPIBucketSetPolicyHandler = user_api.BucketSetPolicyHandlerFunc(func(params user_api.BucketSetPolicyParams, session *models.Principal) middleware.Responder {
bucketSetPolicyResp, err := getBucketSetPolicyResponse(session, params.Name, params.Body)
if err != nil {
return user_api.NewBucketSetPolicyDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return user_api.NewBucketSetPolicyDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewBucketSetPolicyOK().WithPayload(bucketSetPolicyResp)
})
@@ -92,22 +90,20 @@ func getaAcountUsageInfo(ctx context.Context, client MinioAdmin) ([]*models.Buck
}
// getListBucketsResponse performs listBuckets() and serializes it to the handler's output
func getListBucketsResponse(session *models.Principal) (*models.ListBucketsResponse, error) {
func getListBucketsResponse(session *models.Principal) (*models.ListBucketsResponse, *models.Error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
buckets, err := getaAcountUsageInfo(ctx, adminClient)
if err != nil {
log.Println("error accountingUsageInfo:", err)
return nil, err
return nil, prepareError(err)
}
// serialize output
@@ -128,26 +124,23 @@ func makeBucket(ctx context.Context, client MinioClient, bucketName string) erro
}
// getMakeBucketResponse performs makeBucket() to create a bucket with its access policy
func getMakeBucketResponse(session *models.Principal, br *models.MakeBucketRequest) error {
func getMakeBucketResponse(session *models.Principal, br *models.MakeBucketRequest) *models.Error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
// bucket request needed to proceed
if br == nil {
log.Println("error bucket body not in request")
return errors.New(500, "error bucket body not in request")
return prepareError(errBucketBodyNotInRequest)
}
mClient, err := newMinioClient(session)
if err != nil {
log.Println("error creating MinIO Client:", err)
return err
return prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
if err := makeBucket(ctx, minioClient, *br.Name); err != nil {
log.Println("error making bucket:", err)
return err
return prepareError(err)
}
return nil
}
@@ -182,14 +175,13 @@ func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName s
// getBucketSetPolicyResponse calls setBucketAccessPolicy() to set a access policy to a bucket
// and returns the serialized output.
func getBucketSetPolicyResponse(session *models.Principal, bucketName string, req *models.SetBucketPolicyRequest) (*models.Bucket, error) {
func getBucketSetPolicyResponse(session *models.Principal, bucketName string, req *models.SetBucketPolicyRequest) (*models.Bucket, *models.Error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
log.Println("error creating MinIO Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -197,14 +189,12 @@ func getBucketSetPolicyResponse(session *models.Principal, bucketName string, re
// set bucket access policy
if err := setBucketAccessPolicy(ctx, minioClient, bucketName, req.Access); err != nil {
log.Println("error setting bucket access policy:", err)
return nil, err
return nil, prepareError(err)
}
// get updated bucket details and return it
bucket, err := getBucketInfo(minioClient, bucketName)
if err != nil {
log.Println("error getting bucket's info:", err)
return nil, err
return nil, prepareError(err)
}
return bucket, nil
}
@@ -215,23 +205,23 @@ func removeBucket(client MinioClient, bucketName string) error {
}
// getDeleteBucketResponse performs removeBucket() to delete a bucket
func getDeleteBucketResponse(session *models.Principal, params user_api.DeleteBucketParams) error {
func getDeleteBucketResponse(session *models.Principal, params user_api.DeleteBucketParams) *models.Error {
if params.Name == "" {
log.Println("error bucket name not in request")
return errors.New(500, "error bucket name not in request")
return prepareError(errBucketNameNotInRequest)
}
bucketName := params.Name
mClient, err := newMinioClient(session)
if err != nil {
log.Println("error creating MinIO Client:", err)
return err
return prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
return removeBucket(minioClient, bucketName)
if err := removeBucket(minioClient, bucketName); err != nil {
return prepareError(err)
}
return nil
}
// getBucketInfo return bucket information including name, policy access, size and creation date
@@ -264,11 +254,10 @@ func getBucketInfo(client MinioClient, bucketName string) (*models.Bucket, error
}
// getBucketInfoResponse calls getBucketInfo() to get the bucket's info
func getBucketInfoResponse(session *models.Principal, params user_api.BucketInfoParams) (*models.Bucket, error) {
func getBucketInfoResponse(session *models.Principal, params user_api.BucketInfoParams) (*models.Bucket, *models.Error) {
mClient, err := newMinioClient(session)
if err != nil {
log.Println("error creating MinIO Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -276,8 +265,7 @@ func getBucketInfoResponse(session *models.Principal, params user_api.BucketInfo
bucket, err := getBucketInfo(minioClient, params.Name)
if err != nil {
log.Println("error getting bucket's info:", err)
return nil, err
return nil, prepareError(err)
}
return bucket, nil

View File

@@ -18,7 +18,6 @@ package restapi
import (
"context"
"log"
"strings"
"github.com/go-openapi/runtime/middleware"
@@ -34,21 +33,21 @@ func registerBucketEventsHandlers(api *operations.ConsoleAPI) {
api.UserAPIListBucketEventsHandler = user_api.ListBucketEventsHandlerFunc(func(params user_api.ListBucketEventsParams, session *models.Principal) middleware.Responder {
listBucketEventsResponse, err := getListBucketEventsResponse(session, params)
if err != nil {
return user_api.NewListBucketEventsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return user_api.NewListBucketEventsDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewListBucketEventsOK().WithPayload(listBucketEventsResponse)
})
// create bucket event
api.UserAPICreateBucketEventHandler = user_api.CreateBucketEventHandlerFunc(func(params user_api.CreateBucketEventParams, session *models.Principal) middleware.Responder {
if err := getCreateBucketEventsResponse(session, params.BucketName, params.Body); err != nil {
return user_api.NewCreateBucketEventDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return user_api.NewCreateBucketEventDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewCreateBucketEventCreated()
})
// delete bucket event
api.UserAPIDeleteBucketEventHandler = user_api.DeleteBucketEventHandlerFunc(func(params user_api.DeleteBucketEventParams, session *models.Principal) middleware.Responder {
if err := getDeleteBucketEventsResponse(session, params.BucketName, params.Arn, params.Body.Events, params.Body.Prefix, params.Body.Suffix); err != nil {
return user_api.NewDeleteBucketEventDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return user_api.NewDeleteBucketEventDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewDeleteBucketEventNoContent()
})
@@ -125,11 +124,10 @@ func listBucketEvents(client MinioClient, bucketName string) ([]*models.Notifica
}
// getListBucketsResponse performs listBucketEvents() and serializes it to the handler's output
func getListBucketEventsResponse(session *models.Principal, params user_api.ListBucketEventsParams) (*models.ListBucketEventsResponse, error) {
func getListBucketEventsResponse(session *models.Principal, params user_api.ListBucketEventsParams) (*models.ListBucketEventsResponse, *models.Error) {
mClient, err := newMinioClient(session)
if err != nil {
log.Println("error creating MinIO Client:", err)
return nil, err
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -137,8 +135,7 @@ func getListBucketEventsResponse(session *models.Principal, params user_api.List
bucketEvents, err := listBucketEvents(minioClient, params.BucketName)
if err != nil {
log.Println("error listing bucket events:", err)
return nil, err
return nil, prepareError(err)
}
// serialize output
listBucketsResponse := &models.ListBucketEventsResponse{
@@ -178,20 +175,18 @@ func createBucketEvent(ctx context.Context, client MCClient, arn string, notific
}
// getCreateBucketEventsResponse calls createBucketEvent to add a bucket event notification
func getCreateBucketEventsResponse(session *models.Principal, bucketName string, eventReq *models.BucketEventRequest) error {
func getCreateBucketEventsResponse(session *models.Principal, bucketName string, eventReq *models.BucketEventRequest) *models.Error {
ctx := context.Background()
s3Client, err := newS3BucketClient(session, bucketName)
if err != nil {
log.Println("error creating S3Client:", err)
return err
return prepareError(err)
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
err = createBucketEvent(ctx, mcClient, *eventReq.Configuration.Arn, eventReq.Configuration.Events, eventReq.Configuration.Prefix, eventReq.Configuration.Suffix, eventReq.IgnoreExisting)
if err != nil {
log.Println("error creating bucket event:", err)
return err
return prepareError(err)
}
return nil
}
@@ -215,20 +210,18 @@ func joinNotificationEvents(events []models.NotificationEventType) string {
}
// getDeleteBucketEventsResponse calls deleteBucketEventNotification() to delete a bucket event notification
func getDeleteBucketEventsResponse(session *models.Principal, bucketName string, arn string, events []models.NotificationEventType, prefix, suffix *string) error {
func getDeleteBucketEventsResponse(session *models.Principal, bucketName string, arn string, events []models.NotificationEventType, prefix, suffix *string) *models.Error {
ctx := context.Background()
s3Client, err := newS3BucketClient(session, bucketName)
if err != nil {
log.Println("error creating S3Client:", err)
return err
return prepareError(err)
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
err = deleteBucketEventNotification(ctx, mcClient, arn, events, prefix, suffix)
if err != nil {
log.Println("error deleting bucket event:", err)
return err
return prepareError(err)
}
return nil
}

View File

@@ -18,11 +18,9 @@ package restapi
import (
"context"
"errors"
"log"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/pkg/acl"
"github.com/minio/console/pkg/auth"
@@ -32,17 +30,12 @@ import (
"github.com/minio/console/restapi/operations/user_api"
)
var (
errorGeneric = errors.New("an error occurred, please try again")
errInvalidCredentials = errors.New("invalid Login")
)
func registerLoginHandlers(api *operations.ConsoleAPI) {
// get login strategy
api.UserAPILoginDetailHandler = user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
loginDetails, err := getLoginDetailsResponse()
if err != nil {
return user_api.NewLoginDetailDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return user_api.NewLoginDetailDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewLoginDetailOK().WithPayload(loginDetails)
})
@@ -50,21 +43,21 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
api.UserAPILoginHandler = user_api.LoginHandlerFunc(func(params user_api.LoginParams) middleware.Responder {
loginResponse, err := getLoginResponse(params.Body)
if err != nil {
return user_api.NewLoginDefault(401).WithPayload(&models.Error{Code: 401, Message: swag.String(err.Error())})
return user_api.NewLoginDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewLoginCreated().WithPayload(loginResponse)
})
api.UserAPILoginOauth2AuthHandler = user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params.Body)
if err != nil {
return user_api.NewLoginOauth2AuthDefault(401).WithPayload(&models.Error{Code: 401, Message: swag.String(err.Error())})
return user_api.NewLoginOauth2AuthDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewLoginOauth2AuthCreated().WithPayload(loginResponse)
})
api.UserAPILoginOperatorHandler = user_api.LoginOperatorHandlerFunc(func(params user_api.LoginOperatorParams) middleware.Responder {
loginResponse, err := getLoginOperatorResponse(params.Body)
if err != nil {
return user_api.NewLoginOperatorDefault(401).WithPayload(&models.Error{Code: 401, Message: swag.String(err.Error())})
return user_api.NewLoginOperatorDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewLoginOperatorCreated().WithPayload(loginResponse)
})
@@ -76,8 +69,7 @@ func login(credentials ConsoleCredentials, actions []string) (*string, error) {
// try to obtain consoleCredentials,
tokens, err := credentials.Get()
if err != nil {
log.Println("error authenticating user", err)
return nil, errInvalidCredentials
return nil, err
}
// if we made it here, the consoleCredentials work, generate a jwt with claims
jwt, err := auth.NewEncryptedTokenForClient(&tokens, actions)
@@ -103,32 +95,29 @@ func getConfiguredRegionForLogin(client MinioAdmin) (string, error) {
}
// getLoginResponse performs login() and serializes it to the handler's output
func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, error) {
func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, *models.Error) {
ctx := context.Background()
mAdmin, err := newSuperMAdminClient()
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, errorGeneric
return nil, prepareError(err)
}
adminClient := adminClient{client: mAdmin}
// obtain the configured MinIO region
// need it for user authentication
location, err := getConfiguredRegionForLogin(adminClient)
if err != nil {
return nil, err
return nil, prepareError(err)
}
creds, err := newConsoleCredentials(*lr.AccessKey, *lr.SecretKey, location)
if err != nil {
log.Println("error login:", err)
return nil, errInvalidCredentials
return nil, prepareError(err)
}
credentials := consoleCredentials{consoleCredentials: creds}
// obtain the current policy assigned to this user
// necessary for generating the list of allowed endpoints
userInfo, err := adminClient.getUserInfo(ctx, *lr.AccessKey)
if err != nil {
log.Println("error login:", err)
return nil, errInvalidCredentials
return nil, prepareError(err)
}
policy, _ := adminClient.getPolicy(ctx, userInfo.PolicyName)
// by default every user starts with an empty array of available actions
@@ -141,7 +130,7 @@ func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, error) {
}
sessionID, err := login(credentials, actions)
if err != nil {
return nil, err
return nil, prepareError(errInvalidCredentials, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{
@@ -151,7 +140,7 @@ func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, error) {
}
// getLoginDetailsResponse returns information regarding the Console authentication mechanism.
func getLoginDetailsResponse() (*models.LoginDetails, error) {
func getLoginDetailsResponse() (*models.LoginDetails, *models.Error) {
ctx := context.Background()
loginStrategy := models.LoginDetailsLoginStrategyForm
redirectURL := ""
@@ -162,8 +151,7 @@ func getLoginDetailsResponse() (*models.LoginDetails, error) {
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil)
if err != nil {
log.Println("error getting new oauth2 provider client", err)
return nil, errorGeneric
return nil, prepareError(err)
}
// Validate user against IDP
identityProvider := &auth.IdentityProvider{Client: oauth2Client}
@@ -180,31 +168,29 @@ func loginOauth2Auth(ctx context.Context, provider *auth.IdentityProvider, code,
userIdentity, err := provider.VerifyIdentity(ctx, code, state)
if err != nil {
log.Println("error validating user identity against idp:", err)
return nil, errorGeneric
return nil, errInvalidCredentials
}
return userIdentity, nil
}
func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.LoginResponse, error) {
func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.LoginResponse, *models.Error) {
ctx := context.Background()
if oauth2.IsIdpEnabled() {
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil)
if err != nil {
log.Println("error getting new oauth2 client:", err)
return nil, errorGeneric
return nil, prepareError(err)
}
// initialize new identity provider
identityProvider := &auth.IdentityProvider{Client: oauth2Client}
// Validate user against IDP
identity, err := loginOauth2Auth(ctx, identityProvider, *lr.Code, *lr.State)
if err != nil {
return nil, err
return nil, prepareError(errInvalidCredentials, nil, err)
}
mAdmin, err := newSuperMAdminClient()
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, errorGeneric
return nil, prepareError(err)
}
adminClient := adminClient{client: mAdmin}
accessKey := identity.Email
@@ -213,12 +199,11 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
// need it for user authentication
location, err := getConfiguredRegionForLogin(adminClient)
if err != nil {
return nil, err
return nil, prepareError(err)
}
// create user in MinIO
if _, err := addUser(ctx, adminClient, &accessKey, &secretKey, []string{}); err != nil {
log.Println("error adding user:", err)
return nil, errorGeneric
return nil, prepareError(err)
}
// rollback user if there's an error after this point
defer func() {
@@ -231,26 +216,23 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
// assign the "consoleAdmin" policy to this user
policyName := oauth2.GetIDPPolicyForUser()
if err := setPolicy(ctx, adminClient, policyName, accessKey, models.PolicyEntityUser); err != nil {
log.Println("error setting policy:", err)
return nil, errorGeneric
return nil, prepareError(err)
}
// obtain the current policy details, necessary for generating the list of allowed endpoints
policy, err := adminClient.getPolicy(ctx, policyName)
if err != nil {
log.Println("error reading policy:", err)
return nil, errorGeneric
return nil, prepareError(err)
}
actions := acl.GetActionsStringFromPolicy(policy)
// User was created correctly, create a new session/JWT
creds, err := newConsoleCredentials(accessKey, secretKey, location)
if err != nil {
log.Println("error login:", err)
return nil, errorGeneric
return nil, prepareError(err)
}
credentials := consoleCredentials{consoleCredentials: creds}
jwt, err := login(credentials, actions)
if err != nil {
return nil, err
return nil, prepareError(errInvalidCredentials, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{
@@ -258,21 +240,20 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
}
return loginResponse, nil
}
return nil, errorGeneric
return nil, prepareError(errorGeneric)
}
// getLoginOperatorResponse validate the provided service account token against k8s api
func getLoginOperatorResponse(lmr *models.LoginOperatorRequest) (*models.LoginResponse, error) {
func getLoginOperatorResponse(lmr *models.LoginOperatorRequest) (*models.LoginResponse, *models.Error) {
creds, err := newConsoleCredentials("", *lmr.Jwt, "")
if err != nil {
log.Println("error login:", err)
return nil, errInvalidCredentials
return nil, prepareError(err)
}
credentials := consoleCredentials{consoleCredentials: creds}
var actions []string
jwt, err := login(credentials, actions)
if err != nil {
return nil, err
return nil, prepareError(errInvalidCredentials, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{

Some files were not shown because too many files have changed in this diff Show More