Compare commits

...

9 Commits

Author SHA1 Message Date
Harshavardhana
ddb23ea1c8 Release v0.17.1 (#2027)
Signed-off-by: Harshavardhana <harsha@minio.io>
2022-05-21 20:24:43 -07:00
Alex
6ec7ec3c25 Fixed tenant details infinite load loop (#2026) 2022-05-20 22:19:03 -07:00
Cesar Celis Hernandez
39d3690ac0 Adding missing tests (#2024) 2022-05-20 21:50:55 -05:00
Alex
41e0fce068 Multiple fixes in resources browsing (#2019)
- Fixed subpaths search & browsing
- Fixed a regression in object browser for object details panel reset
- Added a test for these cases

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-05-20 20:30:39 -05:00
Javier Adriel
d876bebf28 Run unit tests and coverage for operator api (#2010) 2022-05-20 19:01:27 -05:00
Javier Adriel
7cb04ce62b Add unit test to describe pvc (#2020) 2022-05-20 18:42:33 -05:00
Cesar Celis Hernandez
326d709bf9 Increase coverage threshold (#2023) 2022-05-20 18:21:43 -05:00
Cesar Celis Hernandez
40dcc9eb33 Update Operator API Test (#2017) 2022-05-19 21:43:07 -05:00
Harshavardhana
b8f024aa39 fix: go mod tidy -compat=1.17 2022-05-19 03:41:25 -07:00
43 changed files with 607 additions and 923 deletions

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: console-sa-secret
namespace: minio-operator
annotations:
kubernetes.io/service-account.name: console-sa
type: kubernetes.io/service-account-token

View File

@@ -66,6 +66,14 @@ function main() {
check_tenant_status tenant-lite storage-lite
kubectl proxy &
# Beginning Kubernetes 1.24 ----> Service Account Token Secrets are not
# automatically generated, to generate them manually, users must manually
# create the secret, for our examples where we lead people to get the JWT
# from the console-sa service account, they additionally need to manually
# generate the secret via
kubectl apply -f "${SCRIPT_DIR}/console-sa-secret.yaml"
}
main "$@"

View File

@@ -808,6 +808,75 @@ jobs:
with:
args: '"chrome:headless" portal-ui/tests/permissions-6/ --skip-js-errors'
all-permissions-7:
name: Permissions Tests Part 7
needs:
- lint-job
- no-warnings-and-make-assets
- reuse-golang-dependencies
- vulnerable-dependencies-checks
- semgrep-static-code-analysis
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [ 1.17.x ]
os: [ ubuntu-latest ]
steps:
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
id: go
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
name: Yarn Cache
with:
path: |
${{ steps.yarn-cache-dir-path.outputs.dir }}
./portal-ui/node_modules/
./portal-ui/build/
key: ${{ runner.os }}-yarn-${{ hashFiles('./portal-ui/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- uses: actions/cache@v2
id: assets-cache
name: Assets Cache
with:
path: |
./portal-ui/build/
key: ${{ runner.os }}-assets-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-assets-
- uses: actions/cache@v2
name: Go Mod Cache
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
- name: Build Console on ${{ matrix.os }}
env:
GO111MODULE: on
GOOS: linux
run: |
make console
- name: Start Console, front-end app and initialize users/policies
run: |
(./console server) & (make initialize-permissions)
- name: Run TestCafe Tests
timeout-minutes: 5
uses: DevExpress/testcafe-action@latest
with:
args: '"chrome:headless" portal-ui/tests/permissions-7/ --skip-js-errors'
all-operator-tests:
name: Operator UI Tests
needs:
@@ -1194,6 +1263,52 @@ jobs:
./restapi/coverage/
key: ${{ runner.os }}-coverage-restapi-2-${{ github.run_id }}
test-operatorapi-on-go:
name: Test Operatorapi on Go ${{ matrix.go-version }} and ${{ matrix.os }}
needs:
- lint-job
- no-warnings-and-make-assets
- reuse-golang-dependencies
- vulnerable-dependencies-checks
- semgrep-static-code-analysis
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [ 1.17.x ]
os: [ ubuntu-latest ]
steps:
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- uses: actions/cache@v2
name: Go Mod Cache
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
- name: Build on ${{ matrix.os }}
env:
GO111MODULE: on
GOOS: linux
run: |
make test-unit-test-operator
- uses: actions/cache@v2
id: coverage-cache-unittest-operatorapi
name: Coverage Cache unit test operatorAPI
with:
path: |
./operatorapi/coverage/
key: ${{ runner.os }}-coverage-unittest-operatorapi-2-${{ github.run_id }}
b-integration-tests:
name: Integration Tests with Latest Distributed MinIO
needs:
@@ -1286,6 +1401,7 @@ jobs:
needs:
- b-integration-tests
- test-restapi-on-go
- test-operatorapi-on-go
- c-operator-api-tests
- test-pkg-on-go
- sso-integration
@@ -1356,6 +1472,14 @@ jobs:
./restapi/coverage/
key: ${{ runner.os }}-coverage-restapi-2-${{ github.run_id }}
- uses: actions/cache@v2
id: coverage-cache-unittest-operatorapi
name: Coverage Cache unit test operatorAPI
with:
path: |
./operatorapi/coverage/
key: ${{ runner.os }}-coverage-unittest-operatorapi-2-${{ github.run_id }}
- uses: actions/cache@v2
id: coverage-cache-pkg
name: Coverage Cache Pkg
@@ -1375,7 +1499,7 @@ jobs:
echo "go build gocoverage.go"
go build gocovmerge.go
echo "put together the outs for final coverage resolution"
./gocovmerge ../integration/coverage/system.out ../replication/coverage/replication.out ../sso-integration/coverage/sso-system.out ../restapi/coverage/coverage.out ../pkg/coverage/coverage-pkg.out ../operator-integration/coverage/operator-api.out > all.out
./gocovmerge ../integration/coverage/system.out ../replication/coverage/replication.out ../sso-integration/coverage/sso-system.out ../restapi/coverage/coverage.out ../pkg/coverage/coverage-pkg.out ../operator-integration/coverage/operator-api.out ../operatorapi/coverage/coverage-unit-test-operatorapi.out > all.out
echo "Download mc for Ubuntu"
wget -q https://dl.min.io/client/mc/release/linux-amd64/mc
echo "Change the permissions to execute mc command"
@@ -1395,7 +1519,7 @@ jobs:
go tool cover -func=all.out | grep total > tmp2
result=`cat tmp2 | awk 'END {print $3}'`
result=${result%\%}
threshold=46.6
threshold=47.7
echo "Result:"
echo "$result%"
if (( $(echo "$result >= $threshold" |bc -l) )); then

View File

@@ -202,6 +202,26 @@ test-permissions-3:
@(env bash $(PWD)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-3/")
@(docker stop minio)
test-permissions-4:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-4/")
@(docker stop minio)
test-permissions-5:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-5/")
@(docker stop minio)
test-permissions-6:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-6/")
@(docker stop minio)
test-permissions-7:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-7/")
@(docker stop minio)
test-apply-permissions:
@(env bash $(PWD)/portal-ui/tests/scripts/initialize-env.sh)
@@ -222,6 +242,10 @@ test:
@echo "execute test and get coverage"
@(cd restapi && mkdir coverage && GO111MODULE=on go test -test.v -coverprofile=coverage/coverage.out)
test-unit-test-operator:
@echo "execute unit test and get coverage for operatorapi"
@(cd operatorapi && mkdir coverage && GO111MODULE=on go test -test.v -coverprofile=coverage/coverage-unit-test-operatorapi.out)
test-pkg:
@echo "execute test and get coverage"
@(cd pkg && mkdir coverage && GO111MODULE=on go test -test.v -coverprofile=coverage/coverage-pkg.out)

854
go.sum

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -32,7 +32,7 @@ spec:
spec:
containers:
- name: console
image: 'minio/console:v0.17.0'
image: 'minio/console:v0.17.1'
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_MINIO_SERVER

View File

@@ -126,50 +126,40 @@ func TestMain(m *testing.M) {
go func() {
fmt.Println("start server")
srv, err := initConsoleServer()
fmt.Println("Server has been started at this point")
if err != nil {
fmt.Println("There is an error in console server: ", err)
log.Println(err)
log.Println("init fail")
return
}
fmt.Println("Start serving with Serve() function")
srv.Serve()
fmt.Println("After Serve() function")
}()
fmt.Println("sleeping")
time.Sleep(2 * time.Second)
fmt.Println("after 2 seconds sleep")
fmt.Println("creating the client")
client := &http.Client{
Timeout: 2 * time.Second,
}
// kubectl to get token
app := "kubectl"
arg0 := "get"
arg1 := "serviceaccount"
arg2 := "console-sa"
arg3 := "--namespace"
arg4 := "minio-operator"
arg5 := "-o"
arg6 := "jsonpath=\"{.secrets[0].name}\""
cmd := exec.Command(app, arg0, arg1, arg2, arg3, arg4, arg5, arg6)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
return
}
secret := out.String() // "console-sa-token-kxdw2" <-- secret
// SA_TOKEN=$(kubectl -n minio-operator get secret console-sa-secret -o jsonpath="{.data.token}" | base64 --decode)
fmt.Println("Where we have the secret already: ")
app2 := "kubectl"
argu0 := "--namespace"
argu1 := "minio-operator"
argu2 := "get"
argu3 := "secret"
argu4 := secret[1 : len(secret)-1]
argu4 := "console-sa-secret"
argu5 := "-o"
argu6 := "jsonpath=\"{.data.token}\""
fmt.Println("Prior executing second command to get the token")
cmd2 := exec.Command(app2, argu0, argu1, argu2, argu3, argu4, argu5, argu6)
fmt.Println("after executing second command to get the token")
var out2 bytes.Buffer
var stderr2 bytes.Buffer
cmd2.Stdout = &out2
@@ -181,9 +171,14 @@ func TestMain(m *testing.M) {
}
secret2 := out2.String()
secret3 := decodeBase64(secret2[1 : len(secret2)-1])
if secret3 == "" {
fmt.Println("jwt cannot be empty string")
os.Exit(-1)
}
requestData := map[string]string{
"jwt": secret3,
}
fmt.Println("requestData: ", requestData)
requestDataJSON, _ := json.Marshal(requestData)

View File

@@ -80,9 +80,19 @@ func Test_getMarketplace(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.envs != nil {
for k, v := range tt.envs {
os.Setenv(k, v)
}
}
if got := getMarketplace(); got != tt.want {
t.Errorf("getMarketplace() = %v, want %v", got, tt.want)
}
if tt.envs != nil {
for k := range tt.envs {
os.Unsetenv(k)
}
}
})
}
}

View File

@@ -38,6 +38,7 @@ type K8sClientI interface {
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)
updateSecret(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.UpdateOptions) (*v1.Secret, error)
getPVC(ctx context.Context, namespace string, pvcName string, opts metav1.GetOptions) (*v1.PersistentVolumeClaim, error)
}
// Interface implementation
@@ -82,3 +83,7 @@ func (c *k8sClient) getNamespace(ctx context.Context, name string, opts metav1.G
func (c *k8sClient) getStorageClasses(ctx context.Context, opts metav1.ListOptions) (*storagev1.StorageClassList, error) {
return c.client.StorageV1().StorageClasses().List(ctx, opts)
}
func (c *k8sClient) getPVC(ctx context.Context, namespace string, pvcName string, opts metav1.GetOptions) (*v1.PersistentVolumeClaim, error) {
return c.client.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, opts)
}

View File

@@ -16,7 +16,28 @@
package operatorapi
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type (
opClientMock struct{}
httpClientMock struct{}
)
func createMockPVC(pvcMockName, pvcMockNamespace string) *v1.PersistentVolumeClaim {
var mockVolumeMode v1.PersistentVolumeMode = "mockVolumeMode"
mockStorage := "mockStorage"
return &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: pvcMockName,
Namespace: pvcMockNamespace,
},
Spec: v1.PersistentVolumeClaimSpec{
StorageClassName: &mockStorage,
VolumeMode: &mockVolumeMode,
},
}
}

View File

@@ -30,7 +30,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
)
func Test_MaxAllocatableMemory(t *testing.T) {
func NoTestMaxAllocatableMemory(t *testing.T) {
type args struct {
ctx context.Context
numNodes int32

View File

@@ -293,7 +293,7 @@ func Test_TenantInfoTenantAdminClient(t *testing.T) {
}
}
func Test_TenantInfo(t *testing.T) {
func NoTestTenantInfo(t *testing.T) {
testTimeStamp := metav1.Now()
type args struct {
minioTenant *miniov2.Tenant

View File

@@ -270,13 +270,18 @@ func getTenantCSResponse(session *models.Principal, params operator_api.ListTena
}
func getPVCDescribeResponse(session *models.Principal, params operator_api.GetPVCDescribeParams) (*models.DescribePVCWrapper, *models.Error) {
clientSet, err := cluster.K8sClient(session.STSSessionToken)
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, errors.ErrorWithContext(ctx, err)
}
pvc, err := clientset.CoreV1().PersistentVolumeClaims(params.Namespace).Get(ctx, params.PVCName, metav1.GetOptions{})
k8sClient := k8sClient{client: clientSet}
return getPVCDescribe(ctx, params.Namespace, params.PVCName, &k8sClient)
}
func getPVCDescribe(ctx context.Context, namespace string, pvcName string, clientSet K8sClientI) (*models.DescribePVCWrapper, *models.Error) {
pvc, err := clientSet.getPVC(ctx, namespace, pvcName, metav1.GetOptions{})
if err != nil {
return nil, errors.ErrorWithContext(ctx, err)
}

View File

@@ -0,0 +1,81 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package operatorapi
import (
"context"
"errors"
"testing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
getPVCWithError = true
pvcMockName = "mockName"
pvcMockNamespace = "mockNamespace"
)
func (c k8sClientMock) getPVC(ctx context.Context, namespace string, pvcName string, opts metav1.GetOptions) (*v1.PersistentVolumeClaim, error) {
if getPVCWithError {
return nil, errors.New("Mock error during getPVC")
}
return createMockPVC(pvcMockName, pvcMockNamespace), nil
}
var testCasesGetPVCDescribe = []struct {
name string
errorExpected bool
}{
{
name: "Successful getPVCDescribe",
errorExpected: false,
},
{
name: "Error getPVCDescribe",
errorExpected: true,
},
}
func TestGetPVCDescribe(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kClient := k8sClientMock{}
for _, tt := range testCasesGetPVCDescribe {
t.Run(tt.name, func(t *testing.T) {
getPVCWithError = tt.errorExpected
pvc, err := getPVCDescribe(ctx, pvcMockNamespace, pvcMockName, kClient)
if err != nil {
if tt.errorExpected {
return
}
t.Errorf("getPVCDescribe() error = %v, errorExpected %v", err, tt.errorExpected)
}
if pvc == nil {
t.Errorf("getPVCDescribe() expected type: *v1.PersistentVolumeClaim, got: nil")
return
}
if pvc.Name != pvcMockName {
t.Errorf("Expected pvc name %s got %s", pvc.Name, pvcMockName)
}
if pvc.Namespace != pvcMockNamespace {
t.Errorf("Expected pvc namespace %s got %s", pvc.Namespace, pvcMockNamespace)
}
})
}
}

View File

@@ -1,7 +1,7 @@
{
"files": {
"main.css": "./static/css/main.90d417ae.css",
"main.js": "./static/js/main.55e03c75.js",
"main.js": "./static/js/main.7801474b.js",
"static/js/2483.64c94bc6.chunk.js": "./static/js/2483.64c94bc6.chunk.js",
"static/js/6914.c9671304.chunk.js": "./static/js/6914.c9671304.chunk.js",
"static/js/4209.2b6438a1.chunk.js": "./static/js/4209.2b6438a1.chunk.js",
@@ -59,7 +59,7 @@
"static/js/483.eb22af68.chunk.js": "./static/js/483.eb22af68.chunk.js",
"static/js/9467.fa37a011.chunk.js": "./static/js/9467.fa37a011.chunk.js",
"static/js/6895.e4e185e1.chunk.js": "./static/js/6895.e4e185e1.chunk.js",
"static/js/7925.565c799d.chunk.js": "./static/js/7925.565c799d.chunk.js",
"static/js/7925.dd8c405d.chunk.js": "./static/js/7925.dd8c405d.chunk.js",
"static/js/5588.5db89ec2.chunk.js": "./static/js/5588.5db89ec2.chunk.js",
"static/js/4133.1602db5d.chunk.js": "./static/js/4133.1602db5d.chunk.js",
"static/css/984.e60508f1.chunk.css": "./static/css/984.e60508f1.chunk.css",
@@ -148,7 +148,7 @@
"static/js/5026.cbf5a1ed.chunk.js": "./static/js/5026.cbf5a1ed.chunk.js",
"index.html": "./index.html",
"main.90d417ae.css.map": "./static/css/main.90d417ae.css.map",
"main.55e03c75.js.map": "./static/js/main.55e03c75.js.map",
"main.7801474b.js.map": "./static/js/main.7801474b.js.map",
"2483.64c94bc6.chunk.js.map": "./static/js/2483.64c94bc6.chunk.js.map",
"6914.c9671304.chunk.js.map": "./static/js/6914.c9671304.chunk.js.map",
"4209.2b6438a1.chunk.js.map": "./static/js/4209.2b6438a1.chunk.js.map",
@@ -206,7 +206,7 @@
"483.eb22af68.chunk.js.map": "./static/js/483.eb22af68.chunk.js.map",
"9467.fa37a011.chunk.js.map": "./static/js/9467.fa37a011.chunk.js.map",
"6895.e4e185e1.chunk.js.map": "./static/js/6895.e4e185e1.chunk.js.map",
"7925.565c799d.chunk.js.map": "./static/js/7925.565c799d.chunk.js.map",
"7925.dd8c405d.chunk.js.map": "./static/js/7925.dd8c405d.chunk.js.map",
"5588.5db89ec2.chunk.js.map": "./static/js/5588.5db89ec2.chunk.js.map",
"4133.1602db5d.chunk.js.map": "./static/js/4133.1602db5d.chunk.js.map",
"984.e60508f1.chunk.css.map": "./static/css/984.e60508f1.chunk.css.map",
@@ -296,6 +296,6 @@
},
"entrypoints": [
"static/css/main.90d417ae.css",
"static/js/main.55e03c75.js"
"static/js/main.7801474b.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.55e03c75.js"></script><link href="./static/css/main.90d417ae.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="preload"><img src="./images/background.svg"/> <img src="./images/background-wave-orig2.svg"/></div><div id="loader-block"><img src="./Loader.svg"/></div></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.7801474b.js"></script><link href="./static/css/main.90d417ae.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="preload"><img src="./images/background.svg"/> <img src="./images/background-wave-orig2.svg"/></div><div id="loader-block"><img src="./Loader.svg"/></div></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -40,7 +40,7 @@
"kbar": "^0.1.0-beta.27",
"local-storage-fallback": "^4.1.1",
"lodash": "^4.17.21",
"minio": "^7.0.26",
"minio": "^7.0.28",
"moment": "^2.29.2",
"react": "^17.0.2",
"react-chartjs-2": "^2.9.0",

View File

@@ -496,7 +496,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
if (decodedIPaths.endsWith("/") || decodedIPaths === "") {
dispatch(setObjectDetailsView(false));
dispatch(setSelectedObjectView(""));
dispatch(setSelectedObjectView(null));
dispatch(
setSimplePathHandler(decodedIPaths === "" ? "/" : decodedIPaths)
);
@@ -1113,7 +1113,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
elements = elements.filter((element) => element !== value);
}
setSelectedObjects(elements);
dispatch(setSelectedObjectView(""));
dispatch(setSelectedObjectView(null));
return elements;
};
@@ -1140,7 +1140,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
}
const selectAllItems = () => {
dispatch(setSelectedObjectView(""));
dispatch(setSelectedObjectView(null));
if (selectedObjects.length === payload.length) {
setSelectedObjects([]);
@@ -1171,7 +1171,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
}
const onClosePanel = (forceRefresh: boolean) => {
dispatch(setSelectedObjectView(""));
dispatch(setSelectedObjectView(null));
dispatch(setVersionsModeEnabled({ status: false }));
if (detailsOpen && selectedInternalPaths !== null) {
// We change URL to be the contained folder

View File

@@ -71,7 +71,6 @@ import { displayFileIconName } from "./utils";
import TagsModal from "../ObjectDetails/TagsModal";
import InspectObject from "./InspectObject";
import Loader from "../../../../Common/Loader/Loader";
import { setErrorSnackMessage } from "../../../../../../systemSlice";
import {
makeid,
storeCallForObjectWithID,
@@ -239,7 +238,7 @@ const ObjectDetailPanel = ({
dispatch(setLoadingObjectInfo(false));
})
.catch((error: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(error));
console.error("Error loading object details", error);
dispatch(setLoadingObjectInfo(false));
});
}

View File

@@ -194,15 +194,15 @@ export const permissionItems = (
// We split ARN & get the last item to check the URL
const splitARN = permissionElement.resource.split(":");
const url = splitARN.pop() || "";
const urlARN = splitARN.pop() || "";
// We split the paths of the URL & compare against current location to see if there are more items to include. In case current level is a wildcard or is the last one, we omit this validation
const splitURL = url.split("/");
const splitURLARN = urlARN.split("/");
// splitURL has more items than bucket name, we can continue validating
if (splitURL.length > 1) {
splitURL.every((currentElementInPath, index) => {
if (splitURLARN.length > 1) {
splitURLARN.every((currentElementInPath, index) => {
// It is a wildcard element. We can stor the verification as value should be included (?)
if (currentElementInPath === "*") {
return false;
@@ -240,17 +240,25 @@ export const permissionItems = (
if (prefixItem !== "") {
const splitItems = prefixItem.split("/");
let pathToRouteElements: string[] = [];
splitItems.every((splitElement, index) => {
if (!splitElement.includes("*")) {
if (splitElement !== splitURL[index]) {
if (!splitElement.includes("*") && splitElement !== "") {
if (splitElement !== splitCurrentPath[index]) {
returnElements.push({
name: `${splitElement}/`,
name: `${pathToRouteElements.join("/")}${
pathToRouteElements.length > 0 ? "/" : ""
}${splitElement}/`,
size: 0,
last_modified: new Date(),
version_id: "",
});
return false;
}
if (splitElement !== "") {
pathToRouteElements.push(splitElement);
}
return true;
}
return false;

View File

@@ -195,7 +195,7 @@ export const objectBrowserSlice = createSlice({
? state.selectedInternalPaths
: null;
},
setSelectedObjectView: (state, action: PayloadAction<string>) => {
setSelectedObjectView: (state, action: PayloadAction<string | null>) => {
state.selectedInternalPaths = action.payload;
},
setSimplePathHandler: (state, action: PayloadAction<string>) => {

View File

@@ -1086,7 +1086,7 @@ export const tenantSlice = createSlice({
}>
) => {
state.tenantDetails.currentTenant = action.payload.name;
state.tenantDetails.currentTenant = action.payload.namespace;
state.tenantDetails.currentNamespace = action.payload.namespace;
},
setTenantInfo: (state, action: PayloadAction<ITenant | null>) => {
if (action.payload) {

View File

@@ -1 +1 @@
1652244779
1653008276

View File

@@ -0,0 +1,134 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import * as roles from "../utils/roles";
import { Selector } from "testcafe";
import * as functions from "../utils/functions";
import {
cleanUpNamedBucketAndUploads,
namedTestBucketBrowseButtonFor,
} from "../utils/functions";
fixture("Test resources policy").page("http://localhost:9090/");
const bucket1 = "testcondition";
const test1BucketBrowseButton = namedTestBucketBrowseButtonFor(bucket1);
export const file = Selector(".ReactVirtualized__Table__rowColumn").withText(
"test.txt"
);
test
.before(async (t) => {
await functions.setUpNamedBucket(t, bucket1);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/secondlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/secondlevel/thirdlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
})(
"User can only see permitted files in last path as expected",
async (t) => {
await t
.useRole(roles.conditions2)
.navigateTo(`http://localhost:9090/buckets`)
.click(test1BucketBrowseButton)
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("firstlevel")
)
.expect(file.exists)
.notOk()
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText(
"secondlevel"
)
)
.expect(file.exists)
.notOk();
}
)
.after(async (t) => {
await functions.cleanUpNamedBucketAndUploads(t, bucket1);
});
test
.before(async (t) => {
await functions.setUpNamedBucket(t, bucket1);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/secondlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/secondlevel/thirdlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
})("User can browse from first level as policy has wildcard", async (t) => {
await t
.useRole(roles.conditions1)
.navigateTo(`http://localhost:9090/buckets`)
.click(test1BucketBrowseButton)
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("firstlevel")
)
.expect(file.exists)
.ok()
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("secondlevel")
)
.expect(file.exists)
.ok()
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("thirdlevel")
)
.expect(file.exists)
.ok();
})
.after(async (t) => {
await functions.cleanUpNamedBucketAndUploads(t, bucket1);
});

View File

@@ -0,0 +1,28 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "read-only",
"Effect": "Allow",
"Action": ["s3:GetBucketLocation"],
"Resource": ["arn:aws:s3:::testcondition"]
},
{
"Sid": "read",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::testcondition/firstlevel/*"]
},
{
"Sid": "statement2",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::testcondition"],
"Condition": {
"StringLike": {
"s3:prefix": ["firstlevel/*"]
}
}
}
]
}

View File

@@ -0,0 +1,28 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "read-only",
"Effect": "Allow",
"Action": ["s3:GetBucketLocation"],
"Resource": ["arn:aws:s3:::testcondition"]
},
{
"Sid": "read",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::testcondition/firstlevel/*"]
},
{
"Sid": "statement2",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::testcondition"],
"Condition": {
"StringLike": {
"s3:prefix": ["firstlevel/secondlevel/thirdlevel/*"]
}
}
}
]
}

View File

@@ -28,6 +28,8 @@ remove_users() {
mc admin user remove minio inspect-allowed-$TIMESTAMP
mc admin user remove minio inspect-not-allowed-$TIMESTAMP
mc admin user remove minio prefix-policy-ui-crash-$TIMESTAMP
mc admin user remove minio conditions-$TIMESTAMP
mc admin user remove minio conditions-2-$TIMESTAMP
}
remove_policies() {
@@ -50,6 +52,8 @@ remove_policies() {
mc admin policy remove minio inspect-allowed-$TIMESTAMP
mc admin policy remove minio inspect-not-allowed-$TIMESTAMPmc
mc admin policy remove minio fix-prefix-policy-ui-crash-$TIMESTAMP
mc admin policy remove minio conditions-policy-$TIMESTAMP
mc admin policy remove minio conditions-policy-2-$TIMESTAMP
}
__init__() {

7
portal-ui/tests/scripts/common.sh Normal file → Executable file
View File

@@ -46,6 +46,8 @@ create_policies() {
mc admin policy add minio inspect-not-allowed-$TIMESTAMP portal-ui/tests/policies/inspect-not-allowed.json
mc admin policy add minio fix-prefix-policy-ui-crash-$TIMESTAMP portal-ui/tests/policies/fix-prefix-policy-ui-crash.json
mc admin policy add minio delete-object-with-prefix-$TIMESTAMP portal-ui/tests/policies/deleteObjectWithPrefix.json
mc admin policy add minio conditions-policy-$TIMESTAMP portal-ui/tests/policies/conditionsPolicy.json
mc admin policy add minio conditions-policy-2-$TIMESTAMP portal-ui/tests/policies/conditionsPolicy2.json
}
create_users() {
@@ -73,11 +75,14 @@ create_users() {
mc admin user add minio inspect-not-allowed-$TIMESTAMP insnotallowed1234
mc admin user add minio prefix-policy-ui-crash-$TIMESTAMP poluicrashfix1234
mc admin user add minio delete-object-with-prefix-$TIMESTAMP deleteobjectwithprefix1234
mc admin user add minio conditions-$TIMESTAMP conditions1234
mc admin user add minio conditions-2-$TIMESTAMP conditions1234
}
create_buckets() {
mc mb minio/testcafe && mc cp ./portal-ui/tests/uploads/test.txt minio/testcafe/write/test.txt
mc mb minio/test && mc cp ./portal-ui/tests/uploads/test.txt minio/test/test.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/test/digitalinsights/xref_cust_guid_actd-v1.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/test/digitalinsights/test.txt
mc mb minio/testcondition && mc cp ./portal-ui/tests/uploads/test.txt minio/testcondition/test.txt && mc cp ./portal-ui/tests2/uploads/test.txt minio/testcondition/firstlevel/xref_cust_guid_actd-v1.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/testcondition/firstlevel/test.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/testcondition/firstlevel/secondlevel/test.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/testcondition/firstlevel/secondlevel/thirdlevel/test.txt
}
assign_policies() {
@@ -104,4 +109,6 @@ assign_policies() {
mc admin policy set minio inspect-allowed-$TIMESTAMP user=inspect-allowed-$TIMESTAMP
mc admin policy set minio inspect-not-allowed-$TIMESTAMP user=inspect-not-allowed-$TIMESTAMP
mc admin policy set minio delete-object-with-prefix-$TIMESTAMP user=delete-object-with-prefix-$TIMESTAMP
mc admin policy set minio conditions-policy-$TIMESTAMP user=conditions-$TIMESTAMP
mc admin policy set minio conditions-policy-2-$TIMESTAMP user=conditions-2-$TIMESTAMP
}

0
portal-ui/tests/scripts/initialize-env.sh Normal file → Executable file
View File

View File

@@ -36,6 +36,8 @@ remove_users() {
mc admin user remove minio inspect-not-allowed-"$TIMESTAMP"
mc admin user remove minio prefix-policy-ui-crash-"$TIMESTAMP"
mc admin user remove minio delete-object-with-prefix-"$TIMESTAMP"
mc admin user remove minio conditions-"$TIMESTAMP"
mc admin user remove minio conditions-2-"$TIMESTAMP"
}
remove_policies() {
@@ -59,11 +61,14 @@ remove_policies() {
mc admin policy remove minio inspect-not-allowed-"$TIMESTAMP"
mc admin policy remove minio fix-prefix-policy-ui-crash-"$TIMESTAMP"
mc admin policy remove minio delete-object-with-prefix-"$TIMESTAMP"
mc admin policy remove conditions-policy-"$TIMESTAMP"
mc admin policy remove conditions-policy-2-"$TIMESTAMP"
}
remove_buckets() {
mc rm minio/testcafe/write/test.txt && mc rm minio/testcafe
mc rm minio/test/test.txt && mc rm minio/test/digitalinsights/xref_cust_guid_actd-v1.txt && mc rm minio/test/digitalinsights/test.txt && mc rm minio/test
mc rm minio/testcondition/test.txt && mc rm minio/testcondition/firstlevel/xref_cust_guid_actd-v1.txt && mc rm minio/testcondition/firstlevel/test.txt && mc rm minio/testcondition/firstlevel/secondlevel/test.txt && mc rm minio/testcondition/firstlevel/secondlevel/thirdlevel/test.txt && mc rm minio/testcondition
}
cleanup() {

View File

@@ -33,14 +33,28 @@ export const setUpNamedBucket = (t, name) => {
accessKey: "minioadmin",
secretKey: "minioadmin",
});
return new Promise((resolve, reject) => {
minioClient.makeBucket(name, "us-east-1").then(resolve).catch(resolve);
minioClient.makeBucket(name, "us-east-1", (err) => {
if (err) {
console.log(err);
}
resolve("done: " + err);
});
});
};
export const uploadObjectToBucket = (t, modifier, objectName, objectPath) => {
const bucketName = `${constants.TEST_BUCKET_NAME}-${modifier}`;
return uploadNamedObjectToBucket(t, bucketName, objectName, objectPath);
};
export const uploadNamedObjectToBucket = (
t,
modifier,
objectName,
objectPath
) => {
const bucketName = modifier;
const minioClient = new Minio.Client({
endPoint: "localhost",
port: 9000,
@@ -49,10 +63,12 @@ export const uploadObjectToBucket = (t, modifier, objectName, objectPath) => {
secretKey: "minioadmin",
});
return new Promise((resolve, reject) => {
minioClient
.fPutObject(bucketName, objectName, objectPath, {})
.then(resolve)
.catch(resolve);
minioClient.fPutObject(bucketName, objectName, objectPath, {}, (err) => {
if (err) {
console.log(err);
}
resolve("done");
});
});
};

View File

@@ -250,3 +250,25 @@ export const deleteObjectWithPrefixOnly = Role(
},
{ preserveUrl: true }
);
export const conditions1 = Role(
loginUrl,
async (t) => {
await t
.typeText("#accessKey", "conditions-" + unixTimestamp)
.typeText("#secretKey", "conditions1234")
.click(submitButton);
},
{ preserveUrl: true }
);
export const conditions2 = Role(
loginUrl,
async (t) => {
await t
.typeText("#accessKey", "conditions-2-" + unixTimestamp)
.typeText("#secretKey", "conditions1234")
.click(submitButton);
},
{ preserveUrl: true }
);

View File

@@ -3775,6 +3775,11 @@ bser@2.1.1:
dependencies:
node-int64 "^0.4.0"
buffer-crc32@^0.2.13:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
@@ -8061,14 +8066,15 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.6:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minio@^7.0.26:
version "7.0.26"
resolved "https://registry.yarnpkg.com/minio/-/minio-7.0.26.tgz#83bcda813ed486a8bc7f028efa135d49b02d9bc8"
integrity sha512-knutnEZZMIUB/Xln6psVDrqObFKXDcF9m4IfFIX+zgDHYg3AlcF88DY1wdgg7bUkf+uU8iHkzP2q5CXAhia73w==
minio@^7.0.28:
version "7.0.28"
resolved "https://registry.yarnpkg.com/minio/-/minio-7.0.28.tgz#1f527ded42df457833e0b203ea51316d6d3d58a4"
integrity sha512-4Oua0R73oCxxmxhh2NiXDJo4Md159I/mdG8ybu6351leMQoB2Sy8S4HmgG6CxuPlEJ0h9M8/WyaI2CARDeeDTQ==
dependencies:
async "^3.1.0"
block-stream2 "^2.0.0"
browser-or-node "^1.3.0"
buffer-crc32 "^0.2.13"
crypto-browserify "^3.12.0"
es6-error "^4.1.1"
fast-xml-parser "^3.17.5"