Skip to content

Commit 4ba06c0

Browse files
feat: add terraform registry providers and modules downloads (#10793)
1 parent 8cd1480 commit 4ba06c0

5 files changed

+236
-0
lines changed

services/terraform/terraform-base.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Joi from 'joi'
2+
import { nonNegativeInteger } from '../validators.js'
3+
import { BaseJsonService } from '../index.js'
4+
5+
const description =
6+
'[Terraform Registry](https://registry.terraform.io) is an interactive resource for discovering a wide selection of integrations (providers), configuration packages (modules), and security rules (policies) for use with Terraform.'
7+
8+
const schema = Joi.object({
9+
data: Joi.object({
10+
attributes: Joi.object({
11+
month: nonNegativeInteger,
12+
total: nonNegativeInteger,
13+
week: nonNegativeInteger,
14+
year: nonNegativeInteger,
15+
}).required(),
16+
}),
17+
})
18+
19+
const intervalMap = {
20+
dw: {
21+
transform: json => json.data.attributes.week,
22+
interval: 'week',
23+
},
24+
dm: {
25+
transform: json => json.data.attributes.month,
26+
interval: 'month',
27+
},
28+
dy: {
29+
transform: json => json.data.attributes.year,
30+
interval: 'year',
31+
},
32+
dt: {
33+
transform: json => json.data.attributes.total,
34+
interval: '',
35+
},
36+
}
37+
38+
class BaseTerraformService extends BaseJsonService {
39+
static _cacheLength = 3600
40+
41+
async fetch({ kind, object }) {
42+
const url = `https://registry.terraform.io/v2/${kind}/${object}/downloads/summary`
43+
return this._requestJson({
44+
schema,
45+
url,
46+
})
47+
}
48+
}
49+
50+
export { BaseTerraformService, intervalMap, description }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { renderDownloadsBadge } from '../downloads.js'
2+
import { pathParams } from '../index.js'
3+
import {
4+
BaseTerraformService,
5+
description,
6+
intervalMap,
7+
} from './terraform-base.js'
8+
9+
export default class TerraformModuleDownloads extends BaseTerraformService {
10+
static category = 'downloads'
11+
12+
static route = {
13+
base: 'terraform/module',
14+
pattern: ':interval(dw|dm|dy|dt)/:namespace/:name/:provider',
15+
}
16+
17+
static openApi = {
18+
'/terraform/module/{interval}/{namespace}/{name}/{provider}': {
19+
get: {
20+
summary: 'Terraform Module Downloads',
21+
description,
22+
parameters: pathParams(
23+
{
24+
name: 'interval',
25+
example: 'dy',
26+
schema: { type: 'string', enum: this.getEnum('interval') },
27+
description: 'Weekly, Monthly, Yearly or Total downloads',
28+
},
29+
{
30+
name: 'namespace',
31+
example: 'hashicorp',
32+
},
33+
{
34+
name: 'name',
35+
example: 'consul',
36+
},
37+
{
38+
name: 'provider',
39+
example: 'aws',
40+
},
41+
),
42+
},
43+
},
44+
}
45+
46+
static defaultBadgeData = { label: 'downloads' }
47+
48+
async handle({ interval, namespace, name, provider }) {
49+
const { transform } = intervalMap[interval]
50+
const json = await this.fetch({
51+
kind: 'modules',
52+
object: `${namespace}/${name}/${provider}`,
53+
})
54+
55+
return renderDownloadsBadge({
56+
downloads: transform(json),
57+
interval: intervalMap[interval].interval,
58+
})
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { createServiceTester } from '../tester.js'
2+
import { isMetric, isMetricOverTimePeriod } from '../test-validators.js'
3+
4+
export const t = await createServiceTester()
5+
6+
t.create('weekly downloads (valid)')
7+
.get('/dw/hashicorp/consul/aws.json')
8+
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
9+
10+
t.create('monthly downloads (valid)')
11+
.get('/dm/hashicorp/consul/aws.json')
12+
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
13+
14+
t.create('yearly downloads (valid)')
15+
.get('/dy/hashicorp/consul/aws.json')
16+
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
17+
18+
t.create('total downloads (valid)')
19+
.get('/dt/hashicorp/consul/aws.json')
20+
.expectBadge({ label: 'downloads', message: isMetric })
21+
22+
t.create('weekly downloads (not found)')
23+
.get('/dw/not/real/module.json')
24+
.expectBadge({ label: 'downloads', message: 'not found' })
25+
26+
t.create('monthly downloads (not found)')
27+
.get('/dm/not/real/module.json')
28+
.expectBadge({ label: 'downloads', message: 'not found' })
29+
30+
t.create('yearly downloads (not found)')
31+
.get('/dy/not/real/module.json')
32+
.expectBadge({ label: 'downloads', message: 'not found' })
33+
34+
t.create('total downloads (not found)')
35+
.get('/dt/not/real/module.json')
36+
.expectBadge({ label: 'downloads', message: 'not found' })
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { renderDownloadsBadge } from '../downloads.js'
2+
import { pathParams } from '../index.js'
3+
import {
4+
BaseTerraformService,
5+
description,
6+
intervalMap,
7+
} from './terraform-base.js'
8+
9+
export default class TerraformProviderDownloads extends BaseTerraformService {
10+
static category = 'downloads'
11+
12+
static route = {
13+
base: 'terraform/provider',
14+
pattern: ':interval(dw|dm|dy|dt)/:providerId',
15+
}
16+
17+
static openApi = {
18+
'/terraform/provider/{interval}/{providerId}': {
19+
get: {
20+
summary: 'Terraform Provider Downloads',
21+
description,
22+
parameters: pathParams(
23+
{
24+
name: 'interval',
25+
example: 'dy',
26+
schema: { type: 'string', enum: this.getEnum('interval') },
27+
description: 'Weekly, Monthly, Yearly or Total downloads',
28+
},
29+
{
30+
name: 'providerId',
31+
example: '323',
32+
description:
33+
'The provider ID can be found using `https://registry.terraform.io/v2/providers/{namespace}/{name}`',
34+
},
35+
),
36+
},
37+
},
38+
}
39+
40+
static defaultBadgeData = { label: 'downloads' }
41+
42+
async handle({ interval, providerId }) {
43+
const { transform } = intervalMap[interval]
44+
const json = await this.fetch({
45+
kind: 'providers',
46+
object: providerId,
47+
})
48+
49+
return renderDownloadsBadge({
50+
downloads: transform(json),
51+
interval: intervalMap[interval].interval,
52+
})
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { createServiceTester } from '../tester.js'
2+
import { isMetric, isMetricOverTimePeriod } from '../test-validators.js'
3+
4+
export const t = await createServiceTester()
5+
6+
t.create('weekly downloads (valid)')
7+
.get('/dw/323.json')
8+
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
9+
10+
t.create('monthly downloads (valid)')
11+
.get('/dm/323.json')
12+
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
13+
14+
t.create('yearly downloads (valid)')
15+
.get('/dy/323.json')
16+
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
17+
18+
t.create('total downloads (valid)')
19+
.get('/dt/323.json')
20+
.expectBadge({ label: 'downloads', message: isMetric })
21+
22+
t.create('weekly downloads (not found)')
23+
.get('/dw/not-valid.json')
24+
.expectBadge({ label: 'downloads', message: 'not found' })
25+
26+
t.create('monthly downloads (not found)')
27+
.get('/dm/not-valid.json')
28+
.expectBadge({ label: 'downloads', message: 'not found' })
29+
30+
t.create('yearly downloads (not found)')
31+
.get('/dy/not-valid.json')
32+
.expectBadge({ label: 'downloads', message: 'not found' })
33+
34+
t.create('total downloads (not found)')
35+
.get('/dt/not-valid.json')
36+
.expectBadge({ label: 'downloads', message: 'not found' })

0 commit comments

Comments
 (0)