Skip to content
Snippets Groups Projects
Unverified Commit 8f1f787c authored by CanisHelix's avatar CanisHelix Committed by GitHub
Browse files

[GITEA] add new gitea service (release/languages) (#9781)

* add gitea service based on gitlab

* update gitea to use mocks

* add gitea release test

* move tests to use public repo on codeberg and fixes

* add pagination, update tests to live, set gitea_url as required

* add auth test (wip)

* fix base auth test

* fix required optionalUrl, remove default, assume semver from firstpage

* update example to use stable repository
parent 2814de2e
No related branches found
No related tags found
No related merge requests found
......@@ -128,6 +128,7 @@ const publicConfigSchema = Joi.object({
},
restApiVersion: Joi.date().raw().required(),
},
gitea: defaultService,
gitlab: defaultService,
jira: defaultService,
jenkins: Joi.object({
......@@ -168,6 +169,7 @@ const privateConfigSchema = Joi.object({
gh_client_id: Joi.string(),
gh_client_secret: Joi.string(),
gh_token: Joi.string(),
gitea_token: Joi.string(),
gitlab_token: Joi.string(),
jenkins_user: Joi.string(),
jenkins_pass: Joi.string(),
......
......@@ -167,6 +167,15 @@ These settings are used by shields.io for GitHub OAuth app authorization
but will not be necessary for most self-hosted installations. See
[production-hosting.md](./production-hosting.md).
### Gitea
- `GITEA_ORIGINS` (yml: `public.services.gitea.authorizedOrigins`)
- `GITEA_TOKEN` (yml: `private.gitea_token`)
A Gitea [Personal Access Token][gitea-pat] is required for accessing private content. If you need a Gitea token for your self-hosted Shields server then we recommend limiting the scopes to the minimal set necessary for the badges you are using.
[gitea-pat]: https://docs.gitea.com/development/api-usage#generating-and-listing-api-tokens
### GitLab
- `GITLAB_ORIGINS` (yml: `public.services.gitlab.authorizedOrigins`)
......
import { BaseJsonService } from '../index.js'
export default class GiteaBase extends BaseJsonService {
static auth = {
passKey: 'gitea_token',
serviceKey: 'gitea',
}
async fetch({ url, options, schema, httpErrors }) {
return this._requestJson(
this.authHelper.withBearerAuthHeader({
schema,
url,
options,
httpErrors,
}),
)
}
}
import Joi from 'joi'
import { expect } from 'chai'
import nock from 'nock'
import { cleanUpNockAfterEach, defaultContext } from '../test-helpers.js'
import GiteaBase from './gitea-base.js'
class DummyGiteaService extends GiteaBase {
static route = { base: 'fake-base' }
async handle() {
const data = await this.fetch({
schema: Joi.any(),
url: 'https://codeberg.org/api/v1/repos/CanisHelix/shields-badge-test/releases',
})
return { message: data.message }
}
}
describe('GiteaBase', function () {
describe('auth', function () {
cleanUpNockAfterEach()
const config = {
public: {
services: {
gitea: {
authorizedOrigins: ['https://codeberg.org'],
},
},
},
private: {
gitea_token: 'fake-key',
},
}
it('sends the auth information as configured', async function () {
const scope = nock('https://codeberg.org')
.get('/api/v1/repos/CanisHelix/shields-badge-test/releases')
.matchHeader('Authorization', 'Bearer fake-key')
.reply(200, { message: 'fake message' })
expect(
await DummyGiteaService.invoke(defaultContext, config, {}),
).to.not.have.property('isError')
scope.done()
})
})
})
const documentation = `
Note that the gitea_url parameter is required because there is canonical hosted gitea service provided by Gitea.
`
function httpErrorsFor() {
return {
403: 'private repo',
404: 'user or repo not found',
}
}
export { documentation, httpErrorsFor }
import Joi from 'joi'
import { nonNegativeInteger, optionalUrl } from '../validators.js'
import { metric } from '../text-formatters.js'
import { pathParam, queryParam } from '../index.js'
import { documentation, httpErrorsFor } from './gitea-helper.js'
import GiteaBase from './gitea-base.js'
/*
We're expecting a response like { "Python": 39624, "Shell": 104 }
The keys could be anything and {} is a valid response (e.g: for an empty repo)
*/
const schema = Joi.object().pattern(/./, nonNegativeInteger)
const queryParamSchema = Joi.object({
gitea_url: optionalUrl.required(),
}).required()
export default class GiteaLanguageCount extends GiteaBase {
static category = 'analysis'
static route = {
base: 'gitea/languages/count',
pattern: ':user/:repo',
queryParamSchema,
}
static openApi = {
'/gitea/languages/count/{user}/{repo}': {
get: {
summary: 'Gitea language count',
description: documentation,
parameters: [
pathParam({
name: 'user',
example: 'forgejo',
}),
pathParam({
name: 'repo',
example: 'forgejo',
}),
queryParam({
name: 'gitea_url',
example: 'https://codeberg.org',
required: true,
}),
],
},
},
}
static defaultBadgeData = { label: 'languages' }
static render({ languagesCount }) {
return {
message: metric(languagesCount),
color: 'blue',
}
}
async fetch({ user, repo, baseUrl }) {
// https://try.gitea.io/api/swagger#/repository/repoGetLanguages
return super.fetch({
schema,
url: `${baseUrl}/api/v1/repos/${user}/${repo}/languages`,
httpErrors: httpErrorsFor('user or repo not found'),
})
}
async handle({ user, repo }, { gitea_url: baseUrl }) {
const data = await this.fetch({
user,
repo,
baseUrl,
})
return this.constructor.render({ languagesCount: Object.keys(data).length })
}
}
import Joi from 'joi'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('language count (empty repo)')
.get(
'/CanisHelix/shields-badge-test-empty.json?gitea_url=https://codeberg.org',
)
.expectBadge({
label: 'languages',
message: '0',
})
t.create('language count')
.get('/CanisHelix/shields-badge-test.json?gitea_url=https://codeberg.org')
.expectBadge({
label: 'languages',
message: Joi.number().integer().positive(),
})
t.create('language count (user or repo not found)')
.get('/CanisHelix/does-not-exist.json?gitea_url=https://codeberg.org')
.expectBadge({
label: 'languages',
message: 'user or repo not found',
})
import Joi from 'joi'
import { optionalUrl } from '../validators.js'
import { latest, renderVersionBadge } from '../version.js'
import { NotFound, pathParam, queryParam } from '../index.js'
import { documentation, httpErrorsFor } from './gitea-helper.js'
import GiteaBase from './gitea-base.js'
const schema = Joi.array().items(
Joi.object({
name: Joi.string().required(),
tag_name: Joi.string().required(),
prerelease: Joi.boolean().required(),
}),
)
const sortEnum = ['date', 'semver']
const displayNameEnum = ['tag', 'release']
const dateOrderByEnum = ['created_at', 'published_at']
const queryParamSchema = Joi.object({
gitea_url: optionalUrl.required(),
include_prereleases: Joi.equal(''),
sort: Joi.string()
.valid(...sortEnum)
.default('date'),
display_name: Joi.string()
.valid(...displayNameEnum)
.default('tag'),
date_order_by: Joi.string()
.valid(...dateOrderByEnum)
.default('created_at'),
}).required()
export default class GiteaRelease extends GiteaBase {
static category = 'version'
static route = {
base: 'gitea/v/release',
pattern: ':user/:repo',
queryParamSchema,
}
static openApi = {
'/gitea/v/release/{user}/{repo}': {
get: {
summary: 'Gitea Release',
description: documentation,
parameters: [
pathParam({
name: 'user',
example: 'forgejo',
}),
pathParam({
name: 'repo',
example: 'forgejo',
}),
queryParam({
name: 'gitea_url',
example: 'https://codeberg.org',
required: true,
}),
queryParam({
name: 'include_prereleases',
schema: { type: 'boolean' },
example: null,
}),
queryParam({
name: 'sort',
schema: { type: 'string', enum: sortEnum },
example: 'semver',
}),
queryParam({
name: 'display_name',
schema: { type: 'string', enum: displayNameEnum },
example: 'release',
}),
queryParam({
name: 'date_order_by',
schema: { type: 'string', enum: dateOrderByEnum },
example: 'created_at',
}),
],
},
},
}
static defaultBadgeData = { label: 'release' }
async fetch({ user, repo, baseUrl }) {
// https://try.gitea.io/api/swagger#/repository/repoGetRelease
return super.fetch({
schema,
url: `${baseUrl}/api/v1/repos/${user}/${repo}/releases`,
httpErrors: httpErrorsFor(),
})
}
static transform({ releases, isSemver, includePrereleases, displayName }) {
if (releases.length === 0) {
throw new NotFound({ prettyMessage: 'no releases found' })
}
const displayKey = displayName === 'tag' ? 'tag_name' : 'name'
if (isSemver) {
return latest(
releases.map(t => t[displayKey]),
{ pre: includePrereleases },
)
}
if (!includePrereleases) {
const stableReleases = releases.filter(release => !release.prerelease)
if (stableReleases.length > 0) {
return stableReleases[0][displayKey]
}
}
return releases[0][displayKey]
}
async handle(
{ user, repo },
{
gitea_url: baseUrl,
include_prereleases: pre,
sort,
display_name: displayName,
date_order_by: orderBy,
},
) {
const isSemver = sort === 'semver'
const releases = await this.fetch({
user,
repo,
baseUrl,
isSemver,
})
const version = this.constructor.transform({
releases,
isSemver,
includePrereleases: pre !== undefined,
displayName,
})
return renderVersionBadge({ version })
}
}
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('Release (latest by date)')
.get('/CanisHelix/shields-badge-test.json?gitea_url=https://codeberg.org')
.expectBadge({ label: 'release', message: 'v3.0.0', color: 'blue' })
t.create('Release (latest by date, order by created_at)')
.get(
'/CanisHelix/shields-badge-test.json?gitea_url=https://codeberg.org&date_order_by=created_at',
)
.expectBadge({ label: 'release', message: 'v3.0.0', color: 'blue' })
t.create('Release (latest by date, order by published_at)')
.get(
'/CanisHelix/shields-badge-test.json?gitea_url=https://codeberg.org&date_order_by=published_at',
)
.expectBadge({ label: 'release', message: 'v3.0.0', color: 'blue' })
t.create('Release (latest by semver)')
.get(
'/CanisHelix/shields-badge-test.json?gitea_url=https://codeberg.org&sort=semver',
)
.expectBadge({ label: 'release', message: 'v4.0.0', color: 'blue' })
t.create('Release (latest by semver pre-release)')
.get(
'/CanisHelix/shields-badge-test.json?gitea_url=https://codeberg.org&sort=semver&include_prereleases',
)
.expectBadge({ label: 'release', message: 'v5.0.0-rc1', color: 'orange' })
t.create('Release (project not found)')
.get('/CanisHelix/does-not-exist.json?gitea_url=https://codeberg.org')
.expectBadge({ label: 'release', message: 'user or repo not found' })
t.create('Release (no tags)')
.get(
'/CanisHelix/shields-badge-test-empty.json?gitea_url=https://codeberg.org',
)
.expectBadge({ label: 'release', message: 'no releases found' })
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment