Skip to content

Commit 8e0f536

Browse files
committed
Add ability to follow Location types for monorepos
1 parent 75e7a25 commit 8e0f536

5 files changed

Lines changed: 180 additions & 54 deletions

File tree

dist/index.js

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20453,9 +20453,57 @@ const getRepos = async () => {
2045320453
const owner = core.getInput('github_owner')
2045420454
const catalogFile = core.getInput('catalog_file') || 'catalog-info.yaml'
2045520455
const repositoryFilterRegex = new RegExp(repositoryFilter)
20456-
2045720456
const octokit = new Octokit({ auth: GITHUB_TOKEN })
2045820457

20458+
const parseServiceDefinition = async (repo, path, pushMissing) => {
20459+
const repoData = []
20460+
core.debug(`Processing ${path} ...`)
20461+
try {
20462+
const { data } = await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', {
20463+
owner: repo.full_name.split('/')[0],
20464+
repo: repo.name,
20465+
path
20466+
})
20467+
if (data) {
20468+
const base64content = Buffer.from(data.content, 'base64')
20469+
const serviceDefinition = YAML.parse(base64content.toString('utf8'))
20470+
if (serviceDefinition.kind?.toLowerCase() === 'location') {
20471+
repoData.push(...await parseLocationFile(serviceDefinition, repo, path))
20472+
} else {
20473+
serviceDefinition._catalog_file = data.html_url
20474+
serviceDefinition._repo = repo
20475+
serviceDefinition.status = 'OK'
20476+
repoData.push(serviceDefinition)
20477+
}
20478+
}
20479+
} catch (ex) {
20480+
if (pushMissing) {
20481+
repoData.push({
20482+
status: `${catalogFile} missing`,
20483+
_repo: repo
20484+
})
20485+
} else {
20486+
core.warning(`Unable to find ${path} in ${repo.name}, not processing`)
20487+
}
20488+
}
20489+
return repoData
20490+
}
20491+
20492+
const parseLocationFile = async (serviceDefinition, repo, path) => {
20493+
const repoData = []
20494+
const targets = serviceDefinition.spec?.targets
20495+
if (targets && targets.length > 0) {
20496+
for (const target of targets) {
20497+
const pushMissing = false
20498+
const targetDefinition = await parseServiceDefinition(repo, target, pushMissing)
20499+
repoData.push(...targetDefinition)
20500+
}
20501+
} else {
20502+
core.warning(`Location file in ${repo._repo.name} at ${path} specified without valid spec.targets, will be skipped`)
20503+
}
20504+
return repoData
20505+
}
20506+
2045920507
const repos = await octokit.paginate('GET /orgs/{owner}/repos',
2046020508
{
2046120509
owner: owner,
@@ -20468,25 +20516,8 @@ const getRepos = async () => {
2046820516
const repoData = []
2046920517
for (const repo of repos) {
2047020518
if (repo.name.match(repositoryFilterRegex)) {
20471-
try {
20472-
const { data } = await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', {
20473-
owner: repo.full_name.split('/')[0],
20474-
repo: repo.name,
20475-
path: catalogFile
20476-
})
20477-
if (data) {
20478-
const base64content = Buffer.from(data.content, 'base64')
20479-
const serviceDefinition = YAML.parse(base64content.toString('utf8'))
20480-
serviceDefinition._repo = repo
20481-
serviceDefinition.status = 'OK'
20482-
repoData.push(serviceDefinition)
20483-
}
20484-
} catch (ex) {
20485-
repoData.push({
20486-
status: `${catalogFile} missing`,
20487-
_repo: repo
20488-
})
20489-
}
20519+
const pushMissing = true
20520+
repoData.push(...await parseServiceDefinition(repo, catalogFile, pushMissing))
2049020521
}
2049120522
}
2049220523

@@ -20625,12 +20656,13 @@ const { getDependsOn } = __nccwpck_require__(4154)
2062520656
const updateServices = async (repositories, { notion, database, systems, owners }) => {
2062620657
for (const repo of repositories) {
2062720658
// Lets see if we can find the row
20659+
const repoName = repo.metadata?.name || repo._repo.name
2062820660
const search = await notion.databases.query({
2062920661
database_id: database,
2063020662
filter: {
2063120663
property: 'Name',
2063220664
text: {
20633-
equals: repo._repo.name
20665+
equals: repoName
2063420666
}
2063520667
}
2063620668
})
@@ -20640,8 +20672,10 @@ const updateServices = async (repositories, { notion, database, systems, owners
2064020672
// Lets just update the first one to not make the problem worse
2064120673
if (search.results.length > 0) {
2064220674
const pageId = search.results[0].id
20675+
core.debug(`Updating notion info for ${repoName}`)
2064320676
await updateNotionRow(repo, pageId, { notion, database, systems, owners })
2064420677
} else {
20678+
core.debug(`Creating notion info for ${repoName}`)
2064520679
await createNotionRow(repo, { notion, database, systems, owners })
2064620680
}
2064720681
}
@@ -20661,7 +20695,7 @@ const updateNotionRow = async (repo, pageId, { notion, database, systems, owners
2066120695
await ensureLinks(pageId, repo.metadata.links, { notion })
2066220696
}
2066320697
} catch (ex) {
20664-
core.error(`Error updating notion document for ${repo._repo.name}: ${ex.message} ...`)
20698+
core.warning(`Error updating notion document for ${repo._repo.name}: ${ex.message} ...`)
2066520699
}
2066620700
}
2066720701

@@ -20681,7 +20715,7 @@ const createNotionRow = async (repo, { notion, database, systems, owners }) => {
2068120715
await ensureLinks(page.id, repo.metadata.links, { notion })
2068220716
}
2068320717
} catch (ex) {
20684-
core.error(`Error creating notion document for ${repo._repo.name}: ${ex.message} ...`)
20718+
core.warning(`Error creating notion document for ${repo._repo.name}: ${ex.message}`)
2068520719
}
2068620720
}
2068720721

@@ -20722,12 +20756,15 @@ const createProperties = (repo, dependsOn, { systems, owners }) => {
2072220756
}
2072320757
}
2072420758

