From 99c31d4800b5a04293d6d22a66afc6a2b9a1b2c2 Mon Sep 17 00:00:00 2001 From: Victor Rubezhny Date: Sun, 15 Sep 2024 19:38:36 +0200 Subject: [PATCH] "Components" sidebar section take a while to load #3850 Fixes: #3850 Signed-off-by: Victor Rubezhny --- .../devfileRegistryWrapper.ts | 4 +- src/odo/odoPreference.ts | 158 ++++++++++++++++++ src/odo/odoWrapper.ts | 32 ---- src/registriesView.ts | 16 +- test/integration/odoWrapper.test.ts | 59 ++++++- 5 files changed, 223 insertions(+), 46 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/odo/odoPreference.ts b/src/odo/odoPreference.ts new file mode 100644 index 000000000..2ce915f3c --- /dev/null +++ b/src/odo/odoPreference.ts @@ -0,0 +1,158 @@ +/*----------------------------------------------------------------------------------------------- + * 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 * as vscode from 'vscode'; +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; +}; + +const DefaultOdoPreference: OdoPreferenceObject = { + kind: 'Preference', + apiversion: 'odo.dev/v1alpha1', + OdoSettings: { + RegistryList: [ + { + Name: 'DefaultDevfileRegistry', + URL: 'https://registry.devfile.io', + secure: false + } as OdoRegistryObject + ], + ConsentTelemetry: false + } as OdoSettingsObject +}; + +export class OdoPreference { + private static instance: OdoPreference; + + 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 { + 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/odoWrapper.test.ts b/test/integration/odoWrapper.test.ts index ba26fe9ad..4cc94275b 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; @@ -102,42 +104,43 @@ suite('./odo/odoWrapper.ts', function () { }); }); + // TODO: Move this out of odoWrapper suite('registries', function () { const TEST_REGISTRY = 'TestRegistry'; suiteSetup(async function () { - const registries = await Odo.Instance.getRegistries(); + const registries = await OdoPreference.Instance.getRegistries(); if (registries.find((registry) => registry.name === TEST_REGISTRY)) { - await Odo.Instance.removeRegistry(TEST_REGISTRY); + await OdoPreference.Instance.removeRegistry(TEST_REGISTRY); } }); suiteTeardown(async function () { try { - await Odo.Instance.removeRegistry(TEST_REGISTRY); + await OdoPreference.Instance.removeRegistry(TEST_REGISTRY); } catch { // do nothing, it's probably already deleted } }); test('getRegistries()', async function () { - const registries = await Odo.Instance.getRegistries(); + const registries = await OdoPreference.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(); + await OdoPreference.Instance.addRegistry(TEST_REGISTRY, 'https://example.org', 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); }); test('removeRegistry()', async function () { - await Odo.Instance.removeRegistry(TEST_REGISTRY); - const registries = await Odo.Instance.getRegistries(); + await OdoPreference.Instance.removeRegistry(TEST_REGISTRY); + 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); @@ -153,13 +156,53 @@ suite('./odo/odoWrapper.ts', function () { suiteSetup(async function() { tmpFolder = await promisify(tmp.dir)(); + await OdoPreference.Instance.getRegistries(); // This creates the ODO preferences, if needed }); suiteTeardown(async function() { await fs.rm(tmpFolder, { recursive: true, force: true }); }); + + 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; + } + + test('createComponentFromTemplateProject()', async function() { + const odoUserPreferenceDir = `${Platform.getUserHomePath()}/.odo`; + const odoUserPreferenceFile = `${odoUserPreferenceDir}/preference.yaml`; + if (await dirExists(odoUserPreferenceDir)) { + if (await fileExists(odoUserPreferenceFile)) { + /* eslint-disable no-console */ + console.log(`createComponentFromTemplateProject(): ODO preference file esixt: ${odoUserPreferenceFile}`); + console.log(`crea: File Content:\n ${await fs.readFile(odoUserPreferenceFile, 'utf8')}`); + /* eslint-enable no-console */ + } else { + fail(`createComponentFromTemplateProject(): ODO preference file not found: ${odoUserPreferenceFile}! `); + } + } else { + fail(`createComponentFromTemplateProject(): ODO preference directory not found: ${odoUserPreferenceDir}!`) + } + await Odo.Instance.createComponentFromTemplateProject(tmpFolder, 'my-component', 8080, COMPONENT_TYPE, COMPONENT_VERSION, 'DefaultDevfileRegistry', 'dotnet50-example'); try { await fs.access(path.join(tmpFolder, 'devfile.yaml'));