diff --git a/models/add_bucket_lifecycle.go b/models/add_bucket_lifecycle.go index 41fd06083..dc94d5f08 100644 --- a/models/add_bucket_lifecycle.go +++ b/models/add_bucket_lifecycle.go @@ -43,9 +43,6 @@ type AddBucketLifecycle struct { // Non required, toggle to disable or enable rule ExpiredObjectDeleteMarker bool `json:"expired_object_delete_marker,omitempty"` - // Required in case of expiry_days or transition fields are not set. it defines an expiry date for ILM - ExpiryDate string `json:"expiry_date,omitempty"` - // Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM ExpiryDays int32 `json:"expiry_days,omitempty"` @@ -67,9 +64,6 @@ type AddBucketLifecycle struct { // Non required field, tags to match ILM files Tags string `json:"tags,omitempty"` - // Required in case of transition_days or expiry fields are not set. it defines a transition date for ILM - TransitionDate string `json:"transition_date,omitempty"` - // Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM TransitionDays int32 `json:"transition_days,omitempty"` diff --git a/models/add_multi_bucket_lifecycle.go b/models/add_multi_bucket_lifecycle.go new file mode 100644 index 000000000..d636c3213 --- /dev/null +++ b/models/add_multi_bucket_lifecycle.go @@ -0,0 +1,168 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +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 ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// AddMultiBucketLifecycle add multi bucket lifecycle +// +// swagger:model addMultiBucketLifecycle +type AddMultiBucketLifecycle struct { + + // buckets + // Required: true + Buckets []string `json:"buckets"` + + // Non required, toggle to disable or enable rule + ExpiredObjectDeleteMarker bool `json:"expired_object_delete_marker,omitempty"` + + // Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM + ExpiryDays int32 `json:"expiry_days,omitempty"` + + // Non required, can be set in case of expiration is enabled + NoncurrentversionExpirationDays int32 `json:"noncurrentversion_expiration_days,omitempty"` + + // Non required, can be set in case of transition is enabled + NoncurrentversionTransitionDays int32 `json:"noncurrentversion_transition_days,omitempty"` + + // Non required, can be set in case of transition is enabled + NoncurrentversionTransitionStorageClass string `json:"noncurrentversion_transition_storage_class,omitempty"` + + // Non required field, it matches a prefix to perform ILM operations on it + Prefix string `json:"prefix,omitempty"` + + // Required only in case of transition is set. it refers to a tier + StorageClass string `json:"storage_class,omitempty"` + + // Non required field, tags to match ILM files + Tags string `json:"tags,omitempty"` + + // Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM + TransitionDays int32 `json:"transition_days,omitempty"` + + // ILM Rule type (Expiry or transition) + // Required: true + // Enum: [expiry transition] + Type *string `json:"type"` +} + +// Validate validates this add multi bucket lifecycle +func (m *AddMultiBucketLifecycle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBuckets(formats); err != nil { + res = append(res, err) + } + + if err := m.validateType(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AddMultiBucketLifecycle) validateBuckets(formats strfmt.Registry) error { + + if err := validate.Required("buckets", "body", m.Buckets); err != nil { + return err + } + + return nil +} + +var addMultiBucketLifecycleTypeTypePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["expiry","transition"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + addMultiBucketLifecycleTypeTypePropEnum = append(addMultiBucketLifecycleTypeTypePropEnum, v) + } +} + +const ( + + // AddMultiBucketLifecycleTypeExpiry captures enum value "expiry" + AddMultiBucketLifecycleTypeExpiry string = "expiry" + + // AddMultiBucketLifecycleTypeTransition captures enum value "transition" + AddMultiBucketLifecycleTypeTransition string = "transition" +) + +// prop value enum +func (m *AddMultiBucketLifecycle) validateTypeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, addMultiBucketLifecycleTypeTypePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *AddMultiBucketLifecycle) validateType(formats strfmt.Registry) error { + + if err := validate.Required("type", "body", m.Type); err != nil { + return err + } + + // value enum + if err := m.validateTypeEnum("type", "body", *m.Type); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this add multi bucket lifecycle based on context it is used +func (m *AddMultiBucketLifecycle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *AddMultiBucketLifecycle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *AddMultiBucketLifecycle) UnmarshalBinary(b []byte) error { + var res AddMultiBucketLifecycle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/multi_lifecycle_result.go b/models/multi_lifecycle_result.go new file mode 100644 index 000000000..b8fdf98eb --- /dev/null +++ b/models/multi_lifecycle_result.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +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 ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// MultiLifecycleResult multi lifecycle result +// +// swagger:model multiLifecycleResult +type MultiLifecycleResult struct { + + // results + Results []*MulticycleResultItem `json:"results"` +} + +// Validate validates this multi lifecycle result +func (m *MultiLifecycleResult) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateResults(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MultiLifecycleResult) validateResults(formats strfmt.Registry) error { + if swag.IsZero(m.Results) { // not required + return nil + } + + for i := 0; i < len(m.Results); i++ { + if swag.IsZero(m.Results[i]) { // not required + continue + } + + if m.Results[i] != nil { + if err := m.Results[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("results" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("results" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this multi lifecycle result based on the context it is used +func (m *MultiLifecycleResult) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateResults(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MultiLifecycleResult) contextValidateResults(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Results); i++ { + + if m.Results[i] != nil { + if err := m.Results[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("results" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("results" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MultiLifecycleResult) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MultiLifecycleResult) UnmarshalBinary(b []byte) error { + var res MultiLifecycleResult + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/multicycle_result_item.go b/models/multicycle_result_item.go new file mode 100644 index 000000000..b0f8472ec --- /dev/null +++ b/models/multicycle_result_item.go @@ -0,0 +1,70 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +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 ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// MulticycleResultItem multicycle result item +// +// swagger:model multicycleResultItem +type MulticycleResultItem struct { + + // bucket name + BucketName string `json:"bucketName,omitempty"` + + // error + Error string `json:"error,omitempty"` +} + +// Validate validates this multicycle result item +func (m *MulticycleResultItem) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this multicycle result item based on context it is used +func (m *MulticycleResultItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MulticycleResultItem) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MulticycleResultItem) UnmarshalBinary(b []byte) error { + var res MulticycleResultItem + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/portal-ui/src/icons/LifecycleConfigIcon.tsx b/portal-ui/src/icons/LifecycleConfigIcon.tsx index ce7cbe3dc..7996bcb9f 100644 --- a/portal-ui/src/icons/LifecycleConfigIcon.tsx +++ b/portal-ui/src/icons/LifecycleConfigIcon.tsx @@ -20,69 +20,38 @@ import { SVGProps } from "react"; const LifecycleConfigIcon = (props: SVGProps) => ( - - - - - - - - - - - - - + + + + - diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx index f00b8fca1..977d754b6 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx @@ -287,6 +287,7 @@ const BucketLifecyclePanel = ({ {!loadingLifecycle && ( +
} diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/BulkLifecycleModal.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/BulkLifecycleModal.tsx new file mode 100644 index 000000000..27aa194b1 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/BulkLifecycleModal.tsx @@ -0,0 +1,462 @@ +// 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 . + +import React, { Fragment, useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import { SelectChangeEvent, Tooltip } from "@mui/material"; +import get from "lodash/get"; +import Grid from "@mui/material/Grid"; +import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; +import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline"; +import { + createTenantCommon, + formFieldStyles, + modalStyleUtils, + spacingUtils, +} from "../../Common/FormComponents/common/styleLibrary"; +import { setModalErrorSnackMessage } from "../../../../actions"; +import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper"; +import PredefinedList from "../../Common/FormComponents/PredefinedList/PredefinedList"; +import api from "../../../../common/api"; +import GenericWizard from "../../Common/GenericWizard/GenericWizard"; +import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; +import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper"; +import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector"; +import { ErrorResponseHandler } from "../../../../common/types"; +import QueryMultiSelector from "../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector"; +import { ITiersDropDown } from "../BucketDetails/AddLifecycleModal"; +import { + ITierElement, + ITierResponse, +} from "../../Configurations/TiersConfiguration/types"; +import { MultiBucketResult } from "../types"; + +interface IBulkReplicationModal { + open: boolean; + closeModalAndRefresh: (clearSelection: boolean) => any; + classes: any; + buckets: string[]; + setModalErrorSnackMessage: typeof setModalErrorSnackMessage; +} + +const styles = (theme: Theme) => + createStyles({ + resultGrid: { + display: "grid", + gridTemplateColumns: "45px auto", + alignItems: "center", + justifyContent: "stretch", + }, + errorIcon: { + paddingTop: 5, + color: "#C72C48", + }, + successIcon: { + paddingTop: 5, + color: "#42C91A", + }, + hide: { + opacity: 0, + transitionDuration: "0.3s", + }, + ...spacingUtils, + ...modalStyleUtils, + ...formFieldStyles, + ...createTenantCommon, + }); + +const AddBulkReplicationModal = ({ + open, + closeModalAndRefresh, + classes, + buckets, + setModalErrorSnackMessage, +}: IBulkReplicationModal) => { + const [addLoading, setAddLoading] = useState(false); + const [loadingTiers, setLoadingTiers] = useState(true); + const [tiersList, setTiersList] = useState([]); + const [prefix, setPrefix] = useState(""); + const [tags, setTags] = useState(""); + const [storageClass, setStorageClass] = useState(""); + const [NCTransitionSC, setNCTransitionSC] = useState(""); + const [expiredObjectDM, setExpiredObjectDM] = useState(false); + const [NCExpirationDays, setNCExpirationDays] = useState("0"); + const [NCTransitionDays, setNCTransitionDays] = useState("0"); + const [ilmType, setIlmType] = useState("expiry"); + const [expiryDays, setExpiryDays] = useState("0"); + const [transitionDays, setTransitionDays] = useState("0"); + const [isFormValid, setIsFormValid] = useState(false); + const [results, setResults] = useState(null); + + useEffect(() => { + if (loadingTiers) { + api + .invoke("GET", `/api/v1/admin/tiers`) + .then((res: ITierResponse) => { + const tiersList: ITierElement[] | null = get(res, "items", []); + + if (tiersList !== null && tiersList.length >= 1) { + const objList = tiersList.map((tier: ITierElement) => { + const tierType = tier.type; + const value = get(tier, `${tierType}.name`, ""); + + return { label: value, value: value }; + }); + + setTiersList(objList); + if (objList.length > 0) { + setStorageClass(objList[0].value); + } + } + setLoadingTiers(false); + }) + .catch((err: ErrorResponseHandler) => { + setLoadingTiers(false); + setModalErrorSnackMessage(err); + }); + } + }, [loadingTiers, setModalErrorSnackMessage]); + + useEffect(() => { + let valid = true; + + if (ilmType !== "expiry") { + if (storageClass === "") { + valid = false; + } + } + setIsFormValid(valid); + }, [ilmType, expiryDays, transitionDays, storageClass]); + + const LogoToShow = ({ errString }: { errString: string }) => { + switch (errString) { + case "": + return ( +
+ +
+ ); + case "n/a": + return null; + default: + if (errString) { + return ( +
+ + + +
+ ); + } + } + return null; + }; + + const createLifecycleRules = (to: any) => { + let rules = {}; + + if (ilmType === "expiry") { + let expiry = { + expiry_days: parseInt(expiryDays), + }; + + rules = { + ...expiry, + noncurrentversion_expiration_days: parseInt(NCExpirationDays), + }; + } else { + let transition = { + transition_days: parseInt(transitionDays), + }; + + rules = { + ...transition, + noncurrentversion_transition_days: parseInt(NCTransitionDays), + noncurrentversion_transition_storage_class: NCTransitionSC, + storage_class: storageClass, + }; + } + + const lifecycleInsert = { + buckets, + type: ilmType, + prefix, + tags, + expired_object_delete_marker: expiredObjectDM, + ...rules, + }; + + api + .invoke("POST", `/api/v1/buckets/multi-lifecycle`, lifecycleInsert) + .then((res: MultiBucketResult) => { + setAddLoading(false); + setResults(res); + to("++"); + }) + .catch((err: ErrorResponseHandler) => { + setAddLoading(false); + setModalErrorSnackMessage(err); + }); + }; + + return ( + { + closeModalAndRefresh(false); + }} + title="Set Lifecycle to multiple buckets" + > + + + + +

