Skip to content

Commit

Permalink
fix: support of activation non default podman machines (#138)
Browse files Browse the repository at this point in the history
Signed-off-by: Denis Golovin <[email protected]>
  • Loading branch information
dgolovin authored Apr 30, 2024
1 parent 8a1dc61 commit 35d8545
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 109 deletions.
7 changes: 5 additions & 2 deletions src/extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from '@podman-desktop/api';
import { authentication, commands } from '@podman-desktop/api';
import * as podmanCli from './podman-cli';
import * as subscription from './subscription';
import { ExtensionTelemetryLogger } from './telemetry';
import { OrganizationService } from '@redhat-developer/rhsm-client';

Expand Down Expand Up @@ -175,7 +176,8 @@ suite('signin command telemetry reports', () => {
};
},
);
vi.spyOn(podmanCli, 'isPodmanMachineRunning').mockReturnValue(false);
vi.spyOn(subscription, 'isRedHatRegistryConfigured').mockResolvedValue(true);
vi.spyOn(podmanCli, 'getRunningPodmanMachineName').mockReturnValue(undefined);
await extension.activate(createExtContext());
expect(commandFunctionCopy!).toBeDefined();
await commandFunctionCopy!();
Expand Down Expand Up @@ -238,7 +240,8 @@ suite('signin command telemetry reports', () => {
};
},
);
vi.spyOn(podmanCli, 'isPodmanMachineRunning').mockReturnValue(true);
vi.spyOn(subscription, 'isRedHatRegistryConfigured').mockReturnValue(true);
vi.spyOn(podmanCli, 'getRunningPodmanMachineName').mockReturnValue('machine1');
vi.spyOn(OrganizationService.prototype, 'checkOrgScaCapability').mockResolvedValue({ body: {} });
await extension.activate(createExtContext());
expect(commandFunctionCopy!).toBeDefined();
Expand Down
88 changes: 27 additions & 61 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,25 @@ import { getAuthConfig } from './configuration';
import { onDidChangeSessions, RedHatAuthenticationService } from './authentication-service';
import { ServiceAccountV1, ContainerRegistryAuthorizerClient } from '@redhat-developer/rhcra-client';
import path from 'node:path';
import { homedir } from 'node:os';
import { accessSync, constants, readFileSync } from 'node:fs';
import { readFileSync } from 'node:fs';
import {
runRpmInstallSubscriptionManager,
runSubscriptionManager,
runSubscriptionManagerActivationStatus,
runSubscriptionManagerRegister,
runSubscriptionManagerUnregister,
runCreateFactsFile,
isPodmanMachineRunning,
runStartPodmanMachine,
runStopPodmanMachine,
getRunningPodmanMachineName,
} from './podman-cli';
import { SubscriptionManagerClient } from '@redhat-developer/rhsm-client';
import { isLinux } from './util';
import { SSOStatusBarItem } from './status-bar-item';
import { ExtensionTelemetryLogger as TelemetryLogger } from './telemetry';
import { signIntoRedHatDeveloperAccount } from './subscription';
import { isRedHatRegistryConfigured } from './subscription';
import { REGISTRY_REDHAT_IO } from './subscription';

let authenticationServicePromise: Promise<RedHatAuthenticationService>;
let currentSession: extensionApi.AuthenticationSession | undefined;
Expand Down Expand Up @@ -70,21 +72,6 @@ function parseJwt(token: string) {
return JSON.parse(jsonPayload);
}

async function signIntoRedHatDeveloperAccount(
createIfNone = true,
): Promise<extensionApi.AuthenticationSession | undefined> {
return extensionApi.authentication.getSession(
'redhat.authentication-provider',
[
'api.iam.registry_service_accounts', //scope that gives access to hydra service accounts API
'api.console',
], // scope that gives access to console.redhat.com APIs
{ createIfNone }, // will request to login in browser if session does not exists
);
}

const REGISTRY_REDHAT_IO = 'registry.redhat.io';

async function createRegistry(
username: string,
secret: string,
Expand All @@ -109,29 +96,6 @@ function removeRegistry(serverUrl: string = REGISTRY_REDHAT_IO): void {
});
}

