diff --git a/.changeset/cool-elephants-march.md b/.changeset/cool-elephants-march.md new file mode 100644 index 0000000000..2396f9553d --- /dev/null +++ b/.changeset/cool-elephants-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitea': patch +--- + +Allow defining `repoVisibility` field for the action `publish:gitea` diff --git a/plugins/scaffolder-backend-module-gitea/README.md b/plugins/scaffolder-backend-module-gitea/README.md index 7eff6130db..5c6aa59b66 100644 --- a/plugins/scaffolder-backend-module-gitea/README.md +++ b/plugins/scaffolder-backend-module-gitea/README.md @@ -10,6 +10,13 @@ To use this action, you will have to add the package using the following command yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-gitea ``` +Alternatively, if you use the new backend system, then register it like this: + +```typescript +// packages/backend/src/index.ts +backend.add(import('@backstage/plugin-scaffolder-backend-module-gitea')); +``` + Configure the action (if not yet done): (you can check the [docs](https://backstage.io/docs/features/software-templates/writing-custom-actions#registering-custom-actions) to see all options): @@ -27,29 +34,29 @@ integrations: password: '' ``` -**Important**: As backstage will issue HTTPS/TLS requests to the gitea instance, it is needed to configure `gitea` with a valid certificate or at least with a -self-signed certificate `gitea cert --host localhost -ca` trusted by a CA authority. Don't forget to set the env var `NODE_EXTRA_CA_CERTS` to point to the CA file before launching backstage ! +**Important**: As backstage will issue `HTTPS/TLS` requests to the gitea instance, it is needed to configure `gitea` with a valid certificate or at least with a +self-signed certificate `gitea cert --host localhost -ca` trusted by a CA authority. Don't forget to set the env var `NODE_EXTRA_CA_CERTS` to point to the CA file before launching backstage or you can set temporarily `NODE_TLS_REJECT_UNAUTHORIZED=0` but this is not recommended for production! When done, you can create a template which: -- Declare the `RepoUrlPicker` within the `spec/parameters` section to select the gitea hosts -- Include a step able to publish by example the newly generated project using the action `action: publish:gitea` +- Declare the `RepoUrlPicker` within the `spec/parameters` section to select the gitea host and to provide the name of the repository +- Add an `enum` list allowing the user to define the visibility about the repository to be created: `public` or `private`. If this field is omitted, `public` is then used by the action. +- Include in a step the action: `publish:gitea` + +**Warning**: The list of the `allowedOwners` of the `repoUrlPicker` must match the list of the `organizations` which are available on the gitea host ! ```yaml -apiVersion: scaffolder.backstage.io/v1beta3 kind: Template metadata: - name: quarkus-web-template - title: Quarkus Hello world - description: Create a simple microservice using Quarkus - tags: - - java - - quarkus + name: simple-gitea-project + title: Create a gitea repository + description: Create a gitea repository spec: - owner: quarkus + owner: guests type: service + parameters: - - title: Git repository Information + - title: Choose a location required: - repoUrl properties: @@ -58,42 +65,33 @@ spec: type: string ui:field: RepoUrlPicker ui:options: + allowedOwners: + - qteam + - qshift allowedHosts: - - gitea.: - - localhost: + - gitea.localtest.me:3333 + + repoVisibility: + title: Visibility of the repository + type: string + default: 'public' + enum: + - 'public' + - 'private' + enumNames: + - 'public' + - 'private' steps: - - id: template - name: Generating component - action: fetch:template - input: - url: ./skeleton - values: - name: ${{ parameters.name }} - + ... - id: publish name: Publishing to a gitea git repository action: publish:gitea input: - description: This is ${{ parameters.component_id }} + description: This is ${{ parameters.repoUrl | parseRepoUrl | pick('repo') }} + repoVisibility: ${{ parameters.repoVisibility }} repoUrl: ${{ parameters.repoUrl }} defaultBranch: main - - - id: register - if: ${{ parameters.dryRun !== true }} - name: Register - action: catalog:register - input: - repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} - catalogInfoPath: 'main/catalog-info.yaml' - - output: - links: - - title: Source Code Repository - url: ${{ steps.publish.output.remoteUrl }} - - title: Open Component in catalog - icon: catalog - entityRef: ${{ steps.register.output.entityRef }} ``` Access the newly gitea repository created using the `repoContentsUrl` ;-) diff --git a/plugins/scaffolder-backend-module-gitea/api-report.md b/plugins/scaffolder-backend-module-gitea/api-report.md index 373cc6258c..b5e20b7b13 100644 --- a/plugins/scaffolder-backend-module-gitea/api-report.md +++ b/plugins/scaffolder-backend-module-gitea/api-report.md @@ -18,6 +18,7 @@ export function createPublishGiteaAction(options: { repoUrl: string; description: string; defaultBranch?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; gitCommitMessage?: string | undefined; gitAuthorName?: string | undefined; gitAuthorEmail?: string | undefined; diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts index ccf0f4ddf6..dd4d038ead 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts @@ -50,6 +50,23 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Initializes a private Gitea repository ', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gitea', + name: 'Publish to Gitea', + input: { + repoUrl: 'gitea.com?repo=repo&owner=owner', + defaultBranch: 'main', + repoVisibility: 'private', + }, + }, + ], + }), + }, { description: 'Initializes a Gitea repository with a default Branch, if not set defaults to main', @@ -150,6 +167,7 @@ export const examples: TemplateExample[] = [ gitAuthorName: 'John Doe', gitAuthorEmail: 'johndoe@email.com', sourcePath: 'repository/', + repoVisibility: 'public', }, }, ], diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts index 8675b1ff97..8a72265df0 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts @@ -53,6 +53,13 @@ describe('publish:gitea', () => { description, }, }); + const mockContextWithPublicRepoVisibility = createMockActionContext({ + input: { + repoUrl: 'gitea.com?repo=repo&owner=owner', + description, + private: false, + }, + }); const server = setupServer(); setupRequestMockHandlers(server); @@ -111,6 +118,7 @@ describe('publish:gitea', () => { ); expect(req.body).toEqual({ name: 'repo', + private: false, description, }); return res( @@ -148,6 +156,71 @@ describe('publish:gitea', () => { ); }); + it('should create a Gitea repository where visibility is public', async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic Z2l0ZWFfdXNlcjpnaXRlYV9wYXNzd29yZA==', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + await action.handler({ + ...mockContextWithPublicRepoVisibility, + input: { + ...mockContextWithPublicRepoVisibility.input, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContextWithPublicRepoVisibility.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'gitea_user', password: 'gitea_password' }, + logger: mockContextWithPublicRepoVisibility.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: undefined, + name: undefined, + }, + }); + }); + afterEach(() => { jest.resetAllMocks(); }); diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index 089f9b2669..6b29ff2a4c 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -81,7 +81,7 @@ const checkGiteaOrg = async ( ); } catch (e) { throw new Error( - `Unable to get the Organization: ${owner}; Error cause: ${e.cause.message}`, + `Unable to get the Organization: ${owner}; Error cause: ${e.message}, code: ${e.cause.code}`, ); } if (response.status !== 200) { @@ -96,10 +96,11 @@ const createGiteaProject = async ( options: { projectName: string; owner?: string; + repoVisibility?: string; description: string; }, ): Promise => { - const { projectName, description, owner } = options; + const { projectName, description, owner, repoVisibility } = options; /* Several options exist to create a repository using either the user or organisation @@ -112,12 +113,23 @@ const createGiteaProject = async ( This is the default scenario that we support currently */ let response: Response; + let isPrivate: boolean; + + if (repoVisibility === 'private') { + isPrivate = true; + } else if (repoVisibility === 'public') { + isPrivate = false; + } else { + // Provide a default value if repoVisibility is neither "private" nor "public" + isPrivate = false; + } const postOptions: RequestInit = { method: 'POST', body: JSON.stringify({ name: projectName, description, + private: isPrivate, }), headers: { ...getGiteaRequestOptions(config).headers, @@ -202,6 +214,7 @@ export function createPublishGiteaAction(options: { repoUrl: string; description: string; defaultBranch?: string; + repoVisibility?: 'private' | 'public'; gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; @@ -229,6 +242,12 @@ export function createPublishGiteaAction(options: { type: 'string', description: `Sets the default branch on the repository. The default value is 'main'`, }, + repoVisibility: { + title: 'Repository Visibility', + description: `Sets the visibility of the repository. The default value is 'public'.`, + type: 'string', + enum: ['private', 'public'], + }, gitCommitMessage: { title: 'Git Commit Message', type: 'string', @@ -274,6 +293,7 @@ export function createPublishGiteaAction(options: { repoUrl, description, defaultBranch = 'main', + repoVisibility = 'public', gitAuthorName, gitAuthorEmail, gitCommitMessage = 'initial commit', @@ -301,6 +321,7 @@ export function createPublishGiteaAction(options: { await createGiteaProject(integrationConfig.config, { description, + repoVisibility, owner: owner, projectName: repo, });