Remote Endpoint Configuration

+ + + +
+ + Lifecycle Configuration + + + + + ) => { + setIlmType(e.target.value as string); + }} + selectorOptions={[ + { value: "expiry", label: "Expiry" }, + { value: "transition", label: "Transition" }, + ]} + /> + + {ilmType === "expiry" ? ( + + + + ) => { + setExpiryDays(e.target.value); + }} + label="Expiry Days" + value={expiryDays} + min="0" + /> + + + + ) => { + setNCExpirationDays(e.target.value); + }} + label="Non-current Expiration Days" + value={NCExpirationDays} + min="0" + /> + + + ) : ( + + + + ) => { + setTransitionDays(e.target.value); + }} + label="Transition Days" + value={transitionDays} + min="0" + /> + + + + ) => { + setNCTransitionDays(e.target.value); + }} + label="Non-current Transition Days" + value={NCTransitionDays} + min="0" + /> + + + + ) => { + setNCTransitionSC(e.target.value); + }} + placeholder="Set Non-current Version Transition Storage Class" + label="Non-current Version Transition Storage Class" + value={NCTransitionSC} + /> + + + ) => { + setStorageClass(e.target.value as string); + }} + options={tiersList} + /> + + + )} +
+
+ +
+ + File Configuration + + + + + ) => { + setPrefix(e.target.value); + }} + label="Prefix" + value={prefix} + /> + + + { + setTags(vl); + }} + keyPlaceholder="Tag Key" + valuePlaceholder="Tag Value" + withBorder + /> + + + + ) => { + setExpiredObjectDM(event.target.checked); + }} + label={"Expired Object Delete Marker"} + /> + +
+
+
+
+ + ), + buttons: [ + { + type: "custom", + label: "Create Rules", + enabled: !loadingTiers && !addLoading && isFormValid, + action: createLifecycleRules, + }, + ], + }, + { + label: "Results", + componentRender: ( + +

Multi Bucket lifecycle Assignments Results

+ + +

Buckets Results

+ {results?.results.map((resultItem) => { + return ( +
+ {LogoToShow({ errString: resultItem.error || "" })} + {resultItem.bucketName} +
+ ); + })} +
+
+
+ ), + buttons: [ + { + type: "custom", + label: "Done", + enabled: !addLoading, + action: () => closeModalAndRefresh(true), + }, + ], + }, + ]} + forModal + /> +
+ ); +}; + +const connector = connect(null, { + setModalErrorSnackMessage, +}); + +export default withStyles(styles)(connector(AddBulkReplicationModal)); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx index fcc21cc1e..18b95551f 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx @@ -22,7 +22,7 @@ import withStyles from "@mui/styles/withStyles"; import { LinearProgress } from "@mui/material"; import Grid from "@mui/material/Grid"; import { Bucket, BucketList } from "../types"; -import { AddIcon, BucketsIcon } from "../../../../icons"; +import {AddIcon, BucketsIcon, LifecycleConfigIcon} from "../../../../icons"; import { AppState } from "../../../../store"; import { setErrorSnackMessage } from "../../../../actions"; import { @@ -50,6 +50,7 @@ import PageLayout from "../../Common/Layout/PageLayout"; import SearchBox from "../../Common/SearchBox"; import VirtualizedList from "../../Common/VirtualizedList/VirtualizedList"; import RBIconButton from "../BucketDetails/SummaryItems/RBIconButton"; +import BulkLifecycleModal from "./BulkLifecycleModal"; const styles = (theme: Theme) => createStyles({ @@ -100,6 +101,7 @@ const ListBuckets = ({ const [selectedBuckets, setSelectedBuckets] = useState([]); const [replicationModalOpen, setReplicationModalOpen] = useState(false); + const [lifecycleModalOpen, setLifecycleModalOpen] = useState(false); const [bulkSelect, setBulkSelect] = useState(false); @@ -174,6 +176,14 @@ const ListBuckets = ({ } }; + const closeBulkLifecycleModal = (unselectAll: boolean) => { + setLifecycleModalOpen(false); + + if (unselectAll) { + setSelectedBuckets([]); + } + }; + const renderItemLine = (index: number) => { const bucket = filteredRecords[index] || null; if (bucket) { @@ -213,6 +223,13 @@ const ListBuckets = ({ closeModalAndRefresh={closeBulkReplicationModal} /> )} + {lifecycleModalOpen && ( + + )} @@ -241,6 +258,18 @@ const ListBuckets = ({ variant={bulkSelect ? "contained" : "outlined"} /> + { + setLifecycleModalOpen(true); + }} + text={""} + icon={} + disabled={selectedBuckets.length === 0} + color={"primary"} + variant={"outlined"} + /> + { diff --git a/portal-ui/src/screens/Console/Buckets/types.tsx b/portal-ui/src/screens/Console/Buckets/types.tsx index 18ff88e2d..0d2b26747 100644 --- a/portal-ui/src/screens/Console/Buckets/types.tsx +++ b/portal-ui/src/screens/Console/Buckets/types.tsx @@ -201,3 +201,12 @@ export interface LifeCycleItem { tags?: any; status?: string; } + +export interface MultiBucketResult { + bucketName: string, + error?: string, +} + +export interface MultiBucketResult { + results: MultiBucketResult[], +} diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index de55fc6d3..ea1e9a33e 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -698,6 +698,39 @@ func init() { } } }, + "/buckets/multi-lifecycle": { + "post": { + "tags": [ + "UserAPI" + ], + "summary": "Add Multi Bucket Lifecycle", + "operationId": "AddMultiBucketLifecycle", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/addMultiBucketLifecycle" + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/multiLifecycleResult" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/buckets/{bucket_name}/delete-objects": { "post": { "tags": [ @@ -3850,10 +3883,6 @@ func init() { "description": "Non required, toggle to disable or enable rule", "type": "boolean" }, - "expiry_date": { - "description": "Required in case of expiry_days or transition fields are not set. it defines an expiry date for ILM", - "type": "string" - }, "expiry_days": { "description": "Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM", "type": "integer", @@ -3888,10 +3917,6 @@ func init() { "description": "Non required field, tags to match ILM files", "type": "string" }, - "transition_date": { - "description": "Required in case of transition_days or expiry fields are not set. it defines a transition date for ILM", - "type": "string" - }, "transition_days": { "description": "Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM", "type": "integer", @@ -3937,6 +3962,73 @@ func init() { } } }, + "addMultiBucketLifecycle": { + "type": "object", + "required": [ + "buckets", + "type" + ], + "properties": { + "buckets": { + "type": "array", + "items": { + "type": "string" + } + }, + "expired_object_delete_marker": { + "description": "Non required, toggle to disable or enable rule", + "type": "boolean" + }, + "expiry_days": { + "description": "Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM", + "type": "integer", + "format": "int32", + "default": 0 + }, + "noncurrentversion_expiration_days": { + "description": "Non required, can be set in case of expiration is enabled", + "type": "integer", + "format": "int32", + "default": 0 + }, + "noncurrentversion_transition_days": { + "description": "Non required, can be set in case of transition is enabled", + "type": "integer", + "format": "int32", + "default": 0 + }, + "noncurrentversion_transition_storage_class": { + "description": "Non required, can be set in case of transition is enabled", + "type": "string" + }, + "prefix": { + "description": "Non required field, it matches a prefix to perform ILM operations on it", + "type": "string" + }, + "storage_class": { + "description": "Required only in case of transition is set. it refers to a tier", + "type": "string" + }, + "tags": { + "description": "Non required field, tags to match ILM files", + "type": "string" + }, + "transition_days": { + "description": "Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM", + "type": "integer", + "format": "int32", + "default": 0 + }, + "type": { + "description": "ILM Rule type (Expiry or transition)", + "type": "string", + "enum": [ + "expiry", + "transition" + ] + } + } + }, "addPolicyRequest": { "type": "object", "required": [ @@ -5043,6 +5135,27 @@ func init() { } } }, + "multiLifecycleResult": { + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/multicycleResultItem" + } + } + } + }, + "multicycleResultItem": { + "type": "object", + "properties": { + "bucketName": { + "type": "string" + }, + "error": { + "type": "string" + } + } + }, "nofiticationService": { "type": "string", "enum": [ @@ -6956,6 +7069,39 @@ func init() { } } }, + "/buckets/multi-lifecycle": { + "post": { + "tags": [ + "UserAPI" + ], + "summary": "Add Multi Bucket Lifecycle", + "operationId": "AddMultiBucketLifecycle", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/addMultiBucketLifecycle" + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/multiLifecycleResult" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/buckets/{bucket_name}/delete-objects": { "post": { "tags": [ @@ -10226,10 +10372,6 @@ func init() { "description": "Non required, toggle to disable or enable rule", "type": "boolean" }, - "expiry_date": { - "description": "Required in case of expiry_days or transition fields are not set. it defines an expiry date for ILM", - "type": "string" - }, "expiry_days": { "description": "Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM", "type": "integer", @@ -10264,10 +10406,6 @@ func init() { "description": "Non required field, tags to match ILM files", "type": "string" }, - "transition_date": { - "description": "Required in case of transition_days or expiry fields are not set. it defines a transition date for ILM", - "type": "string" - }, "transition_days": { "description": "Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM", "type": "integer", @@ -10313,6 +10451,73 @@ func init() { } } }, + "addMultiBucketLifecycle": { + "type": "object", + "required": [ + "buckets", + "type" + ], + "properties": { + "buckets": { + "type": "array", + "items": { + "type": "string" + } + }, + "expired_object_delete_marker": { + "description": "Non required, toggle to disable or enable rule", + "type": "boolean" + }, + "expiry_days": { + "description": "Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM", + "type": "integer", + "format": "int32", + "default": 0 + }, + "noncurrentversion_expiration_days": { + "description": "Non required, can be set in case of expiration is enabled", + "type": "integer", + "format": "int32", + "default": 0 + }, + "noncurrentversion_transition_days": { + "description": "Non required, can be set in case of transition is enabled", + "type": "integer", + "format": "int32", + "default": 0 + }, + "noncurrentversion_transition_storage_class": { + "description": "Non required, can be set in case of transition is enabled", + "type": "string" + }, + "prefix": { + "description": "Non required field, it matches a prefix to perform ILM operations on it", + "type": "string" + }, + "storage_class": { + "description": "Required only in case of transition is set. it refers to a tier", + "type": "string" + }, + "tags": { + "description": "Non required field, tags to match ILM files", + "type": "string" + }, + "transition_days": { + "description": "Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM", + "type": "integer", + "format": "int32", + "default": 0 + }, + "type": { + "description": "ILM Rule type (Expiry or transition)", + "type": "string", + "enum": [ + "expiry", + "transition" + ] + } + } + }, "addPolicyRequest": { "type": "object", "required": [ @@ -11419,6 +11624,27 @@ func init() { } } }, + "multiLifecycleResult": { + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/multicycleResultItem" + } + } + } + }, + "multicycleResultItem": { + "type": "object", + "properties": { + "bucketName": { + "type": "string" + }, + "error": { + "type": "string" + } + } + }, "nofiticationService": { "type": "string", "enum": [ diff --git a/restapi/operations/console_api.go b/restapi/operations/console_api.go index 535936a8b..e5e077344 100644 --- a/restapi/operations/console_api.go +++ b/restapi/operations/console_api.go @@ -74,6 +74,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI { AdminAPIAddGroupHandler: admin_api.AddGroupHandlerFunc(func(params admin_api.AddGroupParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.AddGroup has not yet been implemented") }), + UserAPIAddMultiBucketLifecycleHandler: user_api.AddMultiBucketLifecycleHandlerFunc(func(params user_api.AddMultiBucketLifecycleParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation user_api.AddMultiBucketLifecycle has not yet been implemented") + }), AdminAPIAddNotificationEndpointHandler: admin_api.AddNotificationEndpointHandlerFunc(func(params admin_api.AddNotificationEndpointParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.AddNotificationEndpoint has not yet been implemented") }), @@ -456,6 +459,8 @@ type ConsoleAPI struct { UserAPIAddBucketLifecycleHandler user_api.AddBucketLifecycleHandler // AdminAPIAddGroupHandler sets the operation handler for the add group operation AdminAPIAddGroupHandler admin_api.AddGroupHandler + // UserAPIAddMultiBucketLifecycleHandler sets the operation handler for the add multi bucket lifecycle operation + UserAPIAddMultiBucketLifecycleHandler user_api.AddMultiBucketLifecycleHandler // AdminAPIAddNotificationEndpointHandler sets the operation handler for the add notification endpoint operation AdminAPIAddNotificationEndpointHandler admin_api.AddNotificationEndpointHandler // AdminAPIAddPolicyHandler sets the operation handler for the add policy operation @@ -766,6 +771,9 @@ func (o *ConsoleAPI) Validate() error { if o.AdminAPIAddGroupHandler == nil { unregistered = append(unregistered, "admin_api.AddGroupHandler") } + if o.UserAPIAddMultiBucketLifecycleHandler == nil { + unregistered = append(unregistered, "user_api.AddMultiBucketLifecycleHandler") + } if o.AdminAPIAddNotificationEndpointHandler == nil { unregistered = append(unregistered, "admin_api.AddNotificationEndpointHandler") } @@ -1204,6 +1212,10 @@ func (o *ConsoleAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } + o.handlers["POST"]["/buckets/multi-lifecycle"] = user_api.NewAddMultiBucketLifecycle(o.context, o.UserAPIAddMultiBucketLifecycleHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } o.handlers["POST"]["/admin/notification_endpoints"] = admin_api.NewAddNotificationEndpoint(o.context, o.AdminAPIAddNotificationEndpointHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) diff --git a/restapi/operations/user_api/add_multi_bucket_lifecycle.go b/restapi/operations/user_api/add_multi_bucket_lifecycle.go new file mode 100644 index 000000000..623a36bd9 --- /dev/null +++ b/restapi/operations/user_api/add_multi_bucket_lifecycle.go @@ -0,0 +1,88 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/console/models" +) + +// AddMultiBucketLifecycleHandlerFunc turns a function with the right signature into a add multi bucket lifecycle handler +type AddMultiBucketLifecycleHandlerFunc func(AddMultiBucketLifecycleParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn AddMultiBucketLifecycleHandlerFunc) Handle(params AddMultiBucketLifecycleParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// AddMultiBucketLifecycleHandler interface for that can handle valid add multi bucket lifecycle params +type AddMultiBucketLifecycleHandler interface { + Handle(AddMultiBucketLifecycleParams, *models.Principal) middleware.Responder +} + +// NewAddMultiBucketLifecycle creates a new http.Handler for the add multi bucket lifecycle operation +func NewAddMultiBucketLifecycle(ctx *middleware.Context, handler AddMultiBucketLifecycleHandler) *AddMultiBucketLifecycle { + return &AddMultiBucketLifecycle{Context: ctx, Handler: handler} +} + +/* AddMultiBucketLifecycle swagger:route POST /buckets/multi-lifecycle UserAPI addMultiBucketLifecycle + +Add Multi Bucket Lifecycle + +*/ +type AddMultiBucketLifecycle struct { + Context *middleware.Context + Handler AddMultiBucketLifecycleHandler +} + +func (o *AddMultiBucketLifecycle) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewAddMultiBucketLifecycleParams() + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + *r = *aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/restapi/operations/user_api/add_multi_bucket_lifecycle_parameters.go b/restapi/operations/user_api/add_multi_bucket_lifecycle_parameters.go new file mode 100644 index 000000000..89e034212 --- /dev/null +++ b/restapi/operations/user_api/add_multi_bucket_lifecycle_parameters.go @@ -0,0 +1,102 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "github.com/minio/console/models" +) + +// NewAddMultiBucketLifecycleParams creates a new AddMultiBucketLifecycleParams object +// +// There are no default values defined in the spec. +func NewAddMultiBucketLifecycleParams() AddMultiBucketLifecycleParams { + + return AddMultiBucketLifecycleParams{} +} + +// AddMultiBucketLifecycleParams contains all the bound params for the add multi bucket lifecycle operation +// typically these are obtained from a http.Request +// +// swagger:parameters AddMultiBucketLifecycle +type AddMultiBucketLifecycleParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.AddMultiBucketLifecycle +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewAddMultiBucketLifecycleParams() beforehand. +func (o *AddMultiBucketLifecycleParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.AddMultiBucketLifecycle + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(context.Background()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/restapi/operations/user_api/add_multi_bucket_lifecycle_responses.go b/restapi/operations/user_api/add_multi_bucket_lifecycle_responses.go new file mode 100644 index 000000000..2eff03ed3 --- /dev/null +++ b/restapi/operations/user_api/add_multi_bucket_lifecycle_responses.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/minio/console/models" +) + +// AddMultiBucketLifecycleOKCode is the HTTP code returned for type AddMultiBucketLifecycleOK +const AddMultiBucketLifecycleOKCode int = 200 + +/*AddMultiBucketLifecycleOK A successful response. + +swagger:response addMultiBucketLifecycleOK +*/ +type AddMultiBucketLifecycleOK struct { + + /* + In: Body + */ + Payload *models.MultiLifecycleResult `json:"body,omitempty"` +} + +// NewAddMultiBucketLifecycleOK creates AddMultiBucketLifecycleOK with default headers values +func NewAddMultiBucketLifecycleOK() *AddMultiBucketLifecycleOK { + + return &AddMultiBucketLifecycleOK{} +} + +// WithPayload adds the payload to the add multi bucket lifecycle o k response +func (o *AddMultiBucketLifecycleOK) WithPayload(payload *models.MultiLifecycleResult) *AddMultiBucketLifecycleOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the add multi bucket lifecycle o k response +func (o *AddMultiBucketLifecycleOK) SetPayload(payload *models.MultiLifecycleResult) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *AddMultiBucketLifecycleOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*AddMultiBucketLifecycleDefault Generic error response. + +swagger:response addMultiBucketLifecycleDefault +*/ +type AddMultiBucketLifecycleDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewAddMultiBucketLifecycleDefault creates AddMultiBucketLifecycleDefault with default headers values +func NewAddMultiBucketLifecycleDefault(code int) *AddMultiBucketLifecycleDefault { + if code <= 0 { + code = 500 + } + + return &AddMultiBucketLifecycleDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the add multi bucket lifecycle default response +func (o *AddMultiBucketLifecycleDefault) WithStatusCode(code int) *AddMultiBucketLifecycleDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the add multi bucket lifecycle default response +func (o *AddMultiBucketLifecycleDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the add multi bucket lifecycle default response +func (o *AddMultiBucketLifecycleDefault) WithPayload(payload *models.Error) *AddMultiBucketLifecycleDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the add multi bucket lifecycle default response +func (o *AddMultiBucketLifecycleDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *AddMultiBucketLifecycleDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/user_api/add_multi_bucket_lifecycle_urlbuilder.go b/restapi/operations/user_api/add_multi_bucket_lifecycle_urlbuilder.go new file mode 100644 index 000000000..422095418 --- /dev/null +++ b/restapi/operations/user_api/add_multi_bucket_lifecycle_urlbuilder.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// AddMultiBucketLifecycleURL generates an URL for the add multi bucket lifecycle operation +type AddMultiBucketLifecycleURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *AddMultiBucketLifecycleURL) WithBasePath(bp string) *AddMultiBucketLifecycleURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *AddMultiBucketLifecycleURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *AddMultiBucketLifecycleURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/buckets/multi-lifecycle" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *AddMultiBucketLifecycleURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *AddMultiBucketLifecycleURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *AddMultiBucketLifecycleURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on AddMultiBucketLifecycleURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on AddMultiBucketLifecycleURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *AddMultiBucketLifecycleURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/user_buckets_lifecycle.go b/restapi/user_buckets_lifecycle.go index e480c6016..f7403004b 100644 --- a/restapi/user_buckets_lifecycle.go +++ b/restapi/user_buckets_lifecycle.go @@ -39,6 +39,11 @@ import ( "github.com/minio/console/restapi/operations/user_api" ) +type MultiLifecycleResult struct { + BucketName string + Error string +} + func registerBucketsLifecycleHandlers(api *operations.ConsoleAPI) { api.UserAPIGetBucketLifecycleHandler = user_api.GetBucketLifecycleHandlerFunc(func(params user_api.GetBucketLifecycleParams, session *models.Principal) middleware.Responder { listBucketLifecycleResponse, err := getBucketLifecycleResponse(session, params) @@ -70,6 +75,14 @@ func registerBucketsLifecycleHandlers(api *operations.ConsoleAPI) { return user_api.NewDeleteBucketLifecycleRuleNoContent() }) + api.UserAPIAddMultiBucketLifecycleHandler = user_api.AddMultiBucketLifecycleHandlerFunc(func(params user_api.AddMultiBucketLifecycleParams, session *models.Principal) middleware.Responder { + multiBucketResponse, err := getAddMultiBucketLifecycleResponse(session, params) + if err != nil { + user_api.NewAddMultiBucketLifecycleDefault(int(err.Code)).WithPayload(err) + } + + return user_api.NewAddMultiBucketLifecycleOK().WithPayload(multiBucketResponse) + }) } // getBucketLifecycle() gets lifecycle lists for a bucket from MinIO API and returns their implementations @@ -376,3 +389,98 @@ func getDeleteBucketLifecycleRule(session *models.Principal, params user_api.Del return nil } + +// addMultiBucketLifecycle creates multibuckets lifecycle assignments +func addMultiBucketLifecycle(ctx context.Context, client MinioClient, params user_api.AddMultiBucketLifecycleParams) []MultiLifecycleResult { + bucketsRelation := params.Body.Buckets + + // Parallel Lifecycle rules set + + parallelLifecycleBucket := func(bucketName string) chan MultiLifecycleResult { + remoteProc := make(chan MultiLifecycleResult) + + lifecycleParams := models.AddBucketLifecycle{ + Type: *params.Body.Type, + StorageClass: params.Body.StorageClass, + TransitionDays: params.Body.TransitionDays, + Prefix: params.Body.Prefix, + NoncurrentversionTransitionDays: params.Body.NoncurrentversionTransitionDays, + NoncurrentversionTransitionStorageClass: params.Body.NoncurrentversionTransitionStorageClass, + NoncurrentversionExpirationDays: params.Body.NoncurrentversionExpirationDays, + Tags: params.Body.Tags, + ExpiryDays: params.Body.ExpiryDays, + Disable: false, + ExpiredObjectDeleteMarker: params.Body.ExpiredObjectDeleteMarker, + } + + go func() { + defer close(remoteProc) + + lifecycleParams := user_api.AddBucketLifecycleParams{ + BucketName: bucketName, + Body: &lifecycleParams, + } + + // We add lifecycle rule & expect a response + err := addBucketLifecycle(ctx, client, lifecycleParams) + + var errorReturn = "" + + if err != nil { + errorReturn = err.Error() + } + + retParams := MultiLifecycleResult{ + BucketName: bucketName, + Error: errorReturn, + } + + remoteProc <- retParams + }() + return remoteProc + } + + var lifecycleManagement []chan MultiLifecycleResult + + for _, bucketName := range bucketsRelation { + rBucket := parallelLifecycleBucket(bucketName) + lifecycleManagement = append(lifecycleManagement, rBucket) + } + + var resultsList []MultiLifecycleResult + for _, result := range lifecycleManagement { + res := <-result + resultsList = append(resultsList, res) + } + + return resultsList +} + +// getAddMultiBucketLifecycleResponse returns the response of multibucket lifecycle assignment +func getAddMultiBucketLifecycleResponse(session *models.Principal, params user_api.AddMultiBucketLifecycleParams) (*models.MultiLifecycleResult, *models.Error) { + ctx := context.Background() + mClient, err := newMinioClient(session) + if err != nil { + return nil, prepareError(err) + } + // create a minioClient interface implementation + // defining the client to be used + minioClient := minioClient{client: mClient} + + multiCycleResult := addMultiBucketLifecycle(ctx, minioClient, params) + + var returnList []*models.MulticycleResultItem + + for _, resultItem := range multiCycleResult { + multicycleRS := models.MulticycleResultItem{ + BucketName: resultItem.BucketName, + Error: resultItem.Error, + } + + returnList = append(returnList, &multicycleRS) + } + + finalResult := models.MultiLifecycleResult{Results: returnList} + + return &finalResult, nil +} diff --git a/restapi/user_buckets_lifecycle_test.go b/restapi/user_buckets_lifecycle_test.go index 6326c8305..66ffda317 100644 --- a/restapi/user_buckets_lifecycle_test.go +++ b/restapi/user_buckets_lifecycle_test.go @@ -178,7 +178,6 @@ func TestSetLifecycleRule(t *testing.T) { Prefix: "pref1", StorageClass: "", Tags: "", - TransitionDate: "", TransitionDays: 0, }, } diff --git a/swagger-console.yml b/swagger-console.yml index 9ea62117a..d0087dce9 100644 --- a/swagger-console.yml +++ b/swagger-console.yml @@ -1115,6 +1115,28 @@ paths: tags: - UserAPI + /buckets/multi-lifecycle: + post: + summary: Add Multi Bucket Lifecycle + operationId: AddMultiBucketLifecycle + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/addMultiBucketLifecycle" + responses: + 200: + description: A successful response. + schema: + $ref: "#/definitions/multiLifecycleResult" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - UserAPI + /buckets/{bucket_name}/lifecycle/{lifecycle_id}: put: summary: Update Lifecycle rule @@ -3931,17 +3953,11 @@ definitions: tags: description: Non required field, tags to match ILM files type: string - expiry_date: - description: Required in case of expiry_days or transition fields are not set. it defines an expiry date for ILM - type: string expiry_days: description: Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM type: integer format: int32 default: 0 - transition_date: - description: Required in case of transition_days or expiry fields are not set. it defines a transition date for ILM - type: string transition_days: description: Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM type: integer @@ -4020,6 +4036,73 @@ definitions: description: Non required, can be set in case of transition is enabled type: string + addMultiBucketLifecycle: + type: object + required: + - buckets + - type + properties: + buckets: + type: array + items: + type: string + type: + description: ILM Rule type (Expiry or transition) + type: string + enum: + - expiry + - transition + prefix: + description: Non required field, it matches a prefix to perform ILM operations on it + type: string + tags: + description: Non required field, tags to match ILM files + type: string + expiry_days: + description: Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM + type: integer + format: int32 + default: 0 + transition_days: + description: Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM + type: integer + format: int32 + default: 0 + storage_class: + description: Required only in case of transition is set. it refers to a tier + type: string + expired_object_delete_marker: + description: Non required, toggle to disable or enable rule + type: boolean + noncurrentversion_expiration_days: + description: Non required, can be set in case of expiration is enabled + type: integer + format: int32 + default: 0 + noncurrentversion_transition_days: + description: Non required, can be set in case of transition is enabled + type: integer + format: int32 + default: 0 + noncurrentversion_transition_storage_class: + description: Non required, can be set in case of transition is enabled + type: string + + multicycleResultItem: + type: object + properties: + bucketName: + type: string + error: + type: string + + multiLifecycleResult: + properties: + results: + type: array + items: + $ref: "#/definitions/multicycleResultItem" + prefixAccessPair: type: object properties: