diff --git a/.changeset/ninety-hats-serve.md b/.changeset/ninety-hats-serve.md new file mode 100644 index 0000000000..762e53c242 --- /dev/null +++ b/.changeset/ninety-hats-serve.md @@ -0,0 +1,30 @@ +--- +'@backstage/plugin-catalog-backend-module-azure': patch +--- + +This will add the ability to use Azure DevOps with multi project with a single value which is a new feature as previously this had to be done manually for each project that you wanted to add. + +Right now you would have to fill in multiple values in the config to use multiple projects: + +``` +yourFirstProviderId: # identifies your dataset / provider independent of config changes + organization: myorg + project: 'firstProject' # this will match the firstProject project + repository: '*' # this will match all repos + path: /catalog-info.yaml +yourSecondProviderId: # identifies your dataset / provider independent of config changes + organization: myorg + project: 'secondProject' # this will match the secondProject project + repository: '*' # this will match all repos + path: /catalog-info.yaml +``` + +With this change you can actually have all projects available where your PAT determines which you have access to, so that includes multiple projects: + +``` +yourFirstProviderId: # identifies your dataset / provider independent of config changes + organization: myorg + project: '*' # this will match all projects where your PAT has access to + repository: '*' # this will match all repos + path: /catalog-info.yaml +``` diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 2156f60814..7d53e21a89 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -47,6 +47,11 @@ catalog: frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code timeout: { minutes: 3 } + yourSecondProviderId: # identifies your dataset / provider independent of config changes + organization: myorg + project: '*' # this will match all projects + repository: '*' # this will match all repos + path: /catalog-info.yaml anotherProviderId: # another identifier organization: myorg project: myproject @@ -62,7 +67,7 @@ The parameters available are: - **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host. - **`organization:`** Your Organization slug (or Collection for on-premise users). Required. -- **`project:`** Your project slug. Required. +- **`project:`** _(optional)_ Your project slug. Wildcards are supported as show on the examples above. If not set, all projects will be searched. - **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched. - **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml. - **`schedule`** _(optional)_: @@ -144,7 +149,7 @@ When using a custom pattern, the target is composed of five parts: - The base instance URL, `https://dev.azure.com` in this case - The organization name which is required, `myorg` in this case -- The project name which is required, `myproject` in this case +- The project name which is optional, `myproject` in this case. This defaults to \*, which scans all the projects where the token has access to. - The repository blob to scan, which accepts \* wildcard tokens and must be added after `_git/`. This can simply be `*` to scan all repositories in the project. diff --git a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts index 16f5067197..f26208353a 100644 --- a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts +++ b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts @@ -29,11 +29,11 @@ describe('azure', () => { server.use( rest.post( - `https://almsearch.dev.azure.com/shopify/engineering/_apis/search/codesearchresults`, + `https://almsearch.dev.azure.com/shopify/_apis/search/codesearchresults`, (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Basic OkFCQw=='); expect(req.body).toEqual({ - searchText: 'path:/catalog-info.yaml repo:*', + searchText: 'path:/catalog-info.yaml repo:* proj:engineering', $skip: 0, $top: 1000, }); @@ -64,6 +64,9 @@ describe('azure', () => { repository: { name: 'backstage', }, + project: { + name: 'backstage', + }, }, { fileName: 'catalog-info.yaml', @@ -71,17 +74,20 @@ describe('azure', () => { repository: { name: 'ios-app', }, + project: { + name: 'backstage', + }, }, ], }; server.use( rest.post( - `https://almsearch.dev.azure.com/shopify/engineering/_apis/search/codesearchresults`, + `https://almsearch.dev.azure.com/shopify/_apis/search/codesearchresults`, (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Basic OkFCQw=='); expect(req.body).toEqual({ - searchText: 'path:/catalog-info.yaml repo:*', + searchText: 'path:/catalog-info.yaml repo:* proj:engineering', $skip: 0, $top: 1000, }); @@ -108,6 +114,9 @@ describe('azure', () => { { fileName: 'catalog-info.yaml', path: '/catalog-info.yaml', + project: { + name: '*', + }, repository: { name: 'backstage', }, @@ -117,11 +126,12 @@ describe('azure', () => { server.use( rest.post( - `https://almsearch.dev.azure.com/shopify/engineering/_apis/search/codesearchresults`, + `https://almsearch.dev.azure.com/shopify/_apis/search/codesearchresults`, (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Basic OkFCQw=='); expect(req.body).toEqual({ - searchText: 'path:/catalog-info.yaml repo:backstage', + searchText: + 'path:/catalog-info.yaml repo:backstage proj:engineering', $skip: 0, $top: 1000, }); @@ -151,17 +161,20 @@ describe('azure', () => { repository: { name: 'backstage', }, + project: { + name: '*', + }, }, ], }; server.use( rest.post( - `https://azuredevops.mycompany.com/shopify/engineering/_apis/search/codesearchresults`, + `https://azuredevops.mycompany.com/shopify/_apis/search/codesearchresults`, (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Basic OkFCQw=='); expect(req.body).toEqual({ - searchText: 'path:/catalog-info.yaml repo:*', + searchText: 'path:/catalog-info.yaml repo:* proj:engineering', $skip: 0, $top: 1000, }); @@ -190,16 +203,20 @@ describe('azure', () => { repository: { name: 'backstage', }, + project: { + name: 'engineering', + }, })); }; server.use( rest.post( - `https://almsearch.dev.azure.com/shopify/engineering/_apis/search/codesearchresults`, + `https://almsearch.dev.azure.com/shopify/_apis/search/codesearchresults`, (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Basic OkFCQw=='); expect(req.body).toMatchObject({ - searchText: 'path:/catalog-info.yaml repo:backstage', + searchText: + 'path:/catalog-info.yaml repo:backstage proj:engineering', $top: 1000, }); diff --git a/plugins/catalog-backend-module-azure/src/lib/azure.ts b/plugins/catalog-backend-module-azure/src/lib/azure.ts index 316c9b0b8c..0c96d133d3 100644 --- a/plugins/catalog-backend-module-azure/src/lib/azure.ts +++ b/plugins/catalog-backend-module-azure/src/lib/azure.ts @@ -31,6 +31,9 @@ export interface CodeSearchResultItem { repository: { name: string; }; + project: { + name: string; + }; } const isCloud = (host: string) => host === 'dev.azure.com'; @@ -47,7 +50,7 @@ export async function codeSearch( const searchBaseUrl = isCloud(azureConfig.host) ? 'https://almsearch.dev.azure.com' : `https://${azureConfig.host}`; - const searchUrl = `${searchBaseUrl}/${org}/${project}/_apis/search/codesearchresults?api-version=6.0-preview.1`; + const searchUrl = `${searchBaseUrl}/${org}/_apis/search/codesearchresults?api-version=6.0-preview.1`; let items: CodeSearchResultItem[] = []; let hasMorePages = true; @@ -59,7 +62,7 @@ export async function codeSearch( }), method: 'POST', body: JSON.stringify({ - searchText: `path:${path} repo:${repo || '*'}`, + searchText: `path:${path} repo:${repo || '*'} proj:${project || '*'}`, $skip: items.length, $top: PAGE_SIZE, }), diff --git a/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts index 9000b177fa..38df737179 100644 --- a/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts @@ -135,6 +135,9 @@ describe('AzureDevOpsDiscoveryProcessor', () => { { fileName: 'catalog-info.yaml', path: '/catalog-info.yaml', + project: { + name: '*', + }, repository: { name: 'backstage', }, @@ -142,6 +145,9 @@ describe('AzureDevOpsDiscoveryProcessor', () => { { fileName: 'catalog-info.yaml', path: '/src/catalog-info.yaml', + project: { + name: '*', + }, repository: { name: 'ios-app', }, @@ -191,6 +197,9 @@ describe('AzureDevOpsDiscoveryProcessor', () => { repository: { name: 'backstage', }, + project: { + name: '*', + }, }, ]); const emitter = jest.fn(); @@ -229,6 +238,9 @@ describe('AzureDevOpsDiscoveryProcessor', () => { repository: { name: 'backstage', }, + project: { + name: '*', + }, }, ]); const emitter = jest.fn(); diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts index f7a63f259e..dff1164899 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts @@ -164,6 +164,9 @@ describe('AzureDevOpsEntityProvider', () => { repository: { name: 'myrepo', }, + project: { + name: 'myproject', + }, }, ], 'https://dev.azure.com/myorganization/myproject', @@ -189,6 +192,9 @@ describe('AzureDevOpsEntityProvider', () => { repository: { name: 'myrepo', }, + project: { + name: 'myproject', + }, }, { fileName: 'catalog-info.yaml', @@ -196,6 +202,9 @@ describe('AzureDevOpsEntityProvider', () => { repository: { name: 'myotherrepo', }, + project: { + name: 'myproject', + }, }, ], 'https://dev.azure.com/myorganization/myproject', @@ -277,6 +286,9 @@ describe('AzureDevOpsEntityProvider', () => { repository: { name: 'myrepo', }, + project: { + name: 'myproject', + }, }, { fileName: 'catalog-info.yaml', @@ -284,6 +296,9 @@ describe('AzureDevOpsEntityProvider', () => { repository: { name: 'myotherrepo', }, + project: { + name: 'myproject', + }, }, ], 'https://dev.azure.com/myorganization/myproject', diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts index 98b951bd46..788b9c2f05 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts @@ -166,15 +166,17 @@ export class AzureDevOpsEntityProvider implements EntityProvider { } private createLocationSpec(file: CodeSearchResultItem): LocationSpec { + const target = this.createObjectUrl(file); + return { type: 'url', - target: this.createObjectUrl(file), + target: target, presence: 'required', }; } private createObjectUrl(file: CodeSearchResultItem): string { - const baseUrl = `https://${this.config.host}/${this.config.organization}/${this.config.project}`; + const baseUrl = `https://${this.config.host}/${this.config.organization}/${file.project.name}`; return encodeURI( `${baseUrl}/_git/${file.repository.name}?path=${file.path}`, );