mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 23:45:44 +01:00
Add import protocol (#1558)
Adds a protocol for repository imports (either from an URL, a dump file or a SCM-Manager repository archive). This protocol documents single steps of an import, the time and the user and is accessible via a dedicated REST endpoint or a simple ui. The id of the log is added to the repository imported event, so that plugins like the landingpage or mail can link to these logs.
This commit is contained in:
2
gradle/changelog/import_protocol.yaml
Normal file
2
gradle/changelog/import_protocol.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- type: added
|
||||||
|
description: Added import protocols ([#1558](https://github.com/scm-manager/scm-manager/pull/1558))
|
||||||
@@ -24,9 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import sonia.scm.HandlerEventType;
|
|
||||||
import sonia.scm.event.Event;
|
import sonia.scm.event.Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,13 +34,15 @@ import sonia.scm.event.Event;
|
|||||||
*/
|
*/
|
||||||
@Event
|
@Event
|
||||||
@Getter
|
@Getter
|
||||||
@EqualsAndHashCode(callSuper = true)
|
public class RepositoryImportEvent {
|
||||||
public class RepositoryImportEvent extends RepositoryEvent {
|
|
||||||
|
|
||||||
|
private final Repository item;
|
||||||
|
private final String logId;
|
||||||
private final boolean failed;
|
private final boolean failed;
|
||||||
|
|
||||||
public RepositoryImportEvent(HandlerEventType eventType, Repository repository, boolean failed) {
|
public RepositoryImportEvent(Repository item, boolean failed) {
|
||||||
super(eventType, repository);
|
this.item = item;
|
||||||
|
this.logId = item.getId();
|
||||||
this.failed = failed;
|
this.failed = failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.store;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class InMemoryBlobStoreFactory implements BlobStoreFactory {
|
||||||
|
|
||||||
|
private final Map<String, BlobStore> stores = new HashMap<>();
|
||||||
|
|
||||||
|
private final BlobStore fixedStore;
|
||||||
|
|
||||||
|
public InMemoryBlobStoreFactory() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InMemoryBlobStoreFactory(BlobStore fixedStore) {
|
||||||
|
this.fixedStore = fixedStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlobStore getStore(StoreParameters storeParameters) {
|
||||||
|
if (fixedStore == null) {
|
||||||
|
return stores.computeIfAbsent(computeKey(storeParameters), key -> new InMemoryBlobStore());
|
||||||
|
} else {
|
||||||
|
return fixedStore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String computeKey(StoreParameters storeParameters) {
|
||||||
|
if (storeParameters.getRepositoryId() == null) {
|
||||||
|
return storeParameters.getName();
|
||||||
|
} else {
|
||||||
|
return storeParameters.getName() + "/" + storeParameters.getRepositoryId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
scm-ui/ui-api/src/import.ts
Normal file
32
scm-ui/ui-api/src/import.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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 { ApiResult, useRequiredIndexLink } from "./base";
|
||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { apiClient } from "./apiclient";
|
||||||
|
|
||||||
|
export const useImportLog = (logId: string) : ApiResult<string> => {
|
||||||
|
const link = useRequiredIndexLink("importLog").replace("{logId}", logId);
|
||||||
|
return useQuery<string, Error>(["importLog", logId], () => apiClient.get(link).then(response => response.text()));
|
||||||
|
}
|
||||||
@@ -43,6 +43,7 @@ export * from "./plugins";
|
|||||||
export * from "./repository-roles";
|
export * from "./repository-roles";
|
||||||
export * from "./permissions";
|
export * from "./permissions";
|
||||||
export * from "./sources";
|
export * from "./sources";
|
||||||
|
export * from "./import";
|
||||||
|
|
||||||
export { default as ApiProvider } from "./ApiProvider";
|
export { default as ApiProvider } from "./ApiProvider";
|
||||||
export * from "./ApiProvider";
|
export * from "./ApiProvider";
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type Props = {
|
|||||||
afterTitle?: ReactNode;
|
afterTitle?: ReactNode;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
error?: Error;
|
error?: Error | null;
|
||||||
showContentOnError?: boolean;
|
showContentOnError?: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -121,5 +121,8 @@
|
|||||||
},
|
},
|
||||||
"fileUpload": {
|
"fileUpload": {
|
||||||
"label": "Datei hochladen"
|
"label": "Datei hochladen"
|
||||||
|
},
|
||||||
|
"importLog": {
|
||||||
|
"title": "Importprotokoll"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,5 +122,8 @@
|
|||||||
},
|
},
|
||||||
"fileUpload": {
|
"fileUpload": {
|
||||||
"label": "Upload File"
|
"label": "Upload File"
|
||||||
|
},
|
||||||
|
"importLog": {
|
||||||
|
"title": "Import Log"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import Admin from "../admin/containers/Admin";
|
|||||||
import Profile from "./Profile";
|
import Profile from "./Profile";
|
||||||
import NamespaceRoot from "../repos/namespaces/containers/NamespaceRoot";
|
import NamespaceRoot from "../repos/namespaces/containers/NamespaceRoot";
|
||||||
import ImportRepository from "../repos/containers/ImportRepository";
|
import ImportRepository from "../repos/containers/ImportRepository";
|
||||||
|
import ImportLog from "../repos/importlog/ImportLog";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
me: Me;
|
me: Me;
|
||||||
@@ -96,6 +97,7 @@ class Main extends React.Component<Props> {
|
|||||||
<ProtectedRoute exact path="/groups/:page" component={Groups} authenticated={authenticated} />
|
<ProtectedRoute exact path="/groups/:page" component={Groups} authenticated={authenticated} />
|
||||||
<ProtectedRoute path="/admin" component={Admin} authenticated={authenticated} />
|
<ProtectedRoute path="/admin" component={Admin} authenticated={authenticated} />
|
||||||
<ProtectedRoute path="/me" component={Profile} authenticated={authenticated} />
|
<ProtectedRoute path="/me" component={Profile} authenticated={authenticated} />
|
||||||
|
<ProtectedRoute path="/importlog/:logId" component={ImportLog} authenticated={authenticated} />
|
||||||
<ExtensionPoint
|
<ExtensionPoint
|
||||||
name="main.route"
|
name="main.route"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
|
|||||||
45
scm-ui/ui-webapp/src/repos/importlog/ImportLog.tsx
Normal file
45
scm-ui/ui-webapp/src/repos/importlog/ImportLog.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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 React, { FC } from "react";
|
||||||
|
import { useImportLog, useIndex } from "@scm-manager/ui-api";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { Page } from "@scm-manager/ui-components";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
logId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImportLog: FC = () => {
|
||||||
|
const {logId} = useParams<Params>();
|
||||||
|
const {isLoading, data, error} = useImportLog(logId);
|
||||||
|
const [t] = useTranslation("commons");
|
||||||
|
|
||||||
|
return <Page title={t("importLog.title")} loading={isLoading} error={error}>
|
||||||
|
<pre>{data ? data : null}</pre>
|
||||||
|
</Page>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImportLog;
|
||||||
@@ -116,6 +116,7 @@ public class IndexDtoGenerator extends HalAppenderMapper {
|
|||||||
builder.single(link("repositoryTypes", resourceLinks.repositoryTypeCollection().self()));
|
builder.single(link("repositoryTypes", resourceLinks.repositoryTypeCollection().self()));
|
||||||
builder.single(link("namespaceStrategies", resourceLinks.namespaceStrategies().self()));
|
builder.single(link("namespaceStrategies", resourceLinks.namespaceStrategies().self()));
|
||||||
builder.single(link("repositoryRoles", resourceLinks.repositoryRoleCollection().self()));
|
builder.single(link("repositoryRoles", resourceLinks.repositoryRoleCollection().self()));
|
||||||
|
builder.single(link("importLog", resourceLinks.repository().importLog("IMPORT_LOG_ID").replace("IMPORT_LOG_ID", "{logId}")));
|
||||||
} else {
|
} else {
|
||||||
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,5 +90,6 @@ public class MapperModule extends AbstractModule {
|
|||||||
bind(ApiKeyToApiKeyDtoMapper.class).to(Mappers.getMapperClass(ApiKeyToApiKeyDtoMapper.class));
|
bind(ApiKeyToApiKeyDtoMapper.class).to(Mappers.getMapperClass(ApiKeyToApiKeyDtoMapper.class));
|
||||||
|
|
||||||
bind(RepositoryExportInformationToDtoMapper.class).to(Mappers.getMapperClass(RepositoryExportInformationToDtoMapper.class));
|
bind(RepositoryExportInformationToDtoMapper.class).to(Mappers.getMapperClass(RepositoryExportInformationToDtoMapper.class));
|
||||||
|
bind(RepositoryImportDtoToRepositoryImportParametersMapper.class).to(Mappers.getMapperClass(RepositoryImportDtoToRepositoryImportParametersMapper.class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
import static sonia.scm.api.v2.resources.RepositoryTypeSupportChecker.checkSupport;
|
import static sonia.scm.importexport.RepositoryTypeSupportChecker.checkSupport;
|
||||||
import static sonia.scm.api.v2.resources.RepositoryTypeSupportChecker.type;
|
import static sonia.scm.importexport.RepositoryTypeSupportChecker.type;
|
||||||
|
|
||||||
public class RepositoryExportResource {
|
public class RepositoryExportResource {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import sonia.scm.importexport.FromUrlImporter;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface RepositoryImportDtoToRepositoryImportParametersMapper {
|
||||||
|
FromUrlImporter.RepositoryImportParameters map(RepositoryImportResource.RepositoryImportFromUrlDto dto);
|
||||||
|
}
|
||||||
@@ -27,10 +27,8 @@ package sonia.scm.api.v2.resources;
|
|||||||
import com.fasterxml.jackson.core.JsonFactory;
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
import com.google.common.io.Files;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
@@ -39,30 +37,20 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
|
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
|
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartInputImpl;
|
import org.jboss.resteasy.plugins.providers.multipart.MultipartInputImpl;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.ContextEntry;
|
import sonia.scm.ContextEntry;
|
||||||
import sonia.scm.HandlerEventType;
|
import sonia.scm.importexport.FromBundleImporter;
|
||||||
import sonia.scm.Type;
|
import sonia.scm.importexport.FromUrlImporter;
|
||||||
import sonia.scm.event.ScmEventBus;
|
|
||||||
import sonia.scm.importexport.FullScmRepositoryImporter;
|
import sonia.scm.importexport.FullScmRepositoryImporter;
|
||||||
import sonia.scm.importexport.RepositoryImportExportEncryption;
|
import sonia.scm.importexport.RepositoryImportExportEncryption;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.importexport.RepositoryImportLoggerFactory;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryImportEvent;
|
|
||||||
import sonia.scm.repository.RepositoryManager;
|
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
|
||||||
import sonia.scm.repository.RepositoryPermissions;
|
|
||||||
import sonia.scm.repository.api.Command;
|
import sonia.scm.repository.api.Command;
|
||||||
import sonia.scm.repository.api.ImportFailedException;
|
import sonia.scm.repository.api.ImportFailedException;
|
||||||
import sonia.scm.repository.api.PullCommandBuilder;
|
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
|
||||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
|
||||||
import sonia.scm.util.IOUtil;
|
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
import sonia.scm.web.api.DtoValidator;
|
import sonia.scm.web.api.DtoValidator;
|
||||||
|
|
||||||
@@ -71,55 +59,55 @@ import javax.validation.constraints.NotEmpty;
|
|||||||
import javax.validation.constraints.Pattern;
|
import javax.validation.constraints.Pattern;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DefaultValue;
|
import javax.ws.rs.DefaultValue;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.WebApplicationException;
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.StreamingOutput;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static sonia.scm.api.v2.resources.RepositoryTypeSupportChecker.checkSupport;
|
|
||||||
import static sonia.scm.api.v2.resources.RepositoryTypeSupportChecker.type;
|
|
||||||
|
|
||||||
public class RepositoryImportResource {
|
public class RepositoryImportResource {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RepositoryImportResource.class);
|
private static final Logger logger = LoggerFactory.getLogger(RepositoryImportResource.class);
|
||||||
|
|
||||||
private final RepositoryManager manager;
|
|
||||||
private final RepositoryDtoToRepositoryMapper mapper;
|
private final RepositoryDtoToRepositoryMapper mapper;
|
||||||
private final RepositoryServiceFactory serviceFactory;
|
|
||||||
private final ResourceLinks resourceLinks;
|
private final ResourceLinks resourceLinks;
|
||||||
private final ScmEventBus eventBus;
|
|
||||||
private final FullScmRepositoryImporter fullScmRepositoryImporter;
|
private final FullScmRepositoryImporter fullScmRepositoryImporter;
|
||||||
private final RepositoryImportExportEncryption repositoryImportExportEncryption;
|
private final RepositoryImportExportEncryption repositoryImportExportEncryption;
|
||||||
|
private final RepositoryImportDtoToRepositoryImportParametersMapper importParametersMapper;
|
||||||
|
private final FromUrlImporter fromUrlImporter;
|
||||||
|
private final FromBundleImporter fromBundleImporter;
|
||||||
|
private final RepositoryImportLoggerFactory importLoggerFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RepositoryImportResource(RepositoryManager manager,
|
public RepositoryImportResource(RepositoryDtoToRepositoryMapper mapper,
|
||||||
RepositoryDtoToRepositoryMapper mapper,
|
|
||||||
RepositoryServiceFactory serviceFactory,
|
|
||||||
ResourceLinks resourceLinks,
|
ResourceLinks resourceLinks,
|
||||||
ScmEventBus eventBus,
|
|
||||||
FullScmRepositoryImporter fullScmRepositoryImporter,
|
FullScmRepositoryImporter fullScmRepositoryImporter,
|
||||||
RepositoryImportExportEncryption repositoryImportExportEncryption) {
|
RepositoryImportDtoToRepositoryImportParametersMapper importParametersMapper,
|
||||||
this.manager = manager;
|
RepositoryImportExportEncryption repositoryImportExportEncryption, FromUrlImporter fromUrlImporter,
|
||||||
|
FromBundleImporter fromBundleImporter,
|
||||||
|
RepositoryImportLoggerFactory importLoggerFactory) {
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
this.serviceFactory = serviceFactory;
|
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.fullScmRepositoryImporter = fullScmRepositoryImporter;
|
this.fullScmRepositoryImporter = fullScmRepositoryImporter;
|
||||||
this.repositoryImportExportEncryption = repositoryImportExportEncryption;
|
this.repositoryImportExportEncryption = repositoryImportExportEncryption;
|
||||||
|
this.importParametersMapper = importParametersMapper;
|
||||||
|
this.fromUrlImporter = fromUrlImporter;
|
||||||
|
this.fromBundleImporter = fromBundleImporter;
|
||||||
|
this.importLoggerFactory = importLoggerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -166,49 +154,13 @@ public class RepositoryImportResource {
|
|||||||
public Response importFromUrl(@Context UriInfo uriInfo,
|
public Response importFromUrl(@Context UriInfo uriInfo,
|
||||||
@Pattern(regexp = "\\w{1,10}") @PathParam("type") String type,
|
@Pattern(regexp = "\\w{1,10}") @PathParam("type") String type,
|
||||||
@Valid RepositoryImportResource.RepositoryImportFromUrlDto request) {
|
@Valid RepositoryImportResource.RepositoryImportFromUrlDto request) {
|
||||||
RepositoryPermissions.create().check();
|
if (!type.equals(request.getType())) {
|
||||||
|
|
||||||
Type t = type(manager, type);
|
|
||||||
if (!t.getName().equals(request.getType())) {
|
|
||||||
throw new WebApplicationException("type of import url and repository does not match", Response.Status.BAD_REQUEST);
|
throw new WebApplicationException("type of import url and repository does not match", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
checkSupport(t, Command.PULL);
|
|
||||||
|
|
||||||
logger.info("start {} import for external url {}", type, request.getImportUrl());
|
Repository repository = fromUrlImporter.importFromUrl(importParametersMapper.map(request), mapper.map(request));
|
||||||
|
|
||||||
Repository repository = mapper.map(request);
|
|
||||||
repository.setPermissions(singletonList(new RepositoryPermission(SecurityUtils.getSubject().getPrincipal().toString(), "OWNER", false)));
|
|
||||||
|
|
||||||
try {
|
|
||||||
repository = manager.create(
|
|
||||||
repository,
|
|
||||||
pullChangesFromRemoteUrl(request)
|
|
||||||
);
|
|
||||||
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, false));
|
|
||||||
|
|
||||||
return Response.created(URI.create(resourceLinks.repository().self(repository.getNamespace(), repository.getName()))).build();
|
return Response.created(URI.create(resourceLinks.repository().self(repository.getNamespace(), repository.getName()))).build();
|
||||||
} catch (Exception e) {
|
|
||||||
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, true));
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
Consumer<Repository> pullChangesFromRemoteUrl(RepositoryImportFromUrlDto request) {
|
|
||||||
return repository -> {
|
|
||||||
try (RepositoryService service = serviceFactory.create(repository)) {
|
|
||||||
PullCommandBuilder pullCommand = service.getPullCommand();
|
|
||||||
if (!Strings.isNullOrEmpty(request.getUsername()) && !Strings.isNullOrEmpty(request.getPassword())) {
|
|
||||||
pullCommand
|
|
||||||
.withUsername(request.getUsername())
|
|
||||||
.withPassword(request.getPassword());
|
|
||||||
}
|
|
||||||
|
|
||||||
pullCommand.pull(request.getImportUrl());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InternalRepositoryException(repository, "Failed to import from remote url", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,7 +205,6 @@ public class RepositoryImportResource {
|
|||||||
@Pattern(regexp = "\\w{1,10}") @PathParam("type") String type,
|
@Pattern(regexp = "\\w{1,10}") @PathParam("type") String type,
|
||||||
MultipartFormDataInput input,
|
MultipartFormDataInput input,
|
||||||
@QueryParam("compressed") @DefaultValue("false") boolean compressed) {
|
@QueryParam("compressed") @DefaultValue("false") boolean compressed) {
|
||||||
RepositoryPermissions.create().check();
|
|
||||||
Repository repository = doImportFromBundle(type, input, compressed);
|
Repository repository = doImportFromBundle(type, input, compressed);
|
||||||
|
|
||||||
return Response.created(URI.create(resourceLinks.repository().self(repository.getNamespace(), repository.getName()))).build();
|
return Response.created(URI.create(resourceLinks.repository().self(repository.getNamespace(), repository.getName()))).build();
|
||||||
@@ -303,11 +254,18 @@ public class RepositoryImportResource {
|
|||||||
public Response importFullRepository(@Context UriInfo uriInfo,
|
public Response importFullRepository(@Context UriInfo uriInfo,
|
||||||
@Pattern(regexp = "\\w{1,10}") @PathParam("type") String type,
|
@Pattern(regexp = "\\w{1,10}") @PathParam("type") String type,
|
||||||
MultipartFormDataInput input) {
|
MultipartFormDataInput input) {
|
||||||
RepositoryPermissions.create().check();
|
|
||||||
Repository createdRepository = importFullRepositoryFromInput(input);
|
Repository createdRepository = importFullRepositoryFromInput(input);
|
||||||
return Response.created(URI.create(resourceLinks.repository().self(createdRepository.getNamespace(), createdRepository.getName()))).build();
|
return Response.created(URI.create(resourceLinks.repository().self(createdRepository.getNamespace(), createdRepository.getName()))).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("log/{logId}")
|
||||||
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
|
public StreamingOutput getImportLog(@PathParam("logId") String logId) throws IOException {
|
||||||
|
importLoggerFactory.checkCanReadLog(logId);
|
||||||
|
return out -> importLoggerFactory.getLog(logId, out);
|
||||||
|
}
|
||||||
|
|
||||||
private Repository importFullRepositoryFromInput(MultipartFormDataInput input) {
|
private Repository importFullRepositoryFromInput(MultipartFormDataInput input) {
|
||||||
Map<String, List<InputPart>> formParts = input.getFormDataMap();
|
Map<String, List<InputPart>> formParts = input.getFormDataMap();
|
||||||
InputStream inputStream = extractInputStream(formParts);
|
InputStream inputStream = extractInputStream(formParts);
|
||||||
@@ -332,25 +290,13 @@ public class RepositoryImportResource {
|
|||||||
inputStream = decryptInputStream(inputStream, repositoryDto.getPassword());
|
inputStream = decryptInputStream(inputStream, repositoryDto.getPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
Type t = type(manager, type);
|
if (!type.equals(repositoryDto.getType())) {
|
||||||
checkSupport(t, Command.UNBUNDLE);
|
throw new WebApplicationException("type of import url and repository does not match", Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
Repository repository = mapper.map(repositoryDto);
|
Repository repository = mapper.map(repositoryDto);
|
||||||
repository.setPermissions(singletonList(
|
|
||||||
new RepositoryPermission(SecurityUtils.getSubject().getPrincipal().toString(), "OWNER", false)
|
|
||||||
));
|
|
||||||
|
|
||||||
try {
|
repository = fromBundleImporter.importFromBundle(compressed, inputStream, repository);
|
||||||
repository = manager.create(
|
|
||||||
repository,
|
|
||||||
unbundleImport(inputStream, compressed)
|
|
||||||
);
|
|
||||||
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, false));
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, true));
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
@@ -363,27 +309,6 @@ public class RepositoryImportResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
Consumer<Repository> unbundleImport(InputStream inputStream, boolean compressed) {
|
|
||||||
return repository -> {
|
|
||||||
File file = null;
|
|
||||||
try (RepositoryService service = serviceFactory.create(repository)) {
|
|
||||||
file = File.createTempFile("scm-import-", ".bundle");
|
|
||||||
long length = Files.asByteSink(file).writeFrom(inputStream);
|
|
||||||
logger.info("copied {} bytes to temp, start bundle import", length);
|
|
||||||
service.getUnbundleCommand().setCompressed(compressed).unbundle(file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InternalRepositoryException(repository, "Failed to import from bundle", e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
IOUtil.delete(file);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
logger.warn("could not delete temporary file", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private RepositoryImportFromFileDto extractRepositoryDto(Map<String, List<InputPart>> formParts) {
|
private RepositoryImportFromFileDto extractRepositoryDto(Map<String, List<InputPart>> formParts) {
|
||||||
RepositoryImportFromFileDto repositoryDto = extractFromInputPart(formParts.get("repository"), RepositoryImportFromFileDto.class);
|
RepositoryImportFromFileDto repositoryDto = extractFromInputPart(formParts.get("repository"), RepositoryImportFromFileDto.class);
|
||||||
checkNotNull(repositoryDto, "repository data is required");
|
checkNotNull(repositoryDto, "repository data is required");
|
||||||
|
|||||||
@@ -377,6 +377,10 @@ class ResourceLinks {
|
|||||||
return repositoryImportLinkBuilder.method("getRepositoryImportResource").parameters().method("importFullRepository").parameters(type).href();
|
return repositoryImportLinkBuilder.method("getRepositoryImportResource").parameters().method("importFullRepository").parameters(type).href();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String importLog(String importLogId) {
|
||||||
|
return repositoryImportLinkBuilder.method("getRepositoryImportResource").parameters().method("getImportLog").parameters(importLogId).href();
|
||||||
|
}
|
||||||
|
|
||||||
String archive(String namespace, String name) {
|
String archive(String namespace, String name) {
|
||||||
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("archive").parameters().href();
|
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("archive").parameters().href();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class EnvironmentCheckStep implements ImportStep {
|
|||||||
if (!validEnvironment) {
|
if (!validEnvironment) {
|
||||||
throw new IncompatibleEnvironmentForImportException();
|
throw new IncompatibleEnvironmentForImportException();
|
||||||
}
|
}
|
||||||
|
state.getLogger().step("checked environment");
|
||||||
state.environmentChecked();
|
state.environmentChecked();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.importexport;
|
||||||
|
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.Type;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.ImportRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.RepositoryImportEvent;
|
||||||
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
|
import sonia.scm.repository.api.Command;
|
||||||
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
|
import sonia.scm.util.IOUtil;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static sonia.scm.importexport.RepositoryImportLogger.ImportType.DUMP;
|
||||||
|
import static sonia.scm.importexport.RepositoryTypeSupportChecker.checkSupport;
|
||||||
|
import static sonia.scm.importexport.RepositoryTypeSupportChecker.type;
|
||||||
|
|
||||||
|
public class FromBundleImporter {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FromBundleImporter.class);
|
||||||
|
|
||||||
|
private final RepositoryManager manager;
|
||||||
|
private final RepositoryServiceFactory serviceFactory;
|
||||||
|
private final ScmEventBus eventBus;
|
||||||
|
private final WorkdirProvider workdirProvider;
|
||||||
|
private final RepositoryImportLoggerFactory loggerFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public FromBundleImporter(RepositoryManager manager, RepositoryServiceFactory serviceFactory, ScmEventBus eventBus, WorkdirProvider workdirProvider, RepositoryImportLoggerFactory loggerFactory) {
|
||||||
|
this.manager = manager;
|
||||||
|
this.serviceFactory = serviceFactory;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.workdirProvider = workdirProvider;
|
||||||
|
this.loggerFactory = loggerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Repository importFromBundle(boolean compressed, InputStream inputStream, Repository repository) {
|
||||||
|
RepositoryPermissions.create().check();
|
||||||
|
|
||||||
|
Type t = type(manager, repository.getType());
|
||||||
|
checkSupport(t, Command.UNBUNDLE);
|
||||||
|
|
||||||
|
repository.setPermissions(singletonList(
|
||||||
|
new RepositoryPermission(SecurityUtils.getSubject().getPrincipal().toString(), "OWNER", false)
|
||||||
|
));
|
||||||
|
|
||||||
|
RepositoryImportLogger logger = loggerFactory.createLogger();
|
||||||
|
|
||||||
|
try {
|
||||||
|
repository = manager.create(repository, unbundleImport(inputStream, compressed, logger));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.failed(e);
|
||||||
|
eventBus.post(new RepositoryImportEvent(repository, true));
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.post(new RepositoryImportEvent(repository, false));
|
||||||
|
return repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<Repository> unbundleImport(InputStream inputStream, boolean compressed, RepositoryImportLogger logger) {
|
||||||
|
return repository -> {
|
||||||
|
logger.start(DUMP, repository);
|
||||||
|
File workdir = workdirProvider.createNewWorkdir(repository.getId());
|
||||||
|
try (RepositoryService service = serviceFactory.create(repository)) {
|
||||||
|
logger.step("writing temporary dump file");
|
||||||
|
File file = File.createTempFile("scm-import-", ".bundle", workdir);
|
||||||
|
long length = Files.asByteSink(file).writeFrom(inputStream);
|
||||||
|
LOG.info("copied {} bytes to temp, start bundle import", length);
|
||||||
|
logger.step("importing repository data from dump file");
|
||||||
|
runUnbundleCommand(compressed, service, file);
|
||||||
|
logger.finished();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.failed(e);
|
||||||
|
throw new InternalRepositoryException(repository, "Failed to import from bundle", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
IOUtil.delete(workdir);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOG.warn("could not delete temporary file", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runUnbundleCommand(boolean compressed, RepositoryService service, File file) throws IOException {
|
||||||
|
AtomicReference<RepositoryHookEvent> eventSink = new AtomicReference<>();
|
||||||
|
service.getUnbundleCommand()
|
||||||
|
.setCompressed(compressed)
|
||||||
|
.setPostEventSink(eventSink::set)
|
||||||
|
.unbundle(file);
|
||||||
|
RepositoryHookEvent repositoryHookEvent = eventSink.get();
|
||||||
|
if (repositoryHookEvent != null) {
|
||||||
|
eventBus.post(new ImportRepositoryHookEvent(repositoryHookEvent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.importexport;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.AlreadyExistsException;
|
||||||
|
import sonia.scm.Type;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryImportEvent;
|
||||||
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
|
import sonia.scm.repository.api.Command;
|
||||||
|
import sonia.scm.repository.api.ImportFailedException;
|
||||||
|
import sonia.scm.repository.api.PullCommandBuilder;
|
||||||
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static sonia.scm.ContextEntry.ContextBuilder.noContext;
|
||||||
|
import static sonia.scm.importexport.RepositoryImportLogger.ImportType.URL;
|
||||||
|
import static sonia.scm.importexport.RepositoryTypeSupportChecker.checkSupport;
|
||||||
|
import static sonia.scm.importexport.RepositoryTypeSupportChecker.type;
|
||||||
|
|
||||||
|
public class FromUrlImporter {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FromUrlImporter.class);
|
||||||
|
|
||||||
|
private final RepositoryManager manager;
|
||||||
|
private final RepositoryServiceFactory serviceFactory;
|
||||||
|
private final ScmEventBus eventBus;
|
||||||
|
private final RepositoryImportLoggerFactory loggerFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public FromUrlImporter(RepositoryManager manager, RepositoryServiceFactory serviceFactory, ScmEventBus eventBus, RepositoryImportLoggerFactory loggerFactory) {
|
||||||
|
this.manager = manager;
|
||||||
|
this.serviceFactory = serviceFactory;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.loggerFactory = loggerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Repository importFromUrl(RepositoryImportParameters parameters, Repository repository) {
|
||||||
|
Type t = type(manager, repository.getType());
|
||||||
|
RepositoryPermissions.create().check();
|
||||||
|
checkSupport(t, Command.PULL);
|
||||||
|
|
||||||
|
LOG.info("start {} import for external url {}", repository.getType(), parameters.getImportUrl());
|
||||||
|
|
||||||
|
repository.setPermissions(singletonList(new RepositoryPermission(SecurityUtils.getSubject().getPrincipal().toString(), "OWNER", false)));
|
||||||
|
|
||||||
|
RepositoryImportLogger logger = loggerFactory.createLogger();
|
||||||
|
Repository createdRepository;
|
||||||
|
try {
|
||||||
|
createdRepository = manager.create(
|
||||||
|
repository,
|
||||||
|
pullChangesFromRemoteUrl(parameters, logger)
|
||||||
|
);
|
||||||
|
} catch (AlreadyExistsException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger.started()) {
|
||||||
|
logger.failed(e);
|
||||||
|
}
|
||||||
|
eventBus.post(new RepositoryImportEvent(repository, true));
|
||||||
|
throw new ImportFailedException(noContext(), "Could not import repository from url " + parameters.getImportUrl(), e);
|
||||||
|
}
|
||||||
|
eventBus.post(new RepositoryImportEvent(createdRepository, false));
|
||||||
|
return createdRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<Repository> pullChangesFromRemoteUrl(RepositoryImportParameters parameters, RepositoryImportLogger logger) {
|
||||||
|
return repository -> {
|
||||||
|
logger.start(URL, repository);
|
||||||
|
try (RepositoryService service = serviceFactory.create(repository)) {
|
||||||
|
PullCommandBuilder pullCommand = service.getPullCommand();
|
||||||
|
if (!Strings.isNullOrEmpty(parameters.getUsername()) && !Strings.isNullOrEmpty(parameters.getPassword())) {
|
||||||
|
logger.step("setting username and password for pull");
|
||||||
|
pullCommand
|
||||||
|
.withUsername(parameters.getUsername())
|
||||||
|
.withPassword(parameters.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.step("pulling repository from " + parameters.getImportUrl());
|
||||||
|
pullCommand.pull(parameters.getImportUrl());
|
||||||
|
logger.finished();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalRepositoryException(repository, "Failed to import from remote url: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class RepositoryImportParameters {
|
||||||
|
private String importUrl;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
import sonia.scm.ContextEntry;
|
import sonia.scm.ContextEntry;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryImportEvent;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.api.ImportFailedException;
|
import sonia.scm.repository.api.ImportFailedException;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -42,8 +44,9 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
import static sonia.scm.util.Archives.createTarInputStream;
|
|
||||||
import static sonia.scm.ContextEntry.ContextBuilder.noContext;
|
import static sonia.scm.ContextEntry.ContextBuilder.noContext;
|
||||||
|
import static sonia.scm.importexport.RepositoryImportLogger.ImportType.FULL;
|
||||||
|
import static sonia.scm.util.Archives.createTarInputStream;
|
||||||
|
|
||||||
public class FullScmRepositoryImporter {
|
public class FullScmRepositoryImporter {
|
||||||
|
|
||||||
@@ -53,6 +56,7 @@ public class FullScmRepositoryImporter {
|
|||||||
private final RepositoryManager repositoryManager;
|
private final RepositoryManager repositoryManager;
|
||||||
private final RepositoryImportExportEncryption repositoryImportExportEncryption;
|
private final RepositoryImportExportEncryption repositoryImportExportEncryption;
|
||||||
private final ScmEventBus eventBus;
|
private final ScmEventBus eventBus;
|
||||||
|
private final RepositoryImportLoggerFactory loggerFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FullScmRepositoryImporter(EnvironmentCheckStep environmentCheckStep,
|
public FullScmRepositoryImporter(EnvironmentCheckStep environmentCheckStep,
|
||||||
@@ -61,15 +65,17 @@ public class FullScmRepositoryImporter {
|
|||||||
RepositoryImportStep repositoryImportStep,
|
RepositoryImportStep repositoryImportStep,
|
||||||
RepositoryManager repositoryManager,
|
RepositoryManager repositoryManager,
|
||||||
RepositoryImportExportEncryption repositoryImportExportEncryption,
|
RepositoryImportExportEncryption repositoryImportExportEncryption,
|
||||||
ScmEventBus eventBus
|
RepositoryImportLoggerFactory loggerFactory,
|
||||||
) {
|
ScmEventBus eventBus) {
|
||||||
this.repositoryManager = repositoryManager;
|
this.repositoryManager = repositoryManager;
|
||||||
|
this.loggerFactory = loggerFactory;
|
||||||
this.repositoryImportExportEncryption = repositoryImportExportEncryption;
|
this.repositoryImportExportEncryption = repositoryImportExportEncryption;
|
||||||
importSteps = new ImportStep[]{environmentCheckStep, metadataImportStep, storeImportStep, repositoryImportStep};
|
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
importSteps = new ImportStep[]{environmentCheckStep, metadataImportStep, storeImportStep, repositoryImportStep};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Repository importFromStream(Repository repository, InputStream inputStream, String password) {
|
public Repository importFromStream(Repository repository, InputStream inputStream, String password) {
|
||||||
|
RepositoryPermissions.create().check();
|
||||||
try {
|
try {
|
||||||
if (inputStream.available() > 0) {
|
if (inputStream.available() > 0) {
|
||||||
try (
|
try (
|
||||||
@@ -103,8 +109,17 @@ public class FullScmRepositoryImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RepositoryImportLogger startLogger(Repository repository) {
|
||||||
|
RepositoryImportLogger logger = loggerFactory.createLogger();
|
||||||
|
logger.start(FULL, repository);
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
private Repository run(Repository repository, TarArchiveInputStream tais) throws IOException {
|
private Repository run(Repository repository, TarArchiveInputStream tais) throws IOException {
|
||||||
ImportState state = new ImportState(repositoryManager.create(repository));
|
Repository createdRepository = repositoryManager.create(repository);
|
||||||
|
RepositoryImportLogger logger = startLogger(repository);
|
||||||
|
ImportState state = new ImportState(createdRepository, logger);
|
||||||
|
logger.repositoryCreated(state.getRepository());
|
||||||
try {
|
try {
|
||||||
TarArchiveEntry tarArchiveEntry;
|
TarArchiveEntry tarArchiveEntry;
|
||||||
while ((tarArchiveEntry = tais.getNextTarEntry()) != null) {
|
while ((tarArchiveEntry = tais.getNextTarEntry()) != null) {
|
||||||
@@ -112,21 +127,28 @@ public class FullScmRepositoryImporter {
|
|||||||
handle(tais, state, tarArchiveEntry);
|
handle(tais, state, tarArchiveEntry);
|
||||||
}
|
}
|
||||||
stream(importSteps).forEach(step -> step.finish(state));
|
stream(importSteps).forEach(step -> step.finish(state));
|
||||||
|
state.getLogger().finished();
|
||||||
return state.getRepository();
|
return state.getRepository();
|
||||||
|
} catch (RuntimeException | IOException e) {
|
||||||
|
state.getLogger().failed(e);
|
||||||
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
stream(importSteps).forEach(step -> step.cleanup(state));
|
stream(importSteps).forEach(step -> step.cleanup(state));
|
||||||
if (state.success()) {
|
if (state.success()) {
|
||||||
// send all pending events on successful import
|
// send all pending events on successful import
|
||||||
state.getPendingEvents().forEach(eventBus::post);
|
state.getPendingEvents().forEach(eventBus::post);
|
||||||
|
eventBus.post(new RepositoryImportEvent(repository, false));
|
||||||
} else {
|
} else {
|
||||||
// Delete the repository if any error occurs during the import
|
// Delete the repository if any error occurs during the import
|
||||||
repositoryManager.delete(state.getRepository());
|
repositoryManager.delete(state.getRepository());
|
||||||
|
eventBus.post(new RepositoryImportEvent(repository, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handle(TarArchiveInputStream tais, ImportState state, TarArchiveEntry currentEntry) {
|
private void handle(TarArchiveInputStream tais, ImportState state, TarArchiveEntry currentEntry) {
|
||||||
|
state.getLogger().step("inspecting file " + currentEntry.getName());
|
||||||
for (ImportStep step : importSteps) {
|
for (ImportStep step : importSteps) {
|
||||||
if (step.handle(currentEntry, state, tais)) {
|
if (step.handle(currentEntry, state, tais)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import java.util.Optional;
|
|||||||
|
|
||||||
class ImportState {
|
class ImportState {
|
||||||
|
|
||||||
|
private final RepositoryImportLogger logger;
|
||||||
|
|
||||||
private Repository repository;
|
private Repository repository;
|
||||||
|
|
||||||
private boolean environmentChecked;
|
private boolean environmentChecked;
|
||||||
@@ -48,11 +50,8 @@ class ImportState {
|
|||||||
|
|
||||||
private final List<Object> pendingEvents = new ArrayList<>();
|
private final List<Object> pendingEvents = new ArrayList<>();
|
||||||
|
|
||||||
ImportState(Repository repository) {
|
ImportState(Repository repository, RepositoryImportLogger logger) {
|
||||||
this.repository = repository;
|
this.logger = logger;
|
||||||
}
|
|
||||||
|
|
||||||
public void setRepository(Repository repository) {
|
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +103,10 @@ class ImportState {
|
|||||||
this.pendingEvents.add(event);
|
this.pendingEvents.add(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RepositoryImportLogger getLogger() {
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<Object> getPendingEvents() {
|
public Collection<Object> getPendingEvents() {
|
||||||
return Collections.unmodifiableCollection(pendingEvents);
|
return Collections.unmodifiableCollection(pendingEvents);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class MetadataImportStep implements ImportStep {
|
|||||||
RepositoryMetadataXmlGenerator.RepositoryMetadata metadata = JAXB.unmarshal(new NoneClosingInputStream(inputStream), RepositoryMetadataXmlGenerator.RepositoryMetadata.class);
|
RepositoryMetadataXmlGenerator.RepositoryMetadata metadata = JAXB.unmarshal(new NoneClosingInputStream(inputStream), RepositoryMetadataXmlGenerator.RepositoryMetadata.class);
|
||||||
if (metadata != null && metadata.getPermissions() != null) {
|
if (metadata != null && metadata.getPermissions() != null) {
|
||||||
state.setPermissions(new HashSet<>(metadata.getPermissions()));
|
state.setPermissions(new HashSet<>(metadata.getPermissions()));
|
||||||
|
state.getLogger().step("reading repository metadata with permissions");
|
||||||
} else {
|
} else {
|
||||||
state.setPermissions(Collections.emptySet());
|
state.setPermissions(Collections.emptySet());
|
||||||
}
|
}
|
||||||
@@ -69,6 +70,7 @@ class MetadataImportStep implements ImportStep {
|
|||||||
@Override
|
@Override
|
||||||
public void finish(ImportState state) {
|
public void finish(ImportState state) {
|
||||||
LOG.trace("Saving permissions for imported repository");
|
LOG.trace("Saving permissions for imported repository");
|
||||||
|
state.getLogger().step("setting permissions for repository from import");
|
||||||
importRepositoryPermissions(state.getRepository(), state.getRepositoryPermissions());
|
importRepositoryPermissions(state.getRepository(), state.getRepositoryPermissions());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.importexport;
|
||||||
|
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.store.Blob;
|
||||||
|
import sonia.scm.store.BlobStore;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
class RepositoryImportLogger {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RepositoryImportLogger.class);
|
||||||
|
|
||||||
|
private final BlobStore logStore;
|
||||||
|
private PrintWriter print;
|
||||||
|
private Blob blob;
|
||||||
|
|
||||||
|
RepositoryImportLogger(BlobStore logStore) {
|
||||||
|
this.logStore = logStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(ImportType importType, Repository repository) {
|
||||||
|
User user = SecurityUtils.getSubject().getPrincipals().oneByType(User.class);
|
||||||
|
blob = logStore.create(repository.getId());
|
||||||
|
OutputStream outputStream = getBlobOutputStream();
|
||||||
|
writeUser(user, outputStream);
|
||||||
|
print = new PrintWriter(outputStream);
|
||||||
|
print.printf("Import of repository %s/%s%n", repository.getNamespace(), repository.getName());
|
||||||
|
print.printf("Repository type: %s%n", repository.getType());
|
||||||
|
print.printf("Imported from: %s%n", importType);
|
||||||
|
print.printf("Imported by %s (%s)%n", user.getId(), user.getName());
|
||||||
|
print.println();
|
||||||
|
|
||||||
|
addLogEntry("import started");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeUser(User user, OutputStream outputStream) {
|
||||||
|
try {
|
||||||
|
outputStream.write(user.getId().getBytes(UTF_8));
|
||||||
|
outputStream.write(0);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Could not write user to import log blob", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getBlobOutputStream() {
|
||||||
|
try {
|
||||||
|
return blob.getOutputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Could not create logger for import; failed to get output stream from blob", e);
|
||||||
|
return new OutputStream() {
|
||||||
|
@Override
|
||||||
|
public void write(int b) {
|
||||||
|
// this is a dummy
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finished() {
|
||||||
|
step("import finished successfully");
|
||||||
|
writeLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void failed(Exception e) {
|
||||||
|
step("import failed (see next log entry)");
|
||||||
|
print.println(e.getMessage());
|
||||||
|
writeLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void repositoryCreated(Repository createdRepository) {
|
||||||
|
step("created repository: " + createdRepository.getNamespaceAndName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void step(String message) {
|
||||||
|
addLogEntry(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLog() {
|
||||||
|
print.flush();
|
||||||
|
try {
|
||||||
|
blob.commit();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Could not commit blob with import log", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLogEntry(String message) {
|
||||||
|
print.printf("%s - %s%n", Instant.now(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean started() {
|
||||||
|
return blob != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ImportType {
|
||||||
|
FULL, URL, DUMP
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.importexport;
|
||||||
|
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.authz.AuthorizationException;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import sonia.scm.NotFoundException;
|
||||||
|
import sonia.scm.store.BlobStore;
|
||||||
|
import sonia.scm.store.BlobStoreFactory;
|
||||||
|
import sonia.scm.util.IOUtil;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
public class RepositoryImportLoggerFactory {
|
||||||
|
|
||||||
|
private final BlobStoreFactory blobStoreFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RepositoryImportLoggerFactory(BlobStoreFactory blobStoreFactory) {
|
||||||
|
this.blobStoreFactory = blobStoreFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
RepositoryImportLogger createLogger() {
|
||||||
|
return new RepositoryImportLogger(blobStoreFactory.withName("imports").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkCanReadLog(String logId) throws IOException {
|
||||||
|
try (InputStream blob = getBlob(logId)) {
|
||||||
|
// nothing to read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getLog(String logId, OutputStream out) throws IOException {
|
||||||
|
try (InputStream log = getBlob(logId)) {
|
||||||
|
IOUtil.copy(log, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getBlob(String logId) throws IOException {
|
||||||
|
BlobStore importStore = blobStoreFactory.withName("imports").build();
|
||||||
|
InputStream log = importStore
|
||||||
|
.getOptional(logId).orElseThrow(() -> new NotFoundException("Log", logId))
|
||||||
|
.getInputStream();
|
||||||
|
checkPermission(log);
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPermission(InputStream log) throws IOException {
|
||||||
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
String logUser = readUserFrom(log);
|
||||||
|
if (!subject.isPermitted("only:admin:allowed") && !subject.getPrincipal().toString().equals(logUser)) {
|
||||||
|
throw new AuthorizationException("not permitted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readUserFrom(InputStream log) throws IOException {
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
int b;
|
||||||
|
while ((b = log.read()) > 0) {
|
||||||
|
buffer.write(b);
|
||||||
|
}
|
||||||
|
return new String(buffer.toByteArray(), UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,12 +62,14 @@ class RepositoryImportStep implements ImportStep {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(TarArchiveEntry currentEntry, ImportState state, InputStream inputStream) {
|
public boolean handle(TarArchiveEntry currentEntry, ImportState state, InputStream inputStream) {
|
||||||
if (!currentEntry.isDirectory()) {
|
if (!currentEntry.isDirectory() && !currentEntry.getName().contains("/")) {
|
||||||
if (state.isStoreImported()) {
|
if (state.isStoreImported()) {
|
||||||
LOG.trace("Importing directly from tar stream (entry '{}')", currentEntry.getName());
|
LOG.trace("Importing directly from tar stream (entry '{}')", currentEntry.getName());
|
||||||
|
state.getLogger().step("directly importing repository data");
|
||||||
unbundleRepository(state, inputStream);
|
unbundleRepository(state, inputStream);
|
||||||
} else {
|
} else {
|
||||||
LOG.debug("Temporally storing tar entry '{}' in work dir", currentEntry.getName());
|
LOG.debug("Temporarily storing tar entry '{}' in work dir", currentEntry.getName());
|
||||||
|
state.getLogger().step("temporarily storing repository data for later import");
|
||||||
Path path = saveRepositoryDataFromTarArchiveEntry(state.getRepository(), inputStream);
|
Path path = saveRepositoryDataFromTarArchiveEntry(state.getRepository(), inputStream);
|
||||||
state.setTemporaryRepositoryBundle(path);
|
state.setTemporaryRepositoryBundle(path);
|
||||||
}
|
}
|
||||||
@@ -90,6 +92,7 @@ class RepositoryImportStep implements ImportStep {
|
|||||||
|
|
||||||
private void importFromTemporaryPath(ImportState state, Path path) {
|
private void importFromTemporaryPath(ImportState state, Path path) {
|
||||||
LOG.debug("Importing repository from temporary location in work dir");
|
LOG.debug("Importing repository from temporary location in work dir");
|
||||||
|
state.getLogger().step("importing repository from temporary location");
|
||||||
try {
|
try {
|
||||||
unbundleRepository(state, Files.newInputStream(path));
|
unbundleRepository(state, Files.newInputStream(path));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -22,10 +22,11 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.importexport;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.BadRequestException;
|
||||||
import sonia.scm.Type;
|
import sonia.scm.Type;
|
||||||
import sonia.scm.repository.RepositoryHandler;
|
import sonia.scm.repository.RepositoryHandler;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
@@ -36,7 +37,9 @@ import javax.ws.rs.WebApplicationException;
|
|||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
class RepositoryTypeSupportChecker {
|
import static sonia.scm.ContextEntry.ContextBuilder.noContext;
|
||||||
|
|
||||||
|
public class RepositoryTypeSupportChecker {
|
||||||
|
|
||||||
private RepositoryTypeSupportChecker() {
|
private RepositoryTypeSupportChecker() {
|
||||||
}
|
}
|
||||||
@@ -49,7 +52,7 @@ class RepositoryTypeSupportChecker {
|
|||||||
* @param type repository type
|
* @param type repository type
|
||||||
* @param cmd command
|
* @param cmd command
|
||||||
*/
|
*/
|
||||||
static void checkSupport(Type type, Command cmd) {
|
public static void checkSupport(Type type, Command cmd) {
|
||||||
if (!(type instanceof RepositoryType)) {
|
if (!(type instanceof RepositoryType)) {
|
||||||
logger.warn("type {} is not a repository type", type.getName());
|
logger.warn("type {} is not a repository type", type.getName());
|
||||||
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
||||||
@@ -60,17 +63,28 @@ class RepositoryTypeSupportChecker {
|
|||||||
logger.warn("type {} does not support this command {}",
|
logger.warn("type {} does not support this command {}",
|
||||||
type.getName(),
|
type.getName(),
|
||||||
cmd.name());
|
cmd.name());
|
||||||
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
throw new IllegalTypeForImportException("type does not support command");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("javasecurity:S5145") // the type parameter is validated in the resource to only contain valid characters (\w)
|
@SuppressWarnings("javasecurity:S5145") // the type parameter is validated in the resource to only contain valid characters (\w)
|
||||||
static Type type(RepositoryManager manager, String type) {
|
public static Type type(RepositoryManager manager, String type) {
|
||||||
RepositoryHandler handler = manager.getHandler(type);
|
RepositoryHandler handler = manager.getHandler(type);
|
||||||
if (handler == null) {
|
if (handler == null) {
|
||||||
logger.warn("no handler for type {} found", type);
|
logger.warn("no handler for type {} found", type);
|
||||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
throw new IllegalTypeForImportException("unsupported repository type: " + type);
|
||||||
}
|
}
|
||||||
return handler.getType();
|
return handler.getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class IllegalTypeForImportException extends BadRequestException {
|
||||||
|
public IllegalTypeForImportException(String message) {
|
||||||
|
super(noContext(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCode() {
|
||||||
|
return "CISPvega31";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -52,17 +52,18 @@ class StoreImportStep implements ImportStep {
|
|||||||
public boolean handle(TarArchiveEntry entry, ImportState state, InputStream inputStream) {
|
public boolean handle(TarArchiveEntry entry, ImportState state, InputStream inputStream) {
|
||||||
if (entry.getName().equals(STORE_DATA_FILE_NAME) && !entry.isDirectory()) {
|
if (entry.getName().equals(STORE_DATA_FILE_NAME) && !entry.isDirectory()) {
|
||||||
LOG.trace("Importing store from tar");
|
LOG.trace("Importing store from tar");
|
||||||
|
state.getLogger().step("importing stores");
|
||||||
// Inside the repository tar archive stream is another tar archive.
|
// Inside the repository tar archive stream is another tar archive.
|
||||||
// The nested tar archive is wrapped in another TarArchiveInputStream inside the storeImporter
|
// The nested tar archive is wrapped in another TarArchiveInputStream inside the storeImporter
|
||||||
importStores(state.getRepository(), inputStream);
|
importStores(state.getRepository(), inputStream, state.getLogger());
|
||||||
state.storeImported();
|
state.storeImported();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importStores(Repository repository, InputStream inputStream) {
|
private void importStores(Repository repository, InputStream inputStream, RepositoryImportLogger logger) {
|
||||||
storeImporter.importFromTarArchive(repository, inputStream);
|
storeImporter.importFromTarArchive(repository, inputStream, logger);
|
||||||
updateEngine.update(repository.getId());
|
updateEngine.update(repository.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ public class TarArchiveRepositoryStoreImporter {
|
|||||||
this.repositoryStoreImporter = repositoryStoreImporter;
|
this.repositoryStoreImporter = repositoryStoreImporter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void importFromTarArchive(Repository repository, InputStream inputStream) {
|
public void importFromTarArchive(Repository repository, InputStream inputStream, RepositoryImportLogger logger) {
|
||||||
try (TarArchiveInputStream tais = new NoneClosingTarArchiveInputStream(inputStream)) {
|
try (TarArchiveInputStream tais = new NoneClosingTarArchiveInputStream(inputStream)) {
|
||||||
ArchiveEntry entry = tais.getNextEntry();
|
ArchiveEntry entry = tais.getNextEntry();
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
String[] entryPathParts = entry.getName().split(File.separator);
|
String[] entryPathParts = entry.getName().split(File.separator);
|
||||||
validateStorePath(repository, entryPathParts);
|
validateStorePath(repository, entryPathParts);
|
||||||
importStoreByType(repository, tais, entryPathParts);
|
importStoreByType(repository, tais, entryPathParts, logger);
|
||||||
entry = tais.getNextEntry();
|
entry = tais.getNextEntry();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -61,22 +61,26 @@ public class TarArchiveRepositoryStoreImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importStoreByType(Repository repository, TarArchiveInputStream tais, String[] entryPathParts) {
|
private void importStoreByType(Repository repository, TarArchiveInputStream tais, String[] entryPathParts, RepositoryImportLogger logger) {
|
||||||
String storeType = entryPathParts[1];
|
String storeType = entryPathParts[1];
|
||||||
|
String storeName = entryPathParts[2];
|
||||||
if (isDataStore(storeType)) {
|
if (isDataStore(storeType)) {
|
||||||
|
logger.step("importing data store entry for store " + storeName);
|
||||||
repositoryStoreImporter
|
repositoryStoreImporter
|
||||||
.doImport(repository)
|
.doImport(repository)
|
||||||
.importStore(new StoreEntryMetaData(StoreType.DATA, entryPathParts[2]))
|
.importStore(new StoreEntryMetaData(StoreType.DATA, entryPathParts[2]))
|
||||||
.importEntry(entryPathParts[3], tais);
|
.importEntry(entryPathParts[3], tais);
|
||||||
} else if (isConfigStore(storeType)){
|
} else if (isConfigStore(storeType)){
|
||||||
|
logger.step("importing data store entry for store " + storeName);
|
||||||
repositoryStoreImporter
|
repositoryStoreImporter
|
||||||
.doImport(repository)
|
.doImport(repository)
|
||||||
.importStore(new StoreEntryMetaData(StoreType.CONFIG, ""))
|
.importStore(new StoreEntryMetaData(StoreType.CONFIG, ""))
|
||||||
.importEntry(entryPathParts[2], tais);
|
.importEntry(storeName, tais);
|
||||||
} else if(isBlobStore(storeType)) {
|
} else if(isBlobStore(storeType)) {
|
||||||
|
logger.step("importing blob store entry for store " + storeName);
|
||||||
repositoryStoreImporter
|
repositoryStoreImporter
|
||||||
.doImport(repository)
|
.doImport(repository)
|
||||||
.importStore(new StoreEntryMetaData(StoreType.BLOB, entryPathParts[2]))
|
.importStore(new StoreEntryMetaData(StoreType.BLOB, storeName))
|
||||||
.importEntry(entryPathParts[3], tais);
|
.importEntry(entryPathParts[3], tais);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,6 +338,10 @@
|
|||||||
"5GSO9ZkzX1": {
|
"5GSO9ZkzX1": {
|
||||||
"displayName": "Inkompatible Umgebung",
|
"displayName": "Inkompatible Umgebung",
|
||||||
"description": "Die Version dieses SCM-Managers oder eines der installierten Plugins ist zu alt für den Import des Dumps. Bitte installieren Sie die neuesten Versionen. Nähere Informationen finden sich im Log."
|
"description": "Die Version dieses SCM-Managers oder eines der installierten Plugins ist zu alt für den Import des Dumps. Bitte installieren Sie die neuesten Versionen. Nähere Informationen finden sich im Log."
|
||||||
|
},
|
||||||
|
"CISPvega31": {
|
||||||
|
"displayName": "Ungültiger Repository-Typ für Import",
|
||||||
|
"description": "Der Import ist für den gegebenen Repository-Typen nicht möglich."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"namespaceStrategies": {
|
"namespaceStrategies": {
|
||||||
|
|||||||
@@ -338,6 +338,10 @@
|
|||||||
"8YR7aawFW1": {
|
"8YR7aawFW1": {
|
||||||
"displayName": "Wrong current password",
|
"displayName": "Wrong current password",
|
||||||
"description": "The current password is wrong. Please try again."
|
"description": "The current password is wrong. Please try again."
|
||||||
|
},
|
||||||
|
"CISPvega31": {
|
||||||
|
"displayName": "Illegal repository type for import",
|
||||||
|
"description": "The import is not possible for the given repository type."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"namespaceStrategies": {
|
"namespaceStrategies": {
|
||||||
|
|||||||
@@ -134,5 +134,6 @@ class IndexDtoGeneratorTest {
|
|||||||
when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(scmPathInfo));
|
when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(scmPathInfo));
|
||||||
when(resourceLinks.namespaceCollection()).thenReturn(new ResourceLinks.NamespaceCollectionLinks(scmPathInfo));
|
when(resourceLinks.namespaceCollection()).thenReturn(new ResourceLinks.NamespaceCollectionLinks(scmPathInfo));
|
||||||
when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(scmPathInfo, new ResourceLinks.UserLinks(scmPathInfo)));
|
when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(scmPathInfo, new ResourceLinks.UserLinks(scmPathInfo)));
|
||||||
|
when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(scmPathInfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
class MultiPartRequestBuilder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is a slightly adapted copy of Lin Zaho's gist at https://gist.github.com/lin-zhao/9985191
|
||||||
|
*/
|
||||||
|
static void multipartRequest(MockHttpRequest request, Map<String, InputStream> files, RepositoryDto repository) throws IOException {
|
||||||
|
String boundary = UUID.randomUUID().toString();
|
||||||
|
request.contentType("multipart/form-data; boundary=" + boundary);
|
||||||
|
|
||||||
|
//Make sure this is deleted in afterTest()
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
try (OutputStreamWriter formWriter = new OutputStreamWriter(buffer)) {
|
||||||
|
formWriter.append("--").append(boundary);
|
||||||
|
|
||||||
|
for (Map.Entry<String, InputStream> entry : files.entrySet()) {
|
||||||
|
formWriter.append("\n");
|
||||||
|
formWriter.append(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"",
|
||||||
|
entry.getKey(), entry.getKey())).append("\n");
|
||||||
|
formWriter.append("Content-Type: application/octet-stream").append("\n\n");
|
||||||
|
|
||||||
|
InputStream stream = entry.getValue();
|
||||||
|
int b = stream.read();
|
||||||
|
while (b >= 0) {
|
||||||
|
formWriter.write(b);
|
||||||
|
b = stream.read();
|
||||||
|
}
|
||||||
|
stream.close();
|
||||||
|
formWriter.append("\n").append("--").append(boundary);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repository != null) {
|
||||||
|
formWriter.append("\n");
|
||||||
|
formWriter.append("Content-Disposition: form-data; name=\"repository\"").append("\n\n");
|
||||||
|
StringWriter repositoryWriter = new StringWriter();
|
||||||
|
new JsonFactory().createGenerator(repositoryWriter).setCodec(new ObjectMapper()).writeObject(repository);
|
||||||
|
formWriter.append(repositoryWriter.getBuffer().toString()).append("\n");
|
||||||
|
formWriter.append("--").append(boundary);
|
||||||
|
}
|
||||||
|
|
||||||
|
formWriter.append("--");
|
||||||
|
formWriter.flush();
|
||||||
|
}
|
||||||
|
request.setInputStream(new ByteArrayInputStream(buffer.toByteArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.io.Resources;
|
||||||
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
|
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import sonia.scm.api.v2.resources.RepositoryImportResource.RepositoryImportFromFileDto;
|
||||||
|
import sonia.scm.importexport.FromBundleImporter;
|
||||||
|
import sonia.scm.importexport.FromUrlImporter;
|
||||||
|
import sonia.scm.importexport.FullScmRepositoryImporter;
|
||||||
|
import sonia.scm.importexport.RepositoryImportExportEncryption;
|
||||||
|
import sonia.scm.importexport.RepositoryImportLoggerFactory;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
|
import sonia.scm.web.RestDispatcher;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
|
||||||
|
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class RepositoryImportResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
|
private final RestDispatcher dispatcher = new RestDispatcher();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FullScmRepositoryImporter fullScmRepositoryImporter;
|
||||||
|
@Mock
|
||||||
|
private FromUrlImporter fromUrlImporter;
|
||||||
|
@Mock
|
||||||
|
private FromBundleImporter fromBundleImporter;
|
||||||
|
@Mock
|
||||||
|
private RepositoryImportLoggerFactory importLoggerFactory;
|
||||||
|
@Mock
|
||||||
|
private RepositoryImportExportEncryption repositoryImportExportEncryption;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<FromUrlImporter.RepositoryImportParameters> parametersCaptor;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Repository> repositoryCaptor;
|
||||||
|
|
||||||
|
private final URI baseUri = URI.create("/");
|
||||||
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private RepositoryDtoToRepositoryMapperImpl dtoToRepositoryMapper;
|
||||||
|
|
||||||
|
private final MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void prepareEnvironment() {
|
||||||
|
super.repositoryImportResource = new RepositoryImportResource(dtoToRepositoryMapper, resourceLinks, fullScmRepositoryImporter, new RepositoryImportDtoToRepositoryImportParametersMapperImpl(), repositoryImportExportEncryption, fromUrlImporter, fromBundleImporter, importLoggerFactory);
|
||||||
|
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldImportRepositoryFromUrl() throws Exception {
|
||||||
|
when(fromUrlImporter.importFromUrl(parametersCaptor.capture(), repositoryCaptor.capture()))
|
||||||
|
.thenReturn(RepositoryTestData.createHeartOfGold());
|
||||||
|
|
||||||
|
URL url = Resources.getResource("sonia/scm/api/v2/import-repo.json");
|
||||||
|
byte[] importRequest = Resources.toByteArray(url);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/url")
|
||||||
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
|
.content(importRequest);
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
|
||||||
|
assertThat(response.getOutputHeaders().get("Location")).asString().contains("/v2/repositories/hitchhiker/HeartOfGold");
|
||||||
|
|
||||||
|
assertThat(parametersCaptor.getValue().getImportUrl()).isEqualTo("https://scm-manager-org/scm/repo/secret/puzzle42");
|
||||||
|
assertThat(parametersCaptor.getValue().getUsername()).isNull();
|
||||||
|
assertThat(parametersCaptor.getValue().getPassword()).isNull();
|
||||||
|
|
||||||
|
assertThat(repositoryCaptor.getValue().getName()).isEqualTo("HeartOfGold");
|
||||||
|
assertThat(repositoryCaptor.getValue().getNamespace()).isEqualTo("hitchhiker");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldImportRepositoryFromUrlWithCredentials() throws Exception {
|
||||||
|
when(fromUrlImporter.importFromUrl(parametersCaptor.capture(), repositoryCaptor.capture()))
|
||||||
|
.thenReturn(RepositoryTestData.createHeartOfGold());
|
||||||
|
|
||||||
|
URL url = Resources.getResource("sonia/scm/api/v2/import-repo-with-credentials.json");
|
||||||
|
byte[] importRequest = Resources.toByteArray(url);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/url")
|
||||||
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
|
.content(importRequest);
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
|
||||||
|
|
||||||
|
assertThat(parametersCaptor.getValue().getUsername()).isEqualTo("trillian");
|
||||||
|
assertThat(parametersCaptor.getValue().getPassword()).isEqualTo("secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldFailOnImportFromUrlWithDifferentTypes() throws Exception {
|
||||||
|
URL url = Resources.getResource("sonia/scm/api/v2/import-repo.json");
|
||||||
|
byte[] importRequest = Resources.toByteArray(url);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/url")
|
||||||
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
|
.content(importRequest);
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isNotEqualTo(SC_CREATED);
|
||||||
|
|
||||||
|
verify(fromUrlImporter, never()).importFromUrl(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WithCorrectBundle {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void mockImporter() {
|
||||||
|
when(
|
||||||
|
fromBundleImporter.importFromBundle(
|
||||||
|
eq(false),
|
||||||
|
argThat(argument -> streamHasContent(argument, "svn-dump")),
|
||||||
|
argThat(repository -> repository.getName().equals("HeartOfGold"))
|
||||||
|
)
|
||||||
|
).thenReturn(RepositoryTestData.createHeartOfGold());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldImportRepositoryFromBundle() throws Exception {
|
||||||
|
RepositoryImportFromFileDto importDto = createBasicImportDto();
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/bundle");
|
||||||
|
|
||||||
|
MultiPartRequestBuilder.multipartRequest(request, singletonMap("bundle", new ByteArrayInputStream("svn-dump".getBytes(UTF_8))), importDto);
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
|
||||||
|
assertThat(response.getOutputHeaders().get("Location")).asString().contains("/v2/repositories/hitchhiker/HeartOfGold");
|
||||||
|
verify(repositoryImportExportEncryption, never()).decrypt(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldImportRepositoryFromEncryptedBundle() throws Exception {
|
||||||
|
when(repositoryImportExportEncryption.decrypt(any(), eq("hgt2g")))
|
||||||
|
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
|
RepositoryImportFromFileDto importDto = createBasicImportDto();
|
||||||
|
importDto.setPassword("hgt2g");
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/bundle");
|
||||||
|
|
||||||
|
MultiPartRequestBuilder.multipartRequest(request, singletonMap("bundle", new ByteArrayInputStream("svn-dump".getBytes(UTF_8))), importDto);
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
|
||||||
|
assertThat(response.getOutputHeaders().get("Location")).asString().contains("/v2/repositories/hitchhiker/HeartOfGold");
|
||||||
|
}
|
||||||
|
|
||||||
|
private RepositoryImportFromFileDto createBasicImportDto() {
|
||||||
|
RepositoryImportFromFileDto importDto = new RepositoryImportFromFileDto();
|
||||||
|
importDto.setName("HeartOfGold");
|
||||||
|
importDto.setNamespace("hitchhiker");
|
||||||
|
importDto.setType("svn");
|
||||||
|
return importDto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldFailOnImportFromBundleWithDifferentTypes() throws Exception {
|
||||||
|
RepositoryDto repositoryDto = new RepositoryDto();
|
||||||
|
repositoryDto.setName("HeartOfGold");
|
||||||
|
repositoryDto.setNamespace("hitchhiker");
|
||||||
|
repositoryDto.setType("svn");
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/bundle");
|
||||||
|
|
||||||
|
MultiPartRequestBuilder.multipartRequest(request, Collections.singletonMap("bundle", new ByteArrayInputStream("svn-dump".getBytes(StandardCharsets.UTF_8))), repositoryDto);
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isNotEqualTo(SC_CREATED);
|
||||||
|
verify(fromBundleImporter, never()).importFromBundle(any(Boolean.class), any(InputStream.class), any(Repository.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldImportFullRepository() throws Exception {
|
||||||
|
when(
|
||||||
|
fullScmRepositoryImporter.importFromStream(
|
||||||
|
argThat(repository -> repository.getName().equals("HeartOfGold")),
|
||||||
|
argThat(argument -> streamHasContent(argument, "svn-dump")),
|
||||||
|
isNull()
|
||||||
|
)
|
||||||
|
).thenReturn(RepositoryTestData.createHeartOfGold());
|
||||||
|
|
||||||
|
RepositoryDto repositoryDto = new RepositoryDto();
|
||||||
|
repositoryDto.setName("HeartOfGold");
|
||||||
|
repositoryDto.setNamespace("hitchhiker");
|
||||||
|
repositoryDto.setType("svn");
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/full");
|
||||||
|
|
||||||
|
MultiPartRequestBuilder.multipartRequest(request, singletonMap("bundle", new ByteArrayInputStream("svn-dump".getBytes(UTF_8))), repositoryDto);
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
|
||||||
|
assertThat(response.getOutputHeaders().get("Location")).asString().contains("/v2/repositories/hitchhiker/HeartOfGold");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldFindImportLog() throws Exception {
|
||||||
|
doAnswer(
|
||||||
|
invocation -> {
|
||||||
|
invocation.getArgument(1, OutputStream.class).write("some log".getBytes(UTF_8));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
).when(importLoggerFactory).getLog(eq("42"), any(OutputStream.class));
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/log/42");
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||||
|
assertThat(response.getContentAsString()).isEqualTo("some log");
|
||||||
|
verify(importLoggerFactory).checkCanReadLog("42");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean streamHasContent(InputStream argument, String expectedContent) {
|
||||||
|
try {
|
||||||
|
byte[] data = new byte[expectedContent.length()];
|
||||||
|
argument.read(data);
|
||||||
|
return new String(data).equals(expectedContent);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonFactory;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
@@ -37,38 +35,36 @@ import org.jboss.resteasy.mock.MockHttpResponse;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.event.ScmEventBus;
|
|
||||||
import sonia.scm.importexport.ExportFileExtensionResolver;
|
import sonia.scm.importexport.ExportFileExtensionResolver;
|
||||||
import sonia.scm.importexport.ExportService;
|
import sonia.scm.importexport.ExportService;
|
||||||
import sonia.scm.importexport.ExportStatus;
|
import sonia.scm.importexport.ExportStatus;
|
||||||
|
import sonia.scm.importexport.FromBundleImporter;
|
||||||
|
import sonia.scm.importexport.FromUrlImporter;
|
||||||
import sonia.scm.importexport.FullScmRepositoryExporter;
|
import sonia.scm.importexport.FullScmRepositoryExporter;
|
||||||
import sonia.scm.importexport.FullScmRepositoryImporter;
|
import sonia.scm.importexport.FullScmRepositoryImporter;
|
||||||
import sonia.scm.importexport.RepositoryImportExportEncryption;
|
import sonia.scm.importexport.RepositoryImportExportEncryption;
|
||||||
|
import sonia.scm.importexport.RepositoryImportLoggerFactory;
|
||||||
import sonia.scm.repository.CustomNamespaceStrategy;
|
import sonia.scm.repository.CustomNamespaceStrategy;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.NamespaceStrategy;
|
import sonia.scm.repository.NamespaceStrategy;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryHandler;
|
import sonia.scm.repository.RepositoryHandler;
|
||||||
import sonia.scm.repository.RepositoryImportEvent;
|
|
||||||
import sonia.scm.repository.RepositoryInitializer;
|
import sonia.scm.repository.RepositoryInitializer;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
|
||||||
import sonia.scm.repository.RepositoryType;
|
import sonia.scm.repository.RepositoryType;
|
||||||
import sonia.scm.repository.api.BundleCommandBuilder;
|
import sonia.scm.repository.api.BundleCommandBuilder;
|
||||||
import sonia.scm.repository.api.Command;
|
import sonia.scm.repository.api.Command;
|
||||||
import sonia.scm.repository.api.ImportFailedException;
|
|
||||||
import sonia.scm.repository.api.PullCommandBuilder;
|
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
import sonia.scm.repository.api.UnbundleCommandBuilder;
|
|
||||||
import sonia.scm.repository.api.UnbundleResponse;
|
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.web.RestDispatcher;
|
import sonia.scm.web.RestDispatcher;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
@@ -76,23 +72,14 @@ import sonia.scm.web.VndMediaType;
|
|||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
@@ -100,27 +87,22 @@ import static java.util.stream.Stream.of;
|
|||||||
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
|
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
|
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
|
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertThrows;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyMap;
|
import static org.mockito.ArgumentMatchers.anyMap;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.RETURNS_SELF;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.mockito.MockitoAnnotations.openMocks;
|
|
||||||
|
|
||||||
@SubjectAware(
|
@SubjectAware(
|
||||||
username = "trillian",
|
username = "trillian",
|
||||||
@@ -128,6 +110,7 @@ import static org.mockito.MockitoAnnotations.openMocks;
|
|||||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||||
)
|
)
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class RepositoryRootResourceTest extends RepositoryTestBase {
|
public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
private static final String REALM = "AdminRealm";
|
private static final String REALM = "AdminRealm";
|
||||||
@@ -156,8 +139,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
@Mock
|
@Mock
|
||||||
private Set<NamespaceStrategy> strategies;
|
private Set<NamespaceStrategy> strategies;
|
||||||
@Mock
|
@Mock
|
||||||
private ScmEventBus eventBus;
|
|
||||||
@Mock
|
|
||||||
private FullScmRepositoryExporter fullScmRepositoryExporter;
|
private FullScmRepositoryExporter fullScmRepositoryExporter;
|
||||||
@Mock
|
@Mock
|
||||||
private RepositoryExportInformationToDtoMapper exportInformationToDtoMapper;
|
private RepositoryExportInformationToDtoMapper exportInformationToDtoMapper;
|
||||||
@@ -166,8 +147,14 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
@Mock
|
@Mock
|
||||||
private RepositoryImportExportEncryption repositoryImportExportEncryption;
|
private RepositoryImportExportEncryption repositoryImportExportEncryption;
|
||||||
@Mock
|
@Mock
|
||||||
|
private FromUrlImporter fromUrlImporter;
|
||||||
|
@Mock
|
||||||
|
private FromBundleImporter fromBundleImporter;
|
||||||
|
@Mock
|
||||||
private ExportFileExtensionResolver fileExtensionResolver;
|
private ExportFileExtensionResolver fileExtensionResolver;
|
||||||
@Mock
|
@Mock
|
||||||
|
private RepositoryImportLoggerFactory importLoggerFactory;
|
||||||
|
@Mock
|
||||||
private ExportService exportService;
|
private ExportService exportService;
|
||||||
|
|
||||||
@Captor
|
@Captor
|
||||||
@@ -175,27 +162,25 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
private final URI baseUri = URI.create("/");
|
private final URI baseUri = URI.create("/");
|
||||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
private Repository repositoryMarkedAsExported;
|
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private RepositoryToRepositoryDtoMapperImpl repositoryToDtoMapper;
|
private RepositoryToRepositoryDtoMapperImpl repositoryToDtoMapper;
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private RepositoryDtoToRepositoryMapperImpl dtoToRepositoryMapper;
|
private RepositoryDtoToRepositoryMapperImpl dtoToRepositoryMapper;
|
||||||
|
|
||||||
|
private final MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() throws IOException {
|
public void prepareEnvironment() throws IOException {
|
||||||
openMocks(this);
|
|
||||||
super.repositoryToDtoMapper = repositoryToDtoMapper;
|
super.repositoryToDtoMapper = repositoryToDtoMapper;
|
||||||
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
||||||
super.manager = repositoryManager;
|
super.manager = repositoryManager;
|
||||||
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
||||||
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
|
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
|
||||||
super.repositoryImportResource = new RepositoryImportResource(repositoryManager, dtoToRepositoryMapper, serviceFactory, resourceLinks, eventBus, fullScmRepositoryImporter, repositoryImportExportEncryption);
|
super.repositoryImportResource = new RepositoryImportResource(dtoToRepositoryMapper, resourceLinks, fullScmRepositoryImporter, new RepositoryImportDtoToRepositoryImportParametersMapperImpl(), repositoryImportExportEncryption, fromUrlImporter, fromBundleImporter, importLoggerFactory);
|
||||||
super.repositoryExportResource = new RepositoryExportResource(repositoryManager, serviceFactory, fullScmRepositoryExporter, repositoryImportExportEncryption, exportService, exportInformationToDtoMapper, fileExtensionResolver, resourceLinks);
|
super.repositoryExportResource = new RepositoryExportResource(repositoryManager, serviceFactory, fullScmRepositoryExporter, repositoryImportExportEncryption, exportService, exportInformationToDtoMapper, fileExtensionResolver, resourceLinks);
|
||||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
|
||||||
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
|
|
||||||
doReturn(ImmutableSet.of(new CustomNamespaceStrategy()).iterator()).when(strategies).iterator();
|
doReturn(ImmutableSet.of(new CustomNamespaceStrategy()).iterator()).when(strategies).iterator();
|
||||||
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
|
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
|
||||||
trillian.add(new User("trillian"), REALM);
|
trillian.add(new User("trillian"), REALM);
|
||||||
@@ -213,7 +198,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
createRepository("space", "repo");
|
createRepository("space", "repo");
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/other");
|
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/other");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -226,7 +210,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
|
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -241,7 +224,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2);
|
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -256,7 +238,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?q=Rep");
|
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?q=Rep");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -273,7 +254,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space");
|
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -290,7 +270,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space?q=Rep");
|
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space?q=Rep");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -310,7 +289,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo")
|
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo")
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
.content(repository);
|
.content(repository);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -328,7 +306,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo")
|
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo")
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
.content(repository);
|
.content(repository);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -347,7 +324,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo")
|
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo")
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
.content(repository);
|
.content(repository);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -367,7 +343,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "wrong/repo")
|
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "wrong/repo")
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
.content(repository);
|
.content(repository);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -380,7 +355,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
createRepository("space", "repo");
|
createRepository("space", "repo");
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.delete("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
|
MockHttpRequest request = MockHttpRequest.delete("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -403,7 +377,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2)
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2)
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
.content(repositoryJson);
|
.content(repositoryJson);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -424,7 +397,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?initialize=true")
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?initialize=true")
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
.content(repositoryJson);
|
.content(repositoryJson);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -454,7 +426,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2)
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2)
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
.content(repositoryJson);
|
.content(repositoryJson);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -473,7 +444,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
|
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -495,7 +465,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/rename")
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/rename")
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
.content(repository);
|
.content(repository);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -503,202 +472,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
verify(repositoryManager).rename(repository1, "space", "x");
|
verify(repositoryManager).rename(repository1, "space", "x");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldImportRepositoryFromUrl() throws URISyntaxException, IOException {
|
|
||||||
ArgumentCaptor<RepositoryImportEvent> captor = ArgumentCaptor.forClass(RepositoryImportEvent.class);
|
|
||||||
when(manager.getHandler("git")).thenReturn(repositoryHandler);
|
|
||||||
when(repositoryHandler.getType()).thenReturn(new RepositoryType("git", "git", ImmutableSet.of(Command.PULL)));
|
|
||||||
when(manager.create(any(Repository.class), any())).thenReturn(RepositoryTestData.create42Puzzle());
|
|
||||||
|
|
||||||
URL url = Resources.getResource("sonia/scm/api/v2/import-repo.json");
|
|
||||||
byte[] importRequest = Resources.toByteArray(url);
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/url")
|
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
|
||||||
.content(importRequest);
|
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
|
||||||
|
|
||||||
assertEquals(SC_CREATED, response.getStatus());
|
|
||||||
verify(eventBus).post(captor.capture());
|
|
||||||
|
|
||||||
assertThat(captor.getValue().isFailed()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldFailOnImportRepositoryFromUrl() throws URISyntaxException, IOException {
|
|
||||||
ArgumentCaptor<RepositoryImportEvent> captor = ArgumentCaptor.forClass(RepositoryImportEvent.class);
|
|
||||||
when(manager.getHandler("git")).thenReturn(repositoryHandler);
|
|
||||||
when(repositoryHandler.getType()).thenReturn(new RepositoryType("git", "git", ImmutableSet.of(Command.PULL)));
|
|
||||||
doThrow(ImportFailedException.class).when(manager).create(any(Repository.class), any());
|
|
||||||
|
|
||||||
URL url = Resources.getResource("sonia/scm/api/v2/import-repo.json");
|
|
||||||
byte[] importRequest = Resources.toByteArray(url);
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/url")
|
|
||||||
.contentType(VndMediaType.REPOSITORY)
|
|
||||||
.content(importRequest);
|
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
|
||||||
|
|
||||||
assertEquals(500, response.getStatus());
|
|
||||||
verify(eventBus).post(captor.capture());
|
|
||||||
|
|
||||||
assertThat(captor.getValue().isFailed()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldPullChangesFromRemoteUrl() throws IOException {
|
|
||||||
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
|
|
||||||
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
|
|
||||||
|
|
||||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
|
||||||
RepositoryImportResource.RepositoryImportFromUrlDto repositoryImportFromUrlDto = new RepositoryImportResource.RepositoryImportFromUrlDto();
|
|
||||||
repositoryImportFromUrlDto.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
|
|
||||||
repositoryImportFromUrlDto.setNamespace("scmadmin");
|
|
||||||
repositoryImportFromUrlDto.setName("scm-manager");
|
|
||||||
|
|
||||||
Consumer<Repository> repositoryConsumer = repositoryImportResource.pullChangesFromRemoteUrl(repositoryImportFromUrlDto);
|
|
||||||
repositoryConsumer.accept(repository);
|
|
||||||
|
|
||||||
verify(pullCommandBuilder).pull("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldPullChangesFromRemoteUrlWithCredentials() {
|
|
||||||
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
|
|
||||||
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
|
|
||||||
|
|
||||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
|
||||||
RepositoryImportResource.RepositoryImportFromUrlDto repositoryImportFromUrlDto = new RepositoryImportResource.RepositoryImportFromUrlDto();
|
|
||||||
repositoryImportFromUrlDto.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
|
|
||||||
repositoryImportFromUrlDto.setNamespace("scmadmin");
|
|
||||||
repositoryImportFromUrlDto.setName("scm-manager");
|
|
||||||
repositoryImportFromUrlDto.setUsername("trillian");
|
|
||||||
repositoryImportFromUrlDto.setPassword("secret");
|
|
||||||
|
|
||||||
Consumer<Repository> repositoryConsumer = repositoryImportResource.pullChangesFromRemoteUrl(repositoryImportFromUrlDto);
|
|
||||||
repositoryConsumer.accept(repository);
|
|
||||||
|
|
||||||
verify(pullCommandBuilder).withUsername("trillian");
|
|
||||||
verify(pullCommandBuilder).withPassword("secret");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldThrowImportFailedEvent() throws IOException {
|
|
||||||
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
|
|
||||||
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
|
|
||||||
doThrow(ImportFailedException.class).when(pullCommandBuilder).pull(anyString());
|
|
||||||
|
|
||||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
|
||||||
RepositoryImportResource.RepositoryImportFromUrlDto repositoryImportFromUrlDto = new RepositoryImportResource.RepositoryImportFromUrlDto();
|
|
||||||
repositoryImportFromUrlDto.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
|
|
||||||
repositoryImportFromUrlDto.setNamespace("scmadmin");
|
|
||||||
repositoryImportFromUrlDto.setName("scm-manager");
|
|
||||||
|
|
||||||
Consumer<Repository> repositoryConsumer = repositoryImportResource.pullChangesFromRemoteUrl(repositoryImportFromUrlDto);
|
|
||||||
assertThrows(ImportFailedException.class, () -> repositoryConsumer.accept(repository));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldImportRepositoryFromBundle() throws IOException, URISyntaxException {
|
|
||||||
when(manager.getHandler("svn")).thenReturn(repositoryHandler);
|
|
||||||
when(repositoryHandler.getType()).thenReturn(new RepositoryType("svn", "svn", ImmutableSet.of(Command.UNBUNDLE)));
|
|
||||||
when(repositoryManager.create(any(), any())).thenReturn(RepositoryTestData.createHeartOfGold());
|
|
||||||
|
|
||||||
RepositoryDto repositoryDto = new RepositoryDto();
|
|
||||||
repositoryDto.setName("HeartOfGold");
|
|
||||||
repositoryDto.setNamespace("hitchhiker");
|
|
||||||
repositoryDto.setType("svn");
|
|
||||||
|
|
||||||
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
|
|
||||||
byte[] svnDump = Resources.toByteArray(dumpUrl);
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/bundle");
|
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
multipartRequest(request, Collections.singletonMap("bundle", new ByteArrayInputStream(svnDump)), repositoryDto);
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
|
||||||
|
|
||||||
assertEquals(HttpServletResponse.SC_CREATED, response.getStatus());
|
|
||||||
assertEquals("/v2/repositories/hitchhiker/HeartOfGold", response.getOutputHeaders().get("Location").get(0).toString());
|
|
||||||
ArgumentCaptor<RepositoryImportEvent> event = ArgumentCaptor.forClass(RepositoryImportEvent.class);
|
|
||||||
verify(eventBus).post(event.capture());
|
|
||||||
assertFalse(event.getValue().isFailed());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldThrowFailedEventOnImportRepositoryFromBundle() throws IOException, URISyntaxException {
|
|
||||||
when(manager.getHandler("svn")).thenReturn(repositoryHandler);
|
|
||||||
when(repositoryHandler.getType()).thenReturn(new RepositoryType("svn", "svn", ImmutableSet.of(Command.UNBUNDLE)));
|
|
||||||
doThrow(ImportFailedException.class).when(repositoryManager).create(any(), any());
|
|
||||||
|
|
||||||
RepositoryDto repositoryDto = new RepositoryDto();
|
|
||||||
repositoryDto.setName("HeartOfGold");
|
|
||||||
repositoryDto.setNamespace("hitchhiker");
|
|
||||||
repositoryDto.setType("svn");
|
|
||||||
|
|
||||||
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
|
|
||||||
byte[] svnDump = Resources.toByteArray(dumpUrl);
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/bundle");
|
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
multipartRequest(request, Collections.singletonMap("bundle", new ByteArrayInputStream(svnDump)), repositoryDto);
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
|
||||||
|
|
||||||
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
|
|
||||||
ArgumentCaptor<RepositoryImportEvent> event = ArgumentCaptor.forClass(RepositoryImportEvent.class);
|
|
||||||
verify(eventBus).post(event.capture());
|
|
||||||
assertTrue(event.getValue().isFailed());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldImportCompressedBundle() throws IOException {
|
|
||||||
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump.gz");
|
|
||||||
byte[] svnDump = Resources.toByteArray(dumpUrl);
|
|
||||||
|
|
||||||
UnbundleCommandBuilder ubc = mock(UnbundleCommandBuilder.class, RETURNS_SELF);
|
|
||||||
when(ubc.unbundle(any(File.class))).thenReturn(new UnbundleResponse(42));
|
|
||||||
RepositoryService service = mock(RepositoryService.class);
|
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
|
||||||
when(service.getUnbundleCommand()).thenReturn(ubc);
|
|
||||||
InputStream in = new ByteArrayInputStream(svnDump);
|
|
||||||
|
|
||||||
Consumer<Repository> repositoryConsumer = repositoryImportResource.unbundleImport(in, true);
|
|
||||||
repositoryConsumer.accept(RepositoryTestData.createHeartOfGold("svn"));
|
|
||||||
|
|
||||||
verify(ubc).setCompressed(true);
|
|
||||||
verify(ubc).unbundle(any(File.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldImportNonCompressedBundle() throws IOException {
|
|
||||||
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
|
|
||||||
byte[] svnDump = Resources.toByteArray(dumpUrl);
|
|
||||||
|
|
||||||
UnbundleCommandBuilder ubc = mock(UnbundleCommandBuilder.class, RETURNS_SELF);
|
|
||||||
when(ubc.unbundle(any(File.class))).thenReturn(new UnbundleResponse(21));
|
|
||||||
RepositoryService service = mock(RepositoryService.class);
|
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
|
||||||
when(service.getUnbundleCommand()).thenReturn(ubc);
|
|
||||||
InputStream in = new ByteArrayInputStream(svnDump);
|
|
||||||
|
|
||||||
Consumer<Repository> repositoryConsumer = repositoryImportResource.unbundleImport(in, false);
|
|
||||||
repositoryConsumer.accept(RepositoryTestData.createHeartOfGold("svn"));
|
|
||||||
|
|
||||||
verify(ubc, never()).setCompressed(true);
|
|
||||||
verify(ubc).unbundle(any(File.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldMarkRepositoryAsArchived() throws Exception {
|
public void shouldMarkRepositoryAsArchived() throws Exception {
|
||||||
String namespace = "space";
|
String namespace = "space";
|
||||||
@@ -709,7 +482,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/archive")
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/archive")
|
||||||
.content(new byte[]{});
|
.content(new byte[]{});
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -728,7 +500,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/unarchive")
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/unarchive")
|
||||||
.content(new byte[]{});
|
.content(new byte[]{});
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -746,11 +517,9 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
||||||
when(bundleCommandBuilder.getFileExtension()).thenReturn(".bundle");
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/svn");
|
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/svn");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -769,11 +538,9 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
||||||
when(bundleCommandBuilder.getFileExtension()).thenReturn(".bundle");
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/svn?compressed=true");
|
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/svn?compressed=true");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -790,12 +557,8 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||||
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/full");
|
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/full");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
@@ -812,9 +575,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||||
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/full")
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/full")
|
||||||
.contentType(VndMediaType.REPOSITORY_EXPORT)
|
.contentType(VndMediaType.REPOSITORY_EXPORT)
|
||||||
@@ -837,9 +597,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||||
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/full")
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/full")
|
||||||
.contentType(VndMediaType.REPOSITORY_EXPORT)
|
.contentType(VndMediaType.REPOSITORY_EXPORT)
|
||||||
@@ -860,9 +617,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||||
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
|
||||||
|
|
||||||
when(exportService.isExporting(repository)).thenReturn(true);
|
when(exportService.isExporting(repository)).thenReturn(true);
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
@@ -877,19 +631,13 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldDeleteRepositoryExport() throws URISyntaxException, IOException {
|
public void shouldDeleteRepositoryExport() throws URISyntaxException {
|
||||||
String namespace = "space";
|
String namespace = "space";
|
||||||
String name = "repo";
|
String name = "repo";
|
||||||
Repository repository = createRepository(namespace, name, "svn");
|
Repository repository = createRepository(namespace, name, "svn");
|
||||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||||
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
|
||||||
|
|
||||||
when(exportService.isExporting(repository)).thenReturn(false);
|
|
||||||
when(exportService.getData(repository)).thenReturn(new ByteArrayInputStream("".getBytes()));
|
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.delete("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export");
|
.delete("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
@@ -908,9 +656,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||||
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
|
||||||
|
|
||||||
when(exportService.isExporting(repository)).thenReturn(false);
|
when(exportService.isExporting(repository)).thenReturn(false);
|
||||||
doThrow(NotFoundException.class).when(exportService).checkExportIsAvailable(repository);
|
doThrow(NotFoundException.class).when(exportService).checkExportIsAvailable(repository);
|
||||||
|
|
||||||
@@ -932,9 +677,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||||
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
|
||||||
|
|
||||||
when(exportService.isExporting(repository)).thenReturn(true);
|
when(exportService.isExporting(repository)).thenReturn(true);
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
@@ -954,9 +696,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||||
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
|
||||||
|
|
||||||
when(exportService.isExporting(repository)).thenReturn(false);
|
when(exportService.isExporting(repository)).thenReturn(false);
|
||||||
when(exportService.getData(repository)).thenReturn(new ByteArrayInputStream("content".getBytes()));
|
when(exportService.getData(repository)).thenReturn(new ByteArrayInputStream("content".getBytes()));
|
||||||
when(exportService.getFileExtension(repository)).thenReturn("tar.gz.enc");
|
when(exportService.getFileExtension(repository)).thenReturn("tar.gz.enc");
|
||||||
@@ -979,9 +718,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||||
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
|
||||||
|
|
||||||
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
|
|
||||||
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
|
|
||||||
|
|
||||||
RepositoryExportInformationDto dto = new RepositoryExportInformationDto();
|
RepositoryExportInformationDto dto = new RepositoryExportInformationDto();
|
||||||
dto.setExporterName("trillian");
|
dto.setExporterName("trillian");
|
||||||
dto.setCreated(Instant.ofEpochMilli(100));
|
dto.setCreated(Instant.ofEpochMilli(100));
|
||||||
@@ -1011,7 +747,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(repositoryType.getSupportedCommands()).thenReturn(cmds);
|
when(repositoryType.getSupportedCommands()).thenReturn(cmds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private PageResult<Repository> createSingletonPageResult(Repository repository) {
|
private PageResult<Repository> createSingletonPageResult(Repository repository) {
|
||||||
return new PageResult<>(singletonList(repository), 0);
|
return new PageResult<>(singletonList(repository), 0);
|
||||||
}
|
}
|
||||||
@@ -1039,48 +774,4 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(repositoryManager.get(id)).thenReturn(repository);
|
when(repositoryManager.get(id)).thenReturn(repository);
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is a slightly adapted copy of Lin Zaho's gist at https://gist.github.com/lin-zhao/9985191
|
|
||||||
*/
|
|
||||||
private void multipartRequest(MockHttpRequest request, Map<String, InputStream> files, RepositoryDto repository) throws IOException {
|
|
||||||
String boundary = UUID.randomUUID().toString();
|
|
||||||
request.contentType("multipart/form-data; boundary=" + boundary);
|
|
||||||
|
|
||||||
//Make sure this is deleted in afterTest()
|
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
|
||||||
try (OutputStreamWriter formWriter = new OutputStreamWriter(buffer)) {
|
|
||||||
formWriter.append("--").append(boundary);
|
|
||||||
|
|
||||||
for (Map.Entry<String, InputStream> entry : files.entrySet()) {
|
|
||||||
formWriter.append("\n");
|
|
||||||
formWriter.append(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"",
|
|
||||||
entry.getKey(), entry.getKey())).append("\n");
|
|
||||||
formWriter.append("Content-Type: application/octet-stream").append("\n\n");
|
|
||||||
|
|
||||||
InputStream stream = entry.getValue();
|
|
||||||
int b = stream.read();
|
|
||||||
while (b >= 0) {
|
|
||||||
formWriter.write(b);
|
|
||||||
b = stream.read();
|
|
||||||
}
|
|
||||||
stream.close();
|
|
||||||
formWriter.append("\n").append("--").append(boundary);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repository != null) {
|
|
||||||
formWriter.append("\n");
|
|
||||||
formWriter.append("Content-Disposition: form-data; name=\"repository\"").append("\n\n");
|
|
||||||
StringWriter repositoryWriter = new StringWriter();
|
|
||||||
new JsonFactory().createGenerator(repositoryWriter).setCodec(new ObjectMapper()).writeObject(repository);
|
|
||||||
formWriter.append(repositoryWriter.getBuffer().toString()).append("\n");
|
|
||||||
formWriter.append("--").append(boundary);
|
|
||||||
}
|
|
||||||
|
|
||||||
formWriter.append("--");
|
|
||||||
formWriter.flush();
|
|
||||||
}
|
|
||||||
request.setInputStream(new ByteArrayInputStream(buffer.toByteArray()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.importexport;
|
||||||
|
|
||||||
|
import com.google.common.io.Resources;
|
||||||
|
import org.apache.shiro.authz.AuthorizationException;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.mockito.Answers;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryHandler;
|
||||||
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
|
import sonia.scm.repository.RepositoryType;
|
||||||
|
import sonia.scm.repository.api.Command;
|
||||||
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
import sonia.scm.repository.api.UnbundleCommandBuilder;
|
||||||
|
import sonia.scm.repository.api.UnbundleResponse;
|
||||||
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static java.util.Collections.singleton;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
class FromBundleImporterTest {
|
||||||
|
|
||||||
|
public static final Repository REPOSITORY = RepositoryTestData.createHeartOfGold("svn");
|
||||||
|
@Mock
|
||||||
|
private RepositoryManager manager;
|
||||||
|
@Mock
|
||||||
|
private RepositoryHandler repositoryHandler;
|
||||||
|
@Mock
|
||||||
|
private RepositoryServiceFactory serviceFactory;
|
||||||
|
@Mock
|
||||||
|
private ScmEventBus eventBus;
|
||||||
|
@Mock
|
||||||
|
private WorkdirProvider workdirProvider;
|
||||||
|
@Mock
|
||||||
|
private RepositoryImportLoggerFactory loggerFactory;
|
||||||
|
@Mock
|
||||||
|
private RepositoryImportLogger logger;
|
||||||
|
@Mock(answer = Answers.RETURNS_SELF)
|
||||||
|
private UnbundleCommandBuilder unbundleCommandBuilder;
|
||||||
|
@Mock
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private FromBundleImporter importer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void mockSubject() {
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void cleanupSubject() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WithPermission {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void initMocks(@TempDir Path temp) throws IOException {
|
||||||
|
when(subject.getPrincipal()).thenReturn("dent");
|
||||||
|
when(workdirProvider.createNewWorkdir(REPOSITORY.getId())).thenReturn(temp.toFile());
|
||||||
|
when(manager.create(eq(REPOSITORY), any())).thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
invocation.getArgument(1, Consumer.class).accept(REPOSITORY);
|
||||||
|
return REPOSITORY;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
when(manager.getHandler("svn")).thenReturn(repositoryHandler);
|
||||||
|
RepositoryType repositoryType = mock(RepositoryType.class);
|
||||||
|
when(repositoryHandler.getType()).thenReturn(repositoryType);
|
||||||
|
when(repositoryType.getSupportedCommands()).thenReturn(singleton(Command.UNBUNDLE));
|
||||||
|
when(loggerFactory.createLogger()).thenReturn(logger);
|
||||||
|
|
||||||
|
when(unbundleCommandBuilder.unbundle(any(File.class))).thenReturn(new UnbundleResponse(42));
|
||||||
|
RepositoryService service = mock(RepositoryService.class);
|
||||||
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
|
when(service.getUnbundleCommand()).thenReturn(unbundleCommandBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldImportCompressedBundle() throws IOException {
|
||||||
|
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump.gz");
|
||||||
|
InputStream in = new ByteArrayInputStream(Resources.toByteArray(dumpUrl));
|
||||||
|
|
||||||
|
importer.importFromBundle(true, in, REPOSITORY);
|
||||||
|
|
||||||
|
verify(unbundleCommandBuilder).setCompressed(true);
|
||||||
|
verify(unbundleCommandBuilder).unbundle(any(File.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldImportNonCompressedBundle() throws IOException {
|
||||||
|
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
|
||||||
|
InputStream in = new ByteArrayInputStream(Resources.toByteArray(dumpUrl));
|
||||||
|
|
||||||
|
importer.importFromBundle(false, in, REPOSITORY);
|
||||||
|
|
||||||
|
verify(unbundleCommandBuilder, never()).setCompressed(true);
|
||||||
|
verify(unbundleCommandBuilder).unbundle(any(File.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSetPermissionForCurrentUser() throws IOException {
|
||||||
|
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
|
||||||
|
InputStream in = new ByteArrayInputStream(Resources.toByteArray(dumpUrl));
|
||||||
|
|
||||||
|
Repository createdRepository = importer.importFromBundle(false, in, REPOSITORY);
|
||||||
|
|
||||||
|
assertThat(createdRepository.getPermissions())
|
||||||
|
.hasSize(1);
|
||||||
|
RepositoryPermission permission = createdRepository.getPermissions().iterator().next();
|
||||||
|
assertThat(permission.getName()).isEqualTo("dent");
|
||||||
|
assertThat(permission.isGroupPermission()).isFalse();
|
||||||
|
assertThat(permission.getRole()).isEqualTo("OWNER");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFailWithoutPermission() throws IOException {
|
||||||
|
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
|
||||||
|
InputStream in = new ByteArrayInputStream(Resources.toByteArray(dumpUrl));
|
||||||
|
|
||||||
|
doThrow(new AuthorizationException()).when(subject).checkPermission("repository:create");
|
||||||
|
|
||||||
|
assertThrows(AuthorizationException.class, () -> importer.importFromBundle(false, in, REPOSITORY));
|
||||||
|
|
||||||
|
verify(manager, never()).create(any(), any());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.importexport;
|
||||||
|
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryHandler;
|
||||||
|
import sonia.scm.repository.RepositoryImportEvent;
|
||||||
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
|
import sonia.scm.repository.RepositoryType;
|
||||||
|
import sonia.scm.repository.api.ImportFailedException;
|
||||||
|
import sonia.scm.repository.api.PullCommandBuilder;
|
||||||
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static java.util.Collections.singleton;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.Mockito.RETURNS_SELF;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.repository.api.Command.PULL;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class FromUrlImporterTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RepositoryManager manager;
|
||||||
|
@Mock
|
||||||
|
private RepositoryServiceFactory serviceFactory;
|
||||||
|
@Mock
|
||||||
|
private RepositoryService service;
|
||||||
|
@Mock
|
||||||
|
private ScmEventBus eventBus;
|
||||||
|
@Mock
|
||||||
|
private RepositoryImportLoggerFactory loggerFactory;
|
||||||
|
@Mock
|
||||||
|
private RepositoryImportLogger logger;
|
||||||
|
@Mock
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private FromUrlImporter importer;
|
||||||
|
|
||||||
|
private final Repository repository = RepositoryTestData.createHeartOfGold("git");
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpMocks() {
|
||||||
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
|
when(loggerFactory.createLogger()).thenReturn(logger);
|
||||||
|
when(manager.create(any(), any())).thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
Repository repository = invocation.getArgument(0, Repository.class);
|
||||||
|
Repository createdRepository = repository.clone();
|
||||||
|
createdRepository.setNamespace("created");
|
||||||
|
invocation.getArgument(1, Consumer.class).accept(createdRepository);
|
||||||
|
return createdRepository;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpRepositoryType() {
|
||||||
|
RepositoryHandler repositoryHandler = mock(RepositoryHandler.class);
|
||||||
|
when(manager.getHandler(repository.getType())).thenReturn(repositoryHandler);
|
||||||
|
RepositoryType repositoryType = mock(RepositoryType.class);
|
||||||
|
when(repositoryHandler.getType()).thenReturn(repositoryType);
|
||||||
|
when(repositoryType.getSupportedCommands()).thenReturn(singleton(PULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void mockSubject() {
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
when(subject.getPrincipal()).thenReturn("trillian");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void cleanupSubject() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPullChangesFromRemoteUrl() throws IOException {
|
||||||
|
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
|
||||||
|
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
|
||||||
|
|
||||||
|
FromUrlImporter.RepositoryImportParameters parameters = new FromUrlImporter.RepositoryImportParameters();
|
||||||
|
parameters.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
|
||||||
|
|
||||||
|
Repository createdRepository = importer.importFromUrl(parameters, repository);
|
||||||
|
|
||||||
|
assertThat(createdRepository.getNamespace()).isEqualTo("created");
|
||||||
|
verify(pullCommandBuilder).pull("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
|
||||||
|
verify(logger).finished();
|
||||||
|
verify(eventBus).post(argThat(
|
||||||
|
event -> {
|
||||||
|
assertThat(event).isInstanceOf(RepositoryImportEvent.class);
|
||||||
|
RepositoryImportEvent repositoryImportEvent = (RepositoryImportEvent) event;
|
||||||
|
assertThat(repositoryImportEvent.getItem().getNamespace()).isEqualTo("created");
|
||||||
|
assertThat(repositoryImportEvent.isFailed()).isFalse();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPullChangesFromRemoteUrlWithCredentials() {
|
||||||
|
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
|
||||||
|
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
|
||||||
|
|
||||||
|
FromUrlImporter.RepositoryImportParameters parameters = new FromUrlImporter.RepositoryImportParameters();
|
||||||
|
parameters.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
|
||||||
|
parameters.setUsername("trillian");
|
||||||
|
parameters.setPassword("secret");
|
||||||
|
|
||||||
|
importer.importFromUrl(parameters, repository);
|
||||||
|
|
||||||
|
verify(pullCommandBuilder).withUsername("trillian");
|
||||||
|
verify(pullCommandBuilder).withPassword("secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowImportFailedEvent() throws IOException {
|
||||||
|
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
|
||||||
|
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
|
||||||
|
doThrow(TestException.class).when(pullCommandBuilder).pull(anyString());
|
||||||
|
when(logger.started()).thenReturn(true);
|
||||||
|
|
||||||
|
FromUrlImporter.RepositoryImportParameters parameters = new FromUrlImporter.RepositoryImportParameters();
|
||||||
|
parameters.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
|
||||||
|
|
||||||
|
assertThrows(ImportFailedException.class, () -> importer.importFromUrl(parameters, repository));
|
||||||
|
verify(logger).failed(argThat(e -> e instanceof TestException));
|
||||||
|
verify(eventBus).post(argThat(
|
||||||
|
event -> {
|
||||||
|
assertThat(event).isInstanceOf(RepositoryImportEvent.class);
|
||||||
|
RepositoryImportEvent repositoryImportEvent = (RepositoryImportEvent) event;
|
||||||
|
assertThat(repositoryImportEvent.getItem()).isEqualTo(repository);
|
||||||
|
assertThat(repositoryImportEvent.isFailed()).isTrue();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestException extends RuntimeException {}
|
||||||
|
}
|
||||||
@@ -25,6 +25,9 @@
|
|||||||
package sonia.scm.importexport;
|
package sonia.scm.importexport;
|
||||||
|
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
|
import org.apache.shiro.authz.AuthorizationException;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -40,6 +43,7 @@ import sonia.scm.event.ScmEventBus;
|
|||||||
import sonia.scm.repository.ImportRepositoryHookEvent;
|
import sonia.scm.repository.ImportRepositoryHookEvent;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryHookEvent;
|
import sonia.scm.repository.RepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.RepositoryImportEvent;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
@@ -64,6 +68,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
import static org.mockito.Mockito.lenient;
|
import static org.mockito.Mockito.lenient;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
@@ -94,6 +100,14 @@ class FullScmRepositoryImporterTest {
|
|||||||
private RepositoryImportExportEncryption repositoryImportExportEncryption;
|
private RepositoryImportExportEncryption repositoryImportExportEncryption;
|
||||||
@Mock
|
@Mock
|
||||||
private WorkdirProvider workdirProvider;
|
private WorkdirProvider workdirProvider;
|
||||||
|
@Mock
|
||||||
|
private RepositoryImportLogger logger;
|
||||||
|
@Mock
|
||||||
|
private RepositoryImportLoggerFactory loggerFactory;
|
||||||
|
@Mock
|
||||||
|
private Subject subject;
|
||||||
|
@Mock
|
||||||
|
private ScmEventBus eventBus;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private EnvironmentCheckStep environmentCheckStep;
|
private EnvironmentCheckStep environmentCheckStep;
|
||||||
@@ -104,9 +118,6 @@ class FullScmRepositoryImporterTest {
|
|||||||
@InjectMocks
|
@InjectMocks
|
||||||
private RepositoryImportStep repositoryImportStep;
|
private RepositoryImportStep repositoryImportStep;
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ScmEventBus eventBus;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private RepositoryHookEvent event;
|
private RepositoryHookEvent event;
|
||||||
|
|
||||||
@@ -124,14 +135,25 @@ class FullScmRepositoryImporterTest {
|
|||||||
repositoryImportStep,
|
repositoryImportStep,
|
||||||
repositoryManager,
|
repositoryManager,
|
||||||
repositoryImportExportEncryption,
|
repositoryImportExportEncryption,
|
||||||
eventBus
|
loggerFactory,
|
||||||
);
|
eventBus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void initRepositoryService() {
|
void initRepositoryService() {
|
||||||
lenient().when(serviceFactory.create(REPOSITORY)).thenReturn(service);
|
lenient().when(serviceFactory.create(REPOSITORY)).thenReturn(service);
|
||||||
lenient().when(service.getUnbundleCommand()).thenReturn(unbundleCommandBuilder);
|
lenient().when(service.getUnbundleCommand()).thenReturn(unbundleCommandBuilder);
|
||||||
|
lenient().when(loggerFactory.createLogger()).thenReturn(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void initSubject() {
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void cleanupSubject() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -154,6 +176,28 @@ class FullScmRepositoryImporterTest {
|
|||||||
IncompatibleEnvironmentForImportException.class,
|
IncompatibleEnvironmentForImportException.class,
|
||||||
() -> fullImporter.importFromStream(REPOSITORY, importStream, "")
|
() -> fullImporter.importFromStream(REPOSITORY, importStream, "")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
verify(eventBus).post(argThat(
|
||||||
|
event -> {
|
||||||
|
assertThat(event).isInstanceOf(RepositoryImportEvent.class);
|
||||||
|
RepositoryImportEvent repositoryImportEvent = (RepositoryImportEvent) event;
|
||||||
|
assertThat(repositoryImportEvent.getItem()).isEqualTo(REPOSITORY);
|
||||||
|
assertThat(repositoryImportEvent.isFailed()).isTrue();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotImportRepositoryWithoutPermission() throws IOException {
|
||||||
|
doThrow(AuthorizationException.class).when(subject).checkPermission("repository:create");
|
||||||
|
|
||||||
|
InputStream stream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
|
||||||
|
|
||||||
|
assertThrows(AuthorizationException.class, () -> fullImporter.importFromStream(REPOSITORY, stream, null));
|
||||||
|
|
||||||
|
verify(storeImporter, never()).importFromTarArchive(any(Repository.class), any(InputStream.class), any(RepositoryImportLogger.class));
|
||||||
|
verify(repositoryManager, never()).modify(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@@ -174,7 +218,7 @@ class FullScmRepositoryImporterTest {
|
|||||||
Repository repository = fullImporter.importFromStream(REPOSITORY, stream, "");
|
Repository repository = fullImporter.importFromStream(REPOSITORY, stream, "");
|
||||||
|
|
||||||
assertThat(repository).isEqualTo(REPOSITORY);
|
assertThat(repository).isEqualTo(REPOSITORY);
|
||||||
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class));
|
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class), eq(logger));
|
||||||
verify(repositoryManager).modify(REPOSITORY);
|
verify(repositoryManager).modify(REPOSITORY);
|
||||||
Collection<RepositoryPermission> updatedPermissions = REPOSITORY.getPermissions();
|
Collection<RepositoryPermission> updatedPermissions = REPOSITORY.getPermissions();
|
||||||
assertThat(updatedPermissions).hasSize(2);
|
assertThat(updatedPermissions).hasSize(2);
|
||||||
@@ -192,6 +236,33 @@ class FullScmRepositoryImporterTest {
|
|||||||
assertThat(workDirExists).isFalse();
|
assertThat(workDirExists).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSendImportedEventForImportedRepository() throws IOException {
|
||||||
|
InputStream stream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
|
||||||
|
when(unbundleCommandBuilder.setPostEventSink(any())).thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
invocation.getArgument(0, Consumer.class).accept(new RepositoryHookEvent(null, REPOSITORY, null));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ArgumentCaptor<Object> capturedEvents = ArgumentCaptor.forClass(Object.class);
|
||||||
|
doNothing().when(eventBus).post(capturedEvents.capture());
|
||||||
|
|
||||||
|
fullImporter.importFromStream(REPOSITORY, stream, null);
|
||||||
|
|
||||||
|
assertThat(capturedEvents.getAllValues()).hasSize(2);
|
||||||
|
assertThat(capturedEvents.getAllValues()).anyMatch(
|
||||||
|
event ->
|
||||||
|
event instanceof ImportRepositoryHookEvent &&
|
||||||
|
((ImportRepositoryHookEvent) event).getRepository().equals(REPOSITORY)
|
||||||
|
);
|
||||||
|
assertThat(capturedEvents.getAllValues()).anyMatch(
|
||||||
|
event ->
|
||||||
|
event instanceof RepositoryImportEvent &&
|
||||||
|
((RepositoryImportEvent) event).getItem().equals(REPOSITORY)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldTriggerUpdateForImportedRepository() throws IOException {
|
void shouldTriggerUpdateForImportedRepository() throws IOException {
|
||||||
InputStream stream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
|
InputStream stream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
|
||||||
@@ -207,7 +278,8 @@ class FullScmRepositoryImporterTest {
|
|||||||
Repository repository = fullImporter.importFromStream(REPOSITORY, stream, "");
|
Repository repository = fullImporter.importFromStream(REPOSITORY, stream, "");
|
||||||
|
|
||||||
assertThat(repository).isEqualTo(REPOSITORY);
|
assertThat(repository).isEqualTo(REPOSITORY);
|
||||||
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class));
|
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class), eq(logger));
|
||||||
|
verify(repositoryManager).create(REPOSITORY);
|
||||||
verify(repositoryManager).modify(REPOSITORY);
|
verify(repositoryManager).modify(REPOSITORY);
|
||||||
verify(unbundleCommandBuilder).unbundle((InputStream) argThat(argument -> argument.getClass().equals(NoneClosingInputStream.class)));
|
verify(unbundleCommandBuilder).unbundle((InputStream) argThat(argument -> argument.getClass().equals(NoneClosingInputStream.class)));
|
||||||
verify(workdirProvider, never()).createNewWorkdir(REPOSITORY.getId());
|
verify(workdirProvider, never()).createNewWorkdir(REPOSITORY.getId());
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.importexport;
|
||||||
|
|
||||||
|
import com.google.common.io.Resources;
|
||||||
|
import org.apache.shiro.authz.AuthorizationException;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sonia.scm.NotFoundException;
|
||||||
|
import sonia.scm.store.Blob;
|
||||||
|
import sonia.scm.store.InMemoryBlobStore;
|
||||||
|
import sonia.scm.store.InMemoryBlobStoreFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class RepositoryImportLoggerFactoryTest {
|
||||||
|
|
||||||
|
private final Subject subject = mock(Subject.class);
|
||||||
|
|
||||||
|
private final InMemoryBlobStore store = new InMemoryBlobStore();
|
||||||
|
private final RepositoryImportLoggerFactory factory = new RepositoryImportLoggerFactory(new InMemoryBlobStoreFactory(store));
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void initSubject() {
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void cleanupSubject() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReadLogForExportingUser() throws IOException {
|
||||||
|
when(subject.getPrincipal()).thenReturn("dent");
|
||||||
|
|
||||||
|
createLog();
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
factory.getLog("42", out);
|
||||||
|
|
||||||
|
assertLogReadCorrectly(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReadLogForAdmin() throws IOException {
|
||||||
|
when(subject.getPrincipal()).thenReturn("trillian");
|
||||||
|
when(subject.isPermitted(anyString())).thenReturn(true);
|
||||||
|
|
||||||
|
createLog();
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
factory.getLog("42", out);
|
||||||
|
|
||||||
|
assertLogReadCorrectly(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertLogReadCorrectly(ByteArrayOutputStream out) {
|
||||||
|
assertThat(out).asString().contains(
|
||||||
|
"Import of repository hitchhiker/HeartOfGold",
|
||||||
|
"Repository type: git",
|
||||||
|
"Imported from: URL",
|
||||||
|
"Imported by dent (Arthur Dent)",
|
||||||
|
"",
|
||||||
|
"Thu Feb 25 11:11:07 CET 2021 - import started",
|
||||||
|
"Thu Feb 25 11:11:07 CET 2021 - pulling repository from https://github.com/scm-manager/scm-manager",
|
||||||
|
"Thu Feb 25 11:11:08 CET 2021 - import finished successfully"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowNotFoundExceptionForMissingLog() {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
assertThrows(NotFoundException.class, () -> factory.getLog("42", out));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFailWithoutPermission() throws IOException {
|
||||||
|
when(subject.getPrincipal()).thenReturn("trillian");
|
||||||
|
createLog();
|
||||||
|
|
||||||
|
doThrow(AuthorizationException.class).when(subject).checkPermission("only:admin:allowed");
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
assertThrows(AuthorizationException.class, () -> factory.getLog("42", out));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
private void createLog() throws IOException {
|
||||||
|
Blob blob = store.create("42");
|
||||||
|
try (OutputStream outputStream = blob.getOutputStream()) {
|
||||||
|
Resources.copy(
|
||||||
|
Resources.getResource("sonia/scm/importexport/importLog.blob"),
|
||||||
|
outputStream);
|
||||||
|
}
|
||||||
|
blob.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,8 @@ class TarArchiveRepositoryStoreImporterTest {
|
|||||||
|
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
private RepositoryStoreImporter repositoryStoreImporter;
|
private RepositoryStoreImporter repositoryStoreImporter;
|
||||||
|
@Mock
|
||||||
|
private RepositoryImportLogger logger;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private TarArchiveRepositoryStoreImporter tarArchiveRepositoryStoreImporter;
|
private TarArchiveRepositoryStoreImporter tarArchiveRepositoryStoreImporter;
|
||||||
@@ -60,20 +62,20 @@ class TarArchiveRepositoryStoreImporterTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldDoNothingIfNoEntries() {
|
void shouldDoNothingIfNoEntries() {
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes());
|
ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes());
|
||||||
tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, bais);
|
tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, bais, logger);
|
||||||
verify(repositoryStoreImporter, never()).doImport(any(Repository.class));
|
verify(repositoryStoreImporter, never()).doImport(any(Repository.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldImportEachEntry() throws IOException {
|
void shouldImportEachEntry() throws IOException {
|
||||||
InputStream inputStream = Resources.getResource("sonia/scm/repository/import/scm-metadata.tar").openStream();
|
InputStream inputStream = Resources.getResource("sonia/scm/repository/import/scm-metadata.tar").openStream();
|
||||||
tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, inputStream);
|
tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, inputStream, logger);
|
||||||
verify(repositoryStoreImporter, times(2)).doImport(repository);
|
verify(repositoryStoreImporter, times(2)).doImport(repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldThrowImportFailedExceptionIfInvalidStorePath() throws IOException {
|
void shouldThrowImportFailedExceptionIfInvalidStorePath() throws IOException {
|
||||||
InputStream inputStream = Resources.getResource("sonia/scm/repository/import/scm-metadata_invalid.tar").openStream();
|
InputStream inputStream = Resources.getResource("sonia/scm/repository/import/scm-metadata_invalid.tar").openStream();
|
||||||
assertThrows(ImportFailedException.class, () -> tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, inputStream));
|
assertThrows(ImportFailedException.class, () -> tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, inputStream, logger));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"namespace": "hitchhiker",
|
"namespace": "hitchhiker",
|
||||||
"name": "HeartOfGold",
|
"name": "HeartOfGold",
|
||||||
|
"type": "git",
|
||||||
"importUrl": "https://scm-manager-org/scm/repo/secret/puzzle42",
|
"importUrl": "https://scm-manager-org/scm/repo/secret/puzzle42",
|
||||||
"username": "trillian",
|
"username": "trillian",
|
||||||
"password": "secret"
|
"password": "secret"
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user