Skip to content

Commit

Permalink
respect common.ignoreVersion from new system.host.XY.adapter.XY object (
Browse files Browse the repository at this point in the history
#2657)

* respect common.ignoreVersion from new system.host.XY.adapter.XY object

- closes #1930

* added functionality to ignore and recognize via cli

* added readme

* allow to specify ignore version as semver range
  • Loading branch information
foxriver76 authored Apr 24, 2024
1 parent fdc6b3a commit 20b08f3
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 100 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,18 @@ Furthermore, instances and their states will not move to another structure and w
Note, that instances are unique across the whole system and are thus not affected by the described problem. Also objects of `type` instance have a `common.host` attribute
to find the corresponding host.

### Ignoring specific adapter version
**Feature status:** New in 6.0.0

If you know, that a specific version of an adapter is not suitable for you, you may want to ignore this update to avoid accidentally installing it.
You can do so by using the cli command `iobroker version <adapter> --ignore <version>` and to recognize all updates again use `iobroker version <adapter> --recognize`.
If a version of an adapter is ignored, you will not be able to update to this specific version on this ioBroker host.
If you use a multihost environment you might need to execute the commands once per host.

Internally this will set `common.ignoreVersion` to the specified version on the `system.host.<hostName>.adapter.<adapterName>` object.
The version to be ignored can be specified via a semver range. So an absolute version is fine if you only want to ignore one specific update, e.g. `1.5.1`.
Another example, if you want to ignore all updates in the `1.5.x` range, you can specify `~1.5.0`.

### Operating system package management
**Feature status:** New in 6.0.0

Expand Down
73 changes: 59 additions & 14 deletions packages/cli/src/lib/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { CLIMessage } from '@/lib/cli/cliMessage.js';
import { CLIPlugin } from '@/lib/cli/cliPlugin.js';
import { error as CLIError } from '@/lib/cli/messages.js';
import type { CLICommandContext, CLICommandOptions } from '@/lib/cli/cliCommand.js';
import { getRepository } from '@/lib/setup/utils.js';
import { getRepository, ignoreVersion, recognizeVersion } from '@/lib/setup/utils.js';
import { dbConnect, dbConnectAsync, exitApplicationSave } from '@/lib/setup/dbConnection.js';
import { IoBrokerError } from '@/lib/setup/customError.js';
import type { ListType } from '@/lib/setup/setupList.js';
Expand Down Expand Up @@ -486,7 +486,17 @@ function initYargs(): ReturnType<typeof yargs> {
);
})
.command('vendor <passphrase> [<vendor.json>]', 'Update the vendor information using given passphrase')
.command(['version [<adapter>]', 'v [<adapter>]'], 'Show version of js-controller or specified adapter')
.command(['version [<adapter>]', 'v [<adapter>]'], 'Show version of js-controller or specified adapter', {
ignore: {
describe:
'Ignore specific version of this adapter. The adapter will not be upgradeable to this specific version.',
type: 'string'
},
recognize: {
describe: 'No longer ignore specific versions of this adapter.',
type: 'boolean'
}
})
.wrap(null);

