Signed-off-by: bnechyporenko <bnechyporenko@bol.com>
This commit is contained in:
bnechyporenko
2024-02-08 20:43:27 +01:00
parent 72d7c6867a
commit 92582f17a0
8 changed files with 74 additions and 147 deletions
@@ -244,7 +244,6 @@ export function createPublishGithubAction(options: {
repoVariables,
secrets,
ctx.logger,
ctx.checkpoint,
);
const remoteUrl = newRepo.clone_url;
@@ -36,8 +36,6 @@ import {
enableBranchProtectionOnDefaultRepoBranch,
entityRefToName,
} from './gitHelpers';
import { JsonObject } from '@backstage/types';
import { defineCheckpoint } from '@backstage/plugin-scaffolder-common';
const DEFAULT_TIMEOUT_MS = 60_000;
@@ -140,10 +138,6 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
repoVariables: { [key: string]: string } | undefined,
secrets: { [key: string]: string } | undefined,
logger: Logger,
checkpoint?: <U extends JsonObject>(
key: string,
fn: () => Promise<U>,
) => Promise<U>,
) {
// eslint-disable-next-line testing-library/no-await-sync-queries
const user = await client.rest.users.getByUsername({
@@ -154,71 +148,59 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
await validateAccessTeam(client, access);
}
const repoCreation = async () => {
const repoCreationPromise =
user.data.type === 'Organization'
? client.rest.repos.createInOrg({
name: repo,
org: owner,
private: repoVisibility === 'private',
// @ts-ignore https://github.com/octokit/types.ts/issues/522
visibility: repoVisibility,
description: description,
delete_branch_on_merge: deleteBranchOnMerge,
allow_merge_commit: allowMergeCommit,
allow_squash_merge: allowSquashMerge,
squash_merge_commit_title: squashMergeCommitTitle,
squash_merge_commit_message: squashMergeCommitMessage,
allow_rebase_merge: allowRebaseMerge,
allow_auto_merge: allowAutoMerge,
homepage: homepage,
has_projects: hasProjects,
has_wiki: hasWiki,
has_issues: hasIssues,
})
: client.rest.repos.createForAuthenticatedUser({
name: repo,
private: repoVisibility === 'private',
description: description,
delete_branch_on_merge: deleteBranchOnMerge,
allow_merge_commit: allowMergeCommit,
allow_squash_merge: allowSquashMerge,
squash_merge_commit_title: squashMergeCommitTitle,
squash_merge_commit_message: squashMergeCommitMessage,
allow_rebase_merge: allowRebaseMerge,
allow_auto_merge: allowAutoMerge,
homepage: homepage,
has_projects: hasProjects,
has_wiki: hasWiki,
has_issues: hasIssues,
});
const repoCreationPromise =
user.data.type === 'Organization'
? client.rest.repos.createInOrg({
name: repo,
org: owner,
private: repoVisibility === 'private',
// @ts-ignore https://github.com/octokit/types.ts/issues/522
visibility: repoVisibility,
description: description,
delete_branch_on_merge: deleteBranchOnMerge,
allow_merge_commit: allowMergeCommit,
allow_squash_merge: allowSquashMerge,
squash_merge_commit_title: squashMergeCommitTitle,
squash_merge_commit_message: squashMergeCommitMessage,
allow_rebase_merge: allowRebaseMerge,
allow_auto_merge: allowAutoMerge,
homepage: homepage,
has_projects: hasProjects,
has_wiki: hasWiki,
has_issues: hasIssues,
})
: client.rest.repos.createForAuthenticatedUser({
name: repo,
private: repoVisibility === 'private',
description: description,
delete_branch_on_merge: deleteBranchOnMerge,
allow_merge_commit: allowMergeCommit,
allow_squash_merge: allowSquashMerge,
squash_merge_commit_title: squashMergeCommitTitle,
squash_merge_commit_message: squashMergeCommitMessage,
allow_rebase_merge: allowRebaseMerge,
allow_auto_merge: allowAutoMerge,
homepage: homepage,
has_projects: hasProjects,
has_wiki: hasWiki,
has_issues: hasIssues,
});
let newRepo;
let newRepo;
try {
newRepo = (await repoCreationPromise).data;
} catch (e) {
assertError(e);
if (e.message === 'Resource not accessible by integration') {
logger.warn(
`The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`,
);
}
throw new Error(
`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`,
try {
newRepo = (await repoCreationPromise).data;
} catch (e) {
assertError(e);
if (e.message === 'Resource not accessible by integration') {
logger.warn(
`The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`,
);
}
return { clone_url: newRepo.clone_url, html_url: newRepo.html_url };
};
const { clone_url, html_url } = await defineCheckpoint<{
clone_url: string;
html_url: string;
}>({
key: 'v1.task.checkpoint.repo.creation',
checkpoint,
fn: repoCreation,
});
throw new Error(
`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`,
);
}
if (access?.startsWith(`${owner}/`)) {
const [, team] = access.split('/');
@@ -231,19 +213,11 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
});
// No need to add access if it's the person who owns the personal account
} else if (access && access !== owner) {
const addCollaborator = async () => {
await client.rest.repos.addCollaborator({
owner,
repo,
username: access,
permission: 'admin',
});
return {};
};
await defineCheckpoint({
key: 'v1.task.checkpoint.add.collaborator',
checkpoint,
fn: addCollaborator,
await client.rest.repos.addCollaborator({
owner,
repo,
username: access,
permission: 'admin',
});
}
@@ -251,19 +225,11 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
for (const collaborator of collaborators) {
try {
if ('user' in collaborator) {
const addCollaborator = async () => {
await client.rest.repos.addCollaborator({
owner,
repo,
username: entityRefToName(collaborator.user),
permission: collaborator.access,
});
return {};
};
await defineCheckpoint({
key: `v1.task.checkpoint.add.collaborator.${collaborator.user}`,
checkpoint,
fn: addCollaborator,
await client.rest.repos.addCollaborator({
owner,
repo,
username: entityRefToName(collaborator.user),
permission: collaborator.access,
});
} else if ('team' in collaborator) {
await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
@@ -298,19 +264,11 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
}
for (const [key, value] of Object.entries(repoVariables ?? {})) {
const createRepoVariable = async () => {
await client.rest.actions.createRepoVariable({
owner,
repo,
name: key,
value: value,
});
return {};
};
await defineCheckpoint({
key: `v1.task.checkpoint.create.repo.variable.${key}`,
checkpoint,
fn: createRepoVariable,
await client.rest.actions.createRepoVariable({
owner,
repo,
name: key,
value: value,
});
}
@@ -346,7 +304,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
}
}
return { clone_url, html_url };
return newRepo;
}
export async function initRepoPushAndProtect(
@@ -350,10 +350,11 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
logger: taskLogger,
logStream: streamLogger,
workspacePath,
async checkpoint<U extends JsonObject>(
key: string,
async checkpoint<U extends JsonValue>(
keySuffix: string,
fn: () => Promise<U>,
) {
const key = `v1.task.checkpoint.${keySuffix}`;
try {
let prevValue: U | undefined;
if (prevTaskState) {
@@ -100,6 +100,7 @@ export class TaskManager implements TaskContext {
}
async updateCheckpoint?(options: CheckpointRecord): Promise<void> {
// drop the key
if (this.task.state) {
this.task.state[options.key] = { ...options };
} else {
@@ -1,30 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { JsonObject } from '@backstage/types';
export const defineCheckpoint = async <U extends JsonObject>(props: {
checkpoint?: (key: string, fn: () => Promise<U>) => Promise<U>;
key: string;
fn: () => Promise<U>;
}): Promise<U> => {
const { checkpoint, fn, key } = props;
return checkpoint
? checkpoint?.(key, async () => {
return await fn();
})
: fn();
};
-2
View File
@@ -34,5 +34,3 @@ export type {
TemplatePermissionsV1beta3,
TemplateRecoveryV1beta3,
} from './TemplateEntityV1beta3';
export { defineCheckpoint } from './defineCheckpoint';
+2 -2
View File
@@ -16,7 +16,7 @@
import { Logger } from 'winston';
import { Writable } from 'stream';
import { JsonObject } from '@backstage/types';
import { JsonObject, JsonValue } from '@backstage/types';
import { TaskSecrets } from '../tasks';
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
import { UserEntity } from '@backstage/catalog-model';
@@ -35,7 +35,7 @@ export type ActionContext<
secrets?: TaskSecrets;
workspacePath: string;
input: TActionInput;
checkpoint?<U extends JsonObject>(
checkpoint<U extends JsonValue>(
key: string,
fn: () => Promise<U>,
): Promise<U>;
+3 -3
View File
@@ -15,7 +15,7 @@
*/
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import { JsonObject, Observable } from '@backstage/types';
import { JsonObject, JsonValue, Observable } from '@backstage/types';
/**
* TaskSecrets
@@ -36,7 +36,7 @@ export type CheckpointRecord =
| {
key: string;
status: 'success';
value: JsonObject;
value: JsonValue;
}
| {
key: string;
@@ -54,7 +54,7 @@ export type TaskState = {
| { status: 'failed'; reason: string }
| {
status: 'success';
value: JsonObject;
value: JsonValue;
};
};