Add error handling to the catalog-import plugin
This commit is contained in:
@@ -28,12 +28,11 @@ export interface CatalogImportApi {
|
||||
owner: string;
|
||||
repo: string;
|
||||
fileContent: string;
|
||||
}): Promise<{ errorMessage: string | null; link: string }>;
|
||||
}): Promise<{ link: string }>;
|
||||
createRepositoryLocation(options: {
|
||||
token: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
}): Promise<{ errorMessage: string | null }>;
|
||||
}): Promise<void>;
|
||||
generateEntityDefinitions(options: {
|
||||
repo: string;
|
||||
}): Promise<PartialEntity[]>;
|
||||
|
||||
@@ -45,7 +45,15 @@ export class CatalogImportClient implements CatalogImportApi {
|
||||
location: { type: 'github', target: repo },
|
||||
}),
|
||||
},
|
||||
);
|
||||
).catch(e => {
|
||||
throw new Error(`Failed to generate entity definitions, ${e.message}`);
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to generate entity definitions. Received http response ${response.status}: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as AnalyzeLocationResponse;
|
||||
return payload.generateEntities.map(x => x.entity);
|
||||
}
|
||||
@@ -54,22 +62,28 @@ export class CatalogImportClient implements CatalogImportApi {
|
||||
owner,
|
||||
repo,
|
||||
}: {
|
||||
token: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
}): Promise<{ errorMessage: string | null }> {
|
||||
await fetch(`${await this.discoveryApi.getBaseUrl('catalog')}/locations`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}): Promise<void> {
|
||||
const response = await fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/locations`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
type: 'github',
|
||||
target: `https://github.com/${owner}/${repo}/blob/master/catalog-info.yaml`,
|
||||
presence: 'optional',
|
||||
}),
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
type: 'github',
|
||||
target: `https://github.com/${owner}/${repo}/blob/master/catalog-info.yaml`,
|
||||
presence: 'optional',
|
||||
}),
|
||||
});
|
||||
return { errorMessage: null };
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Received http response ${response.status}: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async submitPRToRepo({
|
||||
@@ -82,38 +96,93 @@ export class CatalogImportClient implements CatalogImportApi {
|
||||
owner: string;
|
||||
repo: string;
|
||||
fileContent: string;
|
||||
}): Promise<{ errorMessage: string | null; link: string }> {
|
||||
}): Promise<{ link: string }> {
|
||||
const octo = new Octokit({
|
||||
auth: token,
|
||||
});
|
||||
|
||||
const parentRef = await octo.git.getRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: 'heads/master',
|
||||
});
|
||||
await octo.git.createRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: 'refs/heads/backstage-integration',
|
||||
sha: parentRef.data.object.sha,
|
||||
});
|
||||
await octo.repos.createOrUpdateFileContents({
|
||||
owner,
|
||||
repo,
|
||||
path: 'catalog-info.yaml',
|
||||
message: 'Add backstage.yaml config file',
|
||||
content: btoa(fileContent),
|
||||
branch: 'backstage-integration',
|
||||
});
|
||||
const pullRequestRespone = await octo.pulls.create({
|
||||
owner,
|
||||
repo,
|
||||
title: 'Add catalog-info.yaml config file',
|
||||
head: 'backstage-integration',
|
||||
base: 'master',
|
||||
});
|
||||
const branchName = 'backstage-integration';
|
||||
const fileName = 'catalog-info.yaml';
|
||||
|
||||
return { errorMessage: null, link: pullRequestRespone.data.html_url };
|
||||
const repoData = await octo.repos
|
||||
.get({
|
||||
owner,
|
||||
repo,
|
||||
})
|
||||
.catch(e => {
|
||||
throw new Error(formatHttpErrorMessage("Couldn't fetch repo data", e));
|
||||
});
|
||||
|
||||
const parentRef = await octo.git
|
||||
.getRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `heads/${repoData.data.default_branch}`,
|
||||
})
|
||||
.catch(e => {
|
||||
throw new Error(
|
||||
formatHttpErrorMessage("Couldn't fetch default branch data", e),
|
||||
);
|
||||
});
|
||||
|
||||
await octo.git
|
||||
.createRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `refs/heads/${branchName}`,
|
||||
sha: parentRef.data.object.sha,
|
||||
})
|
||||
.catch(e => {
|
||||
throw new Error(
|
||||
formatHttpErrorMessage(
|
||||
`Couldn't create a new branch with name '${branchName}'`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
await octo.repos
|
||||
.createOrUpdateFileContents({
|
||||
owner,
|
||||
repo,
|
||||
path: fileName,
|
||||
message: `Add ${fileName} config file`,
|
||||
content: btoa(fileContent),
|
||||
branch: branchName,
|
||||
})
|
||||
.catch(e => {
|
||||
throw new Error(
|
||||
formatHttpErrorMessage(
|
||||
`Couldn't create a commit with ${fileName} file added`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
const pullRequestRespone = await octo.pulls
|
||||
.create({
|
||||
owner,
|
||||
repo,
|
||||
title: `Add ${fileName} config file`,
|
||||
head: branchName,
|
||||
base: repoData.data.default_branch,
|
||||
})
|
||||
.catch(e => {
|
||||
throw new Error(
|
||||
formatHttpErrorMessage(
|
||||
`Couldn't create a pull request for ${branchName} branch`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
return { link: pullRequestRespone.data.html_url };
|
||||
}
|
||||
}
|
||||
|
||||
function formatHttpErrorMessage(
|
||||
message: string,
|
||||
error: { status: number; message: string },
|
||||
) {
|
||||
return `${message}, received http response status code ${error.status}: ${error.message}`;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Button, CircularProgress, Grid, Tooltip } from '@material-ui/core';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import { useGithubRepos } from '../util/useGithubRepos';
|
||||
import { ConfigSpec } from './ImportComponentPage';
|
||||
import { errorApiRef, useApi } from '@backstage/core';
|
||||
|
||||
type Props = {
|
||||
nextStep: () => void;
|
||||
@@ -32,14 +33,20 @@ const ComponentConfigDisplay: React.FC<Props> = ({
|
||||
savePRLink,
|
||||
}) => {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const { submitPRToRepo } = useGithubRepos();
|
||||
const onNext = useCallback(async () => {
|
||||
setSubmitting(true);
|
||||
const result = await submitPRToRepo(configFile);
|
||||
savePRLink(result.link);
|
||||
setSubmitting(false);
|
||||
nextStep();
|
||||
}, [submitPRToRepo, configFile, nextStep, savePRLink]);
|
||||
try {
|
||||
setSubmitting(true);
|
||||
const result = await submitPRToRepo(configFile);
|
||||
savePRLink(result.link);
|
||||
setSubmitting(false);
|
||||
nextStep();
|
||||
} catch (e) {
|
||||
setSubmitting(false);
|
||||
errorApi.post(e);
|
||||
}
|
||||
}, [submitPRToRepo, configFile, nextStep, savePRLink, errorApi]);
|
||||
|
||||
return (
|
||||
<Grid container direction="column" spacing={1}>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
@@ -15,6 +13,9 @@ import { Entity } from '@backstage/catalog-model';
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export type RecursivePartial<T> = {
|
||||
[P in keyof T]?: T[P] extends (infer U)[]
|
||||
? RecursivePartial<U>[]
|
||||
|
||||
@@ -30,20 +30,27 @@ export function useGithubRepos() {
|
||||
selectedRepo.repo.split('/')[0],
|
||||
selectedRepo.repo.split('/')[1],
|
||||
];
|
||||
const submitPRResponse = await api.submitPRToRepo({
|
||||
token,
|
||||
owner: ownerName,
|
||||
repo: repoName,
|
||||
fileContent: selectedRepo.config
|
||||
.map(entity => `---\n${YAML.stringify(entity)}`)
|
||||
.join('\n'),
|
||||
});
|
||||
const submitPRResponse = await api
|
||||
.submitPRToRepo({
|
||||
token,
|
||||
owner: ownerName,
|
||||
repo: repoName,
|
||||
fileContent: selectedRepo.config
|
||||
.map(entity => `---\n${YAML.stringify(entity)}`)
|
||||
.join('\n'),
|
||||
})
|
||||
.catch(e => {
|
||||
throw new Error(`Failed to submit PR to repo:\n${e.message}`);
|
||||
});
|
||||
|
||||
await api.createRepositoryLocation({
|
||||
token,
|
||||
owner: selectedRepo.repo.split('/')[0],
|
||||
repo: selectedRepo.repo.split('/')[1],
|
||||
});
|
||||
await api
|
||||
.createRepositoryLocation({
|
||||
owner: selectedRepo.repo.split('/')[0],
|
||||
repo: selectedRepo.repo.split('/')[1],
|
||||
})
|
||||
.catch(e => {
|
||||
throw new Error(`Failed to create repository location:\n${e.message}`);
|
||||
});
|
||||
|
||||
return submitPRResponse;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user