mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-01 11:05:56 +01:00
Add delete method for configuration store (#1814)
Add method to delete configuration stores completely.
This commit is contained in:
4
gradle/changelog/delete_config_store.yaml
Normal file
4
gradle/changelog/delete_config_store.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
- type: Added
|
||||||
|
description: Add method to delete whole configuration store ([#1814](https://github.com/scm-manager/scm-manager/pull/1814))
|
||||||
|
- type: Added
|
||||||
|
description: Move DangerZone styling to ui-components (([#1814](https://github.com/scm-manager/scm-manager/pull/1814)))
|
||||||
@@ -29,10 +29,9 @@ import java.util.function.BooleanSupplier;
|
|||||||
/**
|
/**
|
||||||
* Base class for {@link ConfigurationStore}.
|
* Base class for {@link ConfigurationStore}.
|
||||||
*
|
*
|
||||||
|
* @param <T> type of store objects
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 1.16
|
* @since 1.16
|
||||||
*
|
|
||||||
* @param <T> type of store objects
|
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractStore<T> implements ConfigurationStore<T> {
|
public abstract class AbstractStore<T> implements ConfigurationStore<T> {
|
||||||
|
|
||||||
@@ -64,10 +63,19 @@ public abstract class AbstractStore<T> implements ConfigurationStore<T> {
|
|||||||
this.storeObject = object;
|
this.storeObject = object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete() {
|
||||||
|
if (readOnly.getAsBoolean()) {
|
||||||
|
throw new StoreReadOnlyException();
|
||||||
|
}
|
||||||
|
deleteObject();
|
||||||
|
this.storeObject = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the stored object.
|
* Read the stored object.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @return stored object
|
* @return stored object
|
||||||
*/
|
*/
|
||||||
protected abstract T readObject();
|
protected abstract T readObject();
|
||||||
@@ -75,8 +83,14 @@ public abstract class AbstractStore<T> implements ConfigurationStore<T> {
|
|||||||
/**
|
/**
|
||||||
* Write object to the store.
|
* Write object to the store.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param object object to write
|
* @param object object to write
|
||||||
*/
|
*/
|
||||||
protected abstract void writeObject(T object);
|
protected abstract void writeObject(T object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes store object.
|
||||||
|
*
|
||||||
|
* @since 2.24.0
|
||||||
|
*/
|
||||||
|
protected abstract void deleteObject();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.store;
|
package sonia.scm.store;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -32,17 +32,14 @@ import static java.util.Optional.ofNullable;
|
|||||||
* ConfigurationStore for configuration objects. <strong>Note:</strong> the default
|
* ConfigurationStore for configuration objects. <strong>Note:</strong> the default
|
||||||
* implementation use JAXB to marshall the configuration objects.
|
* implementation use JAXB to marshall the configuration objects.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*
|
|
||||||
* @param <T> type of the configuration objects
|
* @param <T> type of the configuration objects
|
||||||
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public interface ConfigurationStore<T>
|
public interface ConfigurationStore<T> {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the configuration object from store.
|
* Returns the configuration object from store.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @return configuration object from store
|
* @return configuration object from store
|
||||||
*/
|
*/
|
||||||
T get();
|
T get();
|
||||||
@@ -50,20 +47,24 @@ public interface ConfigurationStore<T>
|
|||||||
/**
|
/**
|
||||||
* Returns the configuration object from store.
|
* Returns the configuration object from store.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @return configuration object from store
|
* @return configuration object from store
|
||||||
*/
|
*/
|
||||||
default Optional<T> getOptional() {
|
default Optional<T> getOptional() {
|
||||||
return ofNullable(get());
|
return ofNullable(get());
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the given configuration object to the store.
|
* Stores the given configuration object to the store.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param object configuration object to store
|
* @param object configuration object to store
|
||||||
*/
|
*/
|
||||||
void set(T object);
|
void set(T object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the configuration.
|
||||||
|
* @since 2.24.0
|
||||||
|
*/
|
||||||
|
default void delete() {
|
||||||
|
throw new StoreException("Delete operation is not implemented by the store");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ public class StoreReadOnlyException extends ExceptionWithContext {
|
|||||||
LOG.error(getMessage());
|
LOG.error(getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StoreReadOnlyException() {
|
||||||
|
super(noContext(), "Store is read only, could not delete store");
|
||||||
|
LOG.error(getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCode () {
|
public String getCode () {
|
||||||
return CODE;
|
return CODE;
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ package sonia.scm.store;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.util.IOUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,7 +66,6 @@ public class JAXBConfigurationStore<T> extends AbstractStore<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected T readObject() {
|
protected T readObject() {
|
||||||
LOG.debug("load {} from store {}", type, configFile);
|
LOG.debug("load {} from store {}", type, configFile);
|
||||||
|
|
||||||
@@ -82,4 +83,14 @@ public class JAXBConfigurationStore<T> extends AbstractStore<T> {
|
|||||||
configFile.toPath()
|
configFile.toPath()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteObject() {
|
||||||
|
LOG.debug("deletes {}", configFile.getPath());
|
||||||
|
try {
|
||||||
|
IOUtil.delete(configFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new StoreException("Failed to delete store object " + configFile.getPath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.junit.Test;
|
|||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryReadOnlyChecker;
|
import sonia.scm.repository.RepositoryReadOnlyChecker;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
@@ -44,15 +45,13 @@ public class JAXBConfigurationStoreTest extends StoreTestBase {
|
|||||||
private final RepositoryReadOnlyChecker readOnlyChecker = mock(RepositoryReadOnlyChecker.class);
|
private final RepositoryReadOnlyChecker readOnlyChecker = mock(RepositoryReadOnlyChecker.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ConfigurationStoreFactory createStoreFactory()
|
protected JAXBConfigurationStoreFactory createStoreFactory() {
|
||||||
{
|
|
||||||
return new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver, readOnlyChecker);
|
return new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver, readOnlyChecker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldStoreAndLoadInRepository()
|
public void shouldStoreAndLoadInRepository() {
|
||||||
{
|
|
||||||
Repository repository = new Repository("id", "git", "ns", "n");
|
Repository repository = new Repository("id", "git", "ns", "n");
|
||||||
ConfigurationStore<StoreObject> store = createStoreFactory()
|
ConfigurationStore<StoreObject> store = createStoreFactory()
|
||||||
.withType(StoreObject.class)
|
.withType(StoreObject.class)
|
||||||
@@ -69,8 +68,7 @@ public class JAXBConfigurationStoreTest extends StoreTestBase {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldNotWriteArchivedRepository()
|
public void shouldNotWriteArchivedRepository() {
|
||||||
{
|
|
||||||
Repository repository = new Repository("id", "git", "ns", "n");
|
Repository repository = new Repository("id", "git", "ns", "n");
|
||||||
when(readOnlyChecker.isReadOnly("id")).thenReturn(true);
|
when(readOnlyChecker.isReadOnly("id")).thenReturn(true);
|
||||||
ConfigurationStore<StoreObject> store = createStoreFactory()
|
ConfigurationStore<StoreObject> store = createStoreFactory()
|
||||||
@@ -82,4 +80,38 @@ public class JAXBConfigurationStoreTest extends StoreTestBase {
|
|||||||
StoreObject storeObject = new StoreObject("value");
|
StoreObject storeObject = new StoreObject("value");
|
||||||
assertThrows(RuntimeException.class, () -> store.set(storeObject));
|
assertThrows(RuntimeException.class, () -> store.set(storeObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldDeleteConfigStore() {
|
||||||
|
Repository repository = new Repository("id", "git", "ns", "n");
|
||||||
|
ConfigurationStore<StoreObject> store = createStoreFactory()
|
||||||
|
.withType(StoreObject.class)
|
||||||
|
.withName("test")
|
||||||
|
.forRepository(repository)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
store.set(new StoreObject("value"));
|
||||||
|
|
||||||
|
store.delete();
|
||||||
|
StoreObject storeObject = store.get();
|
||||||
|
|
||||||
|
assertThat(storeObject).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotDeleteStoreForArchivedRepository() {
|
||||||
|
Repository repository = new Repository("id", "git", "ns", "n");
|
||||||
|
when(readOnlyChecker.isReadOnly("id")).thenReturn(false);
|
||||||
|
ConfigurationStore<StoreObject> store = createStoreFactory()
|
||||||
|
.withType(StoreObject.class)
|
||||||
|
.withName("test")
|
||||||
|
.forRepository(repository)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
store.set(new StoreObject());
|
||||||
|
when(readOnlyChecker.isReadOnly("id")).thenReturn(true);
|
||||||
|
|
||||||
|
assertThrows(StoreReadOnlyException.class, store::delete);
|
||||||
|
assertThat(store.getOptional()).isPresent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
scm-ui/ui-components/src/DangerZone.tsx
Normal file
49
scm-ui/ui-components/src/DangerZone.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
export const DangerZone = styled.div`
|
||||||
|
border: 1px solid #ff6a88;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
> .level {
|
||||||
|
flex-flow: wrap;
|
||||||
|
|
||||||
|
.level-left {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-right {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> *:not(:last-child) {
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
border-bottom: solid 2px whitesmoke;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default DangerZone;
|
||||||
@@ -57,6 +57,7 @@ export { default as Paginator } from "./Paginator";
|
|||||||
export { default as LinkPaginator } from "./LinkPaginator";
|
export { default as LinkPaginator } from "./LinkPaginator";
|
||||||
export { default as StatePaginator } from "./StatePaginator";
|
export { default as StatePaginator } from "./StatePaginator";
|
||||||
|
|
||||||
|
export { default as DangerZone } from "./DangerZone";
|
||||||
export { default as FileSize } from "./FileSize";
|
export { default as FileSize } from "./FileSize";
|
||||||
export { default as ProtectedRoute } from "./ProtectedRoute";
|
export { default as ProtectedRoute } from "./ProtectedRoute";
|
||||||
export { default as Help } from "./Help";
|
export { default as Help } from "./Help";
|
||||||
|
|||||||
@@ -24,9 +24,8 @@
|
|||||||
|
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Branch, Repository } from "@scm-manager/ui-types";
|
import { Branch, Repository } from "@scm-manager/ui-types";
|
||||||
import { Subtitle } from "@scm-manager/ui-components";
|
import { DangerZone, Subtitle } from "@scm-manager/ui-components";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DangerZoneContainer } from "../../containers/RepositoryDangerZone";
|
|
||||||
import DeleteBranch from "./DeleteBranch";
|
import DeleteBranch from "./DeleteBranch";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -51,7 +50,7 @@ const BranchDangerZone: FC<Props> = ({ repository, branch }) => {
|
|||||||
<>
|
<>
|
||||||
<hr />
|
<hr />
|
||||||
<Subtitle subtitle={t("branch.dangerZone")} />
|
<Subtitle subtitle={t("branch.dangerZone")} />
|
||||||
<DangerZoneContainer className="px-4 py-5">{dangerZone}</DangerZoneContainer>
|
<DangerZone className="px-4 py-5">{dangerZone}</DangerZone>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,9 +23,8 @@
|
|||||||
*/
|
*/
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import styled from "styled-components";
|
|
||||||
import { Repository } from "@scm-manager/ui-types";
|
import { Repository } from "@scm-manager/ui-types";
|
||||||
import { Subtitle } from "@scm-manager/ui-components";
|
import { DangerZone, Subtitle } from "@scm-manager/ui-components";
|
||||||
import RenameRepository from "./RenameRepository";
|
import RenameRepository from "./RenameRepository";
|
||||||
import DeleteRepo from "./DeleteRepo";
|
import DeleteRepo from "./DeleteRepo";
|
||||||
import ArchiveRepo from "./ArchiveRepo";
|
import ArchiveRepo from "./ArchiveRepo";
|
||||||
@@ -35,28 +34,6 @@ type Props = {
|
|||||||
repository: Repository;
|
repository: Repository;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DangerZoneContainer = styled.div`
|
|
||||||
border: 1px solid #ff6a88;
|
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
> .level {
|
|
||||||
flex-flow: wrap;
|
|
||||||
|
|
||||||
.level-left {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-right {
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> *:not(:last-child) {
|
|
||||||
padding-bottom: 1.5rem;
|
|
||||||
border-bottom: solid 2px whitesmoke;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const RepositoryDangerZone: FC<Props> = ({ repository }) => {
|
const RepositoryDangerZone: FC<Props> = ({ repository }) => {
|
||||||
const [t] = useTranslation("repos");
|
const [t] = useTranslation("repos");
|
||||||
|
|
||||||
@@ -81,7 +58,7 @@ const RepositoryDangerZone: FC<Props> = ({ repository }) => {
|
|||||||
<>
|
<>
|
||||||
<hr />
|
<hr />
|
||||||
<Subtitle subtitle={t("repositoryForm.dangerZone")} />
|
<Subtitle subtitle={t("repositoryForm.dangerZone")} />
|
||||||
<DangerZoneContainer className="px-4 py-5">{dangerZone}</DangerZoneContainer>
|
<DangerZone className="px-4 py-5">{dangerZone}</DangerZone>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,9 +24,8 @@
|
|||||||
|
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Repository, Tag } from "@scm-manager/ui-types";
|
import { Repository, Tag } from "@scm-manager/ui-types";
|
||||||
import { Subtitle } from "@scm-manager/ui-components";
|
import { DangerZone, Subtitle } from "@scm-manager/ui-components";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DangerZoneContainer } from "../../containers/RepositoryDangerZone";
|
|
||||||
import DeleteTag from "./DeleteTag";
|
import DeleteTag from "./DeleteTag";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -51,7 +50,7 @@ const TagDangerZone: FC<Props> = ({ repository, tag }) => {
|
|||||||
<>
|
<>
|
||||||
<hr />
|
<hr />
|
||||||
<Subtitle subtitle={t("tag.dangerZone")} />
|
<Subtitle subtitle={t("tag.dangerZone")} />
|
||||||
<DangerZoneContainer className="px-4 py-5">{dangerZone}</DangerZoneContainer>
|
<DangerZone className="px-4 py-5">{dangerZone}</DangerZone>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user