Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 130d83b

Browse files
committedMar 14, 2025·
fix: migrate ot openapi-typescript and openapi-fetch
Signed-off-by: Denis Golovin <dgolovin@redhat.com>
1 parent 5f22ef9 commit 130d83b

11 files changed

+3702
-216
lines changed
 

‎.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ redhat-authentication.cdix
77
tests/**/output
88
test-resources
99
test-results
10-
10+
src/rh-api/gen

‎package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
}
3535
},
3636
"scripts": {
37-
"build": "vite build && node ./scripts/build.cjs",
37+
"generate:registry-authorizer": "npx openapi-typescript src/rh-api/registry-authorizer-schema.json -o src/rh-api/gen/registry-authorizer.d.ts",
38+
"generate:subscription": "npx openapi-typescript src/rh-api/subscription-schema.json -o src/rh-api/gen/subscription.d.ts",
39+
"build": "pnpm generate:subscription && pnpm generate:registry-authorizer && vite build && node ./scripts/build.cjs",
3840
"watch": "vite build -w",
3941
"format:check": "prettier --end-of-line auto --cache --check \"{src,types,scripts}/**/*.{ts,js}\"",
4042
"format:fix": "prettier --cache --write \"{src,types,scripts}/**/*.{ts,js}\"",
@@ -49,11 +51,10 @@
4951
"dependencies": {
5052
"@podman-desktop/api": "^1.14.1",
5153
"@podman-desktop/podman-extension-api": "^1.14.2",
52-
"@redhat-developer/rhcra-client": "^0.0.1",
53-
"@redhat-developer/rhsm-client": "^0.0.4",
5454
"axios": "^1.8.2",
5555
"js-yaml": "^4.1.0",
5656
"object-hash": "3.0.0",
57+
"openapi-fetch": "^0.13.4",
5758
"openid-client": "^5.7.0"
5859
},
5960
"devDependencies": {

‎pnpm-lock.yaml

+144-165
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/extension.spec.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020

2121
import type { AuthenticationGetSessionOptions, AuthenticationSession, ExtensionContext } from '@podman-desktop/api';
2222
import { authentication, commands } from '@podman-desktop/api';
23-
import { OrganizationService } from '@redhat-developer/rhsm-client';
2423
import { afterEach, beforeEach, expect, suite, test, vi } from 'vitest';
2524

2625
import * as extension from './extension';
2726
import * as podmanCli from './podman-cli';
28-
import * as subscription from './subscription';
27+
import { Organization } from './rh-api/subscription';
28+
import * as subscription from './rh-api/subscription';
2929
import { ExtensionTelemetryLogger } from './telemetry';
3030
import * as util from './util';
3131

@@ -180,8 +180,18 @@ suite('signin command telemetry reports', () => {
180180
},
181181
);
182182
vi.spyOn(subscription, 'isRedHatRegistryConfigured').mockReturnValue(true);
183-
vi.spyOn(podmanCli, 'getConnectionForRunningPodmanMachine').mockReturnValue('machine1');
184-
vi.spyOn(OrganizationService.prototype, 'checkOrgScaCapability').mockResolvedValue({ body: {} });
183+
vi.spyOn(podmanCli, 'getConnectionForRunningPodmanMachine').mockReturnValue({
184+
providerId: '1',
185+
connection: {
186+
name: 'machine1',
187+
type: 'podman',
188+
endpoint: {
189+
socketPath: '/path/to/the/socket',
190+
},
191+
status: () => 'started',
192+
},
193+
});
194+
vi.spyOn(Organization.prototype, 'checkOrgScaCapability').mockResolvedValue({ body: {} });
185195
await extension.activate(createExtContext());
186196
expect(commandFunctionCopy!).toBeDefined();
187197
await commandFunctionCopy!();

‎src/extension.ts

+28-27
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
***********************************************************************/
1818

1919
import * as extensionApi from '@podman-desktop/api';
20-
import type { ServiceAccountV1 } from '@redhat-developer/rhcra-client';
21-
import { ContainerRegistryAuthorizerClient } from '@redhat-developer/rhcra-client';
22-
import { SubscriptionManagerClient } from '@redhat-developer/rhsm-client';
2320

2421
import icon from '../icon.png';
2522
import { onDidChangeSessions, RedHatAuthenticationService } from './authentication-service';
@@ -35,10 +32,11 @@ import {
3532
runSubscriptionManagerRegister,
3633
runSubscriptionManagerUnregister,
3734
} from './podman-cli';
35+
import { ContainerRegistryAuthorizerClient } from './rh-api/registry-authorizer';
36+
import { isRedHatRegistryConfigured, REGISTRY_REDHAT_IO, SubscriptionManagerClient } from './rh-api/subscription';
3837
import { SSOStatusBarItem } from './status-bar-item';
39-
import { isRedHatRegistryConfigured, REGISTRY_REDHAT_IO, signIntoRedHatDeveloperAccount } from './subscription';
4038
import { ExtensionTelemetryLogger as TelemetryLogger } from './telemetry';
41-
import { isLinux } from './util';
39+
import { isLinux, signIntoRedHatDeveloperAccount } from './util';
4240

4341
interface JwtToken {
4442
organization: {
@@ -96,29 +94,32 @@ function removeRegistry(serverUrl: string = REGISTRY_REDHAT_IO): void {
9694
async function createOrReuseRegistryServiceAccount(): Promise<void> {
9795
const currentSession = await signIntoRedHatDeveloperAccount();
9896
const accessTokenJson = parseJwt(currentSession!.accessToken);
99-
const client = new ContainerRegistryAuthorizerClient({
97+
const { serviceAccountsApiV1: saApiV1 } = new ContainerRegistryAuthorizerClient({
10098
BASE: 'https://access.redhat.com/hydra/rest/terms-based-registry',
10199
TOKEN: currentSession!.accessToken,
102100
});
103-
const saApiV1 = client.serviceAccountsApiV1;
104-
let selectedServiceAccount: ServiceAccountV1 | undefined;
105-
try {
106-
selectedServiceAccount = await saApiV1.serviceAccountByNameUsingGet1(
107-
'podman-desktop',
108-
accessTokenJson.organization.id,
109-
);
110-
} catch (err) {
101+
let { data: serviceAccount } = await saApiV1.serviceAccountByNameUsingGet1(
102+
'podman-desktop',
103+
accessTokenJson.organization.id,
104+
);
105+
106+
if (!serviceAccount) {
111107
// ignore error when there is no podman-desktop service account yet
112-
selectedServiceAccount = await saApiV1.createServiceAccountUsingPost1({
108+
const { data: createdServiceAccount } = await saApiV1.createServiceAccountUsingPost1({
113109
name: 'podman-desktop',
114110
description: 'Service account to use from Podman Desktop',
115111
redHatAccountId: accessTokenJson.organization.id,
116112
});
113+
if (createdServiceAccount) {
114+
serviceAccount = createdServiceAccount;
115+
} else {
116+
throw new Error(`Can't create registry authorizer service account.`);
117+
}
117118
}
118119

119120
await createRegistry(
120-
selectedServiceAccount!.credentials!.username!,
121-
selectedServiceAccount!.credentials!.password!,
121+
serviceAccount!.credentials!.username!,
122+
serviceAccount!.credentials!.password!,
122123
currentSession.account.label,
123124
);
124125
}
@@ -131,20 +132,20 @@ async function createOrReuseActivationKey(connection: extensionApi.ProviderConta
131132
TOKEN: currentSession!.accessToken,
132133
});
133134

134-
try {
135-
await client.activationKey.showActivationKey('podman-desktop');
136-
// podman-desktop activation key exists
137-
} catch (err) {
138-
// ignore and continue with activation key creation
139-
// TODO: add check that used role and usage exists in organization
140-
await client.activationKey.createActivationKeys({
135+
const { error: showKeyErr } = await client.activationKey.showActivationKey('podman-desktop');
136+
137+
if (showKeyErr) {
138+
// error is undefined when activation key already exists
139+
const { error: createKeyErr } = await client.activationKey.createActivationKeys({
141140
name: 'podman-desktop',
142141
role: 'RHEl Workstation',
143142
usage: 'Development/Test',
144143
serviceLevel: 'Self-Support',
145144
});
145+
if (createKeyErr) {
146+
throw new Error(createKeyErr.error?.message);
147+
}
146148
}
147-
148149
await runSubscriptionManagerRegister(connection, 'podman-desktop', accessTokenJson.organization.id);
149150
}
150151

@@ -154,8 +155,8 @@ async function isSimpleContentAccessEnabled(): Promise<boolean> {
154155
BASE: 'https://console.redhat.com/api/rhsm/v2',
155156
TOKEN: currentSession!.accessToken,
156157
});
157-
const response = await client.organization.checkOrgScaCapability();
158-
return !!response.body && response.body.simpleContentAccess === 'enabled';
158+
const data = await client.organization.checkOrgScaCapability();
159+
return data?.body?.simpleContentAccess === 'enabled';
159160
}
160161

161162
async function isSubscriptionManagerInstalled(connection: extensionApi.ProviderContainerConnection): Promise<boolean> {

‎src/rh-api/registry-authorizer-schema.json

+1,207
Large diffs are not rendered by default.

‎src/rh-api/registry-authorizer.ts

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**********************************************************************
2+
* Copyright (C) 2024-2025 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
***********************************************************************/
18+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
19+
20+
import createClient from 'openapi-fetch';
21+
22+
import type { paths } from './gen/registry-authorizer';
23+
import { ClientHolder } from './utils';
24+
25+
export const REGISTRY_REDHAT_IO1 = 'registry.redhat.io';
26+
27+
class ServiceAccountApiV1 extends ClientHolder<paths> {
28+
serviceAccountByNameUsingGet1(name: string, rhAccountId: string) {
29+
return this.client.GET('/v1/service-accounts/{rhAccountId}/{name}', {
30+
params: {
31+
path: {
32+
name,
33+
rhAccountId,
34+
},
35+
},
36+
});
37+
}
38+
39+
createServiceAccountUsingPost1(body: { name: string; description: string; redHatAccountId: string }) {
40+
return this.client.POST('/v1/service-accounts', {
41+
body,
42+
});
43+
}
44+
45+
removeServiceAccountUsingDelete1(name: string, rhAccountId: string) {
46+
return this.client.DELETE('/v1/service-accounts/{rhAccountId}/{name}', {
47+
params: {
48+
path: {
49+
name,
50+
rhAccountId,
51+
},
52+
},
53+
});
54+
}
55+
}
56+
57+
export class ContainerRegistryAuthorizerClient extends ClientHolder<paths> {
58+
public serviceAccountsApiV1: ServiceAccountApiV1;
59+
constructor(options: { BASE: string; TOKEN: string }) {
60+
super(createClient<paths>({ baseUrl: options.BASE }), options.TOKEN);
61+
this.serviceAccountsApiV1 = new ServiceAccountApiV1(this.client);
62+
}
63+
}

‎src/rh-api/subscription-schema.json

+2,154
Large diffs are not rendered by default.

‎src/subscription.ts ‎src/rh-api/subscription.ts

+38-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**********************************************************************
2-
* Copyright (C) 2024 Red Hat, Inc.
2+
* Copyright (C) 2024-2025 Red Hat, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,27 +15,18 @@
1515
*
1616
* SPDX-License-Identifier: Apache-2.0
1717
***********************************************************************/
18+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
1819

1920
import { accessSync, constants, readFileSync } from 'node:fs';
2021
import { homedir } from 'node:os';
2122
import path from 'node:path';
2223

23-
import * as extensionApi from '@podman-desktop/api';
24+
import createClient from 'openapi-fetch';
2425

25-
export const REGISTRY_REDHAT_IO = 'registry.redhat.io';
26+
import type { paths } from './gen/subscription';
27+
import { ClientHolder } from './utils';
2628

27-
export async function signIntoRedHatDeveloperAccount(
28-
createIfNone = true,
29-
): Promise<extensionApi.AuthenticationSession | undefined> {
30-
return extensionApi.authentication.getSession(
31-
'redhat.authentication-provider',
32-
[
33-
'api.iam.registry_service_accounts', //scope that gives access to hydra service accounts API
34-
'api.console',
35-
], // scope that gives access to console.redhat.com APIs
36-
{ createIfNone },
37-
);
38-
}
29+
export const REGISTRY_REDHAT_IO = 'registry.redhat.io';
3930

4031
// TODO: add listRegistries to registry API to allow search by
4132
// registry URL
@@ -59,3 +50,35 @@ export function isRedHatRegistryConfigured(): boolean {
5950
}
6051
return configured;
6152
}
53+
54+
export class ActivationKey extends ClientHolder<paths> {
55+
async createActivationKeys(body: { name: string; role: string; usage: string; serviceLevel: string }) {
56+
return this.client.POST('/activation_keys', { body });
57+
}
58+
showActivationKey(name: string) {
59+
return this.client.GET('/activation_keys/{name}', {
60+
params: {
61+
path: {
62+
name,
63+
},
64+
},
65+
});
66+
}
67+
}
68+
69+
export class Organization extends ClientHolder<paths> {
70+
async checkOrgScaCapability() {
71+
const response = await this.client.GET('/organization');
72+
return response.data;
73+
}
74+
}
75+
76+
export class SubscriptionManagerClient extends ClientHolder<paths> {
77+
activationKey: ActivationKey;
78+
organization: Organization;
79+
constructor(options: { BASE: string; TOKEN: string }) {
80+
super(createClient<paths>({ baseUrl: options.BASE }), options.TOKEN);
81+
this.activationKey = new ActivationKey(this.client);
82+
this.organization = new Organization(this.client);
83+
}
84+
}

‎src/rh-api/utils.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**********************************************************************
2+
* Copyright (C) 2025 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
***********************************************************************/
18+
19+
import type { Client } from 'openapi-fetch';
20+
21+
export class ClientHolder<T extends object> {
22+
protected client: Client<T>;
23+
constructor(client: Client<T>, token?: string) {
24+
this.client = client;
25+
if (token) {
26+
this.client.use({
27+
onRequest({ request }) {
28+
request.headers.set('Authorization', `Bearer ${token}`);
29+
return request;
30+
},
31+
});
32+
}
33+
}
34+
}

‎src/util.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**********************************************************************
2-
* Copyright (C) 2024 Red Hat, Inc.
2+
* Copyright (C) 2024-2025 Red Hat, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,9 @@
1818

1919
import { platform } from 'node:os';
2020

21+
import type { AuthenticationSession } from '@podman-desktop/api';
22+
import { authentication } from '@podman-desktop/api';
23+
2124
const windows = platform() === 'win32';
2225
export function isWindows(): boolean {
2326
return windows;
@@ -30,3 +33,14 @@ const linux = platform() === 'linux';
3033
export function isLinux(): boolean {
3134
return linux;
3235
}
36+
37+
export async function signIntoRedHatDeveloperAccount(createIfNone = true): Promise<AuthenticationSession | undefined> {
38+
return authentication.getSession(
39+
'redhat.authentication-provider',
40+
[
41+
'api.iam.registry_service_accounts', //scope that gives access to hydra service accounts API
42+
'api.console',
43+
], // scope that gives access to console.redhat.com APIs
44+
{ createIfNone },
45+
);
46+
}

0 commit comments

Comments
 (0)
Please sign in to comment.