return _yargs;
Expand All @@ -507,7 +517,7 @@ function showHelp(): void {
* @param command - command to execute
* @param args - arguments passed to yargs
* @param params - object with parsed params by yargs, e. g. --force is params.force
* @param callback
* @param callback - callback to be called with the exit code
*/
async function processCommand(
command: string | number,
Expand Down Expand Up @@ -912,17 +922,16 @@ async function processCommand(

if (!adapterDir || !fs.existsSync(adapterDir)) {
try {
// @ts-expect-error todo check or handle null return value
const { stoppedList } = await install.downloadPacket(repoUrl, installName);
await install.installAdapter(installName, repoUrl);
await install.enableInstances(stoppedList, true); // even if unlikely make sure to re-enable disabled instances
if (command !== 'install' && command !== 'i') {
await install.createInstance(name, params);
}
return void callback();
} catch (err) {
console.error(`adapter "${name}" cannot be installed: ${err.message}`);
return void callback(EXIT_CODES.UNKNOWN_ERROR);
} catch (e) {
console.error(`adapter "${name}" cannot be installed: ${e.message}`);
return void callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
}
} else if (command !== 'install' && command !== 'i') {
try {
Expand Down Expand Up @@ -2438,7 +2447,7 @@ async function processCommand(
console.error(`Error: ${err.message}`);
return void callback(EXIT_CODES.CANNOT_GET_UUID);
}
if (obj && obj.native) {
if (obj?.native) {
console.log(obj.native.uuid);
return void callback();
} else {
Expand All @@ -2452,18 +2461,46 @@ async function processCommand(

case 'v':
case 'version': {
const adapter = args[0];
let pckg;
const adapter = params.adapter;

if (params.ignore) {
try {
const { objects } = await dbConnectAsync(false, params);
await ignoreVersion({ adapterName: adapter, version: params.ignore, objects });
} catch (e) {
console.error(e.message);
callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
return;
}
console.log(`Successfully ignored version "${params.ignore}" of adapter "${params.adapter}"!`);
callback();
return;
}

if (params.recognize) {
try {
const { objects } = await dbConnectAsync(false, params);
await recognizeVersion({ adapterName: adapter, objects });
} catch (e) {
console.error(e.message);
callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
}
console.log(`Successfully recognized all versions of adapter "${params.adapter}" again!`);
callback();
return;
}

let packJson;
if (adapter) {
try {
pckg = require(`${tools.appName.toLowerCase()}.${adapter}/package.json`);
packJson = require(`${tools.appName.toLowerCase()}.${adapter}/package.json`);
} catch {
pckg = { version: `"${adapter}" not found` };
packJson = { version: `"${adapter}" not found` };
}
} else {
pckg = require(`@iobroker/js-controller-common/package.json`);
packJson = require(`@iobroker/js-controller-common/package.json`);
}
console.log(pckg.version);
console.log(packJson.version);

return void callback();
}
Expand Down Expand Up @@ -2792,6 +2829,11 @@ const OBJECTS_THAT_CANNOT_BE_DELETED = [
'system.user.admin'
];

/**
* Deletes given objects from the database
*
* @param ids ids to delete from database
*/
async function delObjects(ids: string[]): Promise<void> {
const { objects } = await dbConnectAsync(false);

Expand All @@ -2808,6 +2850,9 @@ async function delObjects(ids: string[]): Promise<void> {
}
}

/**
* Deletes all states from the database
*/
async function delStates(): Promise<number> {
const { states } = await dbConnectAsync(false);

Expand Down
25 changes: 11 additions & 14 deletions packages/cli/src/lib/setup/setupInstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import semver from 'semver';
import child_process from 'node:child_process';
import axios from 'axios';
import { URL } from 'node:url';
import { Upload } from './setupUpload.js';
import { PacketManager } from './setupPacketManager.js';
import { getRepository } from './utils.js';
import { Upload } from '@/lib/setup/setupUpload.js';
import { PacketManager } from '@/lib/setup/setupPacketManager.js';
import { getRepository } from '@/lib/setup/utils.js';
import type { Client as StatesRedisClient } from '@iobroker/db-states-redis';
import type { Client as ObjectsRedisClient } from '@iobroker/db-objects-redis';
import type { ProcessExitCallback } from '../_Types.js';
import type { ProcessExitCallback } from '@/lib/_Types.js';
import { IoBrokerError } from '@/lib/setup/customError.js';
import type { CommandResult } from '@alcalzone/pak';
import { SYSTEM_ADAPTER_PREFIX } from '@iobroker/js-controller-common/constants';
import { IoBrokerError } from './customError.js';
import { createRequire } from 'node:module';

// eslint-disable-next-line unicorn/prefer-module
Expand Down Expand Up @@ -158,7 +158,7 @@ export class Install {
packetName: string,
options?: CLIDownloadPacketOptions,
stoppedList?: ioBroker.InstanceObject[]
): Promise<DownloadPacketReturnObject | void> {
): Promise<DownloadPacketReturnObject> {
let url;
if (!options || typeof options !== 'object') {
options = {};
Expand All @@ -168,12 +168,7 @@ export class Install {
let sources: Record<string, any>;

if (!repoUrl || !tools.isObject(repoUrl)) {
try {
sources = await getRepository({ repoName: repoUrl, objects: this.objects });
} catch (e) {
console.error(e.message);
return this.processExit(e instanceof IoBrokerError ? e.code : e);
}
sources = await getRepository({ repoName: repoUrl, objects: this.objects });
} else {
sources = repoUrl;
}
Expand Down Expand Up @@ -279,7 +274,10 @@ export class Install {
console.error(
`host.${hostname} Unknown packetName ${packetName}. Please install packages from outside the repository using "${tools.appNameLowerCase} url <url-or-package>"!`
);
return this.processExit(EXIT_CODES.UNKNOWN_PACKET_NAME);
throw new IoBrokerError({
code: EXIT_CODES.UNKNOWN_PACKET_NAME,
message: `Unknown packetName ${packetName}. Please install packages from outside the repository using npm!`
});
}

/**
Expand Down Expand Up @@ -698,7 +696,6 @@ export class Install {
}
_installCount++;

// @ts-expect-error TODO needs adaption
const { stoppedList } = await this.downloadPacket(repoUrl, fullName);
await this.installAdapter(adapter, repoUrl, _installCount);
await this.enableInstances(stoppedList, true); // even if unlikely make sure to reenable disabled instances
Expand Down
15 changes: 11 additions & 4 deletions packages/cli/src/lib/setup/setupRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import axios from 'axios';
import fs from 'fs-extra';
import type { Client as ObjectsRedisClient } from '@iobroker/db-objects-redis';
import type { Client as StatesRedisClient } from '@iobroker/db-states-redis';
import { isVersionIgnored } from '@/lib/setup/utils.js';
import path from 'node:path';

export interface CLIRepoOptions {
Expand Down Expand Up @@ -223,7 +224,7 @@ export class Repo {
// not important if fails
}

this.showRepoResult(allSources, flags);
return this.showRepoResult(allSources, flags);
}
}

Expand All @@ -233,7 +234,7 @@ export class Repo {
* @param sources Repo json sources
* @param flags CLI flags
*/
private showRepoResult(sources: Record<string, any>, flags: RepoFlags): void {
private async showRepoResult(sources: Record<string, any>, flags: RepoFlags): Promise<void> {
const installed = tools.getInstalledInfo();
const adapters = Object.keys(sources).sort();

Expand Down Expand Up @@ -263,7 +264,13 @@ export class Repo {
) {
updatable = true;
text = text.padEnd(11 + 15 + 11 + 18);
text += ' [Updatable]';
const isIgnored = await isVersionIgnored({
adapterName: name,
objects: this.objects,
version: sources[name].version
});

text += isIgnored ? ' [Ignored]' : ' [Updatable]';
}
} catch (e) {
console.error(`Cannot determine update info of "${name}": ${e.message}`);
Expand All @@ -279,7 +286,7 @@ export class Repo {
/**
* Update Admin info states with number of updates
*
* @param sources
* @param sources the repository object
*/
private async updateInfo(sources: Record<string, any>): Promise<void> {
const installed = tools.getInstalledInfo();
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/lib/setup/setupSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1088,7 +1088,7 @@ Please DO NOT copy files manually into ioBroker storage directories!`
}

const hostname = tools.getHostName();
const adaptersId = `system.host.${hostname}.adapters`;
const adaptersId = `system.host.${hostname}.adapter`;

const adaptersExist = await this.objects.objectExists(adaptersId);

Expand Down Expand Up @@ -1207,7 +1207,7 @@ Please DO NOT copy files manually into ioBroker storage directories!`
const hostIds = hostsView.rows.map(row => row.id);

for (const hostId of hostIds) {
const hasAdapters = await this.objects.objectExists(`${hostId}.adapters`);
const hasAdapters = await this.objects.objectExists(`${hostId}.adapter`);

if (!hasAdapters) {
console.log(
Expand Down
Loading

0 comments on commit 20b08f3

Please sign in to comment.