Compare commits

...

84 Commits

Author SHA1 Message Date
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
Minio Trusted
9f005b7537 update version v0.3.13 2020-08-11 22:30:18 -07:00
Daniel Valdivia
1ad6e977f2 Tolerate DL MinIO unreachable (#246) 2020-08-11 22:29:33 -07:00
Minio Trusted
e9a64c5479 update to v0.3.12 2020-08-11 21:15:37 -07:00
Daniel Valdivia
a2e7259ccb Allow to Specify the Tenant Console Image. Support Image Pull Secrets… (#245)
* Allow to Specify the Tenant Console Image. Support Image Pull Secrets by Name.

This PR adds support for `console_image` on create tenant and update tenant so the console image can be set by the caller. This is in case the image used is hosted in a private registry.

Also adds support to specify the Image Pull Secret, if it's not specified, the individual image registry credentials can still be specified.

* Add tests for new fields.
2020-08-11 18:20:43 -07:00
Lenin Alevski
d28e66a353 prepareSTSClientTransport tls function refactor (#244)
- Reading root ca certificates operation will run only once after Console
starts, reduce the chance of panics happening during runtime
- Fixed bug in which tls.config insecureSkipVerification configuration
  could get overrided after variable reasignation
2020-08-11 11:32:44 -07:00
Minio Trusted
e0ff6623bb update to version v0.3.11 2020-08-09 19:39:46 -07:00
Lenin Alevski
3d59e9ac30 fix npe for tls console/minio (#243) 2020-08-09 17:19:39 -07:00
Lenin Alevski
cff712f071 rename SSL to TLS in labels, env variables and normal variables/constants (#242) 2020-08-09 16:08:58 -07:00
Minio Trusted
b8bca9d2fe update version to v0.3.10 2020-08-09 14:48:42 -07:00
Lenin Alevski
a6ccae52d2 Enable user provided certificates for Console (#239)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-08-09 14:47:06 -07:00
Daniel Valdivia
bdfa6dc9bf Support Usage API talk to MinIO over TLS with Insecure (#241)
* Support Usage API talk to MinIO over TLS with Insecure

Right now if MinIO is running  with TLS, and the certificate is not trusted by console, we fail usage requests. We need to leverage the support for insecure connections so we can read Health Checks and Usage information.

* Remove unusd import
2020-08-09 14:36:55 -07:00
Lenin Alevski
6eb5731eb5 Upgrade Minio and MC versions (#240)
- Minio: RELEASE.2020-08-08T04-50-06Z
- Mc: RELEASE.2020-08-08T02-33-58Z
2020-08-08 16:32:30 -07:00
Minio Trusted
953574f7a3 update version to v0.3.9 2020-08-07 20:29:26 -07:00
Lenin Alevski
8ec6d695de APIs to define mTLS configuration for KES (#235)
Adding support for user to define KES mTLS configuration for Vault and
Gemalto
2020-08-07 20:23:03 -07:00
Cesar N
47274817fa Allow tolerationSeconds to be empty on Zone tolerations Requests (#238)
Since toleration seconds can be empty, we were forcing it to be an integer defaulting to 0 which
was creating a toleration with value 0 when value should have been nil.
2020-08-07 20:00:16 -07:00
Daniel Valdivia
3b123c6182 Fix EC bug (#237) 2020-08-07 12:28:46 -07:00
Lenin Alevski
d7f72e0c41 update kes dependency to v0.11.0 (#236) 2020-08-06 12:43:04 -07:00
Minio Trusted
c0bf9c5da8 update version to v0.3.8 2020-08-05 12:38:13 -07:00
Daniel Valdivia
16a6524b11 Pass Annotations to PVC (#233) 2020-08-05 12:35:41 -07:00
Minio Trusted
c1963c6122 update to v0.3.7 2020-08-05 11:06:25 -07:00
Cesar N
73154e8dd7 Add missing field on Tenant Creation (#232) 2020-08-05 01:21:35 -07:00
Daniel Valdivia
e2e8cbe46c Erasure Coding Parity (#231) 2020-08-04 22:32:41 -07:00
Cesar N
b9b776c278 Add ImageRegistry field to Tenant Create and Tenant Update (#230) 2020-08-04 20:54:59 -07:00
Cesar N
7710df62ee Add imagePullSecretsName field on Add Tenant request (#227) 2020-08-04 16:04:04 -07:00
Minio Trusted
63e1c554b7 update to v0.3.6 2020-08-03 12:14:15 -07:00
Daniel Valdivia
a9d8f3fc41 Return Disk Usage (#226)
* Return Disk Usage

* Address comments
2020-08-03 12:11:48 -07:00
Minio Trusted
59bf546b4a upgrade to v0.3.5 2020-08-03 09:24:57 -07:00
Lenin Alevski
c3e34dc220 Support for deploying minio/console with IDP integration (#221) 2020-08-02 23:45:54 -07:00
Daniel Valdivia
cd547e9425 Limit Console RAM to 64Mi. Increase Logging for Tenant APIs. (#225) 2020-08-02 23:04:51 -07:00
Harshavardhana
d98b70f0ca update CREDITS with new deps (#222)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-08-02 12:29:58 -07:00
Daniel Valdivia
7ff009ec43 Add Insecure parameter to NewAdminClient function (#224)
When using the madmin client, for some operations such as health checks against a MinIO instnace with TLS we need a client with insecure turned on.
2020-08-02 12:21:21 -07:00
dependabot[bot]
3760c783d0 Bump elliptic from 6.5.2 to 6.5.3 in /portal-ui (#223)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-08-02 09:36:01 -07:00
Daniel Valdivia
a8be3c72aa Release v0.3.4 (#220) 2020-07-30 21:06:58 -07:00
Lenin Alevski
ee8242d72a TLS with user provided certificates and KES support for MinIO (#213)
This PR adds the following features:

- Allow user to provide its own keypair certificates for enable TLS in
  MinIO
- Allow user to configure data encryption at rest in MinIO with KES
- Removes JWT schema for login and instead Console authentication will use
  encrypted session tokens

Enable TLS between client and MinIO with user provided certificates

Instead of using AutoCert feature now the user can provide `cert` and
`key` via `tls` object, values must be valid `x509.Certificate`
formatted files encoded in `base64`

Enable encryption at rest configuring KES

User can deploy KES via Console/Operator by defining the encryption
object, AutoCert must be enabled or custom certificates for KES must be
provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`,
previous configuration of the KMS is necessary.

eg of body request for create-tenant

```
{
    "name": "honeywell",
    "access_key": "minio",
    "secret_key": "minio123",
    "enable_mcs": false,
    "enable_ssl": false,
    "service_name": "honeywell",
    "zones": [
        {
            "name": "honeywell-zone-1",
            "servers": 1,
            "volumes_per_server": 4,
            "volume_configuration": {
                "size": 256000000,
                "storage_class": "vsan-default-storage-policy"
            }
        }
    ],
    "namespace": "default",
    "tls": {
      "tls.crt": "",
      "tls.key": ""
    },
    "encryption": {
        "server": {
          "tls.crt": "",
          "tls.key": ""
        },
        "client": {
          "tls.crt": "",
          "tls.key": ""
        },
      "vault": {
        "endpoint": "http://vault:8200",
        "prefix": "",
        "approle": {
          "id": "",
          "secret": ""
        }
      }
    }
}
```
2020-07-30 17:49:56 -07:00
Daniel Valdivia
88b697f072 Bumps the version of Console when using Operator APIs (#219) 2020-07-30 15:41:20 -07:00
Cesar N
1dabfb4ead Update to minio-operator 3.0.5 (#218) 2020-07-30 15:21:45 -07:00
Daniel Valdivia
410920823a Return Generated Console Credentials (#217)
Whe Console is configured, we auto generate credentials for Console and store them in a secret but we need to return them to the user so he knows what credentials he/she can use to log in to console.
2020-07-30 13:55:11 -07:00
Daniel Valdivia
3ffaeceaf4 Fix NPE on Resource Parsing (#216) 2020-07-29 12:11:48 -07:00
Cesar N
add9023b74 Add Attributes to models.Zone and add functions to parse (#215)
from/to operator.Zone to/from models.Zone

Tenant Add, GetTenantInfo and AddZone now can parse
operator.Zone extra attributes
2020-07-29 01:01:17 -07:00
Harshavardhana
af8eb9a147 fix: rename mcS3Client to mcClient (#214)
Co-authored-by: Minio Trusted <trusted@minio.io>
2020-07-28 13:11:03 -07:00
Daniel Valdivia
1201dcf546 List Tenant Total Size (#211) 2020-07-27 20:03:47 -05:00
Daniel Valdivia
7bf25c897c Update Dockerfile entrypoint (#210) 2020-07-27 17:18:46 -07:00
Daniel Valdivia
27a57b1e51 Add Tenant Usage API (#208) 2020-07-27 14:19:40 -07:00
Daniel Valdivia
c03642fdb1 Validate Add Zone Paramters (#207) 2020-07-27 14:06:12 -07:00
Cesar N
d5b689e254 Upgrade operator version to 3.0.2 (#209) 2020-07-27 13:33:54 -07:00
Minio Trusted
ac6e2f29e4 fix the repo name 2020-07-26 00:40:01 -07:00
137 changed files with 22636 additions and 6069 deletions

View File

@@ -21,7 +21,7 @@ linters:
- structcheck
service:
golangci-lint-version: 1.21.0 # use the fixed version to not introduce new linters unexpectedly
golangci-lint-version: 1.27.0 # use the fixed version to not introduce new linters unexpectedly
run:
skip-dirs:

View File

@@ -2,10 +2,17 @@
# Make sure to check the documentation at http://goreleaser.com
project_name: console
release:
name_template: "Version {{.Version}}"
github:
owner: minio
name: console
before:
hooks:
# you may remove this if you don't use vgo
- go mod tidy
builds:
-
goos:
@@ -16,6 +23,33 @@ builds:
goarch:
- amd64
- arm64
ignore:
- goos: darwin
goarch: arm64
- goos: darwin
goarch: arm
- goos: darwin
goarch: ppc64le
- goos: darwin
goarch: s390x
- goos: windows
goarch: arm64
- goos: windows
goarch: arm
- goos: windows
goarch: ppc64le
- goos: windows
goarch: s390x
- goos: freebsd
goarch: arm
- goos: freebsd
goarch: arm64
- goos: freebsd
goarch: ppc64le
- goos: freebsd
goarch: s390x
env:
- CGO_ENABLED=0
main: ./cmd/console/

6507
CREDITS

File diff suppressed because it is too large Load Diff

View File

@@ -23,4 +23,4 @@ EXPOSE 9090
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=0 /go/src/github.com/minio/console/console .
CMD ["/console"]
ENTRYPOINT ["/console"]

View File

@@ -1,6 +1,12 @@
FROM ubuntu:18.04 as certs
RUN apt-get update -y && apt-get install -y ca-certificates
FROM scratch
MAINTAINER MinIO Development "dev@min.io"
EXPOSE 9090
COPY console /console
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/console"]

View File

@@ -25,8 +25,10 @@ verifiers: getdeps fmt lint
fmt:
@echo "Running $@ check"
@GO111MODULE=on gofmt -d cmd/
@GO111MODULE=on gofmt -d restapi/
@GO111MODULE=on gofmt -d pkg/
@GO111MODULE=on gofmt -d cmd/
@GO111MODULE=on gofmt -d cluster/
lint:
@echo "Running $@ check"

View File

@@ -98,7 +98,7 @@ func getLatestMinIOImage(client HTTPClientI) (*string, error) {
var latestMinIOImage, errLatestMinIOImage = getLatestMinIOImage(
&HTTPClient{
Client: &http.Client{
Timeout: 4 * time.Second,
Timeout: 15 * time.Second,
},
})

View File

@@ -47,12 +47,12 @@ var serverCmd = cli.Command{
},
cli.StringFlag{
Name: "tls-host",
Value: restapi.GetSSLHostname(),
Value: restapi.GetTLSHostname(),
Usage: "HTTPS server hostname",
},
cli.IntFlag{
Name: "tls-port",
Value: restapi.GetSSLPort(),
Value: restapi.GetTLSPort(),
Usage: "HTTPS server port",
},
cli.StringFlag{

24
go.mod
View File

@@ -4,7 +4,6 @@ go 1.13
require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/elazarl/go-bindata-assetfs v1.0.0
github.com/go-openapi/errors v0.19.6
github.com/go-openapi/loads v0.19.5
@@ -15,20 +14,21 @@ require (
github.com/go-openapi/validate v0.19.10
github.com/gorilla/websocket v1.4.2
github.com/jessevdk/go-flags v1.4.0
github.com/json-iterator/go v1.1.10
github.com/minio/cli v1.22.0
github.com/minio/mc v0.0.0-20200725183142-90d22b271f60
github.com/minio/minio v0.0.0-20200725154241-abbf6ce6ccf8
github.com/minio/minio-go/v7 v7.0.2-0.20200722162308-e0105ca08252
github.com/minio/operator v0.0.0-20200725185636-4a625e4fbb31
github.com/minio/kes v0.11.0
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-20200904194631-b8aa01dc5d70
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/satori/go.uuid v1.2.0
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-20200709230013-948cd5f35899
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
k8s.io/api v0.18.0
k8s.io/apimachinery v0.18.0
k8s.io/client-go v0.18.0
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
k8s.io/client-go v0.18.6
)

749
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -15,10 +15,9 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:latest
image: minio/console:v0.3.24
imagePullPolicy: "IfNotPresent"
args:
- /console
- server
ports:
- containerPort: 9090

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

View File

@@ -1,282 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: minio-operator
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: tenants.minio.min.io
spec:
group: minio.min.io
scope: Namespaced
names:
kind: Tenant
singular: tenant
plural: tenants
versions:
- name: v1
served: true
storage: true
schema:
# openAPIV3Schema is the schema for validating custom objects.
# Refer https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#specifying-a-structural-schema
# for more details
openAPIV3Schema:
type: object
properties:
spec:
type: object
x-kubernetes-preserve-unknown-fields: true
properties:
metadata:
type: object
x-kubernetes-preserve-unknown-fields: true
image:
type: string
serviceName:
type: string
serviceAccountName:
type: string
zones:
type: array
items:
type: object
x-kubernetes-preserve-unknown-fields: true
properties:
name:
type: string
servers:
type: integer
volumesPerServer:
type: integer
volumeClaimTemplate:
type: object
x-kubernetes-preserve-unknown-fields: true
resources:
type: object
x-kubernetes-preserve-unknown-fields: true
nodeSelector:
type: object
x-kubernetes-preserve-unknown-fields: true
affinity:
type: object
x-kubernetes-preserve-unknown-fields: true
tolerations:
type: object
x-kubernetes-preserve-unknown-fields: true
mountPath:
type: string
podManagementPolicy:
type: string
enum: [Parallel,OrderedReady]
default: Parallel
requestAutoCert:
type: boolean
default: false
certConfig:
type: object
properties:
commonName:
type: string
organizationName:
type: array
items:
type: string
dnsNames:
type: array
items:
type: string
version:
type: string
mountpath:
type: string
subpath:
type: string
nodeSelector:
type: object
x-kubernetes-preserve-unknown-fields: true
credsSecret:
type: object
x-kubernetes-preserve-unknown-fields: true
env:
type: object
x-kubernetes-preserve-unknown-fields: true
console:
type: object
x-kubernetes-preserve-unknown-fields: true
properties:
metadata:
type: object
x-kubernetes-preserve-unknown-fields: true
image:
type: string
replicas:
type: integer
default: 2
consoleSecret:
type: object
properties:
name:
type: string
resources:
type: object
x-kubernetes-preserve-unknown-fields: true
env:
type: object
x-kubernetes-preserve-unknown-fields: true
kes:
type: object
x-kubernetes-preserve-unknown-fields: true
properties:
metadata:
type: object
x-kubernetes-preserve-unknown-fields: true
image:
type: string
replicas:
type: integer
default: 2
kesSecret:
type: object
properties:
name:
type: string
liveness:
type: object
properties:
initialDelaySeconds:
type: integer
periodSeconds:
type: integer
timeoutSeconds:
type: integer
status:
type: object
properties:
currentState:
type: string
subresources:
# status enables the status subresource.
status: {}
additionalPrinterColumns:
- name: Current State
type: string
jsonPath: ".status.currentState"
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: minio-operator
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: minio-operator-role
rules:
- apiGroups:
- ""
resources:
- namespaces
- secrets
- pods
- services
- events
verbs:
- get
- watch
- create
- list
- delete
- apiGroups:
- apps
resources:
- statefulsets
- deployments
verbs:
- get
- create
- list
- patch
- watch
- update
- delete
- apiGroups:
- batch
resources:
- jobs
verbs:
- get
- create
- list
- patch
- watch
- update
- delete
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
- certificatesigningrequests/approval
- certificatesigningrequests/status
verbs:
- update
- create
- get
- delete
- apiGroups:
- certificates.k8s.io
resourceNames:
- kubernetes.io/legacy-unknown
resources:
- signers
verbs:
- approve
- sign
- apiGroups:
- minio.min.io
resources:
- '*'
verbs:
- '*'
- apiGroups:
- min.io
resources:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: minio-operator-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: minio-operator-role
subjects:
- kind: ServiceAccount
name: minio-operator
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio-operator
namespace: default
spec:
replicas: 1
selector:
matchLabels:
name: minio-operator
template:
metadata:
labels:
name: minio-operator
spec:
containers:
- image: minio/k8s-operator:v3.0.1
imagePullPolicy: IfNotPresent
name: minio-operator
serviceAccountName: minio-operator

View File

@@ -1,3 +0,0 @@
#!/bin/bash
# Get's the latest deployment file from MinIO Operator
curl https://raw.githubusercontent.com/minio/operator/master/minio-operator.yaml > operator-console/base/minio-operator.yaml

View File

@@ -6,18 +6,38 @@ rules:
- apiGroups:
- ""
resources:
- namespaces
- secrets
- pods
- services
- events
- resourcequotas
verbs:
- get
- watch
- create
- list
- patch
- update
- deletecollection
- apiGroups:
- ""
resources:
- namespaces
- pods
- 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,13 +15,12 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:latest
image: minio/console:v0.3.24
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_OPERATOR_MODE
value: "on"
args:
- /console
- server
ports:
- containerPort: 9090

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

View File

@@ -1,282 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: minio-operator
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: tenants.minio.min.io
spec:
group: minio.min.io
scope: Namespaced
names:
kind: Tenant
singular: tenant
plural: tenants
versions:
- name: v1
served: true
storage: true
schema:
# openAPIV3Schema is the schema for validating custom objects.
# Refer https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#specifying-a-structural-schema
# for more details
openAPIV3Schema:
type: object
properties:
spec:
type: object
x-kubernetes-preserve-unknown-fields: true
properties:
metadata:
type: object
x-kubernetes-preserve-unknown-fields: true
image:
type: string
serviceName:
type: string
serviceAccountName:
type: string
zones:
type: array
items:
type: object
x-kubernetes-preserve-unknown-fields: true
properties:
name:
type: string
servers:
type: integer
volumesPerServer:
type: integer
volumeClaimTemplate:
type: object
x-kubernetes-preserve-unknown-fields: true
resources:
type: object
x-kubernetes-preserve-unknown-fields: true
nodeSelector:
type: object
x-kubernetes-preserve-unknown-fields: true
affinity:
type: object
x-kubernetes-preserve-unknown-fields: true
tolerations:
type: object
x-kubernetes-preserve-unknown-fields: true
mountPath:
type: string
podManagementPolicy:
type: string
enum: [Parallel,OrderedReady]
default: Parallel
requestAutoCert:
type: boolean
default: false
certConfig:
type: object
properties:
commonName:
type: string
organizationName:
type: array
items:
type: string
dnsNames:
type: array
items:
type: string
version:
type: string
mountpath:
type: string
subpath:
type: string
nodeSelector:
type: object
x-kubernetes-preserve-unknown-fields: true
credsSecret:
type: object
x-kubernetes-preserve-unknown-fields: true
env:
type: object
x-kubernetes-preserve-unknown-fields: true
console:
type: object
x-kubernetes-preserve-unknown-fields: true
properties:
metadata:
type: object
x-kubernetes-preserve-unknown-fields: true
image:
type: string
replicas:
type: integer
default: 2
consoleSecret:
type: object
properties:
name:
type: string
resources:
type: object
x-kubernetes-preserve-unknown-fields: true
env:
type: object
x-kubernetes-preserve-unknown-fields: true
kes:
type: object
x-kubernetes-preserve-unknown-fields: true
properties:
metadata:
type: object
x-kubernetes-preserve-unknown-fields: true
image:
type: string
replicas:
type: integer
default: 2
kesSecret:
type: object
properties:
name:
type: string
liveness:
type: object
properties:
initialDelaySeconds:
type: integer
periodSeconds:
type: integer
timeoutSeconds:
type: integer
status:
type: object
properties:
currentState:
type: string
subresources:
# status enables the status subresource.
status: {}
additionalPrinterColumns:
- name: Current State
type: string
jsonPath: ".status.currentState"
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: minio-operator
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: minio-operator-role
rules:
- apiGroups:
- ""
resources:
- namespaces
- secrets
- pods
- services
- events
verbs:
- get
- watch
- create
- list
- delete
- apiGroups:
- apps
resources:
- statefulsets
- deployments
verbs:
- get
- create
- list
- patch
- watch
- update
- delete
- apiGroups:
- batch
resources:
- jobs
verbs:
- get
- create
- list
- patch
- watch
- update
- delete
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
- certificatesigningrequests/approval
- certificatesigningrequests/status
verbs:
- update
- create
- get
- delete
- apiGroups:
- certificates.k8s.io
resourceNames:
- kubernetes.io/legacy-unknown
resources:
- signers
verbs:
- approve
- sign
- apiGroups:
- minio.min.io
resources:
- '*'
verbs:
- '*'
- apiGroups:
- min.io
resources:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: minio-operator-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: minio-operator-role
subjects:
- kind: ServiceAccount
name: minio-operator
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio-operator
namespace: default
spec:
replicas: 1
selector:
matchLabels:
name: minio-operator
template:
metadata:
labels:
name: minio-operator
spec:
containers:
- image: minio/k8s-operator:v3.0.1
imagePullPolicy: IfNotPresent
name: minio-operator
serviceAccountName: minio-operator

258
models/aws_configuration.go Normal file
View File

@@ -0,0 +1,258 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// AwsConfiguration aws configuration
//
// swagger:model awsConfiguration
type AwsConfiguration struct {
// secretsmanager
// Required: true
Secretsmanager *AwsConfigurationSecretsmanager `json:"secretsmanager"`
}
// Validate validates this aws configuration
func (m *AwsConfiguration) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateSecretsmanager(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *AwsConfiguration) validateSecretsmanager(formats strfmt.Registry) error {
if err := validate.Required("secretsmanager", "body", m.Secretsmanager); err != nil {
return err
}
if m.Secretsmanager != nil {
if err := m.Secretsmanager.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("secretsmanager")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *AwsConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *AwsConfiguration) UnmarshalBinary(b []byte) error {
var res AwsConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// AwsConfigurationSecretsmanager aws configuration secretsmanager
//
// swagger:model AwsConfigurationSecretsmanager
type AwsConfigurationSecretsmanager struct {
// credentials
// Required: true
Credentials *AwsConfigurationSecretsmanagerCredentials `json:"credentials"`
// endpoint
// Required: true
Endpoint *string `json:"endpoint"`
// kmskey
Kmskey string `json:"kmskey,omitempty"`
// region
// Required: true
Region *string `json:"region"`
}
// Validate validates this aws configuration secretsmanager
func (m *AwsConfigurationSecretsmanager) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCredentials(formats); err != nil {
res = append(res, err)
}
if err := m.validateEndpoint(formats); err != nil {
res = append(res, err)
}
if err := m.validateRegion(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *AwsConfigurationSecretsmanager) validateCredentials(formats strfmt.Registry) error {
if err := validate.Required("secretsmanager"+"."+"credentials", "body", m.Credentials); err != nil {
return err
}
if m.Credentials != nil {
if err := m.Credentials.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("secretsmanager" + "." + "credentials")
}
return err
}
}
return nil
}
func (m *AwsConfigurationSecretsmanager) validateEndpoint(formats strfmt.Registry) error {
if err := validate.Required("secretsmanager"+"."+"endpoint", "body", m.Endpoint); err != nil {
return err
}
return nil
}
func (m *AwsConfigurationSecretsmanager) validateRegion(formats strfmt.Registry) error {
if err := validate.Required("secretsmanager"+"."+"region", "body", m.Region); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *AwsConfigurationSecretsmanager) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *AwsConfigurationSecretsmanager) UnmarshalBinary(b []byte) error {
var res AwsConfigurationSecretsmanager
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// AwsConfigurationSecretsmanagerCredentials aws configuration secretsmanager credentials
//
// swagger:model AwsConfigurationSecretsmanagerCredentials
type AwsConfigurationSecretsmanagerCredentials struct {
// accesskey
// Required: true
Accesskey *string `json:"accesskey"`
// secretkey
// Required: true
Secretkey *string `json:"secretkey"`
// token
Token string `json:"token,omitempty"`
}
// Validate validates this aws configuration secretsmanager credentials
func (m *AwsConfigurationSecretsmanagerCredentials) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAccesskey(formats); err != nil {
res = append(res, err)
}
if err := m.validateSecretkey(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *AwsConfigurationSecretsmanagerCredentials) validateAccesskey(formats strfmt.Registry) error {
if err := validate.Required("secretsmanager"+"."+"credentials"+"."+"accesskey", "body", m.Accesskey); err != nil {
return err
}
return nil
}
func (m *AwsConfigurationSecretsmanagerCredentials) validateSecretkey(formats strfmt.Registry) error {
if err := validate.Required("secretsmanager"+"."+"credentials"+"."+"secretkey", "body", m.Secretkey); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *AwsConfigurationSecretsmanagerCredentials) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *AwsConfigurationSecretsmanagerCredentials) UnmarshalBinary(b []byte) error {
var res AwsConfigurationSecretsmanagerCredentials
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -42,15 +42,39 @@ type CreateTenantRequest struct {
// annotations
Annotations map[string]string `json:"annotations,omitempty"`
// console image
ConsoleImage string `json:"console_image,omitempty"`
// enable console
EnableConsole *bool `json:"enable_console,omitempty"`
// enable ssl
EnableSsl *bool `json:"enable_ssl,omitempty"`
// enable prometheus
EnablePrometheus *bool `json:"enable_prometheus,omitempty"`
// enable tls
EnableTLS *bool `json:"enable_tls,omitempty"`
// encryption
Encryption *EncryptionConfiguration `json:"encryption,omitempty"`
// erasure coding parity
ErasureCodingParity int64 `json:"erasureCodingParity,omitempty"`
// idp
Idp *IdpConfiguration `json:"idp,omitempty"`
// image
Image string `json:"image,omitempty"`
// image pull secret
ImagePullSecret string `json:"image_pull_secret,omitempty"`
// image registry
ImageRegistry *ImageRegistry `json:"image_registry,omitempty"`
// labels
Labels map[string]string `json:"labels,omitempty"`
// mounth path
MounthPath string `json:"mounth_path,omitempty"`
@@ -69,6 +93,9 @@ type CreateTenantRequest struct {
// service name
ServiceName string `json:"service_name,omitempty"`
// tls
TLS *TLSConfiguration `json:"tls,omitempty"`
// zones
// Required: true
Zones []*Zone `json:"zones"`
@@ -78,6 +105,18 @@ type CreateTenantRequest struct {
func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateEncryption(formats); err != nil {
res = append(res, err)
}
if err := m.validateIdp(formats); err != nil {
res = append(res, err)
}
if err := m.validateImageRegistry(formats); err != nil {
res = append(res, err)
}
if err := m.validateName(formats); err != nil {
res = append(res, err)
}
@@ -86,6 +125,10 @@ func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateTLS(formats); err != nil {
res = append(res, err)
}
if err := m.validateZones(formats); err != nil {
res = append(res, err)
}
@@ -96,6 +139,60 @@ func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
return nil
}
func (m *CreateTenantRequest) validateEncryption(formats strfmt.Registry) error {
if swag.IsZero(m.Encryption) { // not required
return nil
}
if m.Encryption != nil {
if err := m.Encryption.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("encryption")
}
return err
}
}
return nil
}
func (m *CreateTenantRequest) validateIdp(formats strfmt.Registry) error {
if swag.IsZero(m.Idp) { // not required
return nil
}
if m.Idp != nil {
if err := m.Idp.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("idp")
}
return err
}
}
return nil
}
func (m *CreateTenantRequest) validateImageRegistry(formats strfmt.Registry) error {
if swag.IsZero(m.ImageRegistry) { // not required
return nil
}
if m.ImageRegistry != nil {
if err := m.ImageRegistry.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("image_registry")
}
return err
}
}
return nil
}
func (m *CreateTenantRequest) validateName(formats strfmt.Registry) error {
if err := validate.Required("name", "body", m.Name); err != nil {
@@ -118,6 +215,24 @@ func (m *CreateTenantRequest) validateNamespace(formats strfmt.Registry) error {
return nil
}
func (m *CreateTenantRequest) validateTLS(formats strfmt.Registry) error {
if swag.IsZero(m.TLS) { // not required
return nil
}
if m.TLS != nil {
if err := m.TLS.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tls")
}
return err
}
}
return nil
}
func (m *CreateTenantRequest) validateZones(formats strfmt.Registry) error {
if err := validate.Required("zones", "body", m.Zones); err != nil {

View File

@@ -23,6 +23,7 @@ package models
// 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"
)
@@ -35,12 +36,42 @@ type CreateTenantResponse struct {
// access key
AccessKey string `json:"access_key,omitempty"`
// console
Console *CreateTenantResponseConsole `json:"console,omitempty"`
// secret key
SecretKey string `json:"secret_key,omitempty"`
}
// Validate validates this create tenant response
func (m *CreateTenantResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsole(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *CreateTenantResponse) 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
}
@@ -61,3 +92,38 @@ func (m *CreateTenantResponse) UnmarshalBinary(b []byte) error {
*m = res
return nil
}
// CreateTenantResponseConsole create tenant response console
//
// swagger:model CreateTenantResponseConsole
type CreateTenantResponseConsole struct {
// access key
AccessKey string `json:"access_key,omitempty"`
// secret key
SecretKey string `json:"secret_key,omitempty"`
}
// Validate validates this create tenant response console
func (m *CreateTenantResponseConsole) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *CreateTenantResponseConsole) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *CreateTenantResponseConsole) UnmarshalBinary(b []byte) error {
var res CreateTenantResponseConsole
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

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

@@ -0,0 +1,191 @@
// 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"
)
// EncryptionConfiguration encryption configuration
//
// swagger:model encryptionConfiguration
type EncryptionConfiguration struct {
// aws
Aws *AwsConfiguration `json:"aws,omitempty"`
// client
Client *KeyPairConfiguration `json:"client,omitempty"`
// gemalto
Gemalto *GemaltoConfiguration `json:"gemalto,omitempty"`
// image
Image string `json:"image,omitempty"`
// server
Server *KeyPairConfiguration `json:"server,omitempty"`
// vault
Vault *VaultConfiguration `json:"vault,omitempty"`
}
// Validate validates this encryption configuration
func (m *EncryptionConfiguration) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAws(formats); err != nil {
res = append(res, err)
}
if err := m.validateClient(formats); err != nil {
res = append(res, err)
}
if err := m.validateGemalto(formats); err != nil {
res = append(res, err)
}
if err := m.validateServer(formats); err != nil {
res = append(res, err)
}
if err := m.validateVault(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *EncryptionConfiguration) validateAws(formats strfmt.Registry) error {
if swag.IsZero(m.Aws) { // not required
return nil
}
if m.Aws != nil {
if err := m.Aws.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("aws")
}
return err
}
}
return nil
}
func (m *EncryptionConfiguration) validateClient(formats strfmt.Registry) error {
if swag.IsZero(m.Client) { // not required
return nil
}
if m.Client != nil {
if err := m.Client.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("client")
}
return err
}
}
return nil
}
func (m *EncryptionConfiguration) validateGemalto(formats strfmt.Registry) error {
if swag.IsZero(m.Gemalto) { // not required
return nil
}
if m.Gemalto != nil {
if err := m.Gemalto.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("gemalto")
}
return err
}
}
return nil
}
func (m *EncryptionConfiguration) validateServer(formats strfmt.Registry) error {
if swag.IsZero(m.Server) { // not required
return nil
}
if m.Server != nil {
if err := m.Server.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("server")
}
return err
}
}
return nil
}
func (m *EncryptionConfiguration) validateVault(formats strfmt.Registry) error {
if swag.IsZero(m.Vault) { // not required
return nil
}
if m.Vault != nil {
if err := m.Vault.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("vault")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *EncryptionConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *EncryptionConfiguration) UnmarshalBinary(b []byte) error {
var res EncryptionConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

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,314 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// GemaltoConfiguration gemalto configuration
//
// swagger:model gemaltoConfiguration
type GemaltoConfiguration struct {
// keysecure
// Required: true
Keysecure *GemaltoConfigurationKeysecure `json:"keysecure"`
}
// Validate validates this gemalto configuration
func (m *GemaltoConfiguration) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateKeysecure(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *GemaltoConfiguration) validateKeysecure(formats strfmt.Registry) error {
if err := validate.Required("keysecure", "body", m.Keysecure); err != nil {
return err
}
if m.Keysecure != nil {
if err := m.Keysecure.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("keysecure")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *GemaltoConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *GemaltoConfiguration) UnmarshalBinary(b []byte) error {
var res GemaltoConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// GemaltoConfigurationKeysecure gemalto configuration keysecure
//
// swagger:model GemaltoConfigurationKeysecure
type GemaltoConfigurationKeysecure struct {
// credentials
// Required: true
Credentials *GemaltoConfigurationKeysecureCredentials `json:"credentials"`
// endpoint
// Required: true
Endpoint *string `json:"endpoint"`
// tls
TLS *GemaltoConfigurationKeysecureTLS `json:"tls,omitempty"`
}
// Validate validates this gemalto configuration keysecure
func (m *GemaltoConfigurationKeysecure) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCredentials(formats); err != nil {
res = append(res, err)
}
if err := m.validateEndpoint(formats); err != nil {
res = append(res, err)
}
if err := m.validateTLS(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *GemaltoConfigurationKeysecure) validateCredentials(formats strfmt.Registry) error {
if err := validate.Required("keysecure"+"."+"credentials", "body", m.Credentials); err != nil {
return err
}
if m.Credentials != nil {
if err := m.Credentials.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("keysecure" + "." + "credentials")
}
return err
}
}
return nil
}
func (m *GemaltoConfigurationKeysecure) validateEndpoint(formats strfmt.Registry) error {
if err := validate.Required("keysecure"+"."+"endpoint", "body", m.Endpoint); err != nil {
return err
}
return nil
}
func (m *GemaltoConfigurationKeysecure) validateTLS(formats strfmt.Registry) error {
if swag.IsZero(m.TLS) { // not required
return nil
}
if m.TLS != nil {
if err := m.TLS.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("keysecure" + "." + "tls")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *GemaltoConfigurationKeysecure) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *GemaltoConfigurationKeysecure) UnmarshalBinary(b []byte) error {
var res GemaltoConfigurationKeysecure
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// GemaltoConfigurationKeysecureCredentials gemalto configuration keysecure credentials
//
// swagger:model GemaltoConfigurationKeysecureCredentials
type GemaltoConfigurationKeysecureCredentials struct {
// domain
// Required: true
Domain *string `json:"domain"`
// retry
Retry int64 `json:"retry,omitempty"`
// token
// Required: true
Token *string `json:"token"`
}
// Validate validates this gemalto configuration keysecure credentials
func (m *GemaltoConfigurationKeysecureCredentials) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateDomain(formats); err != nil {
res = append(res, err)
}
if err := m.validateToken(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *GemaltoConfigurationKeysecureCredentials) validateDomain(formats strfmt.Registry) error {
if err := validate.Required("keysecure"+"."+"credentials"+"."+"domain", "body", m.Domain); err != nil {
return err
}
return nil
}
func (m *GemaltoConfigurationKeysecureCredentials) validateToken(formats strfmt.Registry) error {
if err := validate.Required("keysecure"+"."+"credentials"+"."+"token", "body", m.Token); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *GemaltoConfigurationKeysecureCredentials) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *GemaltoConfigurationKeysecureCredentials) UnmarshalBinary(b []byte) error {
var res GemaltoConfigurationKeysecureCredentials
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// GemaltoConfigurationKeysecureTLS gemalto configuration keysecure TLS
//
// swagger:model GemaltoConfigurationKeysecureTLS
type GemaltoConfigurationKeysecureTLS struct {
// ca
// Required: true
Ca *string `json:"ca"`
}
// Validate validates this gemalto configuration keysecure TLS
func (m *GemaltoConfigurationKeysecureTLS) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCa(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *GemaltoConfigurationKeysecureTLS) validateCa(formats strfmt.Registry) error {
if err := validate.Required("keysecure"+"."+"tls"+"."+"ca", "body", m.Ca); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *GemaltoConfigurationKeysecureTLS) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *GemaltoConfigurationKeysecureTLS) UnmarshalBinary(b []byte) error {
var res GemaltoConfigurationKeysecureTLS
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

299
models/idp_configuration.go Normal file
View File

@@ -0,0 +1,299 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// IdpConfiguration idp configuration
//
// swagger:model idpConfiguration
type IdpConfiguration struct {
// active directory
ActiveDirectory *IdpConfigurationActiveDirectory `json:"active_directory,omitempty"`
// oidc
Oidc *IdpConfigurationOidc `json:"oidc,omitempty"`
}
// Validate validates this idp configuration
func (m *IdpConfiguration) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateActiveDirectory(formats); err != nil {
res = append(res, err)
}
if err := m.validateOidc(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *IdpConfiguration) validateActiveDirectory(formats strfmt.Registry) error {
if swag.IsZero(m.ActiveDirectory) { // not required
return nil
}
if m.ActiveDirectory != nil {
if err := m.ActiveDirectory.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("active_directory")
}
return err
}
}
return nil
}
func (m *IdpConfiguration) validateOidc(formats strfmt.Registry) error {
if swag.IsZero(m.Oidc) { // not required
return nil
}
if m.Oidc != nil {
if err := m.Oidc.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("oidc")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *IdpConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *IdpConfiguration) UnmarshalBinary(b []byte) error {
var res IdpConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// IdpConfigurationActiveDirectory idp configuration active directory
//
// swagger:model IdpConfigurationActiveDirectory
type IdpConfigurationActiveDirectory struct {
// group name attribute
GroupNameAttribute string `json:"group_name_attribute,omitempty"`
// group search base dn
GroupSearchBaseDn string `json:"group_search_base_dn,omitempty"`
// group search filter
GroupSearchFilter string `json:"group_search_filter,omitempty"`
// server insecure
ServerInsecure bool `json:"server_insecure,omitempty"`
// skip tls verification
SkipTLSVerification bool `json:"skip_tls_verification,omitempty"`
// url
// Required: true
URL *string `json:"url"`
// user search filter
// Required: true
UserSearchFilter *string `json:"user_search_filter"`
// username format
// Required: true
UsernameFormat *string `json:"username_format"`
}
// Validate validates this idp configuration active directory
func (m *IdpConfigurationActiveDirectory) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateURL(formats); err != nil {
res = append(res, err)
}
if err := m.validateUserSearchFilter(formats); err != nil {
res = append(res, err)
}
if err := m.validateUsernameFormat(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *IdpConfigurationActiveDirectory) validateURL(formats strfmt.Registry) error {
if err := validate.Required("active_directory"+"."+"url", "body", m.URL); err != nil {
return err
}
return nil
}
func (m *IdpConfigurationActiveDirectory) validateUserSearchFilter(formats strfmt.Registry) error {
if err := validate.Required("active_directory"+"."+"user_search_filter", "body", m.UserSearchFilter); err != nil {
return err
}
return nil
}
func (m *IdpConfigurationActiveDirectory) validateUsernameFormat(formats strfmt.Registry) error {
if err := validate.Required("active_directory"+"."+"username_format", "body", m.UsernameFormat); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *IdpConfigurationActiveDirectory) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *IdpConfigurationActiveDirectory) UnmarshalBinary(b []byte) error {
var res IdpConfigurationActiveDirectory
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// IdpConfigurationOidc idp configuration oidc
//
// swagger:model IdpConfigurationOidc
type IdpConfigurationOidc struct {
// client id
// Required: true
ClientID *string `json:"client_id"`
// secret id
// Required: true
SecretID *string `json:"secret_id"`
// url
// Required: true
URL *string `json:"url"`
}
// Validate validates this idp configuration oidc
func (m *IdpConfigurationOidc) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateClientID(formats); err != nil {
res = append(res, err)
}
if err := m.validateSecretID(formats); err != nil {
res = append(res, err)
}
if err := m.validateURL(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *IdpConfigurationOidc) validateClientID(formats strfmt.Registry) error {
if err := validate.Required("oidc"+"."+"client_id", "body", m.ClientID); err != nil {
return err
}
return nil
}
func (m *IdpConfigurationOidc) validateSecretID(formats strfmt.Registry) error {
if err := validate.Required("oidc"+"."+"secret_id", "body", m.SecretID); err != nil {
return err
}
return nil
}
func (m *IdpConfigurationOidc) validateURL(formats strfmt.Registry) error {
if err := validate.Required("oidc"+"."+"url", "body", m.URL); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *IdpConfigurationOidc) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *IdpConfigurationOidc) UnmarshalBinary(b []byte) error {
var res IdpConfigurationOidc
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

115
models/image_registry.go Normal file
View File

@@ -0,0 +1,115 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// ImageRegistry image registry
//
// swagger:model imageRegistry
type ImageRegistry struct {
// password
// Required: true
Password *string `json:"password"`
// registry
// Required: true
Registry *string `json:"registry"`
// username
// Required: true
Username *string `json:"username"`
}
// Validate validates this image registry
func (m *ImageRegistry) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePassword(formats); err != nil {
res = append(res, err)
}
if err := m.validateRegistry(formats); err != nil {
res = append(res, err)
}
if err := m.validateUsername(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ImageRegistry) validatePassword(formats strfmt.Registry) error {
if err := validate.Required("password", "body", m.Password); err != nil {
return err
}
return nil
}
func (m *ImageRegistry) validateRegistry(formats strfmt.Registry) error {
if err := validate.Required("registry", "body", m.Registry); err != nil {
return err
}
return nil
}
func (m *ImageRegistry) validateUsername(formats strfmt.Registry) error {
if err := validate.Required("username", "body", m.Username); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *ImageRegistry) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ImageRegistry) UnmarshalBinary(b []byte) error {
var res ImageRegistry
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,98 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// KeyPairConfiguration key pair configuration
//
// swagger:model keyPairConfiguration
type KeyPairConfiguration struct {
// crt
// Required: true
Crt *string `json:"crt"`
// key
// Required: true
Key *string `json:"key"`
}
// Validate validates this key pair configuration
func (m *KeyPairConfiguration) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCrt(formats); err != nil {
res = append(res, err)
}
if err := m.validateKey(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *KeyPairConfiguration) validateCrt(formats strfmt.Registry) error {
if err := validate.Required("crt", "body", m.Crt); err != nil {
return err
}
return nil
}
func (m *KeyPairConfiguration) validateKey(formats strfmt.Registry) error {
if err := validate.Required("key", "body", m.Key); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *KeyPairConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *KeyPairConfiguration) UnmarshalBinary(b []byte) error {
var res KeyPairConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

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,272 @@
// 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"
)
// NodeSelectorTerm A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.
//
// swagger:model nodeSelectorTerm
type NodeSelectorTerm struct {
// A list of node selector requirements by node's labels.
MatchExpressions []*NodeSelectorTermMatchExpressionsItems0 `json:"matchExpressions"`
// A list of node selector requirements by node's fields.
MatchFields []*NodeSelectorTermMatchFieldsItems0 `json:"matchFields"`
}
// Validate validates this node selector term
func (m *NodeSelectorTerm) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateMatchExpressions(formats); err != nil {
res = append(res, err)
}
if err := m.validateMatchFields(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *NodeSelectorTerm) validateMatchExpressions(formats strfmt.Registry) error {
if swag.IsZero(m.MatchExpressions) { // not required
return nil
}
for i := 0; i < len(m.MatchExpressions); i++ {
if swag.IsZero(m.MatchExpressions[i]) { // not required
continue
}
if m.MatchExpressions[i] != nil {
if err := m.MatchExpressions[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("matchExpressions" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *NodeSelectorTerm) validateMatchFields(formats strfmt.Registry) error {
if swag.IsZero(m.MatchFields) { // not required
return nil
}
for i := 0; i < len(m.MatchFields); i++ {
if swag.IsZero(m.MatchFields[i]) { // not required
continue
}
if m.MatchFields[i] != nil {
if err := m.MatchFields[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("matchFields" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *NodeSelectorTerm) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *NodeSelectorTerm) UnmarshalBinary(b []byte) error {
var res NodeSelectorTerm
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// NodeSelectorTermMatchExpressionsItems0 A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
//
// swagger:model NodeSelectorTermMatchExpressionsItems0
type NodeSelectorTermMatchExpressionsItems0 struct {
// The label key that the selector applies to.
// Required: true
Key *string `json:"key"`
// Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
// Required: true
Operator *string `json:"operator"`
// An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.
Values []string `json:"values"`
}
// Validate validates this node selector term match expressions items0
func (m *NodeSelectorTermMatchExpressionsItems0) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateKey(formats); err != nil {
res = append(res, err)
}
if err := m.validateOperator(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *NodeSelectorTermMatchExpressionsItems0) validateKey(formats strfmt.Registry) error {
if err := validate.Required("key", "body", m.Key); err != nil {
return err
}
return nil
}
func (m *NodeSelectorTermMatchExpressionsItems0) validateOperator(formats strfmt.Registry) error {
if err := validate.Required("operator", "body", m.Operator); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *NodeSelectorTermMatchExpressionsItems0) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *NodeSelectorTermMatchExpressionsItems0) UnmarshalBinary(b []byte) error {
var res NodeSelectorTermMatchExpressionsItems0
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// NodeSelectorTermMatchFieldsItems0 A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
//
// swagger:model NodeSelectorTermMatchFieldsItems0
type NodeSelectorTermMatchFieldsItems0 struct {
// The label key that the selector applies to.
// Required: true
Key *string `json:"key"`
// Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
// Required: true
Operator *string `json:"operator"`
// An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.
Values []string `json:"values"`
}
// Validate validates this node selector term match fields items0
func (m *NodeSelectorTermMatchFieldsItems0) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateKey(formats); err != nil {
res = append(res, err)
}
if err := m.validateOperator(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *NodeSelectorTermMatchFieldsItems0) validateKey(formats strfmt.Registry) error {
if err := validate.Required("key", "body", m.Key); err != nil {
return err
}
return nil
}
func (m *NodeSelectorTermMatchFieldsItems0) validateOperator(formats strfmt.Registry) error {
if err := validate.Required("operator", "body", m.Operator); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *NodeSelectorTermMatchFieldsItems0) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *NodeSelectorTermMatchFieldsItems0) UnmarshalBinary(b []byte) error {
var res NodeSelectorTermMatchFieldsItems0
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

251
models/pod_affinity_term.go Normal file
View File

@@ -0,0 +1,251 @@
// 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"
)
// PodAffinityTerm Required. A pod affinity term, associated with the corresponding weight.
//
// swagger:model podAffinityTerm
type PodAffinityTerm struct {
// label selector
LabelSelector *PodAffinityTermLabelSelector `json:"labelSelector,omitempty"`
// namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod's namespace"
Namespaces []string `json:"namespaces"`
// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.
// Required: true
TopologyKey *string `json:"topologyKey"`
}
// Validate validates this pod affinity term
func (m *PodAffinityTerm) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateLabelSelector(formats); err != nil {
res = append(res, err)
}
if err := m.validateTopologyKey(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *PodAffinityTerm) validateLabelSelector(formats strfmt.Registry) error {
if swag.IsZero(m.LabelSelector) { // not required
return nil
}
if m.LabelSelector != nil {
if err := m.LabelSelector.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("labelSelector")
}
return err
}
}
return nil
}
func (m *PodAffinityTerm) validateTopologyKey(formats strfmt.Registry) error {
if err := validate.Required("topologyKey", "body", m.TopologyKey); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *PodAffinityTerm) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *PodAffinityTerm) UnmarshalBinary(b []byte) error {
var res PodAffinityTerm
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// PodAffinityTermLabelSelector A label query over a set of resources, in this case pods.
//
// swagger:model PodAffinityTermLabelSelector
type PodAffinityTermLabelSelector struct {
// matchExpressions is a list of label selector requirements. The requirements are ANDed.
MatchExpressions []*PodAffinityTermLabelSelectorMatchExpressionsItems0 `json:"matchExpressions"`
// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
MatchLabels map[string]string `json:"matchLabels,omitempty"`
}
// Validate validates this pod affinity term label selector
func (m *PodAffinityTermLabelSelector) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateMatchExpressions(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *PodAffinityTermLabelSelector) validateMatchExpressions(formats strfmt.Registry) error {
if swag.IsZero(m.MatchExpressions) { // not required
return nil
}
for i := 0; i < len(m.MatchExpressions); i++ {
if swag.IsZero(m.MatchExpressions[i]) { // not required
continue
}
if m.MatchExpressions[i] != nil {
if err := m.MatchExpressions[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("labelSelector" + "." + "matchExpressions" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *PodAffinityTermLabelSelector) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *PodAffinityTermLabelSelector) UnmarshalBinary(b []byte) error {
var res PodAffinityTermLabelSelector
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// PodAffinityTermLabelSelectorMatchExpressionsItems0 A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
//
// swagger:model PodAffinityTermLabelSelectorMatchExpressionsItems0
type PodAffinityTermLabelSelectorMatchExpressionsItems0 struct {
// key is the label key that the selector applies to.
// Required: true
Key *string `json:"key"`
// operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
// Required: true
Operator *string `json:"operator"`
// values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
Values []string `json:"values"`
}
// Validate validates this pod affinity term label selector match expressions items0
func (m *PodAffinityTermLabelSelectorMatchExpressionsItems0) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateKey(formats); err != nil {
res = append(res, err)
}
if err := m.validateOperator(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *PodAffinityTermLabelSelectorMatchExpressionsItems0) validateKey(formats strfmt.Registry) error {
if err := validate.Required("key", "body", m.Key); err != nil {
return err
}
return nil
}
func (m *PodAffinityTermLabelSelectorMatchExpressionsItems0) validateOperator(formats strfmt.Registry) error {
if err := validate.Required("operator", "body", m.Operator); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *PodAffinityTermLabelSelectorMatchExpressionsItems0) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *PodAffinityTermLabelSelectorMatchExpressionsItems0) UnmarshalBinary(b []byte) error {
var res PodAffinityTermLabelSelectorMatchExpressionsItems0
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
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"`
@@ -53,9 +62,6 @@ type Tenant struct {
// total size
TotalSize int64 `json:"total_size,omitempty"`
// used size
UsedSize int64 `json:"used_size,omitempty"`
// zones
Zones []*Zone `json:"zones"`
}

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"`
@@ -47,12 +50,12 @@ type TenantList struct {
// namespace
Namespace string `json:"namespace,omitempty"`
// total size
TotalSize int64 `json:"total_size,omitempty"`
// volume count
VolumeCount int64 `json:"volume_count,omitempty"`
// volume size
VolumeSize int64 `json:"volume_size,omitempty"`
// zone count
ZoneCount int64 `json:"zone_count,omitempty"`
}

63
models/tenant_usage.go Normal file
View File

@@ -0,0 +1,63 @@
// 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"
)
// TenantUsage tenant usage
//
// swagger:model tenantUsage
type TenantUsage struct {
// disk used
DiskUsed int64 `json:"disk_used,omitempty"`
// used
Used int64 `json:"used,omitempty"`
}
// Validate validates this tenant usage
func (m *TenantUsage) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *TenantUsage) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *TenantUsage) UnmarshalBinary(b []byte) error {
var res TenantUsage
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

113
models/tls_configuration.go Normal file
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 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"
)
// TLSConfiguration tls configuration
//
// swagger:model tlsConfiguration
type TLSConfiguration struct {
// console
Console *KeyPairConfiguration `json:"console,omitempty"`
// minio
Minio *KeyPairConfiguration `json:"minio,omitempty"`
}
// Validate validates this tls configuration
func (m *TLSConfiguration) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsole(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 *TLSConfiguration) 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 *TLSConfiguration) 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 *TLSConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *TLSConfiguration) UnmarshalBinary(b []byte) error {
var res TLSConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -34,25 +34,59 @@ import (
// swagger:model updateTenantRequest
type UpdateTenantRequest struct {
// console image
// Pattern: ^((.*?)/(.*?):(.+))$
ConsoleImage string `json:"console_image,omitempty"`
// enable prometheus
EnablePrometheus bool `json:"enable_prometheus,omitempty"`
// image
// Pattern: ^((.*?)/(.*?):(.+))$
Image string `json:"image,omitempty"`
// image pull secret
ImagePullSecret string `json:"image_pull_secret,omitempty"`
// image registry
ImageRegistry *ImageRegistry `json:"image_registry,omitempty"`
}
// Validate validates this update tenant request
func (m *UpdateTenantRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsoleImage(formats); err != nil {
res = append(res, err)
}
if err := m.validateImage(formats); err != nil {
res = append(res, err)
}
if err := m.validateImageRegistry(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *UpdateTenantRequest) validateConsoleImage(formats strfmt.Registry) error {
if swag.IsZero(m.ConsoleImage) { // not required
return nil
}
if err := validate.Pattern("console_image", "body", string(m.ConsoleImage), `^((.*?)/(.*?):(.+))$`); err != nil {
return err
}
return nil
}
func (m *UpdateTenantRequest) validateImage(formats strfmt.Registry) error {
if swag.IsZero(m.Image) { // not required
@@ -66,6 +100,24 @@ func (m *UpdateTenantRequest) validateImage(formats strfmt.Registry) error {
return nil
}
func (m *UpdateTenantRequest) validateImageRegistry(formats strfmt.Registry) error {
if swag.IsZero(m.ImageRegistry) { // not required
return nil
}
if m.ImageRegistry != nil {
if err := m.ImageRegistry.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("image_registry")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *UpdateTenantRequest) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -0,0 +1,310 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// VaultConfiguration vault configuration
//
// swagger:model vaultConfiguration
type VaultConfiguration struct {
// approle
// Required: true
Approle *VaultConfigurationApprole `json:"approle"`
// endpoint
// Required: true
Endpoint *string `json:"endpoint"`
// engine
Engine string `json:"engine,omitempty"`
// namespace
Namespace string `json:"namespace,omitempty"`
// prefix
Prefix string `json:"prefix,omitempty"`
// status
Status *VaultConfigurationStatus `json:"status,omitempty"`
// tls
TLS *VaultConfigurationTLS `json:"tls,omitempty"`
}
// Validate validates this vault configuration
func (m *VaultConfiguration) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateApprole(formats); err != nil {
res = append(res, err)
}
if err := m.validateEndpoint(formats); err != nil {
res = append(res, err)
}
if err := m.validateStatus(formats); err != nil {
res = append(res, err)
}
if err := m.validateTLS(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *VaultConfiguration) validateApprole(formats strfmt.Registry) error {
if err := validate.Required("approle", "body", m.Approle); err != nil {
return err
}
if m.Approle != nil {
if err := m.Approle.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("approle")
}
return err
}
}
return nil
}
func (m *VaultConfiguration) validateEndpoint(formats strfmt.Registry) error {
if err := validate.Required("endpoint", "body", m.Endpoint); err != nil {
return err
}
return nil
}
func (m *VaultConfiguration) validateStatus(formats strfmt.Registry) error {
if swag.IsZero(m.Status) { // not required
return nil
}
if m.Status != nil {
if err := m.Status.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("status")
}
return err
}
}
return nil
}
func (m *VaultConfiguration) validateTLS(formats strfmt.Registry) error {
if swag.IsZero(m.TLS) { // not required
return nil
}
if m.TLS != nil {
if err := m.TLS.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tls")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *VaultConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *VaultConfiguration) UnmarshalBinary(b []byte) error {
var res VaultConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// VaultConfigurationApprole vault configuration approle
//
// swagger:model VaultConfigurationApprole
type VaultConfigurationApprole struct {
// engine
Engine string `json:"engine,omitempty"`
// id
// Required: true
ID *string `json:"id"`
// retry
Retry int64 `json:"retry,omitempty"`
// secret
// Required: true
Secret *string `json:"secret"`
}
// Validate validates this vault configuration approle
func (m *VaultConfigurationApprole) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateID(formats); err != nil {
res = append(res, err)
}
if err := m.validateSecret(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *VaultConfigurationApprole) validateID(formats strfmt.Registry) error {
if err := validate.Required("approle"+"."+"id", "body", m.ID); err != nil {
return err
}
return nil
}
func (m *VaultConfigurationApprole) validateSecret(formats strfmt.Registry) error {
if err := validate.Required("approle"+"."+"secret", "body", m.Secret); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *VaultConfigurationApprole) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *VaultConfigurationApprole) UnmarshalBinary(b []byte) error {
var res VaultConfigurationApprole
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// VaultConfigurationStatus vault configuration status
//
// swagger:model VaultConfigurationStatus
type VaultConfigurationStatus struct {
// ping
Ping int64 `json:"ping,omitempty"`
}
// Validate validates this vault configuration status
func (m *VaultConfigurationStatus) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *VaultConfigurationStatus) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *VaultConfigurationStatus) UnmarshalBinary(b []byte) error {
var res VaultConfigurationStatus
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// VaultConfigurationTLS vault configuration TLS
//
// swagger:model VaultConfigurationTLS
type VaultConfigurationTLS struct {
// ca
Ca string `json:"ca,omitempty"`
// crt
Crt string `json:"crt,omitempty"`
// key
Key string `json:"key,omitempty"`
}
// Validate validates this vault configuration TLS
func (m *VaultConfigurationTLS) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *VaultConfigurationTLS) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *VaultConfigurationTLS) UnmarshalBinary(b []byte) error {
var res VaultConfigurationTLS
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -34,13 +34,25 @@ import (
// swagger:model zone
type Zone struct {
// affinity
Affinity *ZoneAffinity `json:"affinity,omitempty"`
// name
Name string `json:"name,omitempty"`
// NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
NodeSelector map[string]string `json:"node_selector,omitempty"`
// resources
Resources *ZoneResources `json:"resources,omitempty"`
// servers
// Required: true
Servers *int64 `json:"servers"`
// tolerations
Tolerations ZoneTolerations `json:"tolerations,omitempty"`
// volume configuration
// Required: true
VolumeConfiguration *ZoneVolumeConfiguration `json:"volume_configuration"`
@@ -54,10 +66,22 @@ type Zone struct {
func (m *Zone) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAffinity(formats); err != nil {
res = append(res, err)
}
if err := m.validateResources(formats); err != nil {
res = append(res, err)
}
if err := m.validateServers(formats); err != nil {
res = append(res, err)
}
if err := m.validateTolerations(formats); err != nil {
res = append(res, err)
}
if err := m.validateVolumeConfiguration(formats); err != nil {
res = append(res, err)
}
@@ -72,6 +96,42 @@ func (m *Zone) Validate(formats strfmt.Registry) error {
return nil
}
func (m *Zone) validateAffinity(formats strfmt.Registry) error {
if swag.IsZero(m.Affinity) { // not required
return nil
}
if m.Affinity != nil {
if err := m.Affinity.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("affinity")
}
return err
}
}
return nil
}
func (m *Zone) validateResources(formats strfmt.Registry) error {
if swag.IsZero(m.Resources) { // not required
return nil
}
if m.Resources != nil {
if err := m.Resources.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("resources")
}
return err
}
}
return nil
}
func (m *Zone) validateServers(formats strfmt.Registry) error {
if err := validate.Required("servers", "body", m.Servers); err != nil {
@@ -81,6 +141,22 @@ func (m *Zone) validateServers(formats strfmt.Registry) error {
return nil
}
func (m *Zone) validateTolerations(formats strfmt.Registry) error {
if swag.IsZero(m.Tolerations) { // not required
return nil
}
if err := m.Tolerations.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tolerations")
}
return err
}
return nil
}
func (m *Zone) validateVolumeConfiguration(formats strfmt.Registry) error {
if err := validate.Required("volume_configuration", "body", m.VolumeConfiguration); err != nil {
@@ -131,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"`

726
models/zone_affinity.go Normal file
View File

@@ -0,0 +1,726 @@
// 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"
)
// ZoneAffinity If specified, affinity will define the pod's scheduling constraints
//
// swagger:model zoneAffinity
type ZoneAffinity struct {
// node affinity
NodeAffinity *ZoneAffinityNodeAffinity `json:"nodeAffinity,omitempty"`
// pod affinity
PodAffinity *ZoneAffinityPodAffinity `json:"podAffinity,omitempty"`
// pod anti affinity
PodAntiAffinity *ZoneAffinityPodAntiAffinity `json:"podAntiAffinity,omitempty"`
}
// Validate validates this zone affinity
func (m *ZoneAffinity) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateNodeAffinity(formats); err != nil {
res = append(res, err)
}
if err := m.validatePodAffinity(formats); err != nil {
res = append(res, err)
}
if err := m.validatePodAntiAffinity(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneAffinity) validateNodeAffinity(formats strfmt.Registry) error {
if swag.IsZero(m.NodeAffinity) { // not required
return nil
}
if m.NodeAffinity != nil {
if err := m.NodeAffinity.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("nodeAffinity")
}
return err
}
}
return nil
}
func (m *ZoneAffinity) validatePodAffinity(formats strfmt.Registry) error {
if swag.IsZero(m.PodAffinity) { // not required
return nil
}
if m.PodAffinity != nil {
if err := m.PodAffinity.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("podAffinity")
}
return err
}
}
return nil
}
func (m *ZoneAffinity) validatePodAntiAffinity(formats strfmt.Registry) error {
if swag.IsZero(m.PodAntiAffinity) { // not required
return nil
}
if m.PodAntiAffinity != nil {
if err := m.PodAntiAffinity.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("podAntiAffinity")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneAffinity) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneAffinity) UnmarshalBinary(b []byte) error {
var res ZoneAffinity
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// ZoneAffinityNodeAffinity Describes node affinity scheduling rules for the pod.
//
// swagger:model ZoneAffinityNodeAffinity
type ZoneAffinityNodeAffinity struct {
// The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred.
PreferredDuringSchedulingIgnoredDuringExecution []*ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0 `json:"preferredDuringSchedulingIgnoredDuringExecution"`
// required during scheduling ignored during execution
RequiredDuringSchedulingIgnoredDuringExecution *ZoneAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution `json:"requiredDuringSchedulingIgnoredDuringExecution,omitempty"`
}
// Validate validates this zone affinity node affinity
func (m *ZoneAffinityNodeAffinity) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePreferredDuringSchedulingIgnoredDuringExecution(formats); err != nil {
res = append(res, err)
}
if err := m.validateRequiredDuringSchedulingIgnoredDuringExecution(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneAffinityNodeAffinity) validatePreferredDuringSchedulingIgnoredDuringExecution(formats strfmt.Registry) error {
if swag.IsZero(m.PreferredDuringSchedulingIgnoredDuringExecution) { // not required
return nil
}
for i := 0; i < len(m.PreferredDuringSchedulingIgnoredDuringExecution); i++ {
if swag.IsZero(m.PreferredDuringSchedulingIgnoredDuringExecution[i]) { // not required
continue
}
if m.PreferredDuringSchedulingIgnoredDuringExecution[i] != nil {
if err := m.PreferredDuringSchedulingIgnoredDuringExecution[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("nodeAffinity" + "." + "preferredDuringSchedulingIgnoredDuringExecution" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *ZoneAffinityNodeAffinity) validateRequiredDuringSchedulingIgnoredDuringExecution(formats strfmt.Registry) error {
if swag.IsZero(m.RequiredDuringSchedulingIgnoredDuringExecution) { // not required
return nil
}
if m.RequiredDuringSchedulingIgnoredDuringExecution != nil {
if err := m.RequiredDuringSchedulingIgnoredDuringExecution.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("nodeAffinity" + "." + "requiredDuringSchedulingIgnoredDuringExecution")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneAffinityNodeAffinity) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneAffinityNodeAffinity) UnmarshalBinary(b []byte) error {
var res ZoneAffinityNodeAffinity
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0 An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).
//
// swagger:model ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0
type ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0 struct {
// A node selector term, associated with the corresponding weight.
// Required: true
Preference *NodeSelectorTerm `json:"preference"`
// Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.
// Required: true
Weight *int32 `json:"weight"`
}
// Validate validates this zone affinity node affinity preferred during scheduling ignored during execution items0
func (m *ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePreference(formats); err != nil {
res = append(res, err)
}
if err := m.validateWeight(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) validatePreference(formats strfmt.Registry) error {
if err := validate.Required("preference", "body", m.Preference); err != nil {
return err
}
if m.Preference != nil {
if err := m.Preference.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("preference")
}
return err
}
}
return nil
}
func (m *ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) validateWeight(formats strfmt.Registry) error {
if err := validate.Required("weight", "body", m.Weight); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) UnmarshalBinary(b []byte) error {
var res ZoneAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// ZoneAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node.
//
// swagger:model ZoneAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution
type ZoneAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution struct {
// Required. A list of node selector terms. The terms are ORed.
// Required: true
NodeSelectorTerms []*NodeSelectorTerm `json:"nodeSelectorTerms"`
}
// Validate validates this zone affinity node affinity required during scheduling ignored during execution
func (m *ZoneAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateNodeSelectorTerms(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution) validateNodeSelectorTerms(formats strfmt.Registry) error {
if err := validate.Required("nodeAffinity"+"."+"requiredDuringSchedulingIgnoredDuringExecution"+"."+"nodeSelectorTerms", "body", m.NodeSelectorTerms); err != nil {
return err
}
for i := 0; i < len(m.NodeSelectorTerms); i++ {
if swag.IsZero(m.NodeSelectorTerms[i]) { // not required
continue
}
if m.NodeSelectorTerms[i] != nil {
if err := m.NodeSelectorTerms[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("nodeAffinity" + "." + "requiredDuringSchedulingIgnoredDuringExecution" + "." + "nodeSelectorTerms" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution) UnmarshalBinary(b []byte) error {
var res ZoneAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// ZoneAffinityPodAffinity Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)).
//
// swagger:model ZoneAffinityPodAffinity
type ZoneAffinityPodAffinity struct {
// The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.
PreferredDuringSchedulingIgnoredDuringExecution []*ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0 `json:"preferredDuringSchedulingIgnoredDuringExecution"`
// If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.
RequiredDuringSchedulingIgnoredDuringExecution []*PodAffinityTerm `json:"requiredDuringSchedulingIgnoredDuringExecution"`
}
// Validate validates this zone affinity pod affinity
func (m *ZoneAffinityPodAffinity) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePreferredDuringSchedulingIgnoredDuringExecution(formats); err != nil {
res = append(res, err)
}
if err := m.validateRequiredDuringSchedulingIgnoredDuringExecution(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneAffinityPodAffinity) validatePreferredDuringSchedulingIgnoredDuringExecution(formats strfmt.Registry) error {
if swag.IsZero(m.PreferredDuringSchedulingIgnoredDuringExecution) { // not required
return nil
}
for i := 0; i < len(m.PreferredDuringSchedulingIgnoredDuringExecution); i++ {
if swag.IsZero(m.PreferredDuringSchedulingIgnoredDuringExecution[i]) { // not required
continue
}
if m.PreferredDuringSchedulingIgnoredDuringExecution[i] != nil {
if err := m.PreferredDuringSchedulingIgnoredDuringExecution[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("podAffinity" + "." + "preferredDuringSchedulingIgnoredDuringExecution" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *ZoneAffinityPodAffinity) validateRequiredDuringSchedulingIgnoredDuringExecution(formats strfmt.Registry) error {
if swag.IsZero(m.RequiredDuringSchedulingIgnoredDuringExecution) { // not required
return nil
}
for i := 0; i < len(m.RequiredDuringSchedulingIgnoredDuringExecution); i++ {
if swag.IsZero(m.RequiredDuringSchedulingIgnoredDuringExecution[i]) { // not required
continue
}
if m.RequiredDuringSchedulingIgnoredDuringExecution[i] != nil {
if err := m.RequiredDuringSchedulingIgnoredDuringExecution[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("podAffinity" + "." + "requiredDuringSchedulingIgnoredDuringExecution" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneAffinityPodAffinity) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneAffinityPodAffinity) UnmarshalBinary(b []byte) error {
var res ZoneAffinityPodAffinity
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0 The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)
//
// swagger:model ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0
type ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0 struct {
// pod affinity term
// Required: true
PodAffinityTerm *PodAffinityTerm `json:"podAffinityTerm"`
// weight associated with matching the corresponding podAffinityTerm, in the range 1-100.
// Required: true
Weight *int32 `json:"weight"`
}
// Validate validates this zone affinity pod affinity preferred during scheduling ignored during execution items0
func (m *ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePodAffinityTerm(formats); err != nil {
res = append(res, err)
}
if err := m.validateWeight(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) validatePodAffinityTerm(formats strfmt.Registry) error {
if err := validate.Required("podAffinityTerm", "body", m.PodAffinityTerm); err != nil {
return err
}
if m.PodAffinityTerm != nil {
if err := m.PodAffinityTerm.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("podAffinityTerm")
}
return err
}
}
return nil
}
func (m *ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) validateWeight(formats strfmt.Registry) error {
if err := validate.Required("weight", "body", m.Weight); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) UnmarshalBinary(b []byte) error {
var res ZoneAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// ZoneAffinityPodAntiAffinity Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)).
//
// swagger:model ZoneAffinityPodAntiAffinity
type ZoneAffinityPodAntiAffinity struct {
// The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.
PreferredDuringSchedulingIgnoredDuringExecution []*ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0 `json:"preferredDuringSchedulingIgnoredDuringExecution"`
// If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.
RequiredDuringSchedulingIgnoredDuringExecution []*PodAffinityTerm `json:"requiredDuringSchedulingIgnoredDuringExecution"`
}
// Validate validates this zone affinity pod anti affinity
func (m *ZoneAffinityPodAntiAffinity) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePreferredDuringSchedulingIgnoredDuringExecution(formats); err != nil {
res = append(res, err)
}
if err := m.validateRequiredDuringSchedulingIgnoredDuringExecution(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneAffinityPodAntiAffinity) validatePreferredDuringSchedulingIgnoredDuringExecution(formats strfmt.Registry) error {
if swag.IsZero(m.PreferredDuringSchedulingIgnoredDuringExecution) { // not required
return nil
}
for i := 0; i < len(m.PreferredDuringSchedulingIgnoredDuringExecution); i++ {
if swag.IsZero(m.PreferredDuringSchedulingIgnoredDuringExecution[i]) { // not required
continue
}
if m.PreferredDuringSchedulingIgnoredDuringExecution[i] != nil {
if err := m.PreferredDuringSchedulingIgnoredDuringExecution[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("podAntiAffinity" + "." + "preferredDuringSchedulingIgnoredDuringExecution" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *ZoneAffinityPodAntiAffinity) validateRequiredDuringSchedulingIgnoredDuringExecution(formats strfmt.Registry) error {
if swag.IsZero(m.RequiredDuringSchedulingIgnoredDuringExecution) { // not required
return nil
}
for i := 0; i < len(m.RequiredDuringSchedulingIgnoredDuringExecution); i++ {
if swag.IsZero(m.RequiredDuringSchedulingIgnoredDuringExecution[i]) { // not required
continue
}
if m.RequiredDuringSchedulingIgnoredDuringExecution[i] != nil {
if err := m.RequiredDuringSchedulingIgnoredDuringExecution[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("podAntiAffinity" + "." + "requiredDuringSchedulingIgnoredDuringExecution" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneAffinityPodAntiAffinity) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneAffinityPodAntiAffinity) UnmarshalBinary(b []byte) error {
var res ZoneAffinityPodAntiAffinity
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0 The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)
//
// swagger:model ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0
type ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0 struct {
// pod affinity term
// Required: true
PodAffinityTerm *PodAffinityTerm `json:"podAffinityTerm"`
// weight associated with matching the corresponding podAffinityTerm, in the range 1-100.
// Required: true
Weight *int32 `json:"weight"`
}
// Validate validates this zone affinity pod anti affinity preferred during scheduling ignored during execution items0
func (m *ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePodAffinityTerm(formats); err != nil {
res = append(res, err)
}
if err := m.validateWeight(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) validatePodAffinityTerm(formats strfmt.Registry) error {
if err := validate.Required("podAffinityTerm", "body", m.PodAffinityTerm); err != nil {
return err
}
if m.PodAffinityTerm != nil {
if err := m.PodAffinityTerm.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("podAffinityTerm")
}
return err
}
}
return nil
}
func (m *ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) validateWeight(formats strfmt.Registry) error {
if err := validate.Required("weight", "body", m.Weight); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0) UnmarshalBinary(b []byte) error {
var res ZoneAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

63
models/zone_resources.go Normal file
View File

@@ -0,0 +1,63 @@
// 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"
)
// ZoneResources If provided, use these requests and limit for cpu/memory resource allocation
//
// swagger:model zoneResources
type ZoneResources struct {
// Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
Limits map[string]int64 `json:"limits,omitempty"`
// Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
Requests map[string]int64 `json:"requests,omitempty"`
}
// Validate validates this zone resources
func (m *ZoneResources) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *ZoneResources) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneResources) UnmarshalBinary(b []byte) error {
var res ZoneResources
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,81 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// ZoneTolerationSeconds TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.
//
// swagger:model zoneTolerationSeconds
type ZoneTolerationSeconds struct {
// seconds
// Required: true
Seconds *int64 `json:"seconds"`
}
// Validate validates this zone toleration seconds
func (m *ZoneTolerationSeconds) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateSeconds(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneTolerationSeconds) validateSeconds(formats strfmt.Registry) error {
if err := validate.Required("seconds", "body", m.Seconds); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneTolerationSeconds) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneTolerationSeconds) UnmarshalBinary(b []byte) error {
var res ZoneTolerationSeconds
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

133
models/zone_tolerations.go Normal file
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 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"
)
// ZoneTolerations Tolerations allows users to set entries like effect, key, operator, value.
//
// swagger:model zoneTolerations
type ZoneTolerations []*ZoneTolerationsItems0
// Validate validates this zone tolerations
func (m ZoneTolerations) Validate(formats strfmt.Registry) error {
var res []error
for i := 0; i < len(m); i++ {
if swag.IsZero(m[i]) { // not required
continue
}
if m[i] != nil {
if err := m[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
}
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ZoneTolerationsItems0 The pod this Toleration is attached to tolerates any taint that matches the triple <key,value,effect> using the matching operator <operator>.
//
// swagger:model ZoneTolerationsItems0
type ZoneTolerationsItems0 struct {
// Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.
Effect string `json:"effect,omitempty"`
// Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys.
Key string `json:"key,omitempty"`
// Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.
Operator string `json:"operator,omitempty"`
// toleration seconds
TolerationSeconds *ZoneTolerationSeconds `json:"tolerationSeconds,omitempty"`
// Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.
Value string `json:"value,omitempty"`
}
// Validate validates this zone tolerations items0
func (m *ZoneTolerationsItems0) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateTolerationSeconds(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ZoneTolerationsItems0) validateTolerationSeconds(formats strfmt.Registry) error {
if swag.IsZero(m.TolerationSeconds) { // not required
return nil
}
if m.TolerationSeconds != nil {
if err := m.TolerationSeconds.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tolerationSeconds")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ZoneTolerationsItems0) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ZoneTolerationsItems0) UnmarshalBinary(b []byte) error {
var res ZoneTolerationsItems0
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

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

@@ -1,231 +0,0 @@
// 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 auth
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"io"
"log"
"net/http"
"strings"
"time"
jwtgo "github.com/dgrijalva/jwt-go"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
xjwt "github.com/minio/console/pkg/auth/jwt"
"github.com/minio/minio-go/v7/pkg/credentials"
uuid "github.com/satori/go.uuid"
"golang.org/x/crypto/pbkdf2"
)
var (
errAuthentication = errors.New("authentication failed, check your access credentials")
errNoAuthToken = errors.New("JWT token missing")
errReadingToken = errors.New("JWT internal data is malformed")
errClaimsFormat = errors.New("encrypted jwt claims not in the right format")
)
// derivedKey is the key used to encrypt the JWT claims, its derived using pbkdf on CONSOLE_PBKDF_PASSPHRASE with CONSOLE_PBKDF_SALT
var derivedKey = pbkdf2.Key([]byte(xjwt.GetPBKDFPassphrase()), []byte(xjwt.GetPBKDFSalt()), 4096, 32, sha1.New)
// IsJWTValid returns true or false depending if the provided jwt is valid or not
func IsJWTValid(token string) bool {
_, err := JWTAuthenticate(token)
return err == nil
}
// DecryptedClaims claims struct for decrypted credentials
type DecryptedClaims struct {
AccessKeyID string
SecretAccessKey string
SessionToken string
Actions []string
}
// JWTAuthenticate takes a jwt, decode it, extract claims and validate the signature
// if the jwt claims.Data is valid we proceed to decrypt the information inside
//
// returns claims after validation in the following format:
//
// type DecryptedClaims struct {
// AccessKeyID
// SecretAccessKey
// SessionToken
// }
func JWTAuthenticate(token string) (*DecryptedClaims, error) {
if token == "" {
return nil, errNoAuthToken
}
// initialize claims object
claims := xjwt.NewMapClaims()
// populate the claims object
if err := xjwt.ParseWithClaims(token, claims); err != nil {
return nil, errAuthentication
}
// decrypt the claims.Data field
claimTokens, err := decryptClaims(claims.Data)
if err != nil {
// we print decryption token error information for debugging purposes
log.Println(err)
// we return a generic error that doesn't give any information to attackers
return nil, errReadingToken
}
// claimsTokens contains the decrypted STS claims
return claimTokens, nil
}
// NewJWTWithClaimsForClient generates a new jwt with claims based on the provided STS credentials, first
// encrypts the claims and the sign them
func NewJWTWithClaimsForClient(credentials *credentials.Value, actions []string, audience string) (string, error) {
if credentials != nil {
encryptedClaims, err := encryptClaims(credentials.AccessKeyID, credentials.SecretAccessKey, credentials.SessionToken, actions)
if err != nil {
return "", err
}
claims := xjwt.NewStandardClaims()
claims.SetExpiry(time.Now().UTC().Add(xjwt.GetConsoleSTSAndJWTDurationTime()))
claims.SetSubject(uuid.NewV4().String())
claims.SetData(encryptedClaims)
claims.SetAudience(audience)
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
return jwt.SignedString([]byte(xjwt.GetHmacJWTSecret()))
}
return "", errors.New("provided credentials are empty")
}
// encryptClaims() receives the 3 STS claims, concatenate them and encrypt them using AES-GCM
// 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)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// decryptClaims() receives base64 encoded ciphertext, decode it, decrypt it (AES-GCM) and produces a *DecryptedClaims object
func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
decoded, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
log.Println(err)
return nil, errClaimsFormat
}
plaintext, err := decrypt(decoded)
if err != nil {
log.Println(err)
return nil, errClaimsFormat
}
s := strings.Split(string(plaintext), "#")
// Validate that the decrypted string has the right format "accessKeyID:secretAccessKey:sessionToken"
if len(s) != 4 {
return nil, errClaimsFormat
}
accessKeyID, secretAccessKey, sessionToken, actions := s[0], s[1], s[2], s[3]
actionsList := strings.Split(actions, ",")
return &DecryptedClaims{
AccessKeyID: accessKeyID,
SecretAccessKey: secretAccessKey,
SessionToken: sessionToken,
Actions: actionsList,
}, 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)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
cipherText := gcm.Seal(nonce, nonce, plaintext, nil)
return cipherText, 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)
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)
if err != nil {
return nil, err
}
return plaintext, nil
}
// GetTokenFromRequest returns a token from a http Request
// either defined on a cookie `token` or on Authorization header.
//
// Authorization Header needs to be like "Authorization Bearer <jwt_token>"
func GetTokenFromRequest(r *http.Request) (*string, error) {
// Get Auth token
var reqToken string
// Token might come either as a Cookie or as a Header
// if not set in cookie, check if it is set on Header.
tokenCookie, err := r.Cookie("token")
if err != nil {
headerToken := r.Header.Get("Authorization")
// reqToken should come as "Bearer <token>"
splitHeaderToken := strings.Split(headerToken, "Bearer")
if len(splitHeaderToken) <= 1 {
return nil, errNoAuthToken
}
reqToken = strings.TrimSpace(splitHeaderToken[1])
} else {
reqToken = strings.TrimSpace(tokenCookie.Value)
}
return swag.String(reqToken), nil
}
func GetClaimsFromTokenInRequest(req *http.Request) (*models.Principal, error) {
sessionID, err := GetTokenFromRequest(req)
if err != nil {
return nil, err
}
// Perform decryption of the JWT, if Console is able to decrypt the JWT that means a valid session
// was used in the first place to get it
claims, err := JWTAuthenticate(*sessionID)
if err != nil {
return nil, err
}
return &models.Principal{
AccessKeyID: claims.AccessKeyID,
Actions: claims.Actions,
SecretAccessKey: claims.SecretAccessKey,
SessionToken: claims.SessionToken,
}, nil
}

View File

@@ -1,281 +0,0 @@
// 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 jwt
// This file is a re-implementation of the original code here with some
// additional allocation tweaks reproduced using GODEBUG=allocfreetrace=1
// original file https://github.com/dgrijalva/jwt-go/blob/master/parser.go
// borrowed under MIT License https://github.com/dgrijalva/jwt-go/blob/master/LICENSE
import (
"crypto"
"crypto/hmac"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
jwtgo "github.com/dgrijalva/jwt-go"
jsoniter "github.com/json-iterator/go"
)
const (
claimData = "data"
claimSub = "sub"
)
// SigningMethodHMAC - Implements the HMAC-SHA family of signing methods signing methods
// Expects key type of []byte for both signing and validation
type SigningMethodHMAC struct {
Name string
Hash crypto.Hash
}
// Specific instances for HS256, HS384, HS512
var (
SigningMethodHS256 *SigningMethodHMAC
SigningMethodHS384 *SigningMethodHMAC
SigningMethodHS512 *SigningMethodHMAC
)
var (
base64BufPool sync.Pool
hmacSigners []*SigningMethodHMAC
)
func init() {
base64BufPool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 8192)
return &buf
},
}
hmacSigners = []*SigningMethodHMAC{
{"HS256", crypto.SHA256},
{"HS384", crypto.SHA384},
{"HS512", crypto.SHA512},
}
}
// StandardClaims are basically standard claims with "Data"
type StandardClaims struct {
Data string `json:"data,omitempty"`
jwtgo.StandardClaims
}
// MapClaims - implements custom unmarshaller
type MapClaims struct {
Data string `json:"data,omitempty"`
Subject string `json:"sub,omitempty"`
jwtgo.MapClaims
}
// NewStandardClaims - initializes standard claims
func NewStandardClaims() *StandardClaims {
return &StandardClaims{}
}
// SetIssuer sets issuer for these claims
func (c *StandardClaims) SetIssuer(issuer string) {
c.Issuer = issuer
}
// SetAudience sets audience for these claims
func (c *StandardClaims) SetAudience(aud string) {
c.Audience = aud
}
// SetExpiry sets expiry in unix epoch secs
func (c *StandardClaims) SetExpiry(t time.Time) {
c.ExpiresAt = t.Unix()
}
// SetSubject sets unique identifier for the jwt
func (c *StandardClaims) SetSubject(subject string) {
c.Subject = subject
}
// SetData sets the "Data" custom field.
func (c *StandardClaims) SetData(data string) {
c.Data = data
}
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
// claims interface, additionally validates "Data" field.
func (c *StandardClaims) Valid() error {
if err := c.StandardClaims.Valid(); err != nil {
return err
}
if c.Data == "" || c.Subject == "" {
return jwtgo.NewValidationError("data/sub",
jwtgo.ValidationErrorClaimsInvalid)
}
return nil
}
// NewMapClaims - Initializes a new map claims
func NewMapClaims() *MapClaims {
return &MapClaims{MapClaims: jwtgo.MapClaims{}}
}
// Lookup returns the value and if the key is found.
func (c *MapClaims) Lookup(key string) (value string, ok bool) {
var vinterface interface{}
vinterface, ok = c.MapClaims[key]
if ok {
value, ok = vinterface.(string)
}
return
}
// SetExpiry sets expiry in unix epoch secs
func (c *MapClaims) SetExpiry(t time.Time) {
c.MapClaims["exp"] = t.Unix()
}
// SetData sets the "Data" custom field.
func (c *MapClaims) SetData(data string) {
c.MapClaims[claimData] = data
}
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
// claims interface, additionally validates "Data" field.
func (c *MapClaims) Valid() error {
if err := c.MapClaims.Valid(); err != nil {
return err
}
if c.Data == "" || c.Subject == "" {
return jwtgo.NewValidationError("data/subject",
jwtgo.ValidationErrorClaimsInvalid)
}
return nil
}
// Map returns underlying low-level map claims.
func (c *MapClaims) Map() map[string]interface{} {
return c.MapClaims
}
// MarshalJSON marshals the MapClaims struct
func (c *MapClaims) MarshalJSON() ([]byte, error) {
return json.Marshal(c.MapClaims)
}
// https://tools.ietf.org/html/rfc7519#page-11
type jwtHeader struct {
Algorithm string `json:"alg"`
Type string `json:"typ"`
}
// ParseWithClaims - parse the token string, valid methods.
func ParseWithClaims(tokenStr string, claims *MapClaims) error {
bufp := base64BufPool.Get().(*[]byte)
defer base64BufPool.Put(bufp)
signer, err := parseUnverifiedMapClaims(tokenStr, claims, *bufp)
if err != nil {
return err
}
i := strings.LastIndex(tokenStr, ".")
if i < 0 {
return jwtgo.ErrSignatureInvalid
}
n, err := base64Decode(tokenStr[i+1:], *bufp)
if err != nil {
return err
}
var ok bool
claims.Data, ok = claims.Lookup(claimData)
if !ok {
return jwtgo.NewValidationError("data missing",
jwtgo.ValidationErrorClaimsInvalid)
}
claims.Subject, ok = claims.Lookup(claimSub)
if !ok {
return jwtgo.NewValidationError("sub missing",
jwtgo.ValidationErrorClaimsInvalid)
}
hasher := hmac.New(signer.Hash.New, []byte(GetHmacJWTSecret()))
hasher.Write([]byte(tokenStr[:i]))
if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) {
return jwtgo.ErrSignatureInvalid
}
// Signature is valid, lets validate the claims for
// other fields such as expiry etc.
return claims.Valid()
}
// base64Decode returns the bytes represented by the base64 string s.
func base64Decode(s string, buf []byte) (int, error) {
return base64.RawURLEncoding.Decode(buf, []byte(s))
}
// ParseUnverifiedMapClaims - WARNING: Don't use this method unless you know what you're doing
//
// This method parses the token but doesn't validate the signature. It's only
// ever useful in cases where you know the signature is valid (because it has
// been checked previously in the stack) and you want to extract values from
// it.
func parseUnverifiedMapClaims(tokenString string, claims *MapClaims, buf []byte) (*SigningMethodHMAC, error) {
if strings.Count(tokenString, ".") != 2 {
return nil, jwtgo.ErrSignatureInvalid
}
i := strings.Index(tokenString, ".")
j := strings.LastIndex(tokenString, ".")
n, err := base64Decode(tokenString[:i], buf)
if err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
var header = jwtHeader{}
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(buf[:n], &header); err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
n, err = base64Decode(tokenString[i+1:j], buf)
if err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
if err = json.Unmarshal(buf[:n], &claims.MapClaims); err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
for _, signer := range hmacSigners {
if header.Algorithm == signer.Name {
return signer, nil
}
}
return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", header.Algorithm),
jwtgo.ValidationErrorUnverifiable)
}

View File

@@ -24,11 +24,11 @@ import (
)
var (
errInvalidCredentials = errors.New("invalid Credentials")
errInvalidCredentials = errors.New("invalid Login")
)
// GetConsoleCredentialsFromLDAP authenticates the user against MinIO when the LDAP integration is enabled
// if the authentication succeed *credentials.Credentials object is returned and we continue with the normal STSAssumeRole flow
// if the authentication succeed *credentials.Login object is returned and we continue with the normal STSAssumeRole flow
func GetConsoleCredentialsFromLDAP(endpoint, ldapUser, ldapPassword string) (*credentials.Credentials, error) {
creds, err := credentials.NewLDAPIdentity(endpoint, ldapUser, ldapPassword)
if err != nil {

View File

@@ -76,7 +76,7 @@ func isServiceAccountTokenValid(ctx context.Context, operatorClient OperatorClie
return true
}
// GetConsoleCredentialsForOperator will validate the provided JWT (service account token) and return it in the form of credentials.Credentials
// GetConsoleCredentialsForOperator will validate the provided JWT (service account token) and return it in the form of credentials.Login
func GetConsoleCredentialsForOperator(jwt string) (*credentials.Credentials, error) {
ctx := context.Background()
opClientClientSet, err := cluster.OperatorClient(jwt)

323
pkg/auth/token.go Normal file
View File

@@ -0,0 +1,323 @@
// 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 auth
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
"github.com/go-openapi/swag"
"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"
)
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
var derivedKey = pbkdf2.Key([]byte(token.GetPBKDFPassphrase()), []byte(token.GetPBKDFSalt()), 4096, 32, sha1.New)
// IsSessionTokenValid returns true or false depending if the provided session token is valid or not
func IsSessionTokenValid(token string) bool {
_, err := SessionTokenAuthenticate(token)
return err == nil
}
// DecryptedClaims claims struct for decrypted credentials
type DecryptedClaims struct {
AccessKeyID string
SecretAccessKey string
SessionToken string
Actions []string
}
// SessionTokenAuthenticate takes a session token, decode it, extract claims and validate the signature
// if the session token claims are valid we proceed to decrypt the information inside
//
// returns claims after validation in the following format:
//
// type DecryptedClaims struct {
// AccessKeyID
// SecretAccessKey
// SessionToken
// }
func SessionTokenAuthenticate(token string) (*DecryptedClaims, error) {
if token == "" {
return nil, errNoAuthToken
}
// decrypt encrypted token
claimTokens, err := decryptClaims(token)
if err != nil {
// we print decryption token error information for debugging purposes
log.Println(err)
// we return a generic error that doesn't give any information to attackers
return nil, errReadingToken
}
// claimsTokens contains the decrypted JWT for Console
return claimTokens, nil
}
// NewEncryptedTokenForClient generates a new session token with claims based on the provided STS credentials, first
// encrypts the claims and the sign them
func NewEncryptedTokenForClient(credentials *credentials.Value, actions []string) (string, error) {
if credentials != nil {
encryptedClaims, err := encryptClaims(credentials.AccessKeyID, credentials.SecretAccessKey, credentials.SessionToken, actions)
if err != nil {
return "", err
}
return encryptedClaims, nil
}
return "", errors.New("provided credentials are empty")
}
// encryptClaims() receives the STS claims, concatenate them and encrypt them using AES-GCM
// 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, []byte{})
if err != nil {
log.Println(err)
return "", errorGeneric
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// decryptClaims() receives base64 encoded ciphertext, decode it, decrypt it (AES-GCM) and produces a *DecryptedClaims object
func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
decoded, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
log.Println(err)
return nil, errClaimsFormat
}
plaintext, err := decrypt(decoded, []byte{})
if err != nil {
log.Println(err)
return nil, errClaimsFormat
}
s := strings.Split(string(plaintext), "#")
// Validate that the decrypted string has the right format "accessKeyID:secretAccessKey:sessionToken"
if len(s) != 4 {
return nil, errClaimsFormat
}
accessKeyID, secretAccessKey, sessionToken, actions := s[0], s[1], s[2], s[3]
actionsList := strings.Split(actions, ",")
return &DecryptedClaims{
AccessKeyID: accessKeyID,
SecretAccessKey: secretAccessKey,
SessionToken: sessionToken,
Actions: actionsList,
}, nil
}
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:
// iv | AEAD ID | nonce | encrypted data
// 32 1 12 ~ len(data)
func encrypt(plaintext, associatedData []byte) ([]byte, error) {
iv, err := sioutil.Random(32) // 32 bytes IV
if err != nil {
return nil, err
}
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[:16]) // 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
}
sealedBytes := aead.Seal(nil, nonce, plaintext, associatedData)
// ciphertext = iv | AEAD ID | nonce | sealed bytes
var buf bytes.Buffer
buf.Write(iv)
buf.WriteByte(algorithm)
buf.Write(nonce)
buf.Write(sealedBytes)
return buf.Bytes(), nil
}
// 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 (
iv [32]byte
algorithm [1]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, iv[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, algorithm[:]); 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[:16]) // 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
}
plaintext, err := aead.Open(nil, nonce[:], sealedBytes, associatedData)
if err != nil {
return nil, err
}
return plaintext, nil
}
// GetTokenFromRequest returns a token from a http Request
// either defined on a cookie `token` or on Authorization header.
//
// Authorization Header needs to be like "Authorization Bearer <token>"
func GetTokenFromRequest(r *http.Request) (*string, error) {
// Get Auth token
var reqToken string
// Token might come either as a Cookie or as a Header
// if not set in cookie, check if it is set on Header.
tokenCookie, err := r.Cookie("token")
if err != nil {
headerToken := r.Header.Get("Authorization")
// reqToken should come as "Bearer <token>"
splitHeaderToken := strings.Split(headerToken, "Bearer")
if len(splitHeaderToken) <= 1 {
return nil, errNoAuthToken
}
reqToken = strings.TrimSpace(splitHeaderToken[1])
} else {
reqToken = strings.TrimSpace(tokenCookie.Value)
}
return swag.String(reqToken), nil
}
func GetClaimsFromTokenInRequest(req *http.Request) (*models.Principal, error) {
sessionID, err := GetTokenFromRequest(req)
if err != nil {
return nil, err
}
// Perform decryption of the session token, if Console is able to decrypt the session token that means a valid session
// was used in the first place to get it
claims, err := SessionTokenAuthenticate(*sessionID)
if err != nil {
return nil, err
}
return &models.Principal{
AccessKeyID: claims.AccessKeyID,
Actions: claims.Actions,
SecretAccessKey: claims.SecretAccessKey,
SessionToken: claims.SessionToken,
}, nil
}

View File

@@ -14,24 +14,15 @@
// 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 jwt
package token
import (
"strconv"
"time"
"github.com/minio/console/pkg/auth/utils"
"github.com/minio/minio/pkg/env"
)
// defaultHmacJWTPassphrase will be used by default if application is not configured with a custom CONSOLE_HMAC_JWT_SECRET secret
var defaultHmacJWTPassphrase = utils.RandomCharString(64)
// GetHmacJWTSecret returns the 64 bytes secret used for signing the generated JWT for the application
func GetHmacJWTSecret() string {
return env.Get(ConsoleHmacJWTSecret, defaultHmacJWTPassphrase)
}
// ConsoleSTSAndJWTDurationSeconds returns the default session duration for the STS requested tokens and the generated JWTs.
// Ideally both values should match so jwt and Minio sts sessions expires at the same time.
func GetConsoleSTSAndJWTDurationInSeconds() int {
@@ -42,12 +33,6 @@ func GetConsoleSTSAndJWTDurationInSeconds() int {
return duration
}
// GetConsoleSTSAndJWTDurationTime returns GetConsoleSTSAndJWTDurationInSeconds in duration format
func GetConsoleSTSAndJWTDurationTime() time.Duration {
duration := GetConsoleSTSAndJWTDurationInSeconds()
return time.Duration(duration) * time.Second
}
var defaultPBKDFPassphrase = utils.RandomCharString(64)
// GetPBKDFPassphrase returns passphrase for the pbkdf2 function used to encrypt JWT payload

View File

@@ -14,10 +14,9 @@
// 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 jwt
package token
const (
ConsoleHmacJWTSecret = "CONSOLE_HMAC_JWT_SECRET"
ConsoleSTSAndJWTDurationSeconds = "CONSOLE_STS_AND_JWT_DURATION_SECONDS"
ConsolePBKDFPassphrase = "CONSOLE_PBKDF_PASSPHRASE"
ConsolePBKDFSalt = "CONSOLE_PBKDF_SALT"

View File

@@ -23,7 +23,6 @@ import (
"github.com/stretchr/testify/assert"
)
var audience = ""
var creds = &credentials.Value{
AccessKeyID: "fakeAccessKeyID",
SecretAccessKey: "fakeSecretAccessKey",
@@ -35,25 +34,25 @@ var badToken = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiRDMwYWE0ekQ1bWt
func TestNewJWTWithClaimsForClient(t *testing.T) {
funcAssert := assert.New(t)
// Test-1 : NewJWTWithClaimsForClient() is generated correctly without errors
function := "NewJWTWithClaimsForClient()"
jwt, err := NewJWTWithClaimsForClient(creds, []string{""}, audience)
if err != nil || jwt == "" {
// Test-1 : NewEncryptedTokenForClient() is generated correctly without errors
function := "NewEncryptedTokenForClient()"
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
// Test-2 : NewJWTWithClaimsForClient() throws error because of empty credentials
if _, err = NewJWTWithClaimsForClient(nil, []string{""}, audience); err != nil {
// 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())
}
}
func TestJWTAuthenticate(t *testing.T) {
funcAssert := assert.New(t)
// Test-1 : JWTAuthenticate() should correctly return the claims
function := "JWTAuthenticate()"
claims, err := JWTAuthenticate(goodToken)
// Test-1 : SessionTokenAuthenticate() should correctly return the claims
function := "SessionTokenAuthenticate()"
claims, err := SessionTokenAuthenticate(goodToken)
if err != nil || claims == nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err)
} else {
@@ -61,20 +60,20 @@ func TestJWTAuthenticate(t *testing.T) {
funcAssert.Equal(claims.SecretAccessKey, creds.SecretAccessKey)
funcAssert.Equal(claims.SessionToken, creds.SessionToken)
}
// Test-2 : JWTAuthenticate() return an error because of a tampered jwt
if _, err := JWTAuthenticate(badToken); err != nil {
funcAssert.Equal("authentication failed, check your access credentials", err.Error())
// Test-2 : SessionTokenAuthenticate() return an error because of a tampered jwt
if _, err := SessionTokenAuthenticate(badToken); err != nil {
funcAssert.Equal("session token internal data is malformed", err.Error())
}
// Test-3 : JWTAuthenticate() return an error because of an empty jwt
if _, err := JWTAuthenticate(""); err != nil {
funcAssert.Equal("JWT token missing", err.Error())
// Test-3 : SessionTokenAuthenticate() return an error because of an empty jwt
if _, err := SessionTokenAuthenticate(""); err != nil {
funcAssert.Equal("session token missing", err.Error())
}
}
func TestIsJWTValid(t *testing.T) {
funcAssert := assert.New(t)
// Test-1 : JWTAuthenticate() provided token is valid
funcAssert.Equal(true, IsJWTValid(goodToken))
// Test-2 : JWTAuthenticate() provided token is invalid
funcAssert.Equal(false, IsJWTValid(badToken))
// Test-1 : SessionTokenAuthenticate() provided token is valid
funcAssert.Equal(true, IsSessionTokenValid(goodToken))
// Test-2 : SessionTokenAuthenticate() provided token is invalid
funcAssert.Equal(false, IsSessionTokenValid(badToken))
}

146
pkg/kes/kes.go Normal file
View File

@@ -0,0 +1,146 @@
package kes
import (
"crypto/x509"
"encoding/pem"
"errors"
"time"
"github.com/minio/kes"
)
type Identity = kes.Identity
type TLSProxyHeader struct {
ClientCert string `yaml:"cert,omitempty"`
}
type TLSProxy struct {
Identities *[]Identity `yaml:"identities,omitempty"`
Header *TLSProxyHeader `yaml:"header,omitempty"`
}
type TLS struct {
KeyPath string `yaml:"key,omitempty"`
CertPath string `yaml:"cert,omitempty"`
Proxy *TLSProxy `yaml:"proxy,omitempty"`
}
type Policy struct {
Paths []string `yaml:"paths,omitempty"`
Identities []Identity `yaml:"identities,omitempty"`
}
type Expiry struct {
Any time.Duration `yaml:"any,omitempty"`
Unused time.Duration `yaml:"unused,omitempty"`
}
type Cache struct {
Expiry *Expiry `yaml:"expiry,omitempty"`
}
type Log struct {
Error string `yaml:"error,omitempty"`
Audit string `yaml:"audit,omitempty"`
}
type Fs struct {
Path string `yaml:"path,omitempty"`
}
type AppRole struct {
EnginePath string `yaml:"engine,omitempty"`
ID string `yaml:"id,omitempty"`
Secret string `yaml:"secret,omitempty"`
Retry time.Duration `yaml:"retry,omitempty"`
}
type VaultTLS struct {
KeyPath string `yaml:"key,omitempty"`
CertPath string `yaml:"cert,omitempty"`
CAPath string `yaml:"ca,omitempty"`
}
type VaultStatus struct {
Ping time.Duration `yaml:"ping,omitempty"`
}
type Vault struct {
Endpoint string `yaml:"endpoint,omitempty"`
EnginePath string `yaml:"engine,omitempty"`
Namespace string `yaml:"namespace,omitempty"`
Prefix string `yaml:"prefix,omitempty"`
AppRole *AppRole `yaml:"approle,omitempty"`
TLS *VaultTLS `yaml:"tls,omitempty"`
Status *VaultStatus `yaml:"status,omitempty"`
}
type AwsSecretManagerLogin struct {
AccessKey string `yaml:"accesskey"`
SecretKey string `yaml:"secretkey"`
SessionToken string `yaml:"token"`
}
type AwsSecretManager struct {
Endpoint string `yaml:"endpoint,omitempty"`
Region string `yaml:"region,omitempty"`
KmsKey string ` yaml:"kmskey,omitempty"`
Login *AwsSecretManagerLogin `yaml:"credentials,omitempty"`
}
type Aws struct {
SecretsManager *AwsSecretManager `yaml:"secretsmanager,omitempty"`
}
type GemaltoCredentials struct {
Token string `yaml:"token,omitempty"`
Domain string `yaml:"domain,omitempty"`
Retry time.Duration `yaml:"retry,omitempty"`
}
type GemaltoTLS struct {
CAPath string `yaml:"ca,omitempty"`
}
type GemaltoKeySecure struct {
Endpoint string `yaml:"endpoint,omitempty"`
Credentials *GemaltoCredentials `yaml:"credentials,omitempty"`
TLS *GemaltoTLS `yaml:"tls,omitempty"`
}
type Gemalto struct {
KeySecure *GemaltoKeySecure `yaml:"keysecure,omitempty"`
}
type Keys struct {
Fs *Fs `yaml:"fs,omitempty"`
Vault *Vault `yaml:"vault,omitempty"`
Aws *Aws `yaml:"aws,omitempty"`
Gemalto *Gemalto `yaml:"gemalto,omitempty"`
}
type ServerConfig struct {
Addr string `yaml:"address,omitempty"`
Root Identity `yaml:"root,omitempty"`
TLS TLS `yaml:"tls,omitempty"`
Policies map[string]Policy `yaml:"policy,omitempty"`
Cache Cache `yaml:"cache,omitempty"`
Log Log `yaml:"log,omitempty"`
Keys Keys `yaml:"keys,omitempty"`
}
func ParseCertificate(cert []byte) (*x509.Certificate, error) {
for {
var certDERBlock *pem.Block
certDERBlock, cert = pem.Decode(cert)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
return x509.ParseCertificate(certDERBlock.Bytes)
}
}
return nil, errors.New("found no (non-CA) certificate in any PEM block")
}

File diff suppressed because one or more lines are too long

View File

@@ -4877,9 +4877,9 @@
"integrity": "sha512-WOr3SrZ55lUFYugA6sUu3H3ZoxVIH5o3zTSqYS+2DOJJP4hnHmBiD1w432a2YFW/H2G5FIxE6DB06rv+9dUL5g=="
},
"elliptic": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",

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

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// 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
@@ -14,9 +14,21 @@
// 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
export const fileProcess = (evt: any, callback: any) => {
const file = evt.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
const (
// ConsoleTenantMemorySize Memory size to be used when creating Tenant request
ConsoleTenantMemorySize = "CONSOLE_TENANT_MEMORY_SIZE"
)
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

@@ -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

@@ -32,6 +32,7 @@ import DeleteTenant from "./DeleteTenant";
import AddTenant from "./AddTenant";
import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
import history from "../../../../history";
interface ITenantsList {
classes: any;
@@ -122,11 +123,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 +155,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 +193,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);

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

@@ -8,33 +8,34 @@ import Grid from "@material-ui/core/Grid";
import {
factorForDropdown,
getTotalSize,
niceBytes,
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) =>
createStyles({
errorBlock: {
color: "red",
color: "red"
},
buttonContainer: {
textAlign: "right",
textAlign: "right"
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const,
justifyContent: "flex-start" as const
},
sizeFactorContainer: {
marginLeft: 8,
marginLeft: 8
},
bottomContainer: {
display: "flex",
@@ -42,39 +43,39 @@ const styles = (theme: Theme) =>
alignItems: "center",
"& div": {
flexGrow: 1,
width: "100%",
},
width: "100%"
}
},
factorElements: {
display: "flex",
justifyContent: "flex-start",
justifyContent: "flex-start"
},
sizeNumber: {
fontSize: 35,
fontWeight: 700,
textAlign: "center",
textAlign: "center"
},
sizeDescription: {
fontSize: 14,
color: "#777",
textAlign: "center",
textAlign: "center"
},
...modalBasic,
...modalBasic
});
const AddZoneModal = ({
tenant,
classes,
open,
onCloseZoneAndReload,
volumesPerInstance,
volumeSize,
onCloseZoneAndReload
}: 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

@@ -42,25 +42,25 @@ interface ITenantDetailsProps {
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red",
color: "red"
},
buttonContainer: {
textAlign: "right",
textAlign: "right"
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const,
justifyContent: "flex-start" as const
},
sizeFactorContainer: {
marginLeft: 8,
marginLeft: 8
},
containerHeader: {
display: "flex",
justifyContent: "space-between",
justifyContent: "space-between"
},
paperContainer: {
padding: "15px 15px 15px 50px",
padding: "15px 15px 15px 50px"
},
infoGrid: {
display: "grid",
@@ -68,27 +68,27 @@ const styles = (theme: Theme) =>
gridGap: 8,
"& div": {
display: "flex",
alignItems: "center",
alignItems: "center"
},
"& div:nth-child(odd)": {
justifyContent: "flex-end",
fontWeight: 700,
fontWeight: 700
},
"& div:nth-child(2n)": {
paddingRight: 35,
},
paddingRight: 35
}
},
masterActions: {
width: "25%",
minWidth: "120px",
"& div": {
margin: "5px 0px",
},
margin: "5px 0px"
}
},
actionsTray: {
textAlign: "right",
textAlign: "right"
},
...modalBasic,
...modalBasic
});
const mainPagination = {
@@ -99,9 +99,9 @@ const mainPagination = {
page: 0,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true,
native: true
},
ActionsComponent: MinTablePaginationActions,
ActionsComponent: MinTablePaginationActions
};
const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
@@ -142,31 +142,45 @@ 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);
setError("");
setLoading(false);
})
.catch((err) => {
.catch(err => {
setError(err);
setLoading(false);
});
@@ -182,8 +196,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 +214,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 +225,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>Console:</div>
<div>{tenant ? tenant.console_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>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 +252,50 @@ 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={{
...mainPagination,
onChangePage: () => {},
onChangeRowsPerPage: () => {}
}}
/>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -250,7 +250,10 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => {
</Typography>
<Button
component={"a"}
href={loginStrategy.redirect}
href={loginStrategy.redirect.replace(
"%5BHOSTNAME%5D",
window.location.hostname
)}
type="submit"
fullWidth
variant="contained"

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

@@ -60,11 +60,11 @@ func TestListConfig(t *testing.T) {
function := "listConfig()"
// Test-1 : listConfig() get list of two configurations and ensure is output correctly
configListMock := []madmin.HelpKV{
madmin.HelpKV{
{
Key: "region",
Description: "label the location of the server",
},
madmin.HelpKV{
{
Key: "notify_nsq",
Description: "publish bucket notifications to NSQ endpoints",
},

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
}

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,491 @@
// 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,
Metadata: nil,
}
// 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,13 @@ 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"
)
var opClientTenantDeleteMock func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error
@@ -84,11 +88,12 @@ func Test_TenantInfoTenantAdminClient(t *testing.T) {
kClient := k8sClientMock{}
type args struct {
ctx context.Context
client K8sClient
client K8sClientI
namespace string
tenantName string
serviceName string
scheme string
insecure bool
}
tests := []struct {
name string
@@ -234,7 +239,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)
got, err := getTenantAdminClient(tt.args.ctx, tt.args.client, tt.args.namespace, tt.args.tenantName, tt.args.serviceName, tt.args.scheme, tt.args.insecure)
if err != nil {
if tt.wantErr {
return
@@ -252,7 +257,6 @@ func Test_TenantInfo(t *testing.T) {
testTimeStamp := metav1.Now()
type args struct {
minioTenant *operator.Tenant
tenantInfo *usageInfo
}
tests := []struct {
name string
@@ -293,9 +297,6 @@ func Test_TenantInfo(t *testing.T) {
CurrentState: "ready",
},
},
tenantInfo: &usageInfo{
DisksUsage: 1024,
},
},
want: &models.Tenant{
CreationDate: testTimeStamp.String(),
@@ -313,15 +314,149 @@ func Test_TenantInfo(t *testing.T) {
},
},
},
Namespace: "minio-ns",
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
UsedSize: int64(1024),
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",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := getTenantInfo(tt.args.minioTenant, tt.args.tenantInfo)
got := getTenantInfo(tt.args.minioTenant)
if !reflect.DeepEqual(got, tt.want) {
ji, _ := json.Marshal(got)
vi, _ := json.Marshal(tt.want)
@@ -333,12 +468,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 {
@@ -353,6 +489,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
},
@@ -366,17 +503,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)
}
})
@@ -388,7 +663,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)
@@ -415,10 +690,92 @@ func Test_TenantAddZone(t *testing.T) {
Body: &models.Zone{
Name: "zone-1",
Servers: swag.Int64(int64(4)),
VolumeConfiguration: &models.ZoneVolumeConfiguration{
Size: swag.Int64(2147483648),
StorageClassName: "standard",
},
VolumesPerServer: swag.Int32(4),
},
},
},
wantErr: false,
}, {
name: "Add zone, error size",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "default",
mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
params: admin_api.TenantAddZoneParams{
Body: &models.Zone{
Name: "zone-1",
Servers: swag.Int64(int64(4)),
VolumeConfiguration: &models.ZoneVolumeConfiguration{
Size: swag.Int64(0),
StorageClassName: "standard",
},
VolumesPerServer: swag.Int32(4),
},
},
},
wantErr: true,
},
{
name: "Add zone, error servers negative",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "default",
mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
params: admin_api.TenantAddZoneParams{
Body: &models.Zone{
Name: "zone-1",
Servers: swag.Int64(int64(-1)),
VolumeConfiguration: &models.ZoneVolumeConfiguration{
Size: swag.Int64(2147483648),
StorageClassName: "standard",
},
VolumesPerServer: swag.Int32(4),
},
},
},
wantErr: true,
},
{
name: "Add zone, error volumes per server negative",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "default",
mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
params: admin_api.TenantAddZoneParams{
Body: &models.Zone{
Name: "zone-1",
Servers: swag.Int64(int64(4)),
VolumeConfiguration: &models.ZoneVolumeConfiguration{
Size: swag.Int64(2147483648),
StorageClassName: "standard",
},
VolumesPerServer: swag.Int32(-1),
},
},
},
wantErr: true,
},
{
name: "Error on patch, handle error",
@@ -480,7 +837,7 @@ func Test_UpdateTenantAction(t *testing.T) {
type args struct {
ctx context.Context
operatorClient OperatorClient
operatorClient OperatorClientI
httpCl cluster.HTTPClientI
nameSpace string
tenantName string
@@ -492,6 +849,7 @@ func Test_UpdateTenantAction(t *testing.T) {
tests := []struct {
name string
args args
objs []runtime.Object
wantErr bool
}{
{
@@ -562,6 +920,7 @@ func Test_UpdateTenantAction(t *testing.T) {
return &http.Response{}, nil
},
params: admin_api.UpdateTenantParams{
Tenant: "minio-tenant",
Body: &models.UpdateTenantRequest{
Image: "minio/minio:RELEASE.2020-06-03T22-13-49Z",
},
@@ -590,6 +949,7 @@ func Test_UpdateTenantAction(t *testing.T) {
}, nil
},
params: admin_api.UpdateTenantParams{
Tenant: "minio-tenant",
Body: &models.UpdateTenantRequest{
Image: "",
},
@@ -598,7 +958,7 @@ func Test_UpdateTenantAction(t *testing.T) {
wantErr: false,
},
{
name: "Empty image input Error retrieving latest image",
name: "Empty image input Error retrieving latest image, nothing happens",
args: args{
ctx: context.Background(),
operatorClient: opClient,
@@ -615,21 +975,73 @@ func Test_UpdateTenantAction(t *testing.T) {
return nil, errors.New("error")
},
params: admin_api.UpdateTenantParams{
Tenant: "minio-tenant",
Body: &models.UpdateTenantRequest{
Image: "",
},
},
},
wantErr: true,
wantErr: false,
},
{
name: "Update minio console version no errors",
args: args{
ctx: context.Background(),
operatorClient: opClient,
httpCl: httpClientM,
nameSpace: "default",
tenantName: "minio-tenant",
mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockHTTPClientGet: func(url string) (resp *http.Response, err error) {
return nil, errors.New("use default minio")
},
params: admin_api.UpdateTenantParams{
Body: &models.UpdateTenantRequest{
ConsoleImage: "minio/console:v0.3.24",
},
},
},
wantErr: false,
},
{
name: "Update minio image pull secrets no errors",
args: args{
ctx: context.Background(),
operatorClient: opClient,
httpCl: httpClientM,
nameSpace: "default",
tenantName: "minio-tenant",
mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockHTTPClientGet: func(url string) (resp *http.Response, err error) {
return nil, errors.New("use default minio")
},
params: admin_api.UpdateTenantParams{
Body: &models.UpdateTenantRequest{
ImagePullSecret: "minio-regcred",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
opClientTenantGetMock = tt.args.mockTenantGet
opClientTenantPatchMock = tt.args.mockTenantPatch
httpClientGetMock = tt.args.mockHTTPClientGet
cnsClient := fake.NewSimpleClientset(tt.objs...)
t.Run(tt.name, func(t *testing.T) {
if err := updateTenantAction(tt.args.ctx, tt.args.operatorClient, tt.args.httpCl, tt.args.nameSpace, tt.args.params); (err != nil) != tt.wantErr {
t.Errorf("deleteTenantAction() error = %v, wantErr %v", err, tt.wantErr)
if err := updateTenantAction(tt.args.ctx, tt.args.operatorClient, cnsClient.CoreV1(), tt.args.httpCl, tt.args.nameSpace, tt.args.params); (err != nil) != tt.wantErr {
t.Errorf("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

@@ -67,13 +67,13 @@ func TestListUsers(t *testing.T) {
// Test-1 : listUsers() Get response from minio client with two users and return the same number on listUsers()
// mock minIO client
mockUserMap := map[string]madmin.UserInfo{
"ABCDEFGHI": madmin.UserInfo{
"ABCDEFGHI": {
SecretKey: "",
PolicyName: "ABCDEFGHI-policy",
Status: "enabled",
MemberOf: []string{"group1", "group2"},
},
"ZBCDEFGHI": madmin.UserInfo{
"ZBCDEFGHI": {
SecretKey: "",
PolicyName: "ZBCDEFGHI-policy",
Status: "enabled",

View File

@@ -33,8 +33,13 @@ import (
const globalAppName = "console"
// NewAdminClient gives a new client interface
// NewAdminClient gives a new madmin client interface
func NewAdminClient(url, accessKey, secretKey string) (*madmin.AdminClient, *probe.Error) {
return NewAdminClientWithInsecure(url, accessKey, secretKey, false)
}
// NewAdminClientWithInsecure gives a new madmin client interface either secure or insecure based on parameter
func NewAdminClientWithInsecure(url, accessKey, secretKey string, insecure bool) (*madmin.AdminClient, *probe.Error) {
appName := filepath.Base(globalAppName)
s3Client, err := s3AdminNew(&mcCmd.Config{
@@ -44,12 +49,13 @@ func NewAdminClient(url, accessKey, secretKey string) (*madmin.AdminClient, *pro
AppName: appName,
AppVersion: ConsoleVersion,
AppComments: []string{appName, runtime.GOOS, runtime.GOARCH},
Insecure: false,
Insecure: insecure,
})
if err != nil {
return nil, err.Trace(url)
}
s3Client.SetCustomTransport(STSClient.Transport)
stsClient := PrepareSTSClient(insecure)
s3Client.SetCustomTransport(stsClient.Transport)
return s3Client, nil
}
@@ -261,7 +267,8 @@ func newAdminFromClaims(claims *models.Principal) (*madmin.AdminClient, error) {
if err != nil {
return nil, err
}
adminClient.SetCustomTransport(STSClient.Transport)
stsClient := PrepareSTSClient(false)
adminClient.SetCustomTransport(stsClient.Transport)
return adminClient, nil
}

View File

@@ -26,8 +26,8 @@ import (
"github.com/minio/console/models"
"github.com/minio/console/pkg/acl"
"github.com/minio/console/pkg/auth"
xjwt "github.com/minio/console/pkg/auth/jwt"
"github.com/minio/console/pkg/auth/ldap"
xjwt "github.com/minio/console/pkg/auth/token"
mc "github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio-go/v7"
@@ -93,10 +93,10 @@ func (c minioClient) getBucketPolicy(ctx context.Context, bucketName string) (st
return c.client.GetBucketPolicy(ctx, bucketName)
}
// MCS3Client interface with all functions to be implemented
// MCClient interface with all functions to be implemented
// by mock when testing, it should include all mc/S3Client respective api calls
// that are used within this project.
type MCS3Client interface {
type MCClient interface {
addNotificationConfig(ctx context.Context, arn string, events []string, prefix, suffix string, ignoreExisting bool) *probe.Error
removeNotificationConfig(ctx context.Context, arn string, event string, prefix string, suffix string) *probe.Error
watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error)
@@ -106,26 +106,26 @@ type MCS3Client interface {
//
// Define the structure of a mc S3Client and define the functions that are actually used
// from mcS3client api.
type mcS3Client struct {
type mcClient struct {
client *mc.S3Client
}
// implements S3Client.AddNotificationConfig()
func (c mcS3Client) addNotificationConfig(ctx context.Context, arn string, events []string, prefix, suffix string, ignoreExisting bool) *probe.Error {
func (c mcClient) addNotificationConfig(ctx context.Context, arn string, events []string, prefix, suffix string, ignoreExisting bool) *probe.Error {
return c.client.AddNotificationConfig(ctx, arn, events, prefix, suffix, ignoreExisting)
}
// implements S3Client.RemoveNotificationConfig()
func (c mcS3Client) removeNotificationConfig(ctx context.Context, arn string, event string, prefix string, suffix string) *probe.Error {
func (c mcClient) removeNotificationConfig(ctx context.Context, arn string, event string, prefix string, suffix string) *probe.Error {
return c.client.RemoveNotificationConfig(ctx, arn, event, prefix, suffix)
}
func (c mcS3Client) watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error) {
func (c mcClient) watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error) {
return c.client.Watch(ctx, options)
}
// ConsoleCredentials interface with all functions to be implemented
// by mock when testing, it should include all needed consoleCredentials.Credentials api calls
// by mock when testing, it should include all needed consoleCredentials.Login api calls
// that are used within this project.
type ConsoleCredentials interface {
Get() (credentials.Value, error)
@@ -137,12 +137,12 @@ type consoleCredentials struct {
consoleCredentials *credentials.Credentials
}
// implements *Credentials.Get()
// implements *Login.Get()
func (c consoleCredentials) Get() (credentials.Value, error) {
return c.consoleCredentials.Get()
}
// implements *Credentials.Expire()
// implements *Login.Expire()
func (c consoleCredentials) Expire() {
c.consoleCredentials.Expire()
}
@@ -164,7 +164,6 @@ func (s consoleSTSAssumeRole) IsExpired() bool {
// STSClient contains http.client configuration need it by STSAssumeRole
var (
STSClient = PrepareSTSClient()
MinioEndpoint = getMinIOServer()
)
@@ -204,8 +203,9 @@ func newConsoleCredentials(accessKey, secretKey, location string) (*credentials.
Location: location,
DurationSeconds: xjwt.GetConsoleSTSAndJWTDurationInSeconds(),
}
stsClient := PrepareSTSClient(false)
stsAssumeRole := &credentials.STSAssumeRole{
Client: STSClient,
Client: stsClient,
STSEndpoint: MinioEndpoint,
Options: opts,
}
@@ -217,14 +217,14 @@ func newConsoleCredentials(accessKey, secretKey, location string) (*credentials.
// GetClaimsFromJWT decrypt and returns the claims associated to a provided jwt
func GetClaimsFromJWT(jwt string) (*auth.DecryptedClaims, error) {
claims, err := auth.JWTAuthenticate(jwt)
claims, err := auth.SessionTokenAuthenticate(jwt)
if err != nil {
return nil, err
}
return claims, nil
}
// getConsoleCredentialsFromSession returns the *consoleCredentials.Credentials associated to the
// getConsoleCredentialsFromSession returns the *consoleCredentials.Login associated to the
// provided jwt, this is useful for running the Expire() or IsExpired() operations
func getConsoleCredentialsFromSession(claims *models.Principal) *credentials.Credentials {
return credentials.NewStaticV4(claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken)
@@ -234,10 +234,11 @@ func getConsoleCredentialsFromSession(claims *models.Principal) *credentials.Cre
// from the provided jwt
func newMinioClient(claims *models.Principal) (*minio.Client, error) {
creds := getConsoleCredentialsFromSession(claims)
stsClient := PrepareSTSClient(false)
minioClient, err := minio.New(getMinIOEndpoint(), &minio.Options{
Creds: creds,
Secure: getMinIOEndpointIsSecure(),
Transport: STSClient.Transport,
Transport: stsClient.Transport,
})
if err != nil {
return nil, err
@@ -248,7 +249,7 @@ func newMinioClient(claims *models.Principal) (*minio.Client, error) {
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
func newS3BucketClient(claims *models.Principal, bucketName string) (*mc.S3Client, error) {
endpoint := getMinIOServer()
useSSL := getMinIOEndpointIsSecure()
useTLS := getMinIOEndpointIsSecure()
if strings.TrimSpace(bucketName) != "" {
endpoint += fmt.Sprintf("/%s", bucketName)
@@ -258,7 +259,7 @@ func newS3BucketClient(claims *models.Principal, bucketName string) (*mc.S3Clien
return nil, fmt.Errorf("the provided credentials are invalid")
}
s3Config := newS3Config(endpoint, claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken, !useSSL)
s3Config := newS3Config(endpoint, claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken, !useTLS)
client, pErr := mc.S3New(s3Config)
if pErr != nil {
return nil, pErr.Cause

View File

@@ -39,10 +39,6 @@ var TLSPort = "9443"
// TLSRedirect console tls redirect rule
var TLSRedirect = "off"
// defaultTenantMemorySize default value used
// when generating minioTenant request
var defaultTenantMemorySize = "16Gi"
func getAccessKey() string {
return env.Get(ConsoleAccessKey, "minioadmin")
}
@@ -109,15 +105,15 @@ func GetPort() int {
return port
}
// GetSSLHostname gets console ssl hostname set on env variable
// GetTLSHostname gets console tls hostname set on env variable
// or default one
func GetSSLHostname() string {
func GetTLSHostname() string {
return strings.ToLower(env.Get(ConsoleTLSHostname, TLSHostname))
}
// GetSSLPort gets console ssl port set on env variable
// GetTLSPort gets console tls port set on env variable
// or default one
func GetSSLPort() int {
func GetTLSPort() int {
port, err := strconv.Atoi(env.Get(ConsoleTLSPort, TLSPort))
if err != nil {
port = 9443
@@ -175,14 +171,14 @@ func getSecureHostsProxyHeaders() []string {
return []string{}
}
// If SSLRedirect is set to true, then only allow HTTPS requests. Default is true.
func getSSLRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureSSLRedirect, TLSRedirect)) == "on"
// If TLSRedirect is set to true, then only allow HTTPS requests. Default is true.
func getTLSRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureTLSRedirect, TLSRedirect)) == "on"
}
// SSLHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host.
func getSecureSSLHost() string {
return env.Get(ConsoleSecureSSLHost, fmt.Sprintf("%s:%s", TLSHostname, TLSPort))
// TLSHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host.
func getSecureTLSHost() string {
return env.Get(ConsoleSecureTLSHost, fmt.Sprintf("%s:%s", TLSHostname, TLSPort))
}
// STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
@@ -204,9 +200,9 @@ func getSecureSTSPreload() bool {
return strings.ToLower(env.Get(ConsoleSecureSTSPreload, "off")) == "on"
}
// If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
func getSecureSSLTemporaryRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureSSLTemporaryRedirect, "off")) == "on"
// If TLSTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
func getSecureTLSTemporaryRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureTLSTemporaryRedirect, "off")) == "on"
}
// STS header is only included when the connection is HTTPS.
@@ -232,9 +228,3 @@ func getSecureFeaturePolicy() string {
func getSecureExpectCTHeader() string {
return env.Get(ConsoleSecureExpectCTHeader, "")
}
// getTenantMemorySize Memory size value to be used when generating the
// Tenant request
func getTenantMemorySize() string {
return env.Get(ConsoleTenantMemorySize, defaultTenantMemorySize)
}

View File

@@ -63,7 +63,7 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
api.KeyAuth = func(token string, scopes []string) (*models.Principal, error) {
// we are validating the jwt by decrypting the claims inside, if the operation succed that means the jwt
// was generated and signed by us in the first place
claims, err := auth.JWTAuthenticate(token)
claims, err := auth.SessionTokenAuthenticate(token)
if err != nil {
log.Println(err)
return nil, errors.New(401, "incorrect api key auth")
@@ -112,6 +112,8 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
registerTenantHandlers(api)
// Register ResourceQuota handlers
registerResourceQuotaHandlers(api)
// Register Nodes' handlers
registerNodesHandlers(api)
api.PreServerShutdown = func() {}
@@ -149,12 +151,12 @@ func setupGlobalMiddleware(handler http.Handler) http.Handler {
AllowedHosts: getSecureAllowedHosts(),
AllowedHostsAreRegex: getSecureAllowedHostsAreRegex(),
HostsProxyHeaders: getSecureHostsProxyHeaders(),
SSLRedirect: getSSLRedirect(),
SSLHost: getSecureSSLHost(),
SSLRedirect: getTLSRedirect(),
SSLHost: getSecureTLSHost(),
STSSeconds: getSecureSTSSeconds(),
STSIncludeSubdomains: getSecureSTSIncludeSubdomains(),
STSPreload: getSecureSTSPreload(),
SSLTemporaryRedirect: getSecureSSLTemporaryRedirect(),
SSLTemporaryRedirect: getSecureTLSTemporaryRedirect(),
SSLHostFunc: nil,
ForceSTSHeader: getSecureForceSTSHeader(),
FrameDeny: getSecureFrameDeny(),

View File

@@ -41,12 +41,20 @@ const (
ConsoleSecureSTSSeconds = "CONSOLE_SECURE_STS_SECONDS"
ConsoleSecureSTSIncludeSubdomains = "CONSOLE_SECURE_STS_INCLUDE_SUB_DOMAINS"
ConsoleSecureSTSPreload = "CONSOLE_SECURE_STS_PRELOAD"
ConsoleSecureSSLRedirect = "CONSOLE_SECURE_SSL_REDIRECT"
ConsoleSecureSSLHost = "CONSOLE_SECURE_SSL_HOST"
ConsoleSecureSSLTemporaryRedirect = "CONSOLE_SECURE_SSL_TEMPORARY_REDIRECT"
ConsoleSecureTLSRedirect = "CONSOLE_SECURE_TLS_REDIRECT"
ConsoleSecureTLSHost = "CONSOLE_SECURE_TLS_HOST"
ConsoleSecureTLSTemporaryRedirect = "CONSOLE_SECURE_TLS_TEMPORARY_REDIRECT"
ConsoleSecureForceSTSHeader = "CONSOLE_SECURE_FORCE_STS_HEADER"
ConsoleSecurePublicKey = "CONSOLE_SECURE_PUBLIC_KEY"
ConsoleSecureReferrerPolicy = "CONSOLE_SECURE_REFERRER_POLICY"
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)

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