Skip to content

Commit 20b08f3

Browse files
authored
respect common.ignoreVersion from new system.host.XY.adapter.XY object (#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
1 parent fdc6b3a commit 20b08f3

File tree

10 files changed

+263
-100
lines changed

10 files changed

+263
-100
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,18 @@ Furthermore, instances and their states will not move to another structure and w
254254
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
255255
to find the corresponding host.
256256

257+
### Ignoring specific adapter version
258+
**Feature status:** New in 6.0.0
259+
260+
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.
261+
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`.
262+
If a version of an adapter is ignored, you will not be able to update to this specific version on this ioBroker host.
263+
If you use a multihost environment you might need to execute the commands once per host.
264+
265+
Internally this will set `common.ignoreVersion` to the specified version on the `system.host.<hostName>.adapter.<adapterName>` object.
266+
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`.
267+
Another example, if you want to ignore all updates in the `1.5.x` range, you can specify `~1.5.0`.
268+
257269
### Operating system package management
258270
**Feature status:** New in 6.0.0
259271

packages/cli/src/lib/setup.ts

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { CLIMessage } from '@/lib/cli/cliMessage.js';
2020
import { CLIPlugin } from '@/lib/cli/cliPlugin.js';
2121
import { error as CLIError } from '@/lib/cli/messages.js';
2222
import type { CLICommandContext, CLICommandOptions } from '@/lib/cli/cliCommand.js';
23-
import { getRepository } from '@/lib/setup/utils.js';
23+
import { getRepository, ignoreVersion, recognizeVersion } from '@/lib/setup/utils.js';
2424
import { dbConnect, dbConnectAsync, exitApplicationSave } from '@/lib/setup/dbConnection.js';
2525
import { IoBrokerError } from '@/lib/setup/customError.js';
2626
import type { ListType } from '@/lib/setup/setupList.js';
@@ -486,7 +486,17 @@ function initYargs(): ReturnType<typeof yargs> {
486486
);
487487
})
488488
.command('vendor <passphrase> [<vendor.json>]', 'Update the vendor information using given passphrase')
489-
.command(['version [<adapter>]', 'v [<adapter>]'], 'Show version of js-controller or specified adapter')
489+
.command(['version [<adapter>]', 'v [<adapter>]'], 'Show version of js-controller or specified adapter', {
490+
ignore: {
491+
describe:
492+
'Ignore specific version of this adapter. The adapter will not be upgradeable to this specific version.',
493+
type: 'string'
494+
},
495+
recognize: {
496+
describe: 'No longer ignore specific versions of this adapter.',
497+
type: 'boolean'
498+
}
499+
})
490500
.wrap(null);
491501

