Added delete governance bypass option when deleting file versions (#2536)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -24,7 +24,10 @@ import { ConfirmDeleteIcon } from "../../../../../../icons";
|
||||
import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
import { setErrorSnackMessage } from "../../../../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../../../../store";
|
||||
import { AppState, useAppDispatch } from "../../../../../../store";
|
||||
import { hasPermission } from "../../../../../../common/SecureComponent";
|
||||
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
interface IDeleteObjectProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -48,10 +51,22 @@ const DeleteObject = ({
|
||||
const onDelError = (err: ErrorResponseHandler) =>
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
const [deleteVersions, setDeleteVersions] = useState<boolean>(false);
|
||||
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
const [deleteVersions, setDeleteVersions] = useState<boolean>(false);
|
||||
const [bypassGovernance, setBypassGovernance] = useState<boolean>(false);
|
||||
|
||||
const retentionConfig = useSelector(
|
||||
(state: AppState) => state.objectBrowser.retentionConfig
|
||||
);
|
||||
|
||||
const canBypass =
|
||||
hasPermission(
|
||||
[selectedBucket],
|
||||
[IAM_SCOPES.S3_BYPASS_GOVERNANCE_RETENTION]
|
||||
) && retentionConfig?.mode === "governance";
|
||||
|
||||
if (!selectedObjects) {
|
||||
return null;
|
||||
}
|
||||
@@ -76,7 +91,9 @@ const DeleteObject = ({
|
||||
if (toSend) {
|
||||
invokeDeleteApi(
|
||||
"POST",
|
||||
`/api/v1/buckets/${selectedBucket}/delete-objects?all_versions=${deleteVersions}`,
|
||||
`/api/v1/buckets/${selectedBucket}/delete-objects?all_versions=${deleteVersions}${
|
||||
bypassGovernance ? "&bypass=true" : ""
|
||||
}`,
|
||||
toSend
|
||||
);
|
||||
}
|
||||
@@ -111,6 +128,28 @@ const DeleteObject = ({
|
||||
}}
|
||||
description=""
|
||||
/>
|
||||
{canBypass && deleteVersions && (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
<FormSwitchWrapper
|
||||
label={"Bypass Governance Mode"}
|
||||
indicatorLabels={["Yes", "No"]}
|
||||
checked={bypassGovernance}
|
||||
value={"bypass_governance"}
|
||||
id="bypass_governance"
|
||||
name="bypass_governance"
|
||||
onChange={(e) => {
|
||||
setBypassGovernance(!bypassGovernance);
|
||||
}}
|
||||
description=""
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
{deleteVersions && (
|
||||
<Fragment>
|
||||
<div
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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, { Fragment, useEffect, useState } from "react";
|
||||
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import Grid from "@mui/material/Grid";
|
||||
@@ -25,7 +25,11 @@ import ConfirmDialog from "../../../../Common/ModalWrapper/ConfirmDialog";
|
||||
import api from "../../../../../../common/api";
|
||||
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import { setErrorSnackMessage } from "../../../../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../../../../store";
|
||||
import { AppState, useAppDispatch } from "../../../../../../store";
|
||||
import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
import { hasPermission } from "../../../../../../common/SecureComponent";
|
||||
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
interface IDeleteNonCurrentProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -43,13 +47,26 @@ const DeleteNonCurrentVersions = ({
|
||||
const dispatch = useAppDispatch();
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const [typeConfirm, setTypeConfirm] = useState<string>("");
|
||||
const [bypassGovernance, setBypassGovernance] = useState<boolean>(false);
|
||||
|
||||
const retentionConfig = useSelector(
|
||||
(state: AppState) => state.objectBrowser.retentionConfig
|
||||
);
|
||||
|
||||
const canBypass =
|
||||
hasPermission(
|
||||
[selectedBucket],
|
||||
[IAM_SCOPES.S3_BYPASS_GOVERNANCE_RETENTION]
|
||||
) && retentionConfig?.mode === "governance";
|
||||
|
||||
useEffect(() => {
|
||||
if (deleteLoading) {
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/objects?path=${selectedObject}&non_current_versions=true`
|
||||
`/api/v1/buckets/${selectedBucket}/objects?path=${selectedObject}&non_current_versions=true${
|
||||
bypassGovernance ? "&bypass=true" : ""
|
||||
}`
|
||||
)
|
||||
.then(() => {
|
||||
closeDeleteModalAndRefresh(true);
|
||||
@@ -65,6 +82,7 @@ const DeleteNonCurrentVersions = ({
|
||||
dispatch,
|
||||
selectedObject,
|
||||
selectedBucket,
|
||||
bypassGovernance,
|
||||
]);
|
||||
|
||||
if (!selectedObject) {
|
||||
@@ -90,6 +108,28 @@ const DeleteNonCurrentVersions = ({
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete all the non-current versions for:{" "}
|
||||
<b>{decodeURLString(selectedObject)}</b>? <br />
|
||||
{canBypass && (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
<FormSwitchWrapper
|
||||
label={"Bypass Governance Mode"}
|
||||
indicatorLabels={["Yes", "No"]}
|
||||
checked={bypassGovernance}
|
||||
value={"bypass_governance"}
|
||||
id="bypass_governance"
|
||||
name="bypass_governance"
|
||||
onChange={(e) => {
|
||||
setBypassGovernance(!bypassGovernance);
|
||||
}}
|
||||
description=""
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
<br />
|
||||
To continue please type <b>YES, PROCEED</b> in the box.
|
||||
<Grid item xs={12}>
|
||||
|
||||
@@ -25,7 +25,10 @@ import { ConfirmDeleteIcon } from "../../../../../../icons";
|
||||
import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
import { setErrorSnackMessage } from "../../../../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../../../../store";
|
||||
import { AppState, useAppDispatch } from "../../../../../../store";
|
||||
import { hasPermission } from "../../../../../../common/SecureComponent";
|
||||
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
interface IDeleteObjectProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -42,7 +45,6 @@ const DeleteObject = ({
|
||||
deleteOpen,
|
||||
selectedBucket,
|
||||
selectedObject,
|
||||
|
||||
versioning,
|
||||
selectedVersion = "",
|
||||
}: IDeleteObjectProps) => {
|
||||
@@ -54,6 +56,17 @@ const DeleteObject = ({
|
||||
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
const [deleteVersions, setDeleteVersions] = useState<boolean>(false);
|
||||
const [bypassGovernance, setBypassGovernance] = useState<boolean>(false);
|
||||
|
||||
const retentionConfig = useSelector(
|
||||
(state: AppState) => state.objectBrowser.retentionConfig
|
||||
);
|
||||
|
||||
const canBypass =
|
||||
hasPermission(
|
||||
[selectedBucket],
|
||||
[IAM_SCOPES.S3_BYPASS_GOVERNANCE_RETENTION]
|
||||
) && retentionConfig?.mode === "governance";
|
||||
|
||||
if (!selectedObject) {
|
||||
return null;
|
||||
@@ -67,7 +80,7 @@ const DeleteObject = ({
|
||||
selectedVersion !== ""
|
||||
? `&version_id=${selectedVersion}`
|
||||
: `&recursive=${recursive}&all_versions=${deleteVersions}`
|
||||
}`
|
||||
}${bypassGovernance ? "&bypass=true" : ""}`
|
||||
);
|
||||
};
|
||||
|
||||
@@ -115,26 +128,48 @@ const DeleteObject = ({
|
||||
}}
|
||||
description=""
|
||||
/>
|
||||
{deleteVersions && (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
border: "#c83b51 1px solid",
|
||||
borderRadius: 3,
|
||||
padding: 5,
|
||||
backgroundColor: "#c83b5120",
|
||||
color: "#c83b51",
|
||||
}}
|
||||
>
|
||||
This will remove the object as well as all of its versions,{" "}
|
||||
<br />
|
||||
This action is irreversible.
|
||||
</div>
|
||||
<br />
|
||||
Are you sure you want to continue?
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
{canBypass && (deleteVersions || selectedVersion !== "") && (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
<FormSwitchWrapper
|
||||
label={"Bypass Governance Mode"}
|
||||
indicatorLabels={["Yes", "No"]}
|
||||
checked={bypassGovernance}
|
||||
value={"bypass_governance"}
|
||||
id="bypass_governance"
|
||||
name="bypass_governance"
|
||||
onChange={(e) => {
|
||||
setBypassGovernance(!bypassGovernance);
|
||||
}}
|
||||
description=""
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
{deleteVersions && (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
border: "#c83b51 1px solid",
|
||||
borderRadius: 3,
|
||||
padding: 5,
|
||||
backgroundColor: "#c83b5120",
|
||||
color: "#c83b51",
|
||||
}}
|
||||
>
|
||||
This will remove the object as well as all of its versions,{" "}
|
||||
<br />
|
||||
This action is irreversible.
|
||||
</div>
|
||||
<br />
|
||||
Are you sure you want to continue?
|
||||
</Fragment>
|
||||
)}
|
||||
</DialogContentText>
|
||||
|
||||
@@ -50,7 +50,10 @@ import { Badge } from "@mui/material";
|
||||
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
|
||||
import { extensionPreview } from "../utils";
|
||||
import { BucketInfo, BucketQuota } from "../../../types";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import {
|
||||
ErrorResponseHandler,
|
||||
IRetentionConfig,
|
||||
} from "../../../../../../common/types";
|
||||
|
||||
import ScreenTitle from "../../../../Common/ScreenTitle/ScreenTitle";
|
||||
|
||||
@@ -103,7 +106,9 @@ import {
|
||||
setNewObject,
|
||||
setObjectDetailsView,
|
||||
setPreviewOpen,
|
||||
setRetentionConfig,
|
||||
setSearchObjects,
|
||||
setSelectedBucket,
|
||||
setSelectedObjects,
|
||||
setSelectedObjectView,
|
||||
setSelectedPreview,
|
||||
@@ -273,6 +278,9 @@ const ListObjects = () => {
|
||||
const previewOpen = useSelector(
|
||||
(state: AppState) => state.objectBrowser.previewOpen
|
||||
);
|
||||
const selectedBucket = useSelector(
|
||||
(state: AppState) => state.objectBrowser.selectedBucket
|
||||
);
|
||||
|
||||
const loadingBucket = useSelector(selBucketDetailsLoading);
|
||||
const bucketInfo = useSelector(selBucketDetailsInfo);
|
||||
@@ -411,6 +419,7 @@ const ListObjects = () => {
|
||||
.then((res: BucketInfo) => {
|
||||
dispatch(setBucketDetailsLoad(false));
|
||||
dispatch(setBucketInfo(res));
|
||||
dispatch(setSelectedBucket(bucketName));
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setBucketDetailsLoad(false));
|
||||
@@ -419,6 +428,21 @@ const ListObjects = () => {
|
||||
}
|
||||
}, [bucketName, loadingBucket, dispatch]);
|
||||
|
||||
// Load retention Config
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedBucket !== "") {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${selectedBucket}/retention`)
|
||||
.then((res: IRetentionConfig) => {
|
||||
dispatch(setRetentionConfig(res));
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setRetentionConfig(null));
|
||||
});
|
||||
}
|
||||
}, [selectedBucket, dispatch]);
|
||||
|
||||
const closeDeleteMultipleModalAndRefresh = (refresh: boolean) => {
|
||||
setDeleteMultipleOpen(false);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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, { Fragment, useEffect, useState } from "react";
|
||||
|
||||
import { DialogContentText } from "@mui/material";
|
||||
|
||||
@@ -23,7 +23,11 @@ import ConfirmDialog from "../../../../Common/ModalWrapper/ConfirmDialog";
|
||||
import { ConfirmDeleteIcon } from "../../../../../../icons";
|
||||
import api from "../../../../../../common/api";
|
||||
import { setErrorSnackMessage } from "../../../../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../../../../store";
|
||||
import { AppState, useAppDispatch } from "../../../../../../store";
|
||||
import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
import { hasPermission } from "../../../../../../common/SecureComponent";
|
||||
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
interface IDeleteSelectedVersionsProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -42,6 +46,17 @@ const DeleteObject = ({
|
||||
}: IDeleteSelectedVersionsProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const [bypassGovernance, setBypassGovernance] = useState<boolean>(false);
|
||||
|
||||
const retentionConfig = useSelector(
|
||||
(state: AppState) => state.objectBrowser.retentionConfig
|
||||
);
|
||||
|
||||
const canBypass =
|
||||
hasPermission(
|
||||
[selectedBucket],
|
||||
[IAM_SCOPES.S3_BYPASS_GOVERNANCE_RETENTION]
|
||||
) && retentionConfig?.mode === "governance";
|
||||
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
const onConfirmDelete = () => {
|
||||
@@ -62,7 +77,9 @@ const DeleteObject = ({
|
||||
api
|
||||
.invoke(
|
||||
"POST",
|
||||
`/api/v1/buckets/${selectedBucket}/delete-objects?all_versions=false`,
|
||||
`/api/v1/buckets/${selectedBucket}/delete-objects?all_versions=false${
|
||||
bypassGovernance ? "&bypass=true" : ""
|
||||
}`,
|
||||
selectedObjectsRequest
|
||||
)
|
||||
.then(() => {
|
||||
@@ -81,6 +98,7 @@ const DeleteObject = ({
|
||||
selectedBucket,
|
||||
selectedObject,
|
||||
selectedVersions,
|
||||
bypassGovernance,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
@@ -101,6 +119,28 @@ const DeleteObject = ({
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete the selected {selectedVersions.length}{" "}
|
||||
versions for <strong>{selectedObject}</strong>?
|
||||
{canBypass && (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
<FormSwitchWrapper
|
||||
label={"Bypass Governance Mode"}
|
||||
indicatorLabels={["Yes", "No"]}
|
||||
checked={bypassGovernance}
|
||||
value={"bypass_governance"}
|
||||
id="bypass_governance"
|
||||
name="bypass_governance"
|
||||
onChange={(e) => {
|
||||
setBypassGovernance(!bypassGovernance);
|
||||
}}
|
||||
description=""
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</DialogContentText>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
BucketObjectItem,
|
||||
IRestoreLocalObjectList,
|
||||
} from "../Buckets/ListBuckets/Objects/ListObjects/types";
|
||||
import { IRetentionConfig } from "../../../common/types";
|
||||
|
||||
const defaultRewind = {
|
||||
rewindEnabled: false,
|
||||
@@ -28,6 +29,7 @@ const defaultRewind = {
|
||||
};
|
||||
|
||||
const initialState: ObjectBrowserState = {
|
||||
selectedBucket: "",
|
||||
versionsMode: false,
|
||||
loadingObjects: true,
|
||||
objectDetailsOpen: false,
|
||||
@@ -64,6 +66,11 @@ const initialState: ObjectBrowserState = {
|
||||
previewOpen: false,
|
||||
shareFileModalOpen: false,
|
||||
isOpeningObjectDetail: false,
|
||||
retentionConfig: {
|
||||
mode: "",
|
||||
unit: "",
|
||||
validity: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const objectBrowserSlice = createSlice({
|
||||
@@ -340,6 +347,15 @@ export const objectBrowserSlice = createSlice({
|
||||
setIsOpeningOD: (state, action: PayloadAction<boolean>) => {
|
||||
state.isOpeningObjectDetail = action.payload;
|
||||
},
|
||||
setRetentionConfig: (
|
||||
state,
|
||||
action: PayloadAction<IRetentionConfig | null>
|
||||
) => {
|
||||
state.retentionConfig = action.payload;
|
||||
},
|
||||
setSelectedBucket: (state, action: PayloadAction<string>) => {
|
||||
state.selectedBucket = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
export const {
|
||||
@@ -383,6 +399,8 @@ export const {
|
||||
setLoadingRecords,
|
||||
restoreLocalObjectList,
|
||||
setIsOpeningOD,
|
||||
setRetentionConfig,
|
||||
setSelectedBucket,
|
||||
} = objectBrowserSlice.actions;
|
||||
|
||||
export default objectBrowserSlice.reducer;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { BucketObjectItem } from "../Buckets/ListBuckets/Objects/ListObjects/types";
|
||||
import { IRetentionConfig } from "../../../common/types";
|
||||
|
||||
export const REWIND_SET_ENABLE = "REWIND/SET_ENABLE";
|
||||
export const REWIND_RESET_REWIND = "REWIND/RESET_REWIND";
|
||||
@@ -64,6 +65,7 @@ export interface RewindItem {
|
||||
}
|
||||
|
||||
export interface ObjectBrowserState {
|
||||
selectedBucket: string;
|
||||
rewind: RewindItem;
|
||||
objectManager: ObjectManager;
|
||||
searchObjects: string;
|
||||
@@ -90,6 +92,7 @@ export interface ObjectBrowserState {
|
||||
previewOpen: boolean;
|
||||
shareFileModalOpen: boolean;
|
||||
isOpeningObjectDetail: boolean;
|
||||
retentionConfig: IRetentionConfig | null;
|
||||
}
|
||||
|
||||
export interface ObjectManager {
|
||||
|
||||
@@ -1006,6 +1006,11 @@ func init() {
|
||||
"name": "all_versions",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "bypass",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"in": "body",
|
||||
@@ -1539,6 +1544,11 @@ func init() {
|
||||
"type": "boolean",
|
||||
"name": "non_current_versions",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "bypass",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -9438,6 +9448,11 @@ func init() {
|
||||
"name": "all_versions",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "bypass",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"in": "body",
|
||||
@@ -9971,6 +9986,11 @@ func init() {
|
||||
"type": "boolean",
|
||||
"name": "non_current_versions",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "bypass",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
@@ -61,6 +61,10 @@ type DeleteMultipleObjectsParams struct {
|
||||
In: path
|
||||
*/
|
||||
BucketName string
|
||||
/*
|
||||
In: query
|
||||
*/
|
||||
Bypass *bool
|
||||
/*
|
||||
Required: true
|
||||
In: body
|
||||
@@ -89,6 +93,11 @@ func (o *DeleteMultipleObjectsParams) BindRequest(r *http.Request, route *middle
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
qBypass, qhkBypass, _ := qs.GetOK("bypass")
|
||||
if err := o.bindBypass(qBypass, qhkBypass, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if runtime.HasBody(r) {
|
||||
defer r.Body.Close()
|
||||
var body []*models.DeleteFile
|
||||
@@ -160,3 +169,26 @@ func (o *DeleteMultipleObjectsParams) bindBucketName(rawData []string, hasKey bo
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindBypass binds and validates parameter Bypass from query.
|
||||
func (o *DeleteMultipleObjectsParams) bindBypass(rawData []string, hasKey bool, formats strfmt.Registry) error {
|
||||
var raw string
|
||||
if len(rawData) > 0 {
|
||||
raw = rawData[len(rawData)-1]
|
||||
}
|
||||
|
||||
// Required: false
|
||||
// AllowEmptyValue: false
|
||||
|
||||
if raw == "" { // empty values pass all other validations
|
||||
return nil
|
||||
}
|
||||
|
||||
value, err := swag.ConvertBool(raw)
|
||||
if err != nil {
|
||||
return errors.InvalidType("bypass", "query", "bool", raw)
|
||||
}
|
||||
o.Bypass = &value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ type DeleteMultipleObjectsURL struct {
|
||||
BucketName string
|
||||
|
||||
AllVersions *bool
|
||||
Bypass *bool
|
||||
|
||||
_basePath string
|
||||
// avoid unkeyed usage
|
||||
@@ -86,6 +87,14 @@ func (o *DeleteMultipleObjectsURL) Build() (*url.URL, error) {
|
||||
qs.Set("all_versions", allVersionsQ)
|
||||
}
|
||||
|
||||
var bypassQ string
|
||||
if o.Bypass != nil {
|
||||
bypassQ = swag.FormatBool(*o.Bypass)
|
||||
}
|
||||
if bypassQ != "" {
|
||||
qs.Set("bypass", bypassQ)
|
||||
}
|
||||
|
||||
_result.RawQuery = qs.Encode()
|
||||
|
||||
return &_result, nil
|
||||
|
||||
@@ -62,6 +62,10 @@ type DeleteObjectParams struct {
|
||||
/*
|
||||
In: query
|
||||
*/
|
||||
Bypass *bool
|
||||
/*
|
||||
In: query
|
||||
*/
|
||||
NonCurrentVersions *bool
|
||||
/*
|
||||
Required: true
|
||||
@@ -99,6 +103,11 @@ func (o *DeleteObjectParams) BindRequest(r *http.Request, route *middleware.Matc
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
qBypass, qhkBypass, _ := qs.GetOK("bypass")
|
||||
if err := o.bindBypass(qBypass, qhkBypass, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
qNonCurrentVersions, qhkNonCurrentVersions, _ := qs.GetOK("non_current_versions")
|
||||
if err := o.bindNonCurrentVersions(qNonCurrentVersions, qhkNonCurrentVersions, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
@@ -161,6 +170,29 @@ func (o *DeleteObjectParams) bindBucketName(rawData []string, hasKey bool, forma
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindBypass binds and validates parameter Bypass from query.
|
||||
func (o *DeleteObjectParams) bindBypass(rawData []string, hasKey bool, formats strfmt.Registry) error {
|
||||
var raw string
|
||||
if len(rawData) > 0 {
|
||||
raw = rawData[len(rawData)-1]
|
||||
}
|
||||
|
||||
// Required: false
|
||||
// AllowEmptyValue: false
|
||||
|
||||
if raw == "" { // empty values pass all other validations
|
||||
return nil
|
||||
}
|
||||
|
||||
value, err := swag.ConvertBool(raw)
|
||||
if err != nil {
|
||||
return errors.InvalidType("bypass", "query", "bool", raw)
|
||||
}
|
||||
o.Bypass = &value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindNonCurrentVersions binds and validates parameter NonCurrentVersions from query.
|
||||
func (o *DeleteObjectParams) bindNonCurrentVersions(rawData []string, hasKey bool, formats strfmt.Registry) error {
|
||||
var raw string
|
||||
|
||||
@@ -36,6 +36,7 @@ type DeleteObjectURL struct {
|
||||
BucketName string
|
||||
|
||||
AllVersions *bool
|
||||
Bypass *bool
|
||||
NonCurrentVersions *bool
|
||||
Path string
|
||||
Recursive *bool
|
||||
@@ -90,6 +91,14 @@ func (o *DeleteObjectURL) Build() (*url.URL, error) {
|
||||
qs.Set("all_versions", allVersionsQ)
|
||||
}
|
||||
|
||||
var bypassQ string
|
||||
if o.Bypass != nil {
|
||||
bypassQ = swag.FormatBool(*o.Bypass)
|
||||
}
|
||||
if bypassQ != "" {
|
||||
qs.Set("bypass", bypassQ)
|
||||
}
|
||||
|
||||
var nonCurrentVersionsQ string
|
||||
if o.NonCurrentVersions != nil {
|
||||
nonCurrentVersionsQ = swag.FormatBool(*o.NonCurrentVersions)
|
||||
|
||||
@@ -579,7 +579,7 @@ func getDownloadFolderResponse(session *models.Principal, params objectApi.Downl
|
||||
}), nil
|
||||
}
|
||||
|
||||
// getDeleteObjectResponse returns whether there was an errors on deletion of object
|
||||
// getDeleteObjectResponse returns whether there was an error on deletion of object
|
||||
func getDeleteObjectResponse(session *models.Principal, params objectApi.DeleteObjectParams) *models.Error {
|
||||
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
||||
defer cancel()
|
||||
@@ -603,6 +603,7 @@ func getDeleteObjectResponse(session *models.Principal, params objectApi.DeleteO
|
||||
var version string
|
||||
var allVersions bool
|
||||
var nonCurrentVersions bool
|
||||
var bypass bool
|
||||
if params.Recursive != nil {
|
||||
rec = *params.Recursive
|
||||
}
|
||||
@@ -615,28 +616,35 @@ func getDeleteObjectResponse(session *models.Principal, params objectApi.DeleteO
|
||||
if params.NonCurrentVersions != nil {
|
||||
nonCurrentVersions = *params.NonCurrentVersions
|
||||
}
|
||||
if params.Bypass != nil {
|
||||
bypass = *params.Bypass
|
||||
}
|
||||
|
||||
if allVersions && nonCurrentVersions {
|
||||
err := errors.New("cannot set delete all versions and delete non-current versions flags at the same time")
|
||||
return ErrorWithContext(ctx, err)
|
||||
}
|
||||
|
||||
err = deleteObjects(ctx, mcClient, params.BucketName, prefix, version, rec, allVersions, nonCurrentVersions)
|
||||
err = deleteObjects(ctx, mcClient, params.BucketName, prefix, version, rec, allVersions, nonCurrentVersions, bypass)
|
||||
if err != nil {
|
||||
return ErrorWithContext(ctx, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDeleteMultiplePathsResponse returns whether there was an errors on deletion of any object
|
||||
// getDeleteMultiplePathsResponse returns whether there was an error on deletion of any object
|
||||
func getDeleteMultiplePathsResponse(session *models.Principal, params objectApi.DeleteMultipleObjectsParams) *models.Error {
|
||||
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
||||
defer cancel()
|
||||
var version string
|
||||
var allVersions bool
|
||||
var bypass bool
|
||||
if params.AllVersions != nil {
|
||||
allVersions = *params.AllVersions
|
||||
}
|
||||
if params.Bypass != nil {
|
||||
bypass = *params.Bypass
|
||||
}
|
||||
for i := 0; i < len(params.Files); i++ {
|
||||
if params.Files[i].VersionID != "" {
|
||||
version = params.Files[i].VersionID
|
||||
@@ -649,7 +657,7 @@ func getDeleteMultiplePathsResponse(session *models.Principal, params objectApi.
|
||||
// create a mc S3Client interface implementation
|
||||
// defining the client to be used
|
||||
mcClient := mcClient{client: s3Client}
|
||||
err = deleteObjects(ctx, mcClient, params.BucketName, params.Files[i].Path, version, params.Files[i].Recursive, allVersions, false)
|
||||
err = deleteObjects(ctx, mcClient, params.BucketName, params.Files[i].Path, version, params.Files[i].Recursive, allVersions, false, bypass)
|
||||
if err != nil {
|
||||
return ErrorWithContext(ctx, err)
|
||||
}
|
||||
@@ -658,30 +666,29 @@ func getDeleteMultiplePathsResponse(session *models.Principal, params objectApi.
|
||||
}
|
||||
|
||||
// deleteObjects deletes either a single object or multiple objects based on recursive flag
|
||||
func deleteObjects(ctx context.Context, client MCClient, bucket string, path string, versionID string, recursive bool, allVersions bool, nonCurrentVersionsOnly bool) error {
|
||||
func deleteObjects(ctx context.Context, client MCClient, bucket string, path string, versionID string, recursive, allVersions, nonCurrentVersionsOnly, bypass bool) error {
|
||||
// Delete All non-Current versions only.
|
||||
if nonCurrentVersionsOnly {
|
||||
return deleteNonCurrentVersions(ctx, client, bucket, path)
|
||||
return deleteNonCurrentVersions(ctx, client, bucket, path, bypass)
|
||||
}
|
||||
|
||||
if recursive || allVersions {
|
||||
return deleteMultipleObjects(ctx, client, recursive, allVersions)
|
||||
return deleteMultipleObjects(ctx, client, recursive, allVersions, bypass)
|
||||
}
|
||||
|
||||
return deleteSingleObject(ctx, client, bucket, path, versionID)
|
||||
return deleteSingleObject(ctx, client, bucket, path, versionID, bypass)
|
||||
}
|
||||
|
||||
// deleteMultipleObjects uses listing before removal, it can list recursively or not,
|
||||
//
|
||||
// Use cases:
|
||||
// * Remove objects recursively
|
||||
func deleteMultipleObjects(ctx context.Context, client MCClient, recursive, allVersions bool) error {
|
||||
isBypass := false
|
||||
func deleteMultipleObjects(ctx context.Context, client MCClient, recursive, allVersions, isBypass bool) error {
|
||||
isIncomplete := false
|
||||
isRemoveBucket := false
|
||||
forceDelete := false
|
||||
|
||||
if recursive || allVersions {
|
||||
if recursive || (allVersions && !isBypass) {
|
||||
forceDelete = true
|
||||
}
|
||||
|
||||
@@ -722,13 +729,12 @@ func deleteMultipleObjects(ctx context.Context, client MCClient, recursive, allV
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteSingleObject(ctx context.Context, client MCClient, bucket, object string, versionID string) error {
|
||||
func deleteSingleObject(ctx context.Context, client MCClient, bucket, object string, versionID string, isBypass bool) error {
|
||||
targetURL := fmt.Sprintf("%s/%s", bucket, object)
|
||||
contentCh := make(chan *mc.ClientContent, 1)
|
||||
contentCh <- &mc.ClientContent{URL: *newClientURL(targetURL), VersionID: versionID}
|
||||
close(contentCh)
|
||||
|
||||
isBypass := false
|
||||
isIncomplete := false
|
||||
isRemoveBucket := false
|
||||
|
||||
@@ -741,7 +747,7 @@ func deleteSingleObject(ctx context.Context, client MCClient, bucket, object str
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteNonCurrentVersions(ctx context.Context, client MCClient, bucket, path string) error {
|
||||
func deleteNonCurrentVersions(ctx context.Context, client MCClient, bucket, path string, isBypass bool) error {
|
||||
lctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
@@ -773,7 +779,7 @@ func deleteNonCurrentVersions(ctx context.Context, client MCClient, bucket, path
|
||||
}
|
||||
}()
|
||||
|
||||
for result := range client.remove(ctx, false, false, false, false, contentCh) {
|
||||
for result := range client.remove(ctx, false, false, isBypass, false, contentCh) {
|
||||
if result.Err != nil {
|
||||
return result.Err.Cause
|
||||
}
|
||||
|
||||
@@ -734,7 +734,7 @@ func Test_deleteObjects(t *testing.T) {
|
||||
t.Run(tt.test, func(t *testing.T) {
|
||||
mcListMock = tt.args.listFunc
|
||||
mcRemoveMock = tt.args.removeFunc
|
||||
err := deleteObjects(ctx, s3Client1, tt.args.bucket, tt.args.path, tt.args.versionID, tt.args.recursive, false, tt.args.nonCurrent)
|
||||
err := deleteObjects(ctx, s3Client1, tt.args.bucket, tt.args.path, tt.args.versionID, tt.args.recursive, false, tt.args.nonCurrent, false)
|
||||
switch {
|
||||
case err == nil && tt.wantError != nil:
|
||||
t.Errorf("deleteObjects() error: %v, wantErr: %v", err, tt.wantError)
|
||||
|
||||
@@ -351,6 +351,10 @@ paths:
|
||||
in: query
|
||||
required: false
|
||||
type: boolean
|
||||
- name: bypass
|
||||
in: query
|
||||
required: false
|
||||
type: boolean
|
||||
responses:
|
||||
200:
|
||||
description: A successful response.
|
||||
@@ -374,6 +378,10 @@ paths:
|
||||
in: query
|
||||
required: false
|
||||
type: boolean
|
||||
- name: bypass
|
||||
in: query
|
||||
required: false
|
||||
type: boolean
|
||||
- name: files
|
||||
in: body
|
||||
required: true
|
||||
|
||||
Reference in New Issue
Block a user