From 2bd26f7f398092a3fb02264f907a0421da4d3b08 Mon Sep 17 00:00:00 2001 From: Victor Rubezhny Date: Tue, 9 Jul 2024 19:50:26 +0200 Subject: [PATCH] "Components" sidebar section take a while to load #3850 Fixes: #3850 Signed-off-by: Victor Rubezhny --- .../devfileRegistryWrapper.ts | 4 +- src/extension.ts | 9 +- src/odo/odoPreference.ts | 158 ++++++++++++++++++ src/odo/odoWrapper.ts | 32 ---- src/registriesView.ts | 16 +- test/integration/alizerWrapper.test.ts | 2 + test/integration/command.test.ts | 2 + .../devfileRegistryWrapper.test.ts | 74 ++++++-- test/integration/odoWrapper.test.ts | 88 +++++----- 9 files changed, 291 insertions(+), 94 deletions(-) create mode 100644 src/odo/odoPreference.ts diff --git a/src/devfile-registry/devfileRegistryWrapper.ts b/src/devfile-registry/devfileRegistryWrapper.ts index 28be1cc08..cc83c489a 100644 --- a/src/devfile-registry/devfileRegistryWrapper.ts +++ b/src/devfile-registry/devfileRegistryWrapper.ts @@ -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'; @@ -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); diff --git a/src/extension.ts b/src/extension.ts index 6d62675bd..d68e8f0b2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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, @@ -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 @@ -274,6 +275,8 @@ export async function activate(extensionContext: ExtensionContext): Promise 0) { + return path.join(customHomeDir, '.odo', configFileName); + } + + return path.join(Platform.getUserHomePath(), '.odo', configFileName); + } + + public async getRegistries(): Promise { + 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 { + 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 { + 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 { + let mergedPreference = 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 = DefaultOdoPreference.OdoSettings.RegistryList; + isToBeReWritten = true; + } else { + if (!odoPreferences.OdoSettings.RegistryList.find((r) => r.Name === 'DefaultDevfileRegistry')) { + odoPreferences.OdoSettings.RegistryList.push(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 { + try { + if ((await fs.stat(path)).isDirectory()) { + return true; + } + } catch { + // Ignore + } + return false; + } + + private async writeOdoPreference(preference: any): Promise { + 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 { + void vscode.window.showErrorMessage(`An error occured while creating the ODO Preference file at ${odoPreferenceFilePath}!`); + } + } +} \ No newline at end of file diff --git a/src/odo/odoWrapper.ts b/src/odo/odoWrapper.ts index cf3d1e4e8..f8d66c3a3 100644 --- a/src/odo/odoWrapper.ts +++ b/src/odo/odoWrapper.ts @@ -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'; @@ -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 { - return this.loadRegistryFromPreferences(); - } - - public async addRegistry(name: string, url: string, token: string): Promise { - 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 { - 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. * diff --git a/src/registriesView.ts b/src/registriesView.ts index 9fe436f52..72f5b00e8 100644 --- a/src/registriesView.ts +++ b/src/registriesView.ts @@ -14,6 +14,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'; @@ -121,14 +122,21 @@ export class ComponentTypesView implements TreeDataProvider { private async getRegistries(): Promise { + /* eslint-disable no-console */ + console.log('getRegistries: enter'); try { if (!this.registries) { + console.log('getRegistries: "registries" is not set, reading from prefs...'); this.registries = await DevfileRegistry.Instance.getRegistries(); } - } catch { + } catch(_err) { + console.log(`getRegistries: reading from prefs failed: ${_err}`); + this.registries = []; } + console.log(`getRegistries: return ${this.registries.length} registries`); return this.registries; + /* eslint-enable no-console */ } public getListOfRegistries(): Registry[] { @@ -327,9 +335,9 @@ export class ComponentTypesView implements TreeDataProvider { 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); @@ -356,7 +364,7 @@ export class ComponentTypesView implements TreeDataProvider { 'No', ); if (yesNo === 'Yes') { - await Odo.Instance.removeRegistry(registry.name); + await OdoPreference.Instance.removeRegistry(registry.name); ComponentTypesView.instance.removeRegistry(registry); } } diff --git a/test/integration/alizerWrapper.test.ts b/test/integration/alizerWrapper.test.ts index 22c485d8c..fbaf7c58f 100644 --- a/test/integration/alizerWrapper.test.ts +++ b/test/integration/alizerWrapper.test.ts @@ -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'; @@ -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(); diff --git a/test/integration/command.test.ts b/test/integration/command.test.ts index 697e3923b..4d0df25c3 100644 --- a/test/integration/command.test.ts +++ b/test/integration/command.test.ts @@ -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'; @@ -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(); diff --git a/test/integration/devfileRegistryWrapper.test.ts b/test/integration/devfileRegistryWrapper.test.ts index 22d4ef1c2..09ab14354 100644 --- a/test/integration/devfileRegistryWrapper.test.ts +++ b/test/integration/devfileRegistryWrapper.test.ts @@ -6,26 +6,78 @@ import { expect } from 'chai'; import { suite, suiteSetup } from 'mocha'; import { DevfileRegistry } from '../../src/devfile-registry/devfileRegistryWrapper'; +import { OdoPreference } from '../../src/odo/odoPreference'; -suite('./devfile-registry/devfileRegistryWrapper.ts', function () { +suite('Devfile Registry Wrapper tests', function () { + const DEFAULT_DEVFILE_REGISTRY_NAME = 'DefaultDevfileRegistry'; + const DEFAULT_DEVFILE_REGISTRY_URL = 'https://registry.devfile.io'; + + const TEST_REGISTRY_NAME = 'TestRegistry'; + const TEST_REGISTRY_URL = 'https://example.org'; suiteSetup(async function () { + await OdoPreference.Instance.getRegistries(); // This creates the ODO preferences, if needed }); suiteTeardown(async function () { }); - test('getRegistryDevfileInfos()', async function () { - const devfileInfos = await DevfileRegistry.Instance.getRegistryDevfileInfos(); - // TODO: improve - expect(devfileInfos).to.not.be.empty; + suite('Devfile Registry Wrapper tests', function () { + + test('getRegistryDevfileInfos()', async function () { + const devfileInfos = await DevfileRegistry.Instance.getRegistryDevfileInfos(); + // TODO: improve + expect(devfileInfos).to.not.be.empty; + }); + + test('getRegistryDevfile()', async function() { + const devfileInfos = await DevfileRegistry.Instance.getRegistryDevfileInfos(); + const devfile = await DevfileRegistry.Instance.getRegistryDevfile( + devfileInfos[0].registry.url, devfileInfos[0].name, 'latest'); + // some Devfiles don't have starter projects, but the first Devfile is likely .NET + expect(devfile.starterProjects).is.not.empty; + }); }); - test('getRegistryDevfile()', async function() { - const devfileInfos = await DevfileRegistry.Instance.getRegistryDevfileInfos(); - const devfile = await DevfileRegistry.Instance.getRegistryDevfile( - devfileInfos[0].registry.url, devfileInfos[0].name, 'latest'); - // some Devfiles don't have starter projects, but the first Devfile is likely .NET - expect(devfile.starterProjects).is.not.empty; + suite('Devfile Registries', function () { + + suiteSetup(async function () { + if (registries.find((registry) => registry.name === TEST_REGISTRY_NAME)) { + await OdoPreference.Instance.removeRegistry(TEST_REGISTRY_NAME); + } + }); + + suiteTeardown(async function () { + try { + await OdoPreference.Instance.removeRegistry(TEST_REGISTRY_NAME); + } catch { + // do nothing, it's probably already deleted + } + }); + + test('getRegistries()', async function () { + const registries = await OdoPreference.Instance.getRegistries(); + expect(registries).to.be.of.length(1); + const registryNames = registries.map((registry) => registry.name); + expect(registryNames).to.contain(DEFAULT_DEVFILE_REGISTRY_NAME); + const registryUrls = registries.map((registry) => registry.url); + expect(registryUrls).to.contain(DEFAULT_DEVFILE_REGISTRY_URL); + }); + + test('addRegistry()', async function () { + await OdoPreference.Instance.addRegistry(TEST_REGISTRY_NAME, TEST_REGISTRY_URL, undefined); + const registries = await OdoPreference.Instance.getRegistries(); + expect(registries).to.be.of.length(2); + const registryNames = registries.map((registry) => registry.name); + expect(registryNames).to.contain(TEST_REGISTRY_NAME); + }); + + test('removeRegistry()', async function () { + await OdoPreference.Instance.removeRegistry(TEST_REGISTRY_NAME); + const registries = await OdoPreference.Instance.getRegistries(); + expect(registries).to.be.of.length(1); + const registryNames = registries.map((registry) => registry.name); + expect(registryNames).to.not.contain(TEST_REGISTRY_NAME); + }); }); }); diff --git a/test/integration/odoWrapper.test.ts b/test/integration/odoWrapper.test.ts index ba26fe9ad..ad834fc85 100644 --- a/test/integration/odoWrapper.test.ts +++ b/test/integration/odoWrapper.test.ts @@ -12,8 +12,10 @@ import * as tmp from 'tmp'; import { promisify } from 'util'; import { Uri, workspace } from 'vscode'; import { Oc } from '../../src/oc/ocWrapper'; +import { OdoPreference } from '../../src/odo/odoPreference'; import { Odo } from '../../src/odo/odoWrapper'; import { LoginUtil } from '../../src/util/loginUtil'; +import { Platform } from '../../src/util/platform'; suite('./odo/odoWrapper.ts', function () { const isOpenShift: boolean = Boolean(parseInt(process.env.IS_OPENSHIFT, 10)) || false; @@ -21,7 +23,48 @@ suite('./odo/odoWrapper.ts', function () { const username = process.env.CLUSTER_USER || 'developer'; const password = process.env.CLUSTER_PASSWORD || 'developer'; + async function dirExists(path: string): Promise { + try { + if ((await fs.stat(path)).isDirectory()) { + return true; + } + } catch { + // Ignore + } + return false; + } + + async function fileExists(path: string): Promise { + try { + if ((await fs.stat(path)).isFile()) { + return true; + } + } catch { + // Ignore + } + return false; + } + + async function checkOdoPreference() { + const odoUserPreferenceDir = `${Platform.getUserHomePath()}/.odo`; + const odoUserPreferenceFile = `${odoUserPreferenceDir}/preference.yaml`; + if (await dirExists(odoUserPreferenceDir)) { + if (await fileExists(odoUserPreferenceFile)) { + /* eslint-disable no-console */ + console.log(`checkOdoPreference: ODO preference file exists: ${odoUserPreferenceFile}`); + console.log(`checkOdoPreference: File Content:\n ${await fs.readFile(odoUserPreferenceFile, 'utf8')}`); + /* eslint-enable no-console */ + } else { + fail(`checkOdoPreference: ODO preference file not found: ${odoUserPreferenceFile}! `); + } + } else { + fail(`checkOdoPreference: ODO preference directory not found: ${odoUserPreferenceDir}!`) + } + } + suiteSetup(async function () { + await OdoPreference.Instance.getRegistries(); // This creates the ODO preferences, if needed + await checkOdoPreference(); if (isOpenShift) { try { await LoginUtil.Instance.logout(); @@ -57,6 +100,7 @@ suite('./odo/odoWrapper.ts', function () { let tmpFolder2: Uri; suiteSetup(async function () { + await checkOdoPreference(); await Oc.Instance.createProject(project1); tmpFolder1 = Uri.parse(await promisify(tmp.dir)()); tmpFolder2 = Uri.parse(await promisify(tmp.dir)()); @@ -102,48 +146,6 @@ suite('./odo/odoWrapper.ts', function () { }); }); - suite('registries', function () { - const TEST_REGISTRY = 'TestRegistry'; - - suiteSetup(async function () { - const registries = await Odo.Instance.getRegistries(); - if (registries.find((registry) => registry.name === TEST_REGISTRY)) { - await Odo.Instance.removeRegistry(TEST_REGISTRY); - } - }); - - suiteTeardown(async function () { - try { - await Odo.Instance.removeRegistry(TEST_REGISTRY); - } catch { - // do nothing, it's probably already deleted - } - }); - - test('getRegistries()', async function () { - const registries = await Odo.Instance.getRegistries(); - expect(registries).to.be.of.length(1); - const registryNames = registries.map((registry) => registry.name); - expect(registryNames).to.contain('DefaultDevfileRegistry'); - }); - - test('addRegistry()', async function () { - await Odo.Instance.addRegistry(TEST_REGISTRY, 'https://example.org', undefined); - const registries = await Odo.Instance.getRegistries(); - expect(registries).to.be.of.length(2); - const registryNames = registries.map((registry) => registry.name); - expect(registryNames).to.contain(TEST_REGISTRY); - }); - - test('removeRegistry()', async function () { - await Odo.Instance.removeRegistry(TEST_REGISTRY); - const registries = await Odo.Instance.getRegistries(); - expect(registries).to.be.of.length(1); - const registryNames = registries.map((registry) => registry.name); - expect(registryNames).to.not.contain(TEST_REGISTRY); - }); - }); - suite('create component', function() { const COMPONENT_TYPE = 'dotnet50'; @@ -152,6 +154,7 @@ suite('./odo/odoWrapper.ts', function () { let tmpFolder: string; suiteSetup(async function() { + await checkOdoPreference(); tmpFolder = await promisify(tmp.dir)(); }); @@ -206,6 +209,7 @@ suite('./odo/odoWrapper.ts', function () { let componentFolder: string; setup(async function() { + await checkOdoPreference(); componentFolder = await promisify(tmp.dir)(); await Odo.Instance.createComponentFromFolder( 'nodejs',