20759+
// We can use the catalog file location to locate the right path within the repo
20760+
const htmlUrl = repo._catalog_file ? repo._catalog_file.substring(0, repo._catalog_file.lastIndexOf('/')) : repo._repo.html_url
20761+
2072520762
return {
2072620763
Name: {
2072720764
title: [
2072820765
{
2072920766
text: {
20730-
content: repo._repo.name
20767+
content: repo.metadata?.name || repo._repo.name
2073120768
}
2073220769
}
2073320770
]
@@ -20747,7 +20784,7 @@ const createProperties = (repo, dependsOn, { systems, owners }) => {
2074720784
}
2074820785
},
2074920786
URL: {
20750-
url: repo._repo.html_url
20787+
url: htmlUrl
2075120788
},
2075220789
Owner: owner,
2075320790
System: system,

readme.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ It looks like this after it has run:
3434

3535
<img width="1451" alt="Screenshot 2021-12-19 at 12 55 39" src="https://user-images.githubusercontent.com/239305/146673989-01187d53-d2fd-42ba-9968-31442b8cc92d.png">
3636

37-
3837
### Embedded Data
3938

4039
If your descriptor file contains links, these are added to an embedded database within the service page called `Links`.
@@ -58,6 +57,59 @@ This action expects each of your repositories to have a descriptor file format i
5857

5958
Information on the format: https://backstage.io/docs/features/software-catalog/descriptor-format
6059

60+
The following types are supported:
61+
62+
### Component
63+
64+
This is a basic component, with the following fields supported:
65+
66+
```
67+
apiVersion: backstage.io/v1alpha1
68+
kind: Component
69+
metadata:
70+
name: app-ecommerce-web
71+
description: The front end application for noordhoff.nl, plantyn.com and liber.se
72+
links:
73+
- url: https://portal.azure.com/#@infinitaslearning.onmicrosoft.com/resource/subscriptions/4ebd66c4-aaad-4b1b-bb4e-740db9f1fc4d/resourceGroups/ecommerce-pro-rg/overview
74+
title: Azure
75+
icon: dashboard
76+
- url: https://liber-shop.production.infinicloud.app/
77+
title: Production
78+
tags:
79+
- ecommerce
80+
- application
81+
- react
82+
spec:
83+
type: application
84+
lifecycle: production
85+
owner: commercial
86+
system: ecommerce
87+
dependsOn:
88+
- service-cms-api
89+
```
90+
91+
Fields other than those above are currently not supported, but could be with a PR.
92+
93+
### API
94+
95+
This can be used, though we do not support the `spec` attribute, as it is impossible to render things like OpenAPI inside Notion, so these should be linked to via the links, but if you create a file exactly as *Component* above, it will be processed with the different `Kind` property (so be filterable).
96+
97+
### Location
98+
99+
This should be used in the case of a mono-repo, that contains multile sub-components. Place the following in the root. Note that in the current implementation it needs to be a relative path to the root of the repository, and do not start paths with `./`
100+
101+
```
102+
apiVersion: backstage.io/v1alpha1
103+
kind: Location
104+
metadata:
105+
name: org-data
106+
spec:
107+
type: url
108+
targets:
109+
- sub-component-one/catalog-info.yaml
110+
- sub-component-two/catalog-info.yaml
111+
```
112+
61113
## Usage
62114

63115
This is typically deployed as a scheduled action:

src/github.js

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,58 @@ const getRepos = async () => {
77
const repositoryType = core.getInput('repository_type') || 'all'
88
const repositoryFilter = core.getInput('repository_filter') || '.*'
99
const owner = core.getInput('github_owner')
10-
const catalogFile = core.getInput('catalog_file') || 'catalog-info.yaml'
10+
const catalogFile = core.getInput('catalog_file') || 'catalog-info.yaml'
11+
const octokit = new Octokit({ auth: GITHUB_TOKEN })
1112
const repositoryFilterRegex = new RegExp(repositoryFilter)
1213

13-
const octokit = new Octokit({ auth: GITHUB_TOKEN })
14+
const parseServiceDefinition = async (repo, path, pushMissing) => {
15+
const repoData = []
16+
core.debug(`Processing ${path} ...`)
17+
try {
18+
const { data } = await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', {
19+
owner: repo.full_name.split('/')[0],
20+
repo: repo.name,
21+
path
22+
})
23+
if (data) {
24+
const base64content = Buffer.from(data.content, 'base64')
25+
const serviceDefinition = YAML.parse(base64content.toString('utf8'))
26+
if (serviceDefinition.kind?.toLowerCase() === 'location') {
27+
repoData.push(...await parseLocationFile(serviceDefinition, repo, path))
28+
} else {
29+
serviceDefinition._catalog_file = data.html_url
30+
serviceDefinition._repo = repo
31+
serviceDefinition.status = 'OK'
32+
repoData.push(serviceDefinition)
33+
}
34+
}
35+
} catch (ex) {
36+
if (pushMissing) {
37+
repoData.push({
38+
status: `${catalogFile} missing`,
39+
_repo: repo
40+
})
41+
} else {
42+
core.warning(`Unable to find ${path} in ${repo.name}, not processing`)
43+
}
44+
}
45+
return repoData
46+
}
47+
48+
const parseLocationFile = async (serviceDefinition, repo, path) => {
49+
const repoData = []
50+
const targets = serviceDefinition.spec?.targets
51+
if (targets && targets.length > 0) {
52+
for (const target of targets) {
53+
const pushMissing = false
54+
const targetDefinition = await parseServiceDefinition(repo, target, pushMissing)
55+
repoData.push(...targetDefinition)
56+
}
57+
} else {
58+
core.warning(`Location file in ${repo._repo.name} at ${path} specified without valid spec.targets, will be skipped`)
59+
}
60+
return repoData
61+
}
1462

