diff --git a/.changeset/all-socks-taste.md b/.changeset/all-socks-taste.md new file mode 100644 index 0000000000..7f3fd8541c --- /dev/null +++ b/.changeset/all-socks-taste.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': minor +--- + +In the gitlabRepoPush action, add 'auto' possibility for commitAction input. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 66c72b4c46..b345b33ee1 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -31,57 +31,11 @@ import { import path from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; -import { - LoggerService, - resolveSafeChildPath, -} from '@backstage/backend-plugin-api'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabMergeRequest.examples'; -import { createHash } from 'crypto'; -function computeSha256(file: SerializedFile): string { - const hash = createHash('sha256'); - hash.update(file.content); - return hash.digest('hex'); -} - -async function getFileAction( - fileInfo: { file: SerializedFile; targetPath?: string }, - target: { repoID: string; branch: string }, - api: InstanceType, - logger: LoggerService, - remoteFiles: RepositoryTreeSchema[], - defaultCommitAction: - | 'create' - | 'delete' - | 'update' - | 'skip' - | 'auto' = 'auto', -): Promise<'create' | 'delete' | 'update' | 'skip'> { - if (defaultCommitAction === 'auto') { - const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); - - if (remoteFiles?.some(remoteFile => remoteFile.path === filePath)) { - try { - const targetFile = await api.RepositoryFiles.show( - target.repoID, - filePath, - target.branch, - ); - if (computeSha256(fileInfo.file) === targetFile.content_sha256) { - return 'skip'; - } - } catch (error) { - logger.warn( - `Unable to retrieve detailed information for remote file ${filePath}`, - ); - } - return 'update'; - } - return 'create'; - } - return defaultCommitAction; -} +import { getFileAction } from '../util'; async function getReviewersFromApprovalRules( api: InstanceType, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index a3ae415704..b1a88de869 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -26,6 +26,9 @@ import { import { CommitAction } from '@gitbeaker/rest'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabRepoPush.examples'; +import { getFileAction } from '../util'; +import { SerializedFile } from '@backstage/plugin-scaffolder-node'; +import { RepositoryTreeSchema } from '@gitbeaker/rest'; /** * Create a new action that commits into a gitlab repository. @@ -75,7 +78,7 @@ export const createGitlabRepoPushAction = (options: { .optional(), commitAction: z => z - .enum(['create', 'update', 'delete'], { + .enum(['create', 'update', 'delete', 'auto'], { description: 'The action to be used for git commit. Defaults to create, but can be set to update or delete', }) @@ -126,8 +129,44 @@ export const createGitlabRepoPushAction = (options: { gitignore: true, }); - const actions: CommitAction[] = fileContents.map(file => ({ - action: commitAction ?? 'create', + let remoteFiles: RepositoryTreeSchema[] = []; + if ((ctx.input.commitAction ?? 'auto') === 'auto') { + try { + remoteFiles = await api.Repositories.allRepositoryTrees(repoID, { + ref: branchName, + recursive: true, + path: targetPath ?? undefined, + }); + } catch (e) { + ctx.logger.warn( + `Could not retrieve the list of files for ${repoID} (branch: ${branchName}) : ${getErrorMessage( + e, + )}`, + ); + } + } + + const actions: CommitAction[] = ( + ( + await Promise.all( + fileContents.map(async file => { + const action = await getFileAction( + { file, targetPath }, + { repoID, branch: branchName }, + api, + ctx.logger, + remoteFiles, + ctx.input.commitAction, + ); + return { file, action }; + }), + ) + ).filter(o => o.action !== 'skip') as { + file: SerializedFile; + action: CommitAction['action']; + }[] + ).map(({ file, action }) => ({ + action, filePath: targetPath ? path.posix.join(targetPath, file.path) : file.path, diff --git a/plugins/scaffolder-backend-module-gitlab/src/util.ts b/plugins/scaffolder-backend-module-gitlab/src/util.ts index db1468b180..4c40a36acc 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/util.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/util.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { GitLabIntegration, ScmIntegrationRegistry, } from '@backstage/integration'; -import { Gitlab, GroupSchema } from '@gitbeaker/rest'; +import { Gitlab, GroupSchema, RepositoryTreeSchema } from '@gitbeaker/rest'; import { z } from 'zod'; import commonGitlabConfig from './commonGitlabConfig'; +import { SerializedFile } from '@backstage/plugin-scaffolder-node'; + +import { createHash } from 'crypto'; +import path from 'path'; + export const parseRepoHost = (repoUrl: string): string => { let parsed; try { @@ -184,3 +190,47 @@ export async function checkEpicScope( throw new InputError(`Could not find epic scope: ${error.message}`); } } + +function computeSha256(file: SerializedFile): string { + const hash = createHash('sha256'); + hash.update(file.content); + return hash.digest('hex'); +} + +export async function getFileAction( + fileInfo: { file: SerializedFile; targetPath?: string }, + target: { repoID: string; branch: string }, + api: InstanceType, + logger: LoggerService, + remoteFiles: RepositoryTreeSchema[], + defaultCommitAction: + | 'create' + | 'delete' + | 'update' + | 'skip' + | 'auto' = 'auto', +): Promise<'create' | 'delete' | 'update' | 'skip'> { + if (defaultCommitAction === 'auto') { + const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); + + if (remoteFiles?.some(remoteFile => remoteFile.path === filePath)) { + try { + const targetFile = await api.RepositoryFiles.show( + target.repoID, + filePath, + target.branch, + ); + if (computeSha256(fileInfo.file) === targetFile.content_sha256) { + return 'skip'; + } + } catch (error) { + logger.warn( + `Unable to retrieve detailed information for remote file ${filePath}`, + ); + } + return 'update'; + } + return 'create'; + } + return defaultCommitAction; +}