From cb0afaad2b30e4dbbe98a3317a9fe051f24b8908 Mon Sep 17 00:00:00 2001 From: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:31:41 +0000 Subject: [PATCH 1/6] Improved Azure DevOps support for mono-repos Signed-off-by: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> --- .changeset/wicked-elephants-scream.md | 5 ++ .../src/hooks/useBuildRuns.test.tsx | 2 +- .../src/hooks/useGitTags.test.tsx | 2 +- .../src/hooks/usePullRequests.test.tsx | 2 +- .../azure-devops/src/hooks/useReadme.test.tsx | 2 +- .../getAnnotationValuesFromEntity.test.ts | 55 +++++++++++++++++- .../utils/getAnnotationValuesFromEntity.ts | 57 ++++++++++++------- 7 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 .changeset/wicked-elephants-scream.md diff --git a/.changeset/wicked-elephants-scream.md b/.changeset/wicked-elephants-scream.md new file mode 100644 index 0000000000..ab3918afba --- /dev/null +++ b/.changeset/wicked-elephants-scream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops': minor +--- + +Prefer dev.azure.com/build-definition annotation when it is provided, as it is more specific than dev.azure.com/project-repo diff --git a/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx b/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx index 409602eec4..f9415aea55 100644 --- a/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx +++ b/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx @@ -99,7 +99,7 @@ describe('useBuildRuns', () => { renderHook(() => useBuildRuns(entity), { wrapper: Wrapper, }), - ).toThrow('Value for annotation "dev.azure.com/project" was not found'); + ).toThrow('"dev.azure.com" annotations are missing'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/useGitTags.test.tsx b/plugins/azure-devops/src/hooks/useGitTags.test.tsx index 1a5620e16e..fc385697ea 100644 --- a/plugins/azure-devops/src/hooks/useGitTags.test.tsx +++ b/plugins/azure-devops/src/hooks/useGitTags.test.tsx @@ -103,7 +103,7 @@ describe('useGitTags', () => { renderHook(() => useGitTags(entity), { wrapper: Wrapper, }), - ).toThrow('Value for annotation "dev.azure.com/project" was not found'); + ).toThrow('"dev.azure.com" annotations are missing'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/usePullRequests.test.tsx b/plugins/azure-devops/src/hooks/usePullRequests.test.tsx index 3e51c003e6..8fe6c6b312 100644 --- a/plugins/azure-devops/src/hooks/usePullRequests.test.tsx +++ b/plugins/azure-devops/src/hooks/usePullRequests.test.tsx @@ -102,7 +102,7 @@ describe('usePullRequests', () => { renderHook(() => usePullRequests(entity), { wrapper: Wrapper, }), - ).toThrow('Value for annotation "dev.azure.com/project" was not found'); + ).toThrow('"dev.azure.com" annotations are missing'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/useReadme.test.tsx b/plugins/azure-devops/src/hooks/useReadme.test.tsx index ffc33c136b..76cf2cf0cf 100644 --- a/plugins/azure-devops/src/hooks/useReadme.test.tsx +++ b/plugins/azure-devops/src/hooks/useReadme.test.tsx @@ -81,7 +81,7 @@ describe('useReadme', () => { renderHook(() => useReadme(entity), { wrapper: Wrapper, }), - ).toThrow('Value for annotation "dev.azure.com/project" was not found'); + ).toThrow('"dev.azure.com" annotations are missing'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts index c13dc526d8..58481ec388 100644 --- a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts +++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts @@ -140,7 +140,7 @@ describe('getAnnotationValuesFromEntity', () => { }); describe('with only project annotation', () => { - it('should should throw annotation not found error', () => { + it('should throw annotation not found error', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -163,7 +163,7 @@ describe('getAnnotationValuesFromEntity', () => { }); describe('with only build-definition annotation', () => { - it('should should throw annotation not found error', () => { + it('should throw annotation not found error', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -381,4 +381,55 @@ describe('getAnnotationValuesFromEntity', () => { ); }); }); + + describe('projectRepo and buildDefinition are provided', () => { + it('should return project, repo and buildDefinition', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/build-definition': 'buildDefinitionName', + 'dev.azure.com/project-repo': 'projectName/repoName', + }, + }, + }; + const values = getAnnotationValuesFromEntity(entity); + expect(values).toEqual({ + project: 'projectName', + repo: 'repoName', + definition: 'buildDefinitionName', + host: undefined, + org: undefined, + }); + }); + }); + + describe('project, projectRepo and buildDefinition are provided', () => { + it('should prefer project over project-repo.project and return no repo', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/project': 'projectName', + 'dev.azure.com/build-definition': 'buildDefinitionName', + 'dev.azure.com/project-repo': 'ignoredProject/repoName', + }, + }, + }; + const values = getAnnotationValuesFromEntity(entity); + expect(values).toEqual({ + project: 'projectName', + repo: undefined, + definition: 'buildDefinitionName', + host: undefined, + org: undefined, + }); + }); + }); }); diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts index 83091fb155..66a9753da4 100644 --- a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts +++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts @@ -29,34 +29,49 @@ export function getAnnotationValuesFromEntity(entity: Entity): { host?: string; org?: string; } { - const { host, org } = getHostOrg(entity.metadata.annotations); - - const projectRepoValues = getProjectRepo(entity.metadata.annotations); - if (projectRepoValues.project && projectRepoValues.repo) { - return { - project: projectRepoValues.project, - repo: projectRepoValues.repo, - host, - org, - }; - } - + const hostOrg = getHostOrg(entity.metadata.annotations); + const projectRepo = getProjectRepo(entity.metadata.annotations); const project = entity.metadata.annotations?.[AZURE_DEVOPS_PROJECT_ANNOTATION]; - if (!project) { + const definition = + entity.metadata.annotations?.[AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION]; + + if (definition) { + if (project) { + return { + project, + definition, + ...hostOrg, + }; + } + if (projectRepo.project) { + return { + project: projectRepo.project, + repo: projectRepo.repo, + definition, + ...hostOrg, + }; + } throw new Error( `Value for annotation "${AZURE_DEVOPS_PROJECT_ANNOTATION}" was not found`, ); + } else { + if (projectRepo.project) { + return { + project: projectRepo.project, + repo: projectRepo.repo, + ...hostOrg, + }; + } + + if (project) { + throw new Error( + `Value for annotation "${AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION}" was not found`, + ); + } } - const definition = - entity.metadata.annotations?.[AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION]; - if (!definition) { - throw new Error( - `Value for annotation "${AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION}" was not found`, - ); - } - return { project, definition, host, org }; + throw new Error('"dev.azure.com" annotations are missing'); } function getProjectRepo(annotations?: Record): { From 8c9f3a1e19a8f2837875e0947e4e1eee4b1ec436 Mon Sep 17 00:00:00 2001 From: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:11:32 +0000 Subject: [PATCH 2/6] Add additional tests around isAzurePipelinesAvailable Signed-off-by: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> --- plugins/azure-devops/src/plugin.test.ts | 82 ++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/plugins/azure-devops/src/plugin.test.ts b/plugins/azure-devops/src/plugin.test.ts index f23639798f..398b19700c 100644 --- a/plugins/azure-devops/src/plugin.test.ts +++ b/plugins/azure-devops/src/plugin.test.ts @@ -13,10 +13,90 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { azureDevOpsPlugin } from './plugin'; +import { Entity } from '@backstage/catalog-model'; +import { azureDevOpsPlugin, isAzurePipelinesAvailable } from './plugin'; describe('azure-devops', () => { it('should export plugin', () => { expect(azureDevOpsPlugin).toBeDefined(); }); + + describe('isAzurePipelinesAvailable', () => { + it('should be true when project-repo annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/project-repo': 'projectName/repoName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(true); + }); + + it('should be true when project and build-definition annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/project': 'projectName', + 'dev.azure.com/build-definition': 'buildDefinitionName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(true); + }); + + it('should be true when project-repo and build-definition annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/project-repo': 'projectName/repoName', + 'dev.azure.com/build-definition': 'buildDefinitionName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(true); + }); + + it('should be false when only project annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/project': 'projectName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(false); + }); + + it('should be false when only build-definition annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/build-definition': 'buildDefinitionName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(false); + }); + }); }); From c4f43d5cc8996b0f8f884ace0682d1fa3158ff86 Mon Sep 17 00:00:00 2001 From: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:06:53 +0000 Subject: [PATCH 3/6] PR Feedback Signed-off-by: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> --- .changeset/wicked-elephants-scream.md | 2 +- plugins/azure-devops/README.md | 19 +++++++++++++++++++ .../src/hooks/useBuildRuns.test.tsx | 2 +- .../src/hooks/useGitTags.test.tsx | 2 +- .../src/hooks/usePullRequests.test.tsx | 2 +- .../azure-devops/src/hooks/useReadme.test.tsx | 2 +- plugins/azure-devops/src/plugin.test.ts | 12 ++++++++++++ .../utils/getAnnotationValuesFromEntity.ts | 2 +- 8 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.changeset/wicked-elephants-scream.md b/.changeset/wicked-elephants-scream.md index ab3918afba..820c8b5d97 100644 --- a/.changeset/wicked-elephants-scream.md +++ b/.changeset/wicked-elephants-scream.md @@ -2,4 +2,4 @@ '@backstage/plugin-azure-devops': minor --- -Prefer dev.azure.com/build-definition annotation when it is provided, as it is more specific than dev.azure.com/project-repo +Prefer `dev.azure.com/build-definition` annotation when it is provided, as it is more specific than `dev.azure.com/project-repo`. This can also be used as a filter for mono-repos. diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index d7335e54c8..f867fcfe75 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -61,6 +61,25 @@ spec: # ... ``` +#### Mono repos + +If you have multiple entities within a single repo, you will need to specify which pipelines belong to each entity. + +```yaml +dev.azure.com/project-repo: / +dev.azure.com/build-definition: +``` + +#### Pipeline in different project to repo + +If your pipeline is in a different project to the source code, you will need to specify this in the project annotation. + +```yaml +dev.azure.com/project-repo: / +dev.azure.com/build-definition: +dev.azure.com/project: +``` + #### Azure Pipelines Only If you are only using Azure Pipelines along with a different SCM tool then you can use the following two annotations to see Builds: diff --git a/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx b/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx index f9415aea55..e165236fe8 100644 --- a/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx +++ b/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx @@ -99,7 +99,7 @@ describe('useBuildRuns', () => { renderHook(() => useBuildRuns(entity), { wrapper: Wrapper, }), - ).toThrow('"dev.azure.com" annotations are missing'); + ).toThrow('Expected "dev.azure.com" annotations were not found'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/useGitTags.test.tsx b/plugins/azure-devops/src/hooks/useGitTags.test.tsx index fc385697ea..1c07b8868e 100644 --- a/plugins/azure-devops/src/hooks/useGitTags.test.tsx +++ b/plugins/azure-devops/src/hooks/useGitTags.test.tsx @@ -103,7 +103,7 @@ describe('useGitTags', () => { renderHook(() => useGitTags(entity), { wrapper: Wrapper, }), - ).toThrow('"dev.azure.com" annotations are missing'); + ).toThrow('Expected "dev.azure.com" annotations were not found'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/usePullRequests.test.tsx b/plugins/azure-devops/src/hooks/usePullRequests.test.tsx index 8fe6c6b312..65579a2adf 100644 --- a/plugins/azure-devops/src/hooks/usePullRequests.test.tsx +++ b/plugins/azure-devops/src/hooks/usePullRequests.test.tsx @@ -102,7 +102,7 @@ describe('usePullRequests', () => { renderHook(() => usePullRequests(entity), { wrapper: Wrapper, }), - ).toThrow('"dev.azure.com" annotations are missing'); + ).toThrow('Expected "dev.azure.com" annotations were not found'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/useReadme.test.tsx b/plugins/azure-devops/src/hooks/useReadme.test.tsx index 76cf2cf0cf..fdb9080bba 100644 --- a/plugins/azure-devops/src/hooks/useReadme.test.tsx +++ b/plugins/azure-devops/src/hooks/useReadme.test.tsx @@ -81,7 +81,7 @@ describe('useReadme', () => { renderHook(() => useReadme(entity), { wrapper: Wrapper, }), - ).toThrow('"dev.azure.com" annotations are missing'); + ).toThrow('Expected "dev.azure.com" annotations were not found'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/plugin.test.ts b/plugins/azure-devops/src/plugin.test.ts index 398b19700c..d69fed6bab 100644 --- a/plugins/azure-devops/src/plugin.test.ts +++ b/plugins/azure-devops/src/plugin.test.ts @@ -69,6 +69,18 @@ describe('azure-devops', () => { expect(isAzurePipelinesAvailable(entity)).toBe(true); }); + it('should be false when no annotations are present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(false); + }); + it('should be false when only project annotation is present', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts index 66a9753da4..7533c473ed 100644 --- a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts +++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts @@ -71,7 +71,7 @@ export function getAnnotationValuesFromEntity(entity: Entity): { } } - throw new Error('"dev.azure.com" annotations are missing'); + throw new Error('Expected "dev.azure.com" annotations were not found'); } function getProjectRepo(annotations?: Record): { From f5c0586b44b1993c2a29585b183f8470898d0ffb Mon Sep 17 00:00:00 2001 From: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> Date: Thu, 11 Jan 2024 20:13:52 +0000 Subject: [PATCH 4/6] Update .changeset/wicked-elephants-scream.md Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> --- .changeset/wicked-elephants-scream.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/wicked-elephants-scream.md b/.changeset/wicked-elephants-scream.md index 820c8b5d97..f002a48ba8 100644 --- a/.changeset/wicked-elephants-scream.md +++ b/.changeset/wicked-elephants-scream.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-azure-devops': minor +'@backstage/plugin-azure-devops': patch --- Prefer `dev.azure.com/build-definition` annotation when it is provided, as it is more specific than `dev.azure.com/project-repo`. This can also be used as a filter for mono-repos. From 187b30b1ce7e6610ba0816dc9839bb0f6c54d32a Mon Sep 17 00:00:00 2001 From: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> Date: Fri, 12 Jan 2024 13:43:38 +0000 Subject: [PATCH 5/6] Additional review comments Signed-off-by: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> --- plugins/azure-devops/README.md | 4 ++-- .../utils/getAnnotationValuesFromEntity.test.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index f867fcfe75..7119cb0980 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -75,9 +75,9 @@ dev.azure.com/build-definition: If your pipeline is in a different project to the source code, you will need to specify this in the project annotation. ```yaml -dev.azure.com/project-repo: / +dev.azure.com/project-repo: / dev.azure.com/build-definition: -dev.azure.com/project: +dev.azure.com/project: ``` #### Azure Pipelines Only diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts index 58481ec388..94f179e8fb 100644 --- a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts +++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts @@ -18,6 +18,22 @@ import { Entity } from '@backstage/catalog-model'; import { getAnnotationValuesFromEntity } from './getAnnotationValuesFromEntity'; describe('getAnnotationValuesFromEntity', () => { + describe('without any annotations', () => { + it('should return ???', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + }, + }; + expect(() => getAnnotationValuesFromEntity(entity)).toThrow( + 'Expected "dev.azure.com" annotations were not found', + ); + }); + }); + describe('with valid project-repo annotation', () => { it('should return project and repo', () => { const entity: Entity = { From 9da4c78e628de0918682271f81c348dd408af787 Mon Sep 17 00:00:00 2001 From: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> Date: Fri, 12 Jan 2024 13:48:42 +0000 Subject: [PATCH 6/6] Add missed file Signed-off-by: R-Beck-2020 <78100403+R-Beck-2020@users.noreply.github.com> --- .../src/utils/getAnnotationValuesFromEntity.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts index 94f179e8fb..9325a79039 100644 --- a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts +++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts @@ -19,7 +19,7 @@ import { getAnnotationValuesFromEntity } from './getAnnotationValuesFromEntity'; describe('getAnnotationValuesFromEntity', () => { describe('without any annotations', () => { - it('should return ???', () => { + it('should throw annotations not found', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component',