// TODO: add listRegistries to registry API to allow search by
// registry URL
function isRedHatRegistryConfigured(): boolean {
const pathToAuthJson = path.join(homedir(), '.config', 'containers', 'auth.json');
let configured = false;
try {
// TODO: handle all kind problems with file existence, accessibility and parsable content
accessSync(pathToAuthJson, constants.R_OK);
const authFileContent = readFileSync(pathToAuthJson, { encoding: 'utf8' });
const authFileJson: {
auths?: {
[registryUrl: string]: {
auth: string;
};
};
} = JSON.parse(authFileContent);
configured = authFileJson?.auths?.hasOwnProperty(REGISTRY_REDHAT_IO) || false;
} catch (_notAccessibleError) {
// if file is not there, ignore and return default value
}
return configured;
}

async function createOrReuseRegistryServiceAccount(): Promise<void> {
const currentSession = await signIntoRedHatDeveloperAccount();
const accessTokenJson = parseJwt(currentSession!.accessToken);
Expand Down Expand Up @@ -162,7 +126,7 @@ async function createOrReuseRegistryServiceAccount(): Promise<void> {
);
}

async function createOrReuseActivationKey() {
async function createOrReuseActivationKey(machineName: string) {
const currentSession = await signIntoRedHatDeveloperAccount();
const accessTokenJson = parseJwt(currentSession!.accessToken);
const client = new SubscriptionManagerClient({
Expand All @@ -184,7 +148,7 @@ async function createOrReuseActivationKey() {
});
}

await runSubscriptionManagerRegister('podman-desktop', accessTokenJson.organization.id);
await runSubscriptionManagerRegister(machineName, 'podman-desktop', accessTokenJson.organization.id);
}

async function isSimpleContentAccessEnabled(): Promise<boolean> {
Expand All @@ -197,28 +161,31 @@ async function isSimpleContentAccessEnabled(): Promise<boolean> {
return response.body && response.body.simpleContentAccess === 'enabled';
}

async function isSubscriptionManagerInstalled(): Promise<boolean> {
const exitCode = await runSubscriptionManager();
async function isSubscriptionManagerInstalled(machineName: string): Promise<boolean> {
const exitCode = await runSubscriptionManager(machineName);
return exitCode === 0;
}

async function installSubscriptionManger() {
async function installSubscriptionManger(machineName: string) {
try {
return await runRpmInstallSubscriptionManager();
return await runRpmInstallSubscriptionManager(machineName);
} catch (err) {
console.error(`Subscription manager installation failed. ${String(err)}`);
TelemetryLogger.logError('subscriptionManagerInstallationError', { error: String(err) });
throw err;
}
}

async function isPodmanVmSubscriptionActivated() {
const exitCode = await runSubscriptionManagerActivationStatus();
async function isPodmanVmSubscriptionActivated(machineName: string) {
const exitCode = await runSubscriptionManagerActivationStatus(machineName);
return exitCode === 0;
}

async function removeSession(sessionId: string): Promise<void> {
runSubscriptionManagerUnregister().catch(console.error); // ignore error in case vm subscription activation failed on login
const machineName = getRunningPodmanMachineName();
if (machineName) {
runSubscriptionManagerUnregister(machineName).catch(console.error); // ignore error in case vm subscription activation failed on login
}
removeRegistry(); // never fails, even if registry does not exist
const service = await getAuthenticationService();
const session = await service.removeSession(sessionId);
Expand Down Expand Up @@ -276,7 +243,8 @@ async function configureRegistryAndActivateSubscription() {
title: 'Activating Red Hat Subscription',
},
async progress => {
if (!isPodmanMachineRunning()) {
const podmanRunningMachineName = getRunningPodmanMachineName();
if (!podmanRunningMachineName) {
if (isLinux()) {
await extensionApi.window.showInformationMessage(
'Signing into a Red Hat account requires a running Podman machine, and is currently not supported on a Linux host. Please start a Podman machine and try again.',
Expand All @@ -301,18 +269,17 @@ async function configureRegistryAndActivateSubscription() {
}
throw new Error('SCA is not enabled and message closed');
}

if (!(await isSubscriptionManagerInstalled())) {
await installSubscriptionManger();
await runStopPodmanMachine();
await runStartPodmanMachine();
if (!(await isSubscriptionManagerInstalled(podmanRunningMachineName))) {
await installSubscriptionManger(podmanRunningMachineName);
await runStopPodmanMachine(podmanRunningMachineName);
await runStartPodmanMachine(podmanRunningMachineName);
}
if (!(await isPodmanVmSubscriptionActivated())) {
if (!(await isPodmanVmSubscriptionActivated(podmanRunningMachineName))) {
const facts = {
supported_architectures: 'aarch64,x86_64',
};
await runCreateFactsFile(JSON.stringify(facts, undefined, 2));
await createOrReuseActivationKey();
await runCreateFactsFile(podmanRunningMachineName, JSON.stringify(facts, undefined, 2));
await createOrReuseActivationKey(podmanRunningMachineName);
}
}
},
Expand All @@ -326,9 +293,8 @@ async function configureRegistryAndActivateSubscription() {
if (!telemetryData.successful && currentSession?.id) {
removeSession(currentSession.id); // if at least one fail, remove session
}

TelemetryLogger.logUsage('signin', telemetryData);
}
TelemetryLogger.logUsage('signin', telemetryData);
}

export async function activate(context: extensionApi.ExtensionContext): Promise<void> {
Expand Down
41 changes: 22 additions & 19 deletions src/podman-cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ beforeEach(() => {

test('runSubscriptionManager returns 0 when it is installed', async () => {
vi.mocked(podmanProcess.exec).mockResolvedValue(runResult);
const result = await runSubscriptionManager();
const result = await runSubscriptionManager('machine1');
expect(result).toBe(0);
});

Expand All @@ -78,15 +78,15 @@ test('runSubscriptionManager returns 1 when it is not installed', async () => {
stderr: 'stderr output',
toString: () => 'error message',
});
const result = await runSubscriptionManager();
const result = await runSubscriptionManager('machine1');
expect(result).toBe(1);
});

test('runRpmInstallSubscription manager returns 0 when successful', async () => {
vi.mocked(podmanProcess.exec).mockResolvedValue(runResult);
const result = await runRpmInstallSubscriptionManager();
const result = await runRpmInstallSubscriptionManager('machine1');
expect(result).toBe(runResult);
expect(podmanProcess.exec).toBeCalledWith(getPodmanCli(), PODMAN_COMMANDS.RPM_INSTALL_SM());
expect(podmanProcess.exec).toBeCalledWith(getPodmanCli(), PODMAN_COMMANDS.RPM_INSTALL_SM('machine1'));
});

test('runRpmInstallSubscription manager returns none 0 error code when failed and send telemetry', async () => {
Expand All @@ -96,7 +96,7 @@ test('runRpmInstallSubscription manager returns none 0 error code when failed an
});
const consoleError = vi.spyOn(console, 'error');
let error: Error | undefined;
const result = await runRpmInstallSubscriptionManager().catch(err => {
const result = await runRpmInstallSubscriptionManager('machine1').catch(err => {
error = err;
});
expect(String(error)).toBe(String(runError));
Expand All @@ -111,7 +111,7 @@ test('runRpmInstallSubscription manager returns none 0 error code when failed an

test('runSubscriptionManagerActivationStatus returns 0 when it has subscription activated', async () => {
vi.mocked(podmanProcess.exec).mockResolvedValue(runResult);
const result = await runSubscriptionManagerActivationStatus();
const result = await runSubscriptionManagerActivationStatus('machine1');
expect(result).toBe(0);
});

Expand All @@ -122,17 +122,17 @@ test('runSubscriptionManagerActivationStatus returns 1 when it has no active sub
stderr: 'stderr output',
toString: () => 'error message',
});
const result = await runSubscriptionManagerActivationStatus();
const result = await runSubscriptionManagerActivationStatus('machine1');
expect(result).toBe(1);
});

test('runSubscriptionManagerRegister returns 0 when successful', async () => {
vi.mocked(podmanProcess.exec).mockResolvedValue(runResult);
const result = await runSubscriptionManagerRegister('activation-key-name', 'orgId');
const result = await runSubscriptionManagerRegister('machine1', 'activation-key-name', 'orgId');
expect(result).toBe(runResult);
expect(podmanProcess.exec).toBeCalledWith(
getPodmanCli(),
PODMAN_COMMANDS.SM_ACTIVATE_SUBS('activation-key-name', 'orgId'),
PODMAN_COMMANDS.SM_ACTIVATE_SUBS('machine1', 'activation-key-name', 'orgId'),
);
});

Expand All @@ -143,7 +143,7 @@ test('runSubscriptionManagerRegister manager returns none 0 error code when fail
});
const consoleError = vi.spyOn(console, 'error');
let error: Error | undefined;
const result = await runSubscriptionManagerRegister('activation-key-name', 'orgId').catch(err => {
const result = await runSubscriptionManagerRegister('machine1', 'activation-key-name', 'orgId').catch(err => {
error = err;
});
expect(String(error)).toBe(String(runError));
Expand All @@ -158,9 +158,12 @@ test('runSubscriptionManagerRegister manager returns none 0 error code when fail

test('runCreateFactsFile returns 0 when successful', async () => {
vi.mocked(podmanProcess.exec).mockResolvedValue(runResult);
const result = await runCreateFactsFile('{"field":"value"}');
const result = await runCreateFactsFile('machine1', '{"field":"value"}');
expect(result).toBe(runResult);
expect(podmanProcess.exec).toBeCalledWith(getPodmanCli(), PODMAN_COMMANDS.CREATE_FACTS_FILE('{"field":"value"}'));
expect(podmanProcess.exec).toBeCalledWith(
getPodmanCli(),
PODMAN_COMMANDS.CREATE_FACTS_FILE('machine1', '{"field":"value"}'),
);
});

test('runCreateFactsFile manager returns none 0 error code when failed and send telemetry', async () => {
Expand All @@ -170,7 +173,7 @@ test('runCreateFactsFile manager returns none 0 error code when failed and send
});
const consoleError = vi.spyOn(console, 'error');
let error: Error | undefined;
const result = await runCreateFactsFile('{"field":"value"}').catch(err => {
const result = await runCreateFactsFile('machine1', '{"field":"value"}').catch(err => {
error = err;
});
expect(String(error)).toBe(String(runError));
Expand All @@ -185,9 +188,9 @@ test('runCreateFactsFile manager returns none 0 error code when failed and send

test('runStopPodmanMachine returns 0 when successful', async () => {
vi.mocked(podmanProcess.exec).mockResolvedValue(runResult);
const result = await runStopPodmanMachine();
const result = await runStopPodmanMachine('machine1');
expect(result).toBe(runResult);
expect(podmanProcess.exec).toBeCalledWith(getPodmanCli(), PODMAN_COMMANDS.MACHINE_STOP());
expect(podmanProcess.exec).toBeCalledWith(getPodmanCli(), PODMAN_COMMANDS.MACHINE_STOP('machine1'));
});

test('runStopPodmanMachine manager returns none 0 error code when failed and send telemetry', async () => {
Expand All @@ -197,7 +200,7 @@ test('runStopPodmanMachine manager returns none 0 error code when failed and sen
});
const consoleError = vi.spyOn(console, 'error');
let error: Error | undefined;
const result = await runStopPodmanMachine().catch(err => {
const result = await runStopPodmanMachine('machine1').catch(err => {
error = err;
});
expect(String(error)).toBe(String(runError));
Expand All @@ -212,9 +215,9 @@ test('runStopPodmanMachine manager returns none 0 error code when failed and sen

test('runStartPodmanMachine returns 0 when successful', async () => {
vi.mocked(podmanProcess.exec).mockResolvedValue(runResult);
const result = await runStartPodmanMachine();
const result = await runStartPodmanMachine('machine1');
expect(result).toBe(runResult);
expect(podmanProcess.exec).toBeCalledWith(getPodmanCli(), PODMAN_COMMANDS.MACHINE_START());
expect(podmanProcess.exec).toBeCalledWith(getPodmanCli(), PODMAN_COMMANDS.MACHINE_START('machine1'));
});

test('runStartPodmanMachine manager returns none 0 error code when failed and send telemetry', async () => {
Expand All @@ -224,7 +227,7 @@ test('runStartPodmanMachine manager returns none 0 error code when failed and se
});
const consoleError = vi.spyOn(console, 'error');
let error: Error | undefined;
const result = await runStartPodmanMachine().catch(err => {
const result = await runStartPodmanMachine('machine1').catch(err => {
error = err;
});
expect(String(error)).toBe(String(runError));
Expand Down
Loading

0 comments on commit 35d8545

Please sign in to comment.