Added lifecycle rule edit capability (#1539)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2022-02-10 10:34:56 -07:00
committed by GitHub
parent 07ef32bee3
commit 32a3094386
13 changed files with 690 additions and 93 deletions

View File

@@ -42,6 +42,9 @@ type ExpirationResponse struct {
// delete marker
DeleteMarker bool `json:"delete_marker,omitempty"`
// noncurrent expiration days
NoncurrentExpirationDays int64 `json:"noncurrent_expiration_days,omitempty"`
}
// Validate validates this expiration response

View File

@@ -0,0 +1,95 @@
// 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 <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// LifecycleRuleType lifecycle rule type
//
// swagger:model lifecycleRuleType
type LifecycleRuleType string
func NewLifecycleRuleType(value LifecycleRuleType) *LifecycleRuleType {
return &value
}
// Pointer returns a pointer to a freshly-allocated LifecycleRuleType.
func (m LifecycleRuleType) Pointer() *LifecycleRuleType {
return &m
}
const (
// LifecycleRuleTypeExpiry captures enum value "expiry"
LifecycleRuleTypeExpiry LifecycleRuleType = "expiry"
// LifecycleRuleTypeTransition captures enum value "transition"
LifecycleRuleTypeTransition LifecycleRuleType = "transition"
)
// for schema
var lifecycleRuleTypeEnum []interface{}
func init() {
var res []LifecycleRuleType
if err := json.Unmarshal([]byte(`["expiry","transition"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
lifecycleRuleTypeEnum = append(lifecycleRuleTypeEnum, v)
}
}
func (m LifecycleRuleType) validateLifecycleRuleTypeEnum(path, location string, value LifecycleRuleType) error {
if err := validate.EnumCase(path, location, value, lifecycleRuleTypeEnum, true); err != nil {
return err
}
return nil
}
// Validate validates this lifecycle rule type
func (m LifecycleRuleType) Validate(formats strfmt.Registry) error {
var res []error
// value enum
if err := m.validateLifecycleRuleTypeEnum("", "body", m); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this lifecycle rule type based on context it is used
func (m LifecycleRuleType) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

View File

@@ -40,6 +40,12 @@ type TransitionResponse struct {
// days
Days int64 `json:"days,omitempty"`
// noncurrent storage class
NoncurrentStorageClass string `json:"noncurrent_storage_class,omitempty"`
// noncurrent transition days
NoncurrentTransitionDays int64 `json:"noncurrent_transition_days,omitempty"`
// storage class
StorageClass string `json:"storage_class,omitempty"`
}

View File

@@ -24,9 +24,12 @@ package models
import (
"context"
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// UpdateBucketLifecycle update bucket lifecycle
@@ -63,10 +66,67 @@ type UpdateBucketLifecycle struct {
// 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 update bucket lifecycle
func (m *UpdateBucketLifecycle) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateType(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
var updateBucketLifecycleTypeTypePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["expiry","transition"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
updateBucketLifecycleTypeTypePropEnum = append(updateBucketLifecycleTypeTypePropEnum, v)
}
}
const (
// UpdateBucketLifecycleTypeExpiry captures enum value "expiry"
UpdateBucketLifecycleTypeExpiry string = "expiry"
// UpdateBucketLifecycleTypeTransition captures enum value "transition"
UpdateBucketLifecycleTypeTransition string = "transition"
)
// prop value enum
func (m *UpdateBucketLifecycle) validateTypeEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, updateBucketLifecycleTypeTypePropEnum, true); err != nil {
return err
}
return nil
}
func (m *UpdateBucketLifecycle) 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
}

View File

@@ -51,7 +51,7 @@ interface IReplicationModal {
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
}
interface ITiersDropDown {
export interface ITiersDropDown {
label: string;
value: string;
}

View File

@@ -71,6 +71,8 @@ const BucketLifecyclePanel = ({
const [lifecycleRecords, setLifecycleRecords] = useState<LifeCycleItem[]>([]);
const [addLifecycleOpen, setAddLifecycleOpen] = useState<boolean>(false);
const [editLifecycleOpen, setEditLifecycleOpen] = useState<boolean>(false);
const [selectedLifecycleRule, setSelectedLifecycleRule] =
useState<LifeCycleItem | null>(null);
const bucketName = match.params["bucketName"];
@@ -112,6 +114,7 @@ const BucketLifecyclePanel = ({
const closeEditLCAndRefresh = (refresh: boolean) => {
setEditLifecycleOpen(false);
setSelectedLifecycleRule(null);
if (refresh) {
setLoadingLifecycle(true);
}
@@ -182,16 +185,25 @@ const BucketLifecyclePanel = ({
},
];
const lifecycleActions = [
{
type: "view",
onClick(valueToSend: any): any {
setSelectedLifecycleRule(valueToSend);
setEditLifecycleOpen(true);
},
},
];
return (
<Fragment>
{editLifecycleOpen && (
{editLifecycleOpen && selectedLifecycleRule && (
<EditLifecycleConfiguration
open={editLifecycleOpen}
closeModalAndRefresh={closeEditLCAndRefresh}
selectedBucket={bucketName}
lifecycle={{
id: "",
}}
lifecycle={selectedLifecycleRule}
/>
)}
{addLifecycleOpen && (
@@ -232,7 +244,7 @@ const BucketLifecyclePanel = ({
errorProps={{ disabled: true }}
>
<TableWrapper
itemActions={[]}
itemActions={lifecycleActions}
columns={lifecycleColumns}
isLoading={loadingLifecycle}
records={lifecycleRecords}

View File

@@ -14,30 +14,58 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, Fragment } from "react";
import { connect } from "react-redux";
import Grid from "@mui/material/Grid";
import { Button, LinearProgress } from "@mui/material";
import { Button, LinearProgress, SelectChangeEvent } from "@mui/material";
import { Theme } from "@mui/material/styles";
import get from "lodash/get";
import Grid from "@mui/material/Grid";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
import {
createTenantCommon,
formFieldStyles,
modalStyleUtils,
spacingUtils,
} from "../../Common/FormComponents/common/styleLibrary";
import { setModalErrorSnackMessage } from "../../../../actions";
import { LifeCycleItem } from "../types";
import { ErrorResponseHandler } from "../../../../common/types";
import { LifecycleConfigIcon } from "../../../../icons";
import { ITiersDropDown } from "./AddLifecycleModal";
import {
ITierElement,
ITierResponse,
} from "../../Configurations/TiersConfiguration/types";
import api from "../../../../common/api";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import QueryMultiSelector from "../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
import { LifecycleConfigIcon } from "../../../../icons";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
dateSelector: {
"& div": {
borderBottom: 0,
marginBottom: 0,
"& div:nth-child(2)": {
border: "1px solid #EAEAEA",
paddingLeft: 5,
"& div": {
border: 0,
},
},
},
},
...modalBasic,
...spacingUtils,
...modalStyleUtils,
...formFieldStyles,
...createTenantCommon,
});
interface IAddUserContentProps {
@@ -57,15 +85,120 @@ const EditLifecycleConfiguration = ({
open,
setModalErrorSnackMessage,
}: IAddUserContentProps) => {
const [loadingTiers, setLoadingTiers] = useState<boolean>(true);
const [addLoading, setAddLoading] = useState<boolean>(false);
const [tags, setTags] = useState<string>("");
const [enabled, setEnabled] = useState<boolean>(false);
const [tiersList, setTiersList] = useState<ITiersDropDown[]>([]);
const [prefix, setPrefix] = useState("");
const [storageClass, setStorageClass] = useState("");
const [NCTransitionSC, setNCTransitionSC] = useState("");
const [expiredObjectDM, setExpiredObjectDM] = useState<boolean>(false);
const [NCExpirationDays, setNCExpirationDays] = useState<string>("0");
const [NCTransitionDays, setNCTransitionDays] = useState<string>("0");
const [ilmType, setIlmType] = useState<string>("expiry");
const [expiryDays, setExpiryDays] = useState<string>("0");
const [transitionDays, setTransitionDays] = useState<string>("0");
const [isFormValid, setIsFormValid] = useState<boolean>(false);
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);
});
}
}, [loadingTiers]);
useEffect(() => {
let valid = true;
if (ilmType !== "expiry") {
if (storageClass === "") {
valid = false;
}
}
setIsFormValid(valid);
}, [ilmType, expiryDays, transitionDays, storageClass]);
useEffect(() => {
console.log("lifecycle::", lifecycle);
if (lifecycle.status === "Enabled") {
setEnabled(true);
}
let transitionMode = false;
if (lifecycle.transition) {
if (lifecycle.transition.days && lifecycle.transition.days !== 0) {
setTransitionDays(lifecycle.transition.days.toString());
setIlmType("transition");
transitionMode = true;
}
// Fallback to old rules by date
if (
lifecycle.transition.date &&
lifecycle.transition.date !== "0001-01-01T00:00:00Z"
) {
setIlmType("transition");
transitionMode = true;
}
}
if (lifecycle.expiration) {
if (lifecycle.expiration.days && lifecycle.expiration.days !== 0) {
setExpiryDays(lifecycle.expiration.days.toString());
setIlmType("expiry");
transitionMode = false;
}
// Fallback to old rules by date
if (
lifecycle.expiration.date &&
lifecycle.expiration.date !== "0001-01-01T00:00:00Z"
) {
setIlmType("expiry");
transitionMode = false;
}
}
// Transition fields
if (transitionMode) {
setStorageClass(lifecycle.transition?.storage_class || "");
setNCTransitionDays(
lifecycle.transition?.noncurrent_transition_days?.toString() || "0"
);
setNCTransitionSC(lifecycle.transition?.noncurrent_storage_class || "");
} else {
// Expiry fields
setNCExpirationDays(
lifecycle.expiration?.noncurrent_expiration_days?.toString() || "0"
);
}
setExpiredObjectDM(!!lifecycle.expiration?.delete_marker);
setPrefix(lifecycle.prefix || "");
if (lifecycle.tags) {
const tgs = lifecycle.tags.reduce(
(stringLab: string, currItem: any, index: number) => {
@@ -88,14 +221,44 @@ const EditLifecycleConfiguration = ({
}
setAddLoading(true);
if (selectedBucket !== null && lifecycle !== null) {
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 lifecycleUpdate = {
type: ilmType,
disable: !enabled,
prefix,
tags,
expired_object_delete_marker: expiredObjectDM,
...rules,
};
api
.invoke(
"PUT",
`/api/v1/buckets/${selectedBucket}/lifecycle/${lifecycle.id}`,
{
disable: !enabled,
tags: tags,
}
lifecycleUpdate
)
.then((res) => {
setAddLoading(false);
@@ -117,72 +280,217 @@ const EditLifecycleConfiguration = ({
title={"Edit Lifecycle Configuration"}
titleIcon={<LifecycleConfigIcon />}
>
<div className={classes.floatingEnabled}>
<FormSwitchWrapper
indicatorLabels={["Enabled", "Disabled"]}
checked={enabled}
value={"user_enabled"}
id="user-status"
name="user-status"
onChange={(e) => {
setEnabled(e.target.checked);
}}
switchOnly
/>
</div>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
saveRecord(e);
}}
>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="id"
name="id"
label="Id"
value={lifecycle.id}
onChange={() => {}}
disabled
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<FormSwitchWrapper
label="Rule State"
indicatorLabels={["Enabled", "Disabled"]}
checked={enabled}
value={"user_enabled"}
id="user-status"
name="user-status"
onChange={(e) => {
setEnabled(e.target.checked);
}}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<fieldset className={classes.fieldGroup}>
<legend className={classes.descriptionText}>
Lifecycle Configuration
</legend>
<React.Fragment>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
saveRecord(e);
}}
>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
<Grid item xs={12}>
<InputBoxWrapper
id="id"
name="id"
label="Id"
value={lifecycle.id}
onChange={() => {}}
disabled
/>
</Grid>
<Grid item xs={12}>
<QueryMultiSelector
name="tags"
label="Tags"
elements={tags}
onChange={(vl: string) => {
setTags(vl);
}}
keyPlaceholder="Tag Key"
valuePlaceholder="Tag Value"
withBorder
/>
</Grid>
<Grid item xs={12}>
<RadioGroupSelector
currentSelection={ilmType}
id="quota_type"
name="quota_type"
label="ILM Rule"
selectorOptions={[
{ value: "expiry", label: "Expiry" },
{ value: "transition", label: "Transition" },
]}
onChange={() => {}}
disableOptions
/>
</Grid>
{ilmType === "expiry" ? (
<Fragment>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
type="number"
id="expiry_days"
name="expiry_days"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setExpiryDays(e.target.value);
}}
label="Expiry Days"
value={expiryDays}
min="0"
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
type="number"
id="noncurrentversion_expiration_days"
name="noncurrentversion_expiration_days"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setNCExpirationDays(e.target.value);
}}
label="Non-current Expiration Days"
value={NCExpirationDays}
min="0"
/>
</Grid>
</Fragment>
) : (
<Fragment>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
type="number"
id="transition_days"
name="transition_days"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setTransitionDays(e.target.value);
}}
label="Transition Days"
value={transitionDays}
min="0"
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
type="number"
id="noncurrentversion_transition_days"
name="noncurrentversion_transition_days"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setNCTransitionDays(e.target.value);
}}
label="Non-current Transition Days"
value={NCTransitionDays}
min="0"
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="noncurrentversion_t_SC"
name="noncurrentversion_t_SC"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setNCTransitionSC(e.target.value);
}}
placeholder="Set Non-current Version Transition Storage Class"
label="Non-current Version Transition Storage Class"
value={NCTransitionSC}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<SelectWrapper
label="Storage Class"
id="storage_class"
name="storage_class"
value={storageClass}
onChange={(e: SelectChangeEvent<string>) => {
setStorageClass(e.target.value as string);
}}
options={tiersList}
/>
</Grid>
</Fragment>
)}
</fieldset>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
>
Save
</Button>
<Grid item xs={12} className={classes.formFieldRow}>
<fieldset className={classes.fieldGroup}>
<legend className={classes.descriptionText}>
File Configuration
</legend>
<Grid item xs={12}>
<InputBoxWrapper
id="prefix"
name="prefix"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPrefix(e.target.value);
}}
label="Prefix"
value={prefix}
/>
</Grid>
<Grid item xs={12}>
<QueryMultiSelector
name="tags"
label="Tags"
elements={tags}
onChange={(vl: string) => {
setTags(vl);
}}
keyPlaceholder="Tag Key"
valuePlaceholder="Tag Value"
withBorder
/>
</Grid>
<Grid item xs={12}>
<FormSwitchWrapper
value="expired_delete_marker"
id="expired_delete_marker"
name="expired_delete_marker"
checked={expiredObjectDM}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setExpiredObjectDM(event.target.checked);
}}
label={"Expired Object Delete Marker"}
/>
</Grid>
</fieldset>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
</form>
</React.Fragment>
<Grid item xs={12} className={classes.modalButtonBar}>
<Button
type="button"
variant="outlined"
color="primary"
disabled={addLoading}
onClick={() => {
closeModalAndRefresh(false);
}}
>
Cancel
</Button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading || !isFormValid}
>
Save
</Button>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
</form>
</ModalWrapper>
);
};

View File

@@ -181,12 +181,16 @@ export interface BulkReplicationItem {
interface IExpirationLifecycle {
days: number;
date: string;
delete_marker?: boolean;
noncurrent_expiration_days?: number;
}
interface ITransitionLifecycle {
days: number;
date: string;
storage_class?: string;
noncurrent_transition_days?: number;
noncurrent_storage_class?: string;
}
export interface LifeCycleItem {

View File

@@ -146,6 +146,7 @@ export const RadioGroupSelector = ({
[classes.checkedOption]:
selectorOption.value === currentSelection,
})}
/>
);
})}

View File

@@ -4425,6 +4425,10 @@ func init() {
},
"delete_marker": {
"type": "boolean"
},
"noncurrent_expiration_days": {
"type": "integer",
"format": "int64"
}
}
},
@@ -5947,6 +5951,13 @@ func init() {
"type": "integer",
"format": "int64"
},
"noncurrent_storage_class": {
"type": "string"
},
"noncurrent_transition_days": {
"type": "integer",
"format": "int64"
},
"storage_class": {
"type": "string"
}
@@ -5954,6 +5965,9 @@ func init() {
},
"updateBucketLifecycle": {
"type": "object",
"required": [
"type"
],
"properties": {
"disable": {
"description": "Non required, toggle to disable or enable rule",
@@ -6002,6 +6016,14 @@ func init() {
"type": "integer",
"format": "int32",
"default": 0
},
"type": {
"description": "ILM Rule type (Expiry or transition)",
"type": "string",
"enum": [
"expiry",
"transition"
]
}
}
},
@@ -10694,6 +10716,10 @@ func init() {
},
"delete_marker": {
"type": "boolean"
},
"noncurrent_expiration_days": {
"type": "integer",
"format": "int64"
}
}
},
@@ -12216,6 +12242,13 @@ func init() {
"type": "integer",
"format": "int64"
},
"noncurrent_storage_class": {
"type": "string"
},
"noncurrent_transition_days": {
"type": "integer",
"format": "int64"
},
"storage_class": {
"type": "string"
}
@@ -12223,6 +12256,9 @@ func init() {
},
"updateBucketLifecycle": {
"type": "object",
"required": [
"type"
],
"properties": {
"disable": {
"description": "Non required, toggle to disable or enable rule",
@@ -12271,6 +12307,14 @@ func init() {
"type": "integer",
"format": "int32",
"default": 0
},
"type": {
"description": "ILM Rule type (Expiry or transition)",
"type": "string",
"enum": [
"expiry",
"transition"
]
}
}
},

View File

@@ -56,7 +56,7 @@ func registerBucketsLifecycleHandlers(api *operations.ConsoleAPI) {
api.UserAPIUpdateBucketLifecycleHandler = user_api.UpdateBucketLifecycleHandlerFunc(func(params user_api.UpdateBucketLifecycleParams, session *models.Principal) middleware.Responder {
err := getEditBucketLifecycleRule(session, params)
if err != nil {
user_api.NewUpdateBucketLifecycleDefault(int(err.Code)).WithPayload(err)
return user_api.NewUpdateBucketLifecycleDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewUpdateBucketLifecycleOK()
@@ -83,13 +83,30 @@ func getBucketLifecycle(ctx context.Context, client MinioClient, bucketName stri
})
}
rulePrefix := rule.RuleFilter.And.Prefix
if rulePrefix == "" {
rulePrefix = rule.RuleFilter.Prefix
}
rules = append(rules, &models.ObjectBucketLifecycle{
ID: rule.ID,
Status: rule.Status,
Prefix: rule.RuleFilter.And.Prefix,
Expiration: &models.ExpirationResponse{Date: rule.Expiration.Date.Format(time.RFC3339), Days: int64(rule.Expiration.Days), DeleteMarker: rule.Expiration.DeleteMarker.IsEnabled()},
Transition: &models.TransitionResponse{Date: rule.Transition.Date.Format(time.RFC3339), Days: int64(rule.Transition.Days), StorageClass: rule.Transition.StorageClass},
Tags: tags,
ID: rule.ID,
Status: rule.Status,
Prefix: rulePrefix,
Expiration: &models.ExpirationResponse{
Date: rule.Expiration.Date.Format(time.RFC3339),
Days: int64(rule.Expiration.Days),
DeleteMarker: rule.Expiration.DeleteMarker.IsEnabled(),
NoncurrentExpirationDays: int64(rule.NoncurrentVersionExpiration.NoncurrentDays),
},
Transition: &models.TransitionResponse{
Date: rule.Transition.Date.Format(time.RFC3339),
Days: int64(rule.Transition.Days),
StorageClass: rule.Transition.StorageClass,
NoncurrentStorageClass: rule.NoncurrentVersionTransition.StorageClass,
NoncurrentTransitionDays: int64(rule.NoncurrentVersionTransition.NoncurrentDays),
},
Tags: tags,
})
}
@@ -251,7 +268,7 @@ func getAddBucketLifecycleResponse(session *models.Principal, params user_api.Ad
return nil
}
// addBucketLifecycle gets lifecycle lists for a bucket from MinIO API and returns their implementations
// editBucketLifecycle gets lifecycle lists for a bucket from MinIO API and updates the selected lifecycle rule
func editBucketLifecycle(ctx context.Context, client MinioClient, params user_api.UpdateBucketLifecycleParams) error {
// Configuration that is already set.
lfcCfg, err := client.getLifecycleRules(ctx, params.BucketName)
@@ -268,10 +285,9 @@ func editBucketLifecycle(ctx context.Context, client MinioClient, params user_ap
opts := ilm.LifecycleOptions{}
// Verify if transition items are set
if params.Body.ExpiryDays == 0 && params.Body.TransitionDays != 0 {
if params.Body.NoncurrentversionExpirationDays != 0 {
return errors.New("non current version expiration days cannot be set when transition is being configured")
if *params.Body.Type == models.UpdateBucketLifecycleTypeTransition {
if params.Body.TransitionDays == 0 && params.Body.NoncurrentversionTransitionDays == 0 {
return errors.New("you must select transition days or non-current transition days configuration")
}
opts = ilm.LifecycleOptions{
@@ -285,8 +301,10 @@ func editBucketLifecycle(ctx context.Context, client MinioClient, params user_ap
ExpiredObjectDeleteMarker: params.Body.ExpiredObjectDeleteMarker,
NoncurrentVersionTransitionDays: int(params.Body.NoncurrentversionTransitionDays),
NoncurrentVersionTransitionStorageClass: strings.ToUpper(params.Body.NoncurrentversionTransitionStorageClass),
IsTransitionDaysSet: params.Body.TransitionDays != 0,
IsNoncurrentVersionTransitionDaysSet: params.Body.NoncurrentversionTransitionDays != 0,
}
} else if params.Body.TransitionDays == 0 && params.Body.ExpiryDays != 0 { // Verify if expiry configuration is set
} else if *params.Body.Type == models.UpdateBucketLifecycleTypeExpiry { // Verify if expiry configuration is set
if params.Body.NoncurrentversionTransitionDays != 0 {
return errors.New("non current version Transition Days cannot be set when expiry is being configured")
}

View File

@@ -226,9 +226,12 @@ func TestUpdateLifecycleRule(t *testing.T) {
// Test-2 : editBucketLifecycle() Update lifecycle rule
expiryRule := "expiry"
editMock := user_api.UpdateBucketLifecycleParams{
BucketName: "testBucket",
Body: &models.UpdateBucketLifecycle{
Type: &expiryRule,
Disable: false,
ExpiredObjectDeleteMarker: false,
ExpiryDays: int32(16),
@@ -250,6 +253,33 @@ func TestUpdateLifecycleRule(t *testing.T) {
assert.Equal(nil, err, fmt.Sprintf("Failed on %s: Error returned", function))
// Test-2a : editBucketLifecycle() Update lifecycle rule
transitionRule := "transition"
editMock = user_api.UpdateBucketLifecycleParams{
BucketName: "testBucket",
Body: &models.UpdateBucketLifecycle{
Type: &transitionRule,
Disable: false,
ExpiredObjectDeleteMarker: false,
NoncurrentversionTransitionDays: 5,
Prefix: "pref1",
StorageClass: "TEST",
NoncurrentversionTransitionStorageClass: "TESTNC",
Tags: "",
TransitionDays: int32(16),
},
}
minioSetBucketLifecycleMock = func(ctx context.Context, bucketName string, config *lifecycle.Configuration) error {
return nil
}
err = editBucketLifecycle(ctx, minClient, editMock)
assert.Equal(nil, err, fmt.Sprintf("Failed on %s: Error returned", function))
// Test-3 : editBucketLifecycle() returns error
minioSetBucketLifecycleMock = func(ctx context.Context, bucketName string, config *lifecycle.Configuration) error {

View File

@@ -3817,6 +3817,9 @@ definitions:
format: int64
delete_marker:
type: boolean
noncurrent_expiration_days:
type: integer
format: int64
transitionResponse:
type: object
@@ -3828,6 +3831,11 @@ definitions:
days:
type: integer
format: int64
noncurrent_transition_days:
type: integer
format: int64
noncurrent_storage_class:
type: string
lifecycleTag:
type: object
@@ -3905,7 +3913,15 @@ definitions:
updateBucketLifecycle:
type: object
required:
- type
properties:
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