1563
const repos = await octokit.paginate('GET /orgs/{owner}/repos',
1664
{
@@ -24,25 +72,8 @@ const getRepos = async () => {
2472
const repoData = []
2573
for (const repo of repos) {
2674
if (repo.name.match(repositoryFilterRegex)) {
27-
try {
28-
const { data } = await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', {
29-
owner: repo.full_name.split('/')[0],
30-
repo: repo.name,
31-
path: catalogFile
32-
})
33-
if (data) {
34-
const base64content = Buffer.from(data.content, 'base64')
35-
const serviceDefinition = YAML.parse(base64content.toString('utf8'))
36-
serviceDefinition._repo = repo
37-
serviceDefinition.status = 'OK'
38-
repoData.push(serviceDefinition)
39-
}
40-
} catch (ex) {
41-
repoData.push({
42-
status: `${catalogFile} missing`,
43-
_repo: repo
44-
})
45-
}
75+
const pushMissing = true
76+
repoData.push(...await parseServiceDefinition(repo, catalogFile, pushMissing))
4677
}
4778
}
4879

src/local.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ const process = require('process')
44

55
process.env.INPUT_NOTION_TOKEN = process.env.NOTION_TOKEN
66
process.env.INPUT_GITHUB_TOKEN = process.env.GITHUB_TOKEN
7-
process.env.INPUT_REPOSITORY_TYPE = 'public'
7+
process.env.INPUT_REPOSITORY_TYPE = 'all'
88
process.env.INPUT_GITHUB_OWNER = 'infinitaslearning'
9-
process.env.INPUT_REPOSITORY_FILTER = '.*'
9+
process.env.INPUT_REPOSITORY_FILTER = 'notion-github-catalog'
1010
process.env.INPUT_DATABASE = 'cecaf0beb15945158d155866ff9acce8'
1111
process.env.INPUT_OWNER_DATABASE = '7943615f4dba43b3a3b0f991f4f7136d'
1212
process.env.INPUT_SYSTEM_DATABASE = '121534684fe840a1953500e603c2b602'

src/services.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ const { getDependsOn } = require('./depends')
55
const updateServices = async (repositories, { notion, database, systems, owners }) => {
66
for (const repo of repositories) {
77
// Lets see if we can find the row
8+
const repoName = repo.metadata?.name || repo._repo.name
89
const search = await notion.databases.query({
910
database_id: database,
1011
filter: {
1112
property: 'Name',
1213
text: {
13-
equals: repo._repo.name
14+
equals: repoName
1415
}
1516
}
1617
})
@@ -20,8 +21,10 @@ const updateServices = async (repositories, { notion, database, systems, owners
2021
// Lets just update the first one to not make the problem worse
2122
if (search.results.length > 0) {
2223
const pageId = search.results[0].id
24+
core.debug(`Updating notion info for ${repoName}`)
2325
await updateNotionRow(repo, pageId, { notion, database, systems, owners })
2426
} else {
27+
core.debug(`Creating notion info for ${repoName}`)
2528
await createNotionRow(repo, { notion, database, systems, owners })
2629
}
2730
}
@@ -41,7 +44,7 @@ const updateNotionRow = async (repo, pageId, { notion, database, systems, owners
4144
await ensureLinks(pageId, repo.metadata.links, { notion })
4245
}
4346
} catch (ex) {
44-
core.error(`Error updating notion document for ${repo._repo.name}: ${ex.message} ...`)
47+
core.warning(`Error updating notion document for ${repo._repo.name}: ${ex.message} ...`)
4548
}
4649
}
4750

@@ -61,7 +64,7 @@ const createNotionRow = async (repo, { notion, database, systems, owners }) => {
6164
await ensureLinks(page.id, repo.metadata.links, { notion })
6265
}
6366
} catch (ex) {
64-
core.error(`Error creating notion document for ${repo._repo.name}: ${ex.message} ...`)
67+
core.warning(`Error creating notion document for ${repo._repo.name}: ${ex.message}`)
6568
}
6669
}
6770

@@ -102,12 +105,15 @@ const createProperties = (repo, dependsOn, { systems, owners }) => {
102105
}
103106
}
104107

108+
// We can use the catalog file location to locate the right path within the repo
109+
const htmlUrl = repo._catalog_file ? repo._catalog_file.substring(0, repo._catalog_file.lastIndexOf('/')) : repo._repo.html_url
110+
105111
return {
106112
Name: {
107113
title: [
108114
{
109115
text: {
110-
content: repo._repo.name
116+
content: repo.metadata?.name || repo._repo.name
111117
}
112118
}
113119
]
@@ -127,7 +133,7 @@ const createProperties = (repo, dependsOn, { systems, owners }) => {
127133
}
128134
},
129135
URL: {
130-
url: repo._repo.html_url
136+
url: htmlUrl
131137
},
132138
Owner: owner,
133139
System: system,

0 commit comments

Comments
 (0)