Skip to content

Commit

Permalink
"Components" sidebar section take a while to load redhat-developer#3850
Browse files Browse the repository at this point in the history
Fixes: redhat-developer#3850

Signed-off-by: Victor Rubezhny <[email protected]>
  • Loading branch information
vrubezhny committed Oct 7, 2024
1 parent ab6662e commit 6116cf9
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 102 deletions.
4 changes: 2 additions & 2 deletions src/devfile-registry/devfileRegistryWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as https from 'https';
import * as YAML from 'js-yaml';
import { ExecutionContext } from '../cli';
import { Registry } from '../odo/componentType';
import { Odo } from '../odo/odoWrapper';
import { OdoPreference } from '../odo/odoPreference';
import { DevfileData, DevfileInfo } from './devfileInfo';

export const DEVFILE_VERSION_LATEST: string = 'latest';
Expand Down Expand Up @@ -86,7 +86,7 @@ export class DevfileRegistry {
let registries: Registry[] = [];
const key = ExecutionContext.key('getRegistries');
if (this.executionContext && !this.executionContext.has(key)) {
registries = await Odo.Instance.getRegistries();
registries = await OdoPreference.Instance.getRegistries();
this.executionContext.set(key, registries);
} else {
registries = this.executionContext.get(key);
Expand Down
9 changes: 6 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { Context as KcuContext } from '@kubernetes/client-node/dist/config_types';
import {
authentication,
commands, env, ExtensionContext, languages, QuickPickItemKind,
Expand All @@ -25,16 +26,16 @@ import { startTelemetry } from './telemetry';
import { ToolsConfig } from './tools';
import { TokenStore } from './util/credentialManager';
import { getNamespaceKind, KubeConfigUtils, setKubeConfig } from './util/kubeUtils';
import { Context as KcuContext } from '@kubernetes/client-node/dist/config_types';
import { setupWorkspaceDevfileContext } from './util/workspace';
import { registerCommands } from './vscommand';
import { OpenShiftTerminalManager } from './webview/openshift-terminal/openShiftTerminal';
import { WelcomePage } from './welcomePage';
import { registerYamlHandlers } from './yaml/yamlDocumentFeatures';

import { Oc } from './oc/ocWrapper';
import { K8S_RESOURCE_SCHEME, K8S_RESOURCE_SCHEME_READONLY, KubernetesResourceVirtualFileSystemProvider } from './k8s/vfs/kuberesources.virtualfs';
import { KubernetesResourceLinkProvider } from './k8s/vfs/kuberesources.linkprovider';
import { K8S_RESOURCE_SCHEME, K8S_RESOURCE_SCHEME_READONLY, KubernetesResourceVirtualFileSystemProvider } from './k8s/vfs/kuberesources.virtualfs';
import { Oc } from './oc/ocWrapper';
import { OdoPreference } from './odo/odoPreference';

// eslint-disable-next-line @typescript-eslint/no-empty-function
// this method is called when your extension is deactivated
Expand Down Expand Up @@ -274,6 +275,8 @@ export async function activate(extensionContext: ExtensionContext): Promise<unkn
void startTelemetry(extensionContext);
await verifyBinariesInRemoteContainer();

void OdoPreference.Instance.getRegistries(); // Initializes '~/.odo/preference.json', if not initialized yet

// Wait for finishing Kube Config setup
await setKubeConfigPromise;

Expand Down
174 changes: 174 additions & 0 deletions src/odo/odoPreference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import * as fs from 'fs/promises';
import * as YAML from 'js-yaml';
import * as path from 'path';
import { TelemetryProps } from '../telemetry';
import { Platform } from '../util/platform';
import { Registry } from './componentType';


type OdoRegistryObject = {
Name: string;
URL: string;
secure: boolean;
};

type OdoSettingsObject = {
RegistryList: OdoRegistryObject[];
ConsentTelemetry;
};

type OdoPreferenceObject = {
kind: string,
apiversion: string,
OdoSettings: OdoSettingsObject;
};

export class OdoPreferenceError extends Error {
constructor(message: string, public readonly telemetryMessage = message, public readonly parent?, public readonly telemetryProps: TelemetryProps = {}) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
}

export class OdoPreference {
private static instance: OdoPreference;

public static DEFAULT_DEVFILE_REGISTRY_NAME: string = 'DefaultDevfileRegistry';
public static DEFAULT_DEVFILE_REGISTRY_URL: string = 'https://registry.devfile.io';
public static DEFAULT_DEVFILE_REGISTRY_SECURE: boolean = false;

private static DefaultOdoPreference: OdoPreferenceObject = {
kind: 'Preference',
apiversion: 'odo.dev/v1alpha1',
OdoSettings: {
RegistryList: [
{
Name: OdoPreference.DEFAULT_DEVFILE_REGISTRY_NAME,
URL: OdoPreference.DEFAULT_DEVFILE_REGISTRY_URL,
secure: OdoPreference.DEFAULT_DEVFILE_REGISTRY_SECURE
} as OdoRegistryObject
],
ConsentTelemetry: false
} as OdoSettingsObject
};


public static get Instance(): OdoPreference {
if (!OdoPreference.instance) {
OdoPreference.instance = new OdoPreference();
}
return OdoPreference.instance;
}

private getOdoPreferenceFile(): string {
// This value can be provided to set a seperate directory for users 'homedir' resolution
// note for mocking purpose ONLY
const customHomeDir = Platform.ENV.CUSTOM_HOMEDIR;
const configFileName = 'preference.yaml';

if (customHomeDir && customHomeDir.length > 0) {
return path.join(customHomeDir, '.odo', configFileName);
}

return path.join(Platform.getUserHomePath(), '.odo', configFileName);
}

public async getRegistries(): Promise<Registry[]> {
const odoPreference = await this.readOdoPreference();
return odoPreference.OdoSettings.RegistryList.map((r) => {
return {
name: r.Name,
url: r.URL,
secure: r.secure } as Registry;
});
}

public async addRegistry(name: string, url: string, token: string): Promise<Registry> {
const odoPreference = await this.readOdoPreference();
odoPreference.OdoSettings.RegistryList.push({
Name: name,
URL: url,
secure: !!token
} as never);
await this.writeOdoPreference(odoPreference);
return {
name,
secure: !!token,
url,
};
}

public async removeRegistry(name: string): Promise<void> {
const odoPreference = await this.readOdoPreference();
odoPreference.OdoSettings.RegistryList.splice(
odoPreference.OdoSettings.RegistryList.findIndex((registry) => registry.Name === name), 1
);
await this.writeOdoPreference(odoPreference);
}

private async readOdoPreference(): Promise<OdoPreferenceObject> {
let mergedPreference = OdoPreference.DefaultOdoPreference;
const odoPreferenceFilePath = this.getOdoPreferenceFile();
let isToBeReWritten: boolean = false;
try {
const odoPreferenceFile = await fs.readFile(odoPreferenceFilePath, 'utf8');
const odoPreferences = YAML.load(odoPreferenceFile) as OdoPreferenceObject;

// If `odoPreferences.OdoSettings.RegistryList` is `null` or doesn't contain the `DefaultDevfileRegistry` item
// we have to recover at least the 'DefaultDevfileRegistry` item on it because this whole list will be replaced
if (!odoPreferences.OdoSettings.RegistryList) {
odoPreferences.OdoSettings.RegistryList = OdoPreference.DefaultOdoPreference.OdoSettings.RegistryList;
isToBeReWritten = true;
} else {
if (!odoPreferences.OdoSettings.RegistryList.find((r) => r.Name === OdoPreference.DEFAULT_DEVFILE_REGISTRY_NAME)) {
odoPreferences.OdoSettings.RegistryList.push(OdoPreference.DefaultOdoPreference.OdoSettings.RegistryList[0]);
isToBeReWritten = true;
}
}

mergedPreference = { ...mergedPreference, ...odoPreferences };
} catch {
isToBeReWritten = true;
}

if(isToBeReWritten) {
await this.writeOdoPreference(mergedPreference);
}

return mergedPreference;
}

private async dirExists(path: string): Promise<boolean> {
try {
if ((await fs.stat(path)).isDirectory()) {
return true;
}
} catch {
// Ignore
}
return false;
}

private async writeOdoPreference(preference: any): Promise<any> {
const odoPreferenceFilePath = this.getOdoPreferenceFile();
try {
const preferenceYaml = YAML.dump(preference);
const odoPreferenceDir = path.parse(odoPreferenceFilePath).dir;
if (!await this.dirExists(odoPreferenceDir)) {
await fs.mkdir(odoPreferenceDir, { recursive: true, mode: 0o750} );
}
await fs.writeFile(this.getOdoPreferenceFile(), preferenceYaml, 'utf8');
} catch (err) {
throw new OdoPreferenceError(
`An error occured while creating the ODO Preference file at ${odoPreferenceFilePath}!`,
`Error when creating the ODO Preference file: ${odoPreferenceFilePath}`,
err
);
}
}
}
32 changes: 0 additions & 32 deletions src/odo/odoWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { ToolsConfig } from '../tools';
import { ChildProcessUtil, CliExitData } from '../util/childProcessUtil';
import { VsCommandError } from '../vscommand';
import { Command } from './command';
import { Registry } from './componentType';
import { ComponentDescription } from './componentTypeDescription';
import { BindableService } from './odoTypes';

Expand Down Expand Up @@ -176,37 +175,6 @@ export class Odo {
);
}

private async loadRegistryFromPreferences() {
const cliData = await this.execute(new CommandText('odo', 'preference view -o json'));
const prefs = JSON.parse(cliData.stdout) as { registries: Registry[] };
return prefs.registries;
}

public getRegistries(): Promise<Registry[]> {
return this.loadRegistryFromPreferences();
}

public async addRegistry(name: string, url: string, token: string): Promise<Registry> {
const command = new CommandText('odo', `preference add registry ${name} ${url}`);
if (token) {
command.addOption(new CommandOption('--token', token));
}
await this.execute(command);
return {
name,
secure: !!token,
url,
};
}

public async removeRegistry(name: string): Promise<void> {
await this.execute(
new CommandText('odo', `preference remove registry ${name}`, [
new CommandOption('--force'),
]),
);
}

/**
* Deletes all the odo configuration files associated with the component (`.odo`, `devfile.yaml`) located at the given path.
*
Expand Down
11 changes: 6 additions & 5 deletions src/registriesView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
Registry
} from './odo/componentType';
import { StarterProject } from './odo/componentTypeDescription';
import { OdoPreference } from './odo/odoPreference';
import { Odo } from './odo/odoWrapper';
import { inputValue, quickBtn } from './util/inputValue';
import { Progress } from './util/progress';
Expand Down Expand Up @@ -77,8 +78,8 @@ export class ComponentTypesView implements TreeDataProvider<ComponentType> {
let children: ComponentType[] = [];
if (!parent) {
const result = await this.getRegistries();
const newChildren = result.filter((reg) => reg.name === 'DefaultDevfileRegistry')
.concat(result.filter((reg) => reg.name !== 'DefaultDevfileRegistry').sort());
const newChildren = result.filter((reg) => reg.name === OdoPreference.DEFAULT_DEVFILE_REGISTRY_NAME)
.concat(result.filter((reg) => reg.name !== OdoPreference.DEFAULT_DEVFILE_REGISTRY_NAME).sort());
children = newChildren;
}
return children;
Expand Down Expand Up @@ -326,9 +327,9 @@ export class ComponentTypesView implements TreeDataProvider<ComponentType> {

const devfileInfos = await DevfileRegistry.Instance.getDevfileInfoList(regURL);
if (devfileInfos.length > 0) {
const newRegistry = await Odo.Instance.addRegistry(regName, regURL, token);
const newRegistry = await OdoPreference.Instance.addRegistry(regName, regURL, token);
if (registryContext) {
await Odo.Instance.removeRegistry(registryContext.name);
await OdoPreference.Instance.removeRegistry(registryContext.name);
ComponentTypesView.instance.replaceRegistry(registryContext, newRegistry);
} else {
ComponentTypesView.instance.addRegistry(newRegistry);
Expand All @@ -353,7 +354,7 @@ export class ComponentTypesView implements TreeDataProvider<ComponentType> {
const yesNo = await window.showInformationMessage(
`Remove registry '${registry.name}'?`, 'Yes', 'No');
if (yesNo === 'Yes') {
await Odo.Instance.removeRegistry(registry.name);
await OdoPreference.Instance.removeRegistry(registry.name);
ComponentTypesView.instance.removeRegistry(registry);
ComponentTypesView.instance.subject.next();
}
Expand Down
4 changes: 3 additions & 1 deletion test/integration/alizerWrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { promisify } from 'util';
import { Uri, workspace } from 'vscode';
import { Alizer } from '../../src/alizer/alizerWrapper';
import { Oc } from '../../src/oc/ocWrapper';
import { OdoPreference } from '../../src/odo/odoPreference';
import { Odo } from '../../src/odo/odoWrapper';
import { LoginUtil } from '../../src/util/loginUtil';

Expand All @@ -22,6 +23,7 @@ suite('./alizer/alizerWrapper.ts', function () {
const password = process.env.CLUSTER_PASSWORD || 'developer';

suiteSetup(async function () {
await OdoPreference.Instance.getRegistries(); // This creates the ODO preferences, if needed
if (isOpenShift) {
try {
await LoginUtil.Instance.logout();
Expand Down Expand Up @@ -121,7 +123,7 @@ suite('./alizer/alizerWrapper.ts', function () {
await Odo.Instance.createComponentFromTemplateProject(
tmpFolder, 'my-component', 8080,
COMPONENT_TYPE, COMPONENT_VERSION,
'DefaultDevfileRegistry', 'dotnet50-example');
OdoPreference.DEFAULT_DEVFILE_REGISTRY_NAME, 'dotnet50-example');
try {
await fs.access(path.join(tmpFolder, 'devfile.yaml'));
} catch {
Expand Down
6 changes: 4 additions & 2 deletions test/integration/command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { CliChannel } from '../../src/cli';
import { Oc } from '../../src/oc/ocWrapper';
import { Command } from '../../src/odo/command';
import { ComponentDescription } from '../../src/odo/componentTypeDescription';
import { OdoPreference } from '../../src/odo/odoPreference';
import { Odo } from '../../src/odo/odoWrapper';
import { LoginUtil } from '../../src/util/loginUtil';

Expand All @@ -34,6 +35,7 @@ suite('odo commands integration', function () {
const password = process.env.CLUSTER_PASSWORD || 'developer';

suiteSetup(async function() {
await OdoPreference.Instance.getRegistries(); // This creates the ODO preferences, if needed
if (isOpenShift) {
try {
await LoginUtil.Instance.logout();
Expand Down Expand Up @@ -87,7 +89,7 @@ suite('odo commands integration', function () {
Command.createLocalComponent(
componentType,
'2.0.0',
'DefaultDevfileRegistry',
OdoPreference.DEFAULT_DEVFILE_REGISTRY_NAME,
componentName,
8080,
componentStarterProject,
Expand Down Expand Up @@ -409,7 +411,7 @@ suite('odo commands integration', function () {
Command.createLocalComponent(
componentType,
'2.1.1',
'DefaultDevfileRegistry',
OdoPreference.DEFAULT_DEVFILE_REGISTRY_NAME,
componentName,
undefined,
componentStarterProject,
Expand Down
Loading

0 comments on commit 6116cf9

Please sign in to comment.