492502
return _yargs;
@@ -507,7 +517,7 @@ function showHelp(): void {
507517
* @param command - command to execute
508518
* @param args - arguments passed to yargs
509519
* @param params - object with parsed params by yargs, e. g. --force is params.force
510-
* @param callback
520+
* @param callback - callback to be called with the exit code
511521
*/
512522
async function processCommand(
513523
command: string | number,
@@ -912,17 +922,16 @@ async function processCommand(
912922

913923
if (!adapterDir || !fs.existsSync(adapterDir)) {
914924
try {
915-
// @ts-expect-error todo check or handle null return value
916925
const { stoppedList } = await install.downloadPacket(repoUrl, installName);
917926
await install.installAdapter(installName, repoUrl);
918927
await install.enableInstances(stoppedList, true); // even if unlikely make sure to re-enable disabled instances
919928
if (command !== 'install' && command !== 'i') {
920929
await install.createInstance(name, params);
921930
}
922931
return void callback();
923-
} catch (err) {
924-
console.error(`adapter "${name}" cannot be installed: ${err.message}`);
925-
return void callback(EXIT_CODES.UNKNOWN_ERROR);
932+
} catch (e) {
933+
console.error(`adapter "${name}" cannot be installed: ${e.message}`);
934+
return void callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
926935
}
927936
} else if (command !== 'install' && command !== 'i') {
928937
try {
@@ -2438,7 +2447,7 @@ async function processCommand(
24382447
console.error(`Error: ${err.message}`);
24392448
return void callback(EXIT_CODES.CANNOT_GET_UUID);
24402449
}
2441-
if (obj && obj.native) {
2450+
if (obj?.native) {
24422451
console.log(obj.native.uuid);
24432452
return void callback();
24442453
} else {
@@ -2452,18 +2461,46 @@ async function processCommand(
24522461

24532462
case 'v':
24542463
case 'version': {
2455-
const adapter = args[0];
2456-
let pckg;
2464+
const adapter = params.adapter;
2465+
2466+
if (params.ignore) {
2467+
try {
2468+
const { objects } = await dbConnectAsync(false, params);
2469+
await ignoreVersion({ adapterName: adapter, version: params.ignore, objects });
2470+
} catch (e) {
2471+
console.error(e.message);
2472+
callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
2473+
return;
2474+
}
2475+
console.log(`Successfully ignored version "${params.ignore}" of adapter "${params.adapter}"!`);
2476+
callback();
2477+
return;
2478+
}
2479+
2480+
if (params.recognize) {
2481+
try {
2482+
const { objects } = await dbConnectAsync(false, params);
2483+
await recognizeVersion({ adapterName: adapter, objects });
2484+
} catch (e) {
2485+
console.error(e.message);
2486+
callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
2487+
}
2488+
console.log(`Successfully recognized all versions of adapter "${params.adapter}" again!`);
2489+
callback();
2490+
return;
2491+
}
2492+
2493+
let packJson;
24572494
if (adapter) {
24582495
try {
2459-
pckg = require(`${tools.appName.toLowerCase()}.${adapter}/package.json`);
2496+
packJson = require(`${tools.appName.toLowerCase()}.${adapter}/package.json`);
24602497
} catch {
2461-
pckg = { version: `"${adapter}" not found` };
2498+
packJson = { version: `"${adapter}" not found` };
24622499
}
24632500
} else {
2464-
pckg = require(`@iobroker/js-controller-common/package.json`);
2501+
packJson = require(`@iobroker/js-controller-common/package.json`);
24652502
}
2466-
console.log(pckg.version);
2503+
console.log(packJson.version);
24672504

24682505
return void callback();
24692506
}
@@ -2792,6 +2829,11 @@ const OBJECTS_THAT_CANNOT_BE_DELETED = [
27922829
'system.user.admin'
27932830
];
27942831

2832+
/**
2833+
* Deletes given objects from the database
2834+
*
2835+
* @param ids ids to delete from database
2836+
*/
27952837
async function delObjects(ids: string[]): Promise<void> {
27962838
const { objects } = await dbConnectAsync(false);
27972839

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

2853+
/**
2854+
* Deletes all states from the database
2855+
*/
28112856
async function delStates(): Promise<number> {
28122857
const { states } = await dbConnectAsync(false);
28132858

packages/cli/src/lib/setup/setupInstall.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ import semver from 'semver';
1414
import child_process from 'node:child_process';
1515
import axios from 'axios';
1616
import { URL } from 'node:url';
17-
import { Upload } from './setupUpload.js';
18-
import { PacketManager } from './setupPacketManager.js';
19-
import { getRepository } from './utils.js';
17+
import { Upload } from '@/lib/setup/setupUpload.js';
18+
import { PacketManager } from '@/lib/setup/setupPacketManager.js';
19+
import { getRepository } from '@/lib/setup/utils.js';
2020
import type { Client as StatesRedisClient } from '@iobroker/db-states-redis';
2121
import type { Client as ObjectsRedisClient } from '@iobroker/db-objects-redis';
22-
import type { ProcessExitCallback } from '../_Types.js';
22+
import type { ProcessExitCallback } from '@/lib/_Types.js';
23+
import { IoBrokerError } from '@/lib/setup/customError.js';
2324
import type { CommandResult } from '@alcalzone/pak';
2425
import { SYSTEM_ADAPTER_PREFIX } from '@iobroker/js-controller-common/constants';
25-
import { IoBrokerError } from './customError.js';
2626
import { createRequire } from 'node:module';
2727

2828
// eslint-disable-next-line unicorn/prefer-module
@@ -158,7 +158,7 @@ export class Install {
158158
packetName: string,
159159
options?: CLIDownloadPacketOptions,
160160
stoppedList?: ioBroker.InstanceObject[]
161-
): Promise<DownloadPacketReturnObject | void> {
161+
): Promise<DownloadPacketReturnObject> {
162162
let url;
163163
if (!options || typeof options !== 'object') {
164164
options = {};
@@ -168,12 +168,7 @@ export class Install {
168168
let sources: Record<string, any>;
169169

170170
if (!repoUrl || !tools.isObject(repoUrl)) {
171-
try {
172-
sources = await getRepository({ repoName: repoUrl, objects: this.objects });
173-
} catch (e) {
174-
console.error(e.message);
175-
return this.processExit(e instanceof IoBrokerError ? e.code : e);
176-
}
171+
sources = await getRepository({ repoName: repoUrl, objects: this.objects });
177172
} else {
178173
sources = repoUrl;
179174
}
@@ -279,7 +274,10 @@ export class Install {
279274
console.error(
280275
`host.${hostname} Unknown packetName ${packetName}. Please install packages from outside the repository using "${tools.appNameLowerCase} url <url-or-package>"!`
281276
);
282-
return this.processExit(EXIT_CODES.UNKNOWN_PACKET_NAME);
277+
throw new IoBrokerError({
278+
code: EXIT_CODES.UNKNOWN_PACKET_NAME,
279+
message: `Unknown packetName ${packetName}. Please install packages from outside the repository using npm!`
280+
});
283281
}
284282

285283
/**
@@ -698,7 +696,6 @@ export class Install {
698696
}
699697
_installCount++;
700698

701-
// @ts-expect-error TODO needs adaption
702699
const { stoppedList } = await this.downloadPacket(repoUrl, fullName);
703700
await this.installAdapter(adapter, repoUrl, _installCount);
704701
await this.enableInstances(stoppedList, true); // even if unlikely make sure to reenable disabled instances

packages/cli/src/lib/setup/setupRepo.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import axios from 'axios';
33
import fs from 'fs-extra';
44
import type { Client as ObjectsRedisClient } from '@iobroker/db-objects-redis';
55
import type { Client as StatesRedisClient } from '@iobroker/db-states-redis';
6+
import { isVersionIgnored } from '@/lib/setup/utils.js';
67
import path from 'node:path';
78

89
export interface CLIRepoOptions {
@@ -223,7 +224,7 @@ export class Repo {
223224
// not important if fails
224225
}
225226

226-
this.showRepoResult(allSources, flags);
227+
return this.showRepoResult(allSources, flags);
227228
}
228229
}
229230

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

@@ -263,7 +264,13 @@ export class Repo {
263264
) {
264265
updatable = true;
265266
text = text.padEnd(11 + 15 + 11 + 18);
266-
text += ' [Updatable]';
267+
const isIgnored = await isVersionIgnored({
268+
adapterName: name,
269+
objects: this.objects,
270+
version: sources[name].version
271+
});
272+
273+
text += isIgnored ? ' [Ignored]' : ' [Updatable]';
267274
}
268275
} catch (e) {
269276
console.error(`Cannot determine update info of "${name}": ${e.message}`);
@@ -279,7 +286,7 @@ export class Repo {
279286
/**
280287
* Update Admin info states with number of updates
281288
*
282-
* @param sources
289+
* @param sources the repository object
283290
*/
284291
private async updateInfo(sources: Record<string, any>): Promise<void> {
285292
const installed = tools.getInstalledInfo();

packages/cli/src/lib/setup/setupSetup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ Please DO NOT copy files manually into ioBroker storage directories!`
10881088
}
10891089

10901090
const hostname = tools.getHostName();
1091-
const adaptersId = `system.host.${hostname}.adapters`;
1091+
const adaptersId = `system.host.${hostname}.adapter`;
10921092

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

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

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

12121212
if (!hasAdapters) {
12131213
console.log(

0 commit comments

Comments
 (0)