Made gitlab:repo:push action idempotent.

Signed-off-by: Bogdan Nechyporenko <bnechyporenko@bol.com>
This commit is contained in:
Bogdan Nechyporenko
2025-03-12 19:40:46 +01:00
parent fee3afffb5
commit 225c733258
3 changed files with 76 additions and 44 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
---
Made gitlab:repo:push action idempotent.
+39 -24
View File
@@ -154,30 +154,36 @@ export function createGithubRepoCreateAction(options: {
username: owner,
});
await ctx.checkpoint('repo.creation', async () => {
const repoCreationPromise =
user.data.type === 'Organization'
? client.rest.repos.createInOrg({
name: repo,
org: owner,
})
: client.rest.repos.createForAuthenticatedUser({
name: repo,
});
const { repoUrl } = await repoCreationPromise;
return { repoUrl };
await ctx.checkpoint({
key: 'repo.creation.v1',
fn: async () => {
const repoCreationPromise =
user.data.type === 'Organization'
? client.rest.repos.createInOrg({
name: repo,
org: owner,
})
: client.rest.repos.createForAuthenticatedUser({
name: repo,
});
const { repoUrl } = await repoCreationPromise;
return { repoUrl };
},
});
if (secrets) {
await ctx.checkpoint('repo.create.variables', async () => {
for (const [key, value] of Object.entries(repoVariables ?? {})) {
await client.rest.actions.createRepoVariable({
owner,
repo,
name: key,
value: value,
});
}
await ctx.checkpoint({
key: 'repo.create.variables',
fn: async () => {
for (const [key, value] of Object.entries(repoVariables ?? {})) {
await client.rest.actions.createRepoVariable({
owner,
repo,
name: key,
value: value,
});
}
},
});
}
@@ -202,9 +208,12 @@ Checkpoints will allow action authors to create actions where code paths are ign
This will be provided on a context object and action of author provide a key and a callback.
```typescript
await ctx.checkpoint('repo.creation', async () => {
const { repoUrl } = await client.rest.Repository.create({});
return { repoUrl };
await ctx.checkpoint({
key: 'repo.creation',
fn: async () => {
const { repoUrl } = await client.rest.Repository.create({});
return { repoUrl };
},
});
```
@@ -259,6 +268,12 @@ Task state will be stored in the extra column `state` in the table `tasks` with
}
```
Whenever you change the return type of the checkpoint, we encourage you to change the ID.
For example, you can embed the versioning or another indicator for that.
If you'll preserve the same key, and you'll try to restart the affected task, it will fail on this checkpoint.
The cached result will not match with the expected updated return type.
By changing the key, you'll invalidate the cache of the checkpoint.
#### Workspace Persistence
The workspace will be serialized and stored in the database by default. This serialization should occur at the end of a step, and after each checkpoint. It will be possible to provide additional modules to extend the workspace serialization to other providers, such as GCS or S3 instead of the database.
@@ -151,19 +151,24 @@ export const createGitlabRepoPushAction = (options: {
execute_filemode: file.executable,
}));
let branchExists = false;
try {
await api.Branches.show(repoID, branchName);
branchExists = true;
} catch (e: any) {
if (e.cause?.response?.status !== 404) {
throw new InputError(
`Failed to check status of branch '${branchName}'. Please make sure that branch already exists or Backstage has permissions to create one. ${getErrorMessage(
e,
)}`,
);
}
}
const branchExists = await ctx.checkpoint({
key: `branch.exists.${repoID}.${branchName}`,
fn: async () => {
try {
await api.Branches.show(repoID, branchName);
return true;
} catch (e: any) {
if (e.cause?.response?.status !== 404) {
throw new InputError(
`Failed to check status of branch '${branchName}'. Please make sure that branch already exists or Backstage has permissions to create one. ${getErrorMessage(
e,
)}`,
);
}
}
return false;
},
});
if (!branchExists) {
// create a branch using the default branch as ref
@@ -181,15 +186,22 @@ export const createGitlabRepoPushAction = (options: {
}
try {
const commit = await api.Commits.create(
repoID,
branchName,
ctx.input.commitMessage,
actions,
);
const commitId = await ctx.checkpoint({
key: `commit.create.${repoID}.${branchName}`,
fn: async () => {
const commit = await api.Commits.create(
repoID,
branchName,
ctx.input.commitMessage,
actions,
);
return commit.id;
},
});
ctx.output('projectid', repoID);
ctx.output('projectPath', repoID);
ctx.output('commitHash', commit.id);
ctx.output('commitHash', commitId);
} catch (e) {
throw new InputError(
`Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${getErrorMessage(