chore: add strict to Compose, Lima, Kubectl, KubeContext and Registries extensions (#7385)

* chore: add strictNullChecks to Compose, Lima, Kubectl and KubeContext extensions

Signed-off-by: lstocchi <lstocchi@redhat.com>

* chore: convert strictNullChecks to strict

Signed-off-by: lstocchi <lstocchi@redhat.com>

* fix: update tsconfig in registries

Signed-off-by: lstocchi <lstocchi@redhat.com>

* fix: ignore mustache error

Signed-off-by: lstocchi <lstocchi@redhat.com>

* fix: typecheck

Signed-off-by: lstocchi <lstocchi@redhat.com>

---------

Signed-off-by: lstocchi <lstocchi@redhat.com>
This commit is contained in:
Luca Stocchi 2024-05-31 15:10:33 +02:00 committed by GitHub
parent ff705d6134
commit 6b69ae4d54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 142 additions and 59 deletions

View file

@ -155,8 +155,9 @@
"shell-path": "^3.0.0"
},
"devDependencies": {
"adm-zip": "^0.5.12",
"@podman-desktop/api": "^0.0.1",
"@types/mustache": "^4.2.5",
"adm-zip": "^0.5.12",
"mkdirp": "^3.0.1",
"vite": "^5.2.12",
"vitest": "^1.6.0"

View file

@ -46,8 +46,8 @@ export async function installBinaryToSystem(binaryPath: string, binaryName: stri
// Create the appropriate destination path (Windows uses AppData/Local, Linux and Mac use /usr/local/bin)
// and the appropriate command to move the binary to the destination path
let destinationPath: string;
let args: string[];
let command: string;
let args: string[] = [];
let command: string | undefined;
if (system === 'win32') {
destinationPath = path.join(os.homedir(), 'AppData', 'Local', 'Microsoft', 'WindowsApps', `${binaryName}.exe`);
command = 'copy';
@ -74,6 +74,9 @@ export async function installBinaryToSystem(binaryPath: string, binaryName: stri
}
try {
if (!command) {
throw new Error('No command defined');
}
// Use admin prileges / ask for password for copying to /usr/local/bin
await extensionApi.process.exec(command, args, { isAdmin: true });
console.log(`Successfully installed '${binaryName}' binary.`);

View file

@ -19,6 +19,7 @@
import { promises } from 'node:fs';
import type * as extensionApi from '@podman-desktop/api';
// @ts-expect-error ignore type error https://github.com/janl/mustache.js/issues/797
import mustache from 'mustache';
import type { OS } from './os';

View file

@ -50,7 +50,7 @@ export class Detect {
// so let's set the env PATH to the system path before running the command
// to avoid the storage/bin folder to be appended to the PATH
try {
await extensionApi.process.exec('docker-compose', ['--version'], { env: { PATH: process.env.PATH } });
await extensionApi.process.exec('docker-compose', ['--version'], { env: { PATH: process.env.PATH ?? '' } });
return true;
} catch (e) {
return false;

View file

@ -56,13 +56,15 @@ const fsActual = await vi.importActual<typeof import('node:fs')>('node:fs');
const resultREST = JSON.parse(
fsActual.readFileSync(path.resolve(__dirname, '../tests/resources/compose-github-release-all.json'), 'utf8'),
);
const releases: ComposeGithubReleaseArtifactMetadata[] = resultREST.map(release => {
return {
label: release.name || release.tag_name,
tag: release.tag_name,
id: release.id,
};
});
const releases: ComposeGithubReleaseArtifactMetadata[] = resultREST.map(
(release: { name: string; tag_name: string; id: number }) => {
return {
label: release.name || release.tag_name,
tag: release.tag_name,
id: release.id,
};
},
);
beforeEach(() => {
vi.resetAllMocks();

View file

@ -95,7 +95,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
}
// Log if it's downloaded and what version is being selected for download (can be either latest, or chosen by user)
telemetryLogger.logUsage('compose.onboarding.checkDownloadedCommand', {
telemetryLogger?.logUsage('compose.onboarding.checkDownloadedCommand', {
downloaded: isDownloaded === '' ? false : true,
version: composeVersionMetadata?.tag,
});
@ -113,7 +113,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
composeVersionMetadata = await composeDownload.getLatestVersionAsset();
}
let downloaded: boolean;
let downloaded: boolean = false;
try {
// Download
await composeDownload.download(composeVersionMetadata);
@ -124,7 +124,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
} finally {
// Make sure we log the telemetry even if we encounter an error
// If we have downloaded the binary, we can log it as being succcessfully downloaded
telemetryLogger.logUsage('compose.onboarding.downloadCommand', {
telemetryLogger?.logUsage('compose.onboarding.downloadCommand', {
successful: downloaded,
version: composeVersionMetadata?.tag,
});
@ -147,7 +147,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
}
// Log the telemetry that the user picked a version
telemetryLogger.logUsage('compose.onboarding.promptUserForVersion', {
telemetryLogger?.logUsage('compose.onboarding.promptUserForVersion', {
version: composeRelease?.tag,
});
@ -165,12 +165,15 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
async () => {
// This is TEMPORARY until we re-add the "Installing compose system wide" toggle again
// We will just call the handler function directly
let installed: boolean;
let installed: boolean = false;
try {
await handler.installComposeBinary(detect, extensionContext);
// update the cli version
const versionInstalled = composeVersionMetadata.tag.replace('v', '');
composeCliTool.updateVersion({
const versionInstalled = composeVersionMetadata?.tag.replace('v', '');
if (!versionInstalled) {
return;
}
composeCliTool?.updateVersion({
version: versionInstalled,
});
// if installed version is the newest, dispose the updater
@ -180,7 +183,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
}
installed = true;
} finally {
telemetryLogger.logUsage('compose.onboarding.installSystemWideCommand', {
telemetryLogger?.logUsage('compose.onboarding.installSystemWideCommand', {
successful: installed,
});
}
@ -263,10 +266,10 @@ async function postActivate(
// download, install system wide and update cli version
await composeDownload.download(lastReleaseMetadata);
await installBinaryToSystem(binaryPath, 'docker-compose');
composeCliTool.updateVersion({
composeCliTool?.updateVersion({
version: lastReleaseVersion,
});
composeCliToolUpdaterDisposable.dispose();
composeCliToolUpdaterDisposable?.dispose();
},
});
}

View file

@ -94,7 +94,9 @@ export async function installComposeBinary(
}
function getComposeBinarySetting(): boolean {
return extensionApi.configuration.getConfiguration('compose.binary').get<boolean>('installComposeSystemWide');
return (
extensionApi.configuration.getConfiguration('compose.binary').get<boolean>('installComposeSystemWide') ?? false
);
}
// Update both the configuration as well as context values

View file

@ -1,5 +1,6 @@
{
"compilerOptions": {
"strict": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "Node",

View file

@ -0,0 +1,22 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/
declare module 'shell-path' {
export const shellPath: () => Promise<string>;
export const shellPathSync: () => string;
}

View file

@ -21,6 +21,7 @@ import * as path from 'node:path';
import type { AuditRecord, AuditRequestItems, AuditResult, CancellationToken } from '@podman-desktop/api';
import * as extensionApi from '@podman-desktop/api';
// @ts-expect-error ignore type error https://github.com/janl/mustache.js/issues/797
import mustache from 'mustache';
import { parseAllDocuments } from 'yaml';

View file

@ -21,6 +21,11 @@ import * as fs from 'node:fs';
import * as extensionApi from '@podman-desktop/api';
import * as jsYaml from 'js-yaml';
interface KubeConfig {
'current-context': string;
contexts: KubeContext[];
}
interface KubeContext {
name: string;
}
@ -50,7 +55,7 @@ export async function updateContext(
const kubeConfigRawContent = await fs.promises.readFile(kubeconfigFile, 'utf-8');
// parse the content using jsYaml
const kubeConfig = jsYaml.load(kubeConfigRawContent);
const kubeConfig = jsYaml.load(kubeConfigRawContent) as KubeConfig;
// get the current context
const currentContext = kubeConfig?.['current-context'];
@ -129,10 +134,12 @@ async function setContext(newContext: string): Promise<void> {
const kubeConfigRawContent = fs.readFileSync(file, 'utf-8');
// parse the content using jsYaml
const kubeConfig = jsYaml.load(kubeConfigRawContent);
const kubeConfig = jsYaml.load(kubeConfigRawContent) as KubeConfig;
// update the context
kubeConfig['current-context'] = newContext;
if (kubeConfig) {
kubeConfig['current-context'] = newContext;
}
// write again the file using promises fs
await fs.promises.writeFile(

View file

@ -1,5 +1,6 @@
{
"compilerOptions": {
"strict": true,
"lib": ["ES2017", "webworker"],
"sourceMap": true,
"rootDir": "src",

View file

@ -46,8 +46,8 @@ export async function installBinaryToSystem(binaryPath: string, binaryName: stri
// Create the appropriate destination path (Windows uses AppData/Local, Linux and Mac use /usr/local/bin)
// and the appropriate command to move the binary to the destination path
let destinationPath: string;
let args: string[];
let command: string;
let args: string[] = [];
let command: string | undefined;
if (system === 'win32') {
destinationPath = path.join(os.homedir(), 'AppData', 'Local', 'Microsoft', 'WindowsApps', `${binaryName}.exe`);
command = 'copy';
@ -75,6 +75,9 @@ export async function installBinaryToSystem(binaryPath: string, binaryName: stri
try {
// Use admin prileges / ask for password for copying to /usr/local/bin
if (!command) {
throw new Error('No command defined');
}
await extensionApi.process.exec(command, args, { isAdmin: true });
console.log(`Successfully installed '${binaryName}' binary.`);
} catch (error) {

View file

@ -46,7 +46,9 @@ export class Detect {
// so let's set the env PATH to the system path before running the command
// to avoid the storage/bin folder to be appended to the PATH
try {
await extensionApi.process.exec('kubectl', ['version', '--client', 'true'], { env: { PATH: process.env.PATH } });
await extensionApi.process.exec('kubectl', ['version', '--client', 'true'], {
env: { PATH: process.env.PATH ?? '' },
});
return true;
} catch (e) {
return false;

View file

@ -56,13 +56,15 @@ const fsActual = await vi.importActual<typeof import('node:fs')>('node:fs');
const resultREST = JSON.parse(
fsActual.readFileSync(path.resolve(__dirname, '../tests/resources/kubectl-github-release-all.json'), 'utf8'),
);
const releases: KubectlGithubReleaseArtifactMetadata[] = resultREST.map(release => {
return {
label: release.name || release.tag_name,
tag: release.tag_name,
id: release.id,
};
});
const releases: KubectlGithubReleaseArtifactMetadata[] = resultREST.map(
(release: { name: string; tag_name: string; id: number }) => {
return {
label: release.name || release.tag_name,
tag: release.tag_name,
id: release.id,
};
},
);
beforeEach(() => {
vi.resetAllMocks();

View file

@ -31,7 +31,7 @@ import { KubectlGitHubReleases } from './kubectl-github-releases';
const extensionContext = {
subscriptions: [],
storagePath: '/tmp/kubectl-cli',
} as extensionApi.ExtensionContext;
} as unknown as extensionApi.ExtensionContext;
vi.mock('./cli-run', () => ({
installBinaryToSystem: vi.fn(),
@ -366,7 +366,7 @@ describe('postActivate', () => {
const deferredCliUpdate: Promise<CliToolUpdate> = new Promise<CliToolUpdate>(resolve => {
vi.mocked(extensionApi.cli.createCliTool).mockImplementation(() => {
return {
registerUpdate: listener => {
registerUpdate: (listener: CliToolUpdate) => {
resolve(listener);
return {
dispose: vi.fn(),
@ -387,7 +387,7 @@ describe('postActivate', () => {
const deferredCliUpdate: Promise<CliToolUpdate> = new Promise<CliToolUpdate>(resolve => {
vi.mocked(extensionApi.cli.createCliTool).mockImplementation(() => {
return {
registerUpdate: listener => {
registerUpdate: (listener: CliToolUpdate) => {
resolve(listener);
return {
dispose: disposeMock,
@ -409,7 +409,7 @@ describe('postActivate', () => {
const deferredCliUpdate: Promise<CliToolUpdate> = new Promise<CliToolUpdate>(resolve => {
vi.mocked(extensionApi.cli.createCliTool).mockImplementation(() => {
return {
registerUpdate: listener => {
registerUpdate: (listener: CliToolUpdate) => {
resolve(listener);
return {
dispose: vi.fn(),

View file

@ -113,7 +113,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
}
// Log if it's downloaded and what version is being selected for download (can be either latest, or chosen by user)
telemetryLogger.logUsage('kubectl.onboarding.checkDownloadedCommand', {
telemetryLogger?.logUsage('kubectl.onboarding.checkDownloadedCommand', {
downloaded: isDownloaded !== '',
version: kubectlVersionMetadata?.tag,
});
@ -131,14 +131,14 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
kubectlVersionMetadata = await kubectlDownload.getLatestVersionAsset();
}
let downloaded: boolean;
let downloaded: boolean = false;
try {
// Download
await kubectlDownload.download(kubectlVersionMetadata);
// We are all done, so we can set the context value to false / downloaded to true
extensionApi.context.setValue('kubectlIsNotDownloaded', false, 'onboarding');
kubectlCliTool.updateVersion({
kubectlCliTool?.updateVersion({
version: kubectlVersionMetadata.tag.slice(1),
});
// if installed version is the newest, dispose the updater
@ -150,7 +150,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
} finally {
// Make sure we log the telemetry even if we encounter an error
// If we have downloaded the binary, we can log it as being succcessfully downloaded
telemetryLogger.logUsage('kubectl.onboarding.downloadCommand', {
telemetryLogger?.logUsage('kubectl.onboarding.downloadCommand', {
successful: downloaded,
version: kubectlVersionMetadata?.tag,
});
@ -173,7 +173,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
}
// Log the telemetry that the user picked a version
telemetryLogger.logUsage('kubectl.onboarding.promptUserForVersion', {
telemetryLogger?.logUsage('kubectl.onboarding.promptUserForVersion', {
version: kubectlRelease?.tag,
});
@ -191,12 +191,12 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
async () => {
// This is TEMPORARY until we re-add the "Installing kubectl system wide" toggle again
// We will just call the handler function directly
let installed: boolean;
let installed: boolean = false;
try {
await handler.installKubectlBinary(detect, extensionContext);
installed = true;
} finally {
telemetryLogger.logUsage('kubectl.onboarding.installSystemWideCommand', {
telemetryLogger?.logUsage('kubectl.onboarding.installSystemWideCommand', {
successful: installed,
});
}
@ -334,10 +334,10 @@ async function postActivate(
// download, install system wide and update cli version
const binaryPath = await kubectlDownload.download(lastReleaseMetadata);
await installBinaryToSystem(binaryPath, 'kubectl');
kubectlCliTool.updateVersion({
kubectlCliTool?.updateVersion({
version: lastReleaseVersion,
});
kubectlCliToolUpdaterDisposable.dispose();
kubectlCliToolUpdaterDisposable?.dispose();
},
});
}

View file

@ -87,7 +87,9 @@ export async function installKubectlBinary(
}
function getKubectlBinarySetting(): boolean {
return extensionApi.configuration.getConfiguration('kubectl.binary').get<boolean>('installKubectlSystemWide');
return (
extensionApi.configuration.getConfiguration('kubectl.binary').get<boolean>('installKubectlSystemWide') ?? false
);
}
// Update both the configuration as well as context values

View file

@ -1,5 +1,6 @@
{
"compilerOptions": {
"strict": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "Node",
@ -12,5 +13,5 @@
"allowSyntheticDefaultImports": true,
"types": ["node"]
},
"include": ["src"]
"include": ["src", "types/*.d.ts"]
}

View file

@ -0,0 +1,22 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/
declare module 'shell-path' {
export const shellPath: () => Promise<string>;
export const shellPathSync: () => string;
}

View file

@ -97,8 +97,8 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
const socketName: string = configuration.getConfiguration('lima').get('socket') || engineType + '.sock';
const limaHome = 'LIMA_HOME' in process.env ? process.env['LIMA_HOME'] : os.homedir() + '/.lima';
const socketPath = path.resolve(limaHome, instanceName + '/sock/' + socketName);
const configPath = path.resolve(limaHome, instanceName + '/copied-from-guest/kubeconfig.yaml');
const socketPath = path.resolve(limaHome ?? '', instanceName + '/sock/' + socketName);
const configPath = path.resolve(limaHome ?? '', instanceName + '/copied-from-guest/kubeconfig.yaml');
let provider;
if (fs.existsSync(socketPath) || fs.existsSync(configPath)) {
@ -120,7 +120,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
// container provider
if (socketName !== 'kubernetes.sock') {
const providerType = engineType === 'kubernetes' ? 'docker' : (engineType as limaProviderType);
if (fs.existsSync(socketPath)) {
if (fs.existsSync(socketPath) && provider) {
registerProvider(extensionContext, provider, providerType, socketPath, instanceName);
} else {
console.debug(`Could not find socket at ${socketPath}`);
@ -129,7 +129,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
// kubernetes provider
if (engineType === 'kubernetes') {
const providerType = engineType as limaProviderType;
if (fs.existsSync(configPath)) {
if (fs.existsSync(configPath) && provider) {
registerProvider(extensionContext, provider, providerType, configPath, instanceName);
} else {
console.debug(`Could not find config at ${configPath}`);

View file

@ -36,15 +36,15 @@ export class ImageHandler {
// Only proceed if instance was given
if (instanceName) {
let name = image.name;
let filename: string;
const env = Object.assign({}, process.env);
let filename: string | undefined;
const env = Object.assign({}, process.env) as { [key: string]: string };
// Create a name:tag string for the image
if (image.tag) {
name = name + ':' + image.tag;
}
env.PATH = getInstallationPath();
env.PATH = getInstallationPath() ?? '';
try {
// Create a temporary file to store the image
filename = await tmpName();

View file

@ -19,7 +19,7 @@ import * as extensionApi from '@podman-desktop/api';
const macosExtraPath = '/usr/local/bin:/opt/homebrew/bin:/opt/local/bin';
export function getInstallationPath(): string {
export function getInstallationPath(): string | undefined {
const env = process.env;
if (extensionApi.env.isMac) {
if (!env.PATH) {

View file

@ -1,5 +1,6 @@
{
"compilerOptions": {
"strict": true,
"lib": ["ES2017", "webworker"],
"sourceMap": true,
"rootDir": "src",

View file

@ -29,7 +29,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
// remove the image prefix from vite base64 object
const registryEntry = {
...registry,
icon: stripImagePrefix(registry.icon),
icon: stripImagePrefix(registry.icon ?? ''),
};
// Suggest it to the registry and add to subscriptions

View file

@ -1,5 +1,6 @@
{
"compilerOptions": {
"strict": true,
"module": "esnext",
"lib": ["ES2017"],
"sourceMap": true,

View file

@ -4591,6 +4591,11 @@
resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/mustache@^4.2.5":
version "4.2.5"
resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.2.5.tgz#9129f0d6857f976e00e171bbb3460e4b702f84ef"
integrity sha512-PLwiVvTBg59tGFL/8VpcGvqOu3L4OuveNvPi0EYbWchRdEVP++yRUXJPFl+CApKEq13017/4Nf7aQ5lTtHUNsA==
"@types/node-forge@^1.3.0":
version "1.3.10"
resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.10.tgz#62a19d4f75a8b03290578c2b04f294b1a5a71b07"