Skip to content

Commit

Permalink
feat(datasource): add glasskube packages datasource (#29430)
Browse files Browse the repository at this point in the history
Signed-off-by: Jakob Steiner <jakob.steiner@glasskube.eu>
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
  • Loading branch information
3 people committed Jun 26, 2024
1 parent a023058 commit bbde807
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/modules/datasource/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { GithubTagsDatasource } from './github-tags';
import { GitlabPackagesDatasource } from './gitlab-packages';
import { GitlabReleasesDatasource } from './gitlab-releases';
import { GitlabTagsDatasource } from './gitlab-tags';
import { GlasskubePackagesDatasource } from './glasskube-packages';
import { GoDatasource } from './go';
import { GolangVersionDatasource } from './golang-version';
import { GradleVersionDatasource } from './gradle-version';
Expand Down Expand Up @@ -102,6 +103,7 @@ api.set(GithubTagsDatasource.id, new GithubTagsDatasource());
api.set(GitlabPackagesDatasource.id, new GitlabPackagesDatasource());
api.set(GitlabReleasesDatasource.id, new GitlabReleasesDatasource());
api.set(GitlabTagsDatasource.id, new GitlabTagsDatasource());
api.set(GlasskubePackagesDatasource.id, new GlasskubePackagesDatasource());
api.set(GoDatasource.id, new GoDatasource());
api.set(GolangVersionDatasource.id, new GolangVersionDatasource());
api.set(GradleVersionDatasource.id, new GradleVersionDatasource());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# yaml-language-server: $schema=https://glasskube.dev/schemas/v1/package-manifest.json
name: cloudnative-pg
iconUrl: https://github.com/glasskube/glasskube/assets/16959694/99f3192a-587f-4eb9-884d-62800c022992
shortDescription: A comprehensive platform designed to seamlessly manage PostgreSQL databases
longDescription: |
**CloudNativePG** is an open source operator designed to manage PostgreSQL workloads on any supported Kubernetes
cluster running in private, public, hybrid, or multi-cloud environments. CloudNativePG adheres to DevOps principles
and concepts such as declarative configuration and immutable infrastructure.
references:
- label: GitHub
url: https://github.com/cloudnative-pg/cloudnative-pg
- label: Website
url: https://cloudnative-pg.io/
- label: Documentation
url: https://cloudnative-pg.io/documentation/1.23/
defaultNamespace: cnpg-system
manifests:
- url: https://github.com/cloudnative-pg/cloudnative-pg/releases/download/v1.23.1/cnpg-1.23.1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# yaml-language-server: $schema=https://glasskube.dev/schemas/v1/package-manifest.json
name: cloudnative-pg
iconUrl: https://github.com/glasskube/glasskube/assets/16959694/99f3192a-587f-4eb9-884d-62800c022992
shortDescription: A comprehensive platform designed to seamlessly manage PostgreSQL databases
longDescription: |
**CloudNativePG** is an open source operator designed to manage PostgreSQL workloads on any supported Kubernetes
cluster running in private, public, hybrid, or multi-cloud environments. CloudNativePG adheres to DevOps principles
and concepts such as declarative configuration and immutable infrastructure.
defaultNamespace: cnpg-system
manifests:
- url: https://github.com/cloudnative-pg/cloudnative-pg/releases/download/v1.23.1/cnpg-1.23.1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
latestVersion: v1.23.1+1
versions:
- version: v1.22.0+1
- version: v1.23.1+1
153 changes: 153 additions & 0 deletions lib/modules/datasource/glasskube-packages/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { getPkgReleases } from '..';
import { Fixtures } from '../../../../test/fixtures';
import * as httpMock from '../../../../test/http-mock';
import { GlasskubePackagesDatasource } from '.';

describe('modules/datasource/glasskube-packages/index', () => {
const customRegistryUrl = 'https://packages.test.example/packages';
const customVersionsUrl = new URL(
`${customRegistryUrl}/cloudnative-pg/versions.yaml`,
);
const defaultVersionUrl = new URL(
`${GlasskubePackagesDatasource.defaultRegistryUrl}/cloudnative-pg/versions.yaml`,
);
const versionsYaml = Fixtures.get('versions.yaml');
const customPackageManifestUrl = new URL(
`${customRegistryUrl}/cloudnative-pg/v1.23.1+1/package.yaml`,
);
const defaultPackageManifestUrl = new URL(
`${GlasskubePackagesDatasource.defaultRegistryUrl}/cloudnative-pg/v1.23.1+1/package.yaml`,
);
const packageManifestYaml = Fixtures.get('package.yaml');
const packageManifestNoReferencesYaml = Fixtures.get(
'package_no_references.yaml',
);

it('should handle error response on versions request', async () => {
httpMock
.scope(customVersionsUrl.origin)
.get(customVersionsUrl.pathname)
.reply(500, 'internal server error');
await expect(
getPkgReleases({
datasource: GlasskubePackagesDatasource.id,
packageName: 'cloudnative-pg',
registryUrls: [customRegistryUrl],
}),
).rejects.toThrow();
});

it('should handle empty response on versions request', async () => {
httpMock
.scope(customVersionsUrl.origin)
.get(customVersionsUrl.pathname)
.reply(200);
const response = await getPkgReleases({
datasource: GlasskubePackagesDatasource.id,
packageName: 'cloudnative-pg',
registryUrls: [customRegistryUrl],
});
expect(response).toBeNull();
});

it('should handle error response on manifest request', async () => {
httpMock
.scope(customVersionsUrl.origin)
.get(customVersionsUrl.pathname)
.reply(200, versionsYaml);
httpMock
.scope(customPackageManifestUrl.origin)
.get(customPackageManifestUrl.pathname)
.reply(500, 'internal server error');
await expect(
getPkgReleases({
datasource: GlasskubePackagesDatasource.id,
packageName: 'cloudnative-pg',
registryUrls: [customRegistryUrl],
}),
).rejects.toThrow();
});

it('should handle empty response on manifest request', async () => {
httpMock
.scope(customVersionsUrl.origin)
.get(customVersionsUrl.pathname)
.reply(200, versionsYaml);
httpMock
.scope(customPackageManifestUrl.origin)
.get(customPackageManifestUrl.pathname)
.reply(200);
const response = await getPkgReleases({
datasource: GlasskubePackagesDatasource.id,
packageName: 'cloudnative-pg',
registryUrls: [customRegistryUrl],
});
expect(response).toBeNull();
});

it('should handle package manifest without references', async () => {
httpMock
.scope(customVersionsUrl.origin)
.get(customVersionsUrl.pathname)
.reply(200, versionsYaml);
httpMock
.scope(customPackageManifestUrl.origin)
.get(customPackageManifestUrl.pathname)
.reply(200, packageManifestNoReferencesYaml);
const response = await getPkgReleases({
datasource: GlasskubePackagesDatasource.id,
packageName: 'cloudnative-pg',
registryUrls: [customRegistryUrl],
});
expect(response).toEqual({
registryUrl: customRegistryUrl,
tags: { latest: 'v1.23.1+1' },
releases: [{ version: 'v1.22.0+1' }, { version: 'v1.23.1+1' }],
});
});

it('should handle package manifest with references and default url', async () => {
httpMock
.scope(defaultVersionUrl.origin)
.get(defaultVersionUrl.pathname)
.reply(200, versionsYaml);
httpMock
.scope(defaultPackageManifestUrl.origin)
.get(defaultPackageManifestUrl.pathname)
.reply(200, packageManifestYaml);
const response = await getPkgReleases({
datasource: GlasskubePackagesDatasource.id,
packageName: 'cloudnative-pg',
});
expect(response).toEqual({
sourceUrl: 'https://github.com/cloudnative-pg/cloudnative-pg',
homepage: 'https://cloudnative-pg.io/',
registryUrl: GlasskubePackagesDatasource.defaultRegistryUrl,
tags: { latest: 'v1.23.1+1' },
releases: [{ version: 'v1.22.0+1' }, { version: 'v1.23.1+1' }],
});
});

it('should handle package manifest with references and custom url', async () => {
httpMock
.scope(customVersionsUrl.origin)
.get(customVersionsUrl.pathname)
.reply(200, versionsYaml);
httpMock
.scope(customPackageManifestUrl.origin)
.get(customPackageManifestUrl.pathname)
.reply(200, packageManifestYaml);
const response = await getPkgReleases({
datasource: GlasskubePackagesDatasource.id,
packageName: 'cloudnative-pg',
registryUrls: [customRegistryUrl],
});
expect(response).toEqual({
sourceUrl: 'https://github.com/cloudnative-pg/cloudnative-pg',
homepage: 'https://cloudnative-pg.io/',
registryUrl: customRegistryUrl,
tags: { latest: 'v1.23.1+1' },
releases: [{ version: 'v1.22.0+1' }, { version: 'v1.23.1+1' }],
});
});
});
76 changes: 76 additions & 0 deletions lib/modules/datasource/glasskube-packages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { cache } from '../../../util/cache/package/decorator';
import { joinUrlParts } from '../../../util/url';
import * as glasskubeVersioning from '../../versioning/glasskube';
import { Datasource } from '../datasource';
import type { GetReleasesConfig, ReleaseResult } from '../types';
import {
GlasskubePackageManifestYaml,
GlasskubePackageVersions,
GlasskubePackageVersionsYaml,
} from './schema';

export class GlasskubePackagesDatasource extends Datasource {
static readonly id = 'glasskube-packages';
static readonly defaultRegistryUrl =
'https://packages.dl.glasskube.dev/packages';
override readonly customRegistrySupport = true;
override defaultVersioning = glasskubeVersioning.id;

override defaultRegistryUrls = [
GlasskubePackagesDatasource.defaultRegistryUrl,
];

constructor() {
super(GlasskubePackagesDatasource.id);
}

@cache({
namespace: `datasource-${GlasskubePackagesDatasource.id}`,
key: ({ registryUrl, packageName }: GetReleasesConfig) =>
`${registryUrl}:${packageName}`,
})
override async getReleases({
packageName,
registryUrl,
}: GetReleasesConfig): Promise<ReleaseResult | null> {
let versions: GlasskubePackageVersions;
const result: ReleaseResult = { releases: [] };

try {
const response = await this.http.get(
joinUrlParts(registryUrl!, packageName, 'versions.yaml'),
);
versions = GlasskubePackageVersionsYaml.parse(response.body);
} catch (err) {
this.handleGenericErrors(err);
}

result.releases = versions.versions.map((it) => ({
version: it.version,
}));
result.tags = { latest: versions.latestVersion };

try {
const response = await this.http.get(
joinUrlParts(
registryUrl!,
packageName,
versions.latestVersion,
'package.yaml',
),
);
const latestManifest = GlasskubePackageManifestYaml.parse(response.body);
for (const ref of latestManifest?.references ?? []) {
if (ref.label.toLowerCase() === 'github') {
result.sourceUrl = ref.url;
} else if (ref.label.toLowerCase() === 'website') {
result.homepage = ref.url;
}
}
} catch (err) {
this.handleGenericErrors(err);
}

return result;
}
}
23 changes: 23 additions & 0 deletions lib/modules/datasource/glasskube-packages/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { z } from 'zod';
import { Yaml } from '../../../util/schema-utils';

const GlasskubePackageVersions = z.object({
latestVersion: z.string(),
versions: z.array(z.object({ version: z.string() })),
});

const GlasskubePackageManifest = z.object({
references: z.optional(
z.array(
z.object({
label: z.string(),
url: z.string(),
}),
),
),
});

export const GlasskubePackageVersionsYaml = Yaml.pipe(GlasskubePackageVersions);
export const GlasskubePackageManifestYaml = Yaml.pipe(GlasskubePackageManifest);

export type GlasskubePackageVersions = z.infer<typeof GlasskubePackageVersions>;
1 change: 1 addition & 0 deletions lib/util/cache/package/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type PackageCacheNamespace =
| 'datasource-gitlab-releases'
| 'datasource-gitlab-tags-commit'
| 'datasource-gitlab-tags'
| 'datasource-glasskube-packages'
| 'datasource-go-direct'
| 'datasource-go-proxy'
| 'datasource-go'
Expand Down

0 comments on commit bbde807

Please sign in to comment.