diff --git a/.changeset/eager-ears-walk.md b/.changeset/eager-ears-walk.md new file mode 100644 index 0000000000..8982824c95 --- /dev/null +++ b/.changeset/eager-ears-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Made "gitlab:pipeline:trigger" action idempotent diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts index a7edc12320..b051004ccc 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts @@ -62,7 +62,8 @@ export const createTriggerGitlabPipelineAction = (options: { output: pipelineOutputProperties, }, async handler(ctx) { - let pipelineTokenResponse: PipelineTriggerTokenSchema | null = null; + let pipelineTriggerToken: string | undefined = undefined; + let pipelineTriggerId: number | undefined = undefined; const { repoUrl, projectId, tokenDescription, token, branch, variables } = commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); @@ -71,18 +72,28 @@ export const createTriggerGitlabPipelineAction = (options: { const api = getClient({ host, integrations, token }); try { - // Create a pipeline token - pipelineTokenResponse = (await api.PipelineTriggerTokens.create( - projectId, - tokenDescription, - )) as PipelineTriggerTokenSchema; + ({ pipelineTriggerToken, pipelineTriggerId } = await ctx.checkpoint({ + key: `create.pipeline.token.${projectId}`, + fn: async () => { + const res = (await api.PipelineTriggerTokens.create( + projectId, + tokenDescription, + )) as PipelineTriggerTokenSchema; + return { + pipelineTriggerToken: res.token, + pipelineTriggerId: res.id, + }; + }, + })); - if (!pipelineTokenResponse.token) { - ctx.logger.error('Failed to create pipeline token.'); + if (!pipelineTriggerToken) { + ctx.logger.error( + `Failed to create pipeline token for project ${projectId}.`, + ); return; } ctx.logger.info( - `Pipeline token id ${pipelineTokenResponse.id} created.`, + `Pipeline token id ${pipelineTriggerId} created for project ${projectId}.`, ); // Use the pipeline token to trigger the pipeline in the project @@ -90,16 +101,20 @@ export const createTriggerGitlabPipelineAction = (options: { (await api.PipelineTriggerTokens.trigger( projectId, branch, - pipelineTokenResponse.token, + pipelineTriggerToken, { variables }, )) as ExpandedPipelineSchema; if (!pipelineTriggerResponse.id) { - ctx.logger.error('Failed to trigger pipeline.'); + ctx.logger.error( + `Failed to trigger pipeline for project ${projectId}.`, + ); return; } - ctx.logger.info(`Pipeline id ${pipelineTriggerResponse.id} triggered.`); + ctx.logger.info( + `Pipeline id ${pipelineTriggerResponse.id} for project ${projectId} triggered.`, + ); ctx.output('pipelineUrl', pipelineTriggerResponse.web_url); } catch (error: any) { @@ -115,18 +130,26 @@ export const createTriggerGitlabPipelineAction = (options: { ); } finally { // Delete the pipeline token if it was created - if (pipelineTokenResponse && pipelineTokenResponse.id) { + if (pipelineTriggerId) { try { - await api.PipelineTriggerTokens.remove( - projectId, - pipelineTokenResponse.id, - ); + await ctx.checkpoint({ + key: `create.delete.token.${projectId}`, + fn: async () => { + if (pipelineTriggerId) { + // to make the current version of TypeScript happy + await api.PipelineTriggerTokens.remove( + projectId, + pipelineTriggerId, + ); + } + }, + }); ctx.logger.info( - `Deleted pipeline token ${pipelineTokenResponse.id}.`, + `Deleted pipeline with token id ${pipelineTriggerId}.`, ); } catch (error: any) { ctx.logger.error( - `Failed to delete pipeline token id ${pipelineTokenResponse.id}.`, + `Failed to delete pipeline with token id ${pipelineTriggerId}.`, ); } }