@@ -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();
|
||||
};
|
||||
@@ -34,5 +34,3 @@ export type {
|
||||
TemplatePermissionsV1beta3,
|
||||
TemplateRecoveryV1beta3,
|
||||
} from './TemplateEntityV1beta3';
|
||||
|
||||
export { defineCheckpoint } from './defineCheckpoint';
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user