Skip to content

Commit

Permalink
Add [ROS] version service (#8169)
Browse files Browse the repository at this point in the history
* Add [ROS] version service

* review feedback

* add spaces
  • Loading branch information
jtbandes authored Jul 12, 2022
1 parent a668340 commit 7cfd3f5
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 0 deletions.
154 changes: 154 additions & 0 deletions services/ros/ros-version.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import gql from 'graphql-tag'
import Joi from 'joi'
import yaml from 'js-yaml'
import { renderVersionBadge } from '../version.js'
import { GithubAuthV4Service } from '../github/github-auth-service.js'
import { NotFound, InvalidResponse } from '../index.js'

const tagsSchema = Joi.object({
data: Joi.object({
repository: Joi.object({
refs: Joi.object({
edges: Joi.array()
.items({
node: Joi.object({
name: Joi.string().required(),
}).required(),
})
.required(),
}).required(),
}).required(),
}).required(),
}).required()

const contentSchema = Joi.object({
data: Joi.object({
repository: Joi.object({
object: Joi.object({
text: Joi.string().required(),
}).allow(null),
}).required(),
}).required(),
}).required()

const distroSchema = Joi.object({
repositories: Joi.object().required(),
})
const packageSchema = Joi.object({
release: Joi.object({
version: Joi.string().required(),
}).required(),
})

export default class RosVersion extends GithubAuthV4Service {
static category = 'version'

static route = { base: 'ros/v', pattern: ':distro/:packageName' }

static examples = [
{
title: 'ROS Package Index',
namedParams: { distro: 'humble', packageName: 'vision_msgs' },
staticPreview: {
...renderVersionBadge({ version: '4.0.0' }),
label: 'ros | humble',
},
},
]

static defaultBadgeData = { label: 'ros' }

async handle({ distro, packageName }) {
const tagsJson = await this._requestGraphql({
query: gql`
query ($refPrefix: String!) {
repository(owner: "ros", name: "rosdistro") {
refs(
refPrefix: $refPrefix
first: 30
orderBy: { field: TAG_COMMIT_DATE, direction: DESC }
) {
edges {
node {
name
}
}
}
}
}
`,
variables: { refPrefix: `refs/tags/${distro}/` },
schema: tagsSchema,
})

// Filter for tags that look like dates: humble/2022-06-10
const tags = tagsJson.data.repository.refs.edges
.map(edge => edge.node.name)
.filter(tag => /^\d+-\d+-\d+$/.test(tag))
.sort()
.reverse()

const ref = tags[0] ? `refs/tags/${distro}/${tags[0]}` : 'refs/heads/master'
const prettyRef = tags[0] ? `${distro}/${tags[0]}` : 'master'

const contentJson = await this._requestGraphql({
query: gql`
query ($expression: String!) {
repository(owner: "ros", name: "rosdistro") {
object(expression: $expression) {
... on Blob {
text
}
}
}
}
`,
variables: {
expression: `${ref}:${distro}/distribution.yaml`,
},
schema: contentSchema,
})

if (!contentJson.data.repository.object) {
throw new NotFound({
prettyMessage: `distribution.yaml not found: ${distro}@${prettyRef}`,
})
}
const version = this.constructor._parseReleaseVersionFromDistro(
contentJson.data.repository.object.text,
packageName
)

return { ...renderVersionBadge({ version }), label: `ros | ${distro}` }
}

static _parseReleaseVersionFromDistro(distroYaml, packageName) {
let distro
try {
distro = yaml.load(distroYaml)
} catch (err) {
throw new InvalidResponse({
prettyMessage: 'unparseable distribution.yml',
underlyingError: err,
})
}

const validatedDistro = this._validate(distro, distroSchema, {
prettyErrorMessage: 'invalid distribution.yml',
})
if (!validatedDistro.repositories[packageName]) {
throw new NotFound({ prettyMessage: `package not found: ${packageName}` })
}

const packageInfo = this._validate(
validatedDistro.repositories[packageName],
packageSchema,
{
prettyErrorMessage: `invalid section for ${packageName} in distribution.yml`,
}
)

// Strip off "release inc" suffix
return packageInfo.release.version.replace(/-\d+$/, '')
}
}
44 changes: 44 additions & 0 deletions services/ros/ros-version.service.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect } from 'chai'
import RosVersion from './ros-version.service.js'

describe('parseReleaseVersionFromDistro', function () {
it('returns correct version', function () {
expect(
RosVersion._parseReleaseVersionFromDistro(
`
%YAML 1.1
# ROS distribution file
# see REP 143: http://ros.org/reps/rep-0143.html
---
release_platforms:
debian:
- bullseye
rhel:
- '8'
ubuntu:
- jammy
repositories:
vision_msgs:
doc:
type: git
url: https://github.com/ros-perception/vision_msgs.git
version: ros2
release:
tags:
release: release/humble/{package}/{version}
url: https://github.com/ros2-gbp/vision_msgs-release.git
version: 4.0.0-2
source:
test_pull_requests: true
type: git
url: https://github.com/ros-perception/vision_msgs.git
version: ros2
status: developed
type: distribution
version: 2
`,
'vision_msgs'
)
).to.equal('4.0.0')
})
})
28 changes: 28 additions & 0 deletions services/ros/ros-version.tester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { isSemver } from '../test-validators.js'
import { createServiceTester } from '../tester.js'

export const t = await createServiceTester()

t.create('gets the package version of vision_msgs in active distro')
.get('/humble/vision_msgs.json')
.expectBadge({ label: 'ros | humble', message: isSemver })

t.create('gets the package version of vision_msgs in EOL distro')
.get('/lunar/vision_msgs.json')
.expectBadge({ label: 'ros | lunar', message: isSemver })

t.create('returns not found for invalid package')
.get('/humble/this package does not exist - ros test.json')
.expectBadge({
label: 'ros',
color: 'red',
message: 'package not found: this package does not exist - ros test',
})

t.create('returns error for invalid distro')
.get('/xxxxxx/vision_msgs.json')
.expectBadge({
label: 'ros',
color: 'red',
message: 'distribution.yaml not found: xxxxxx@master',
})

0 comments on commit 7cfd3f5

Please sign in to comment.