mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-02-20 17:52:51 +00:00
check for 409 status code, rather than string matching uuid conflicts
Change-type: patch Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
parent
00a6ce84f3
commit
eaaa9c257e
27
package-lock.json
generated
27
package-lock.json
generated
@ -1606,13 +1606,14 @@
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"balena-register-device": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/balena-register-device/-/balena-register-device-6.1.1.tgz",
|
||||
"integrity": "sha512-ztwzs4/x8Pkf1DhZ/vFSteTR1jGJJmGjgnk7p9Fmj3RTa4Q3ucu6LGFm/Ugcc9y95epqAtssP7HStJ9Lsy2/eQ==",
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/balena-register-device/-/balena-register-device-6.1.5.tgz",
|
||||
"integrity": "sha512-fQ2KCCSW+igWb79y1UiJgLjtiZhaKHVj1iR4RajPFE4EuMEa03LP0bMLVuqVBe1ggcYoyfAaKmIFLoGokYOhGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "^3.7.2",
|
||||
"randomstring": "^1.1.5"
|
||||
"randomstring": "^1.1.5",
|
||||
"typed-error": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"balena-semver": {
|
||||
@ -4352,6 +4353,13 @@
|
||||
"object-assign": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
@ -11321,6 +11329,16 @@
|
||||
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
|
||||
"dev": true
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||
@ -11348,6 +11366,7 @@
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
|
@ -62,7 +62,7 @@
|
||||
"@types/supertest": "^2.0.9",
|
||||
"@types/tmp": "^0.1.0",
|
||||
"@types/yargs": "^15.0.4",
|
||||
"balena-register-device": "^6.1.1",
|
||||
"balena-register-device": "^6.1.5",
|
||||
"blinking": "~0.0.3",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.19.0",
|
||||
|
@ -17,7 +17,7 @@ import constants = require('./lib/constants');
|
||||
import {
|
||||
ContractValidationError,
|
||||
ContractViolationError,
|
||||
DuplicateUuidError,
|
||||
isHttpConflictError,
|
||||
ExchangeKeyError,
|
||||
InternalInconsistencyError,
|
||||
} from './lib/errors';
|
||||
@ -868,7 +868,7 @@ export class APIBinder {
|
||||
try {
|
||||
device = await deviceRegister.register(opts).timeout(opts.apiTimeout);
|
||||
} catch (err) {
|
||||
if (DuplicateUuidError(err)) {
|
||||
if (isHttpConflictError(err.response)) {
|
||||
log.debug('UUID already registered, trying a key exchange');
|
||||
device = await this.exchangeKeyAndGetDeviceOrRegenerate(opts);
|
||||
} else {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { endsWith, map, startsWith } from 'lodash';
|
||||
import { endsWith, map } from 'lodash';
|
||||
import TypedError = require('typed-error');
|
||||
|
||||
import { checkInt } from './validation';
|
||||
@ -45,8 +45,8 @@ export class InvalidAppIdError extends TypedError {
|
||||
|
||||
export class UpdatesLockedError extends TypedError {}
|
||||
|
||||
export function DuplicateUuidError(err: Error) {
|
||||
return startsWith(err.message, '"uuid" must be unique');
|
||||
export function isHttpConflictError(err: StatusCodeError): boolean {
|
||||
return checkInt(err.statusCode) === 409;
|
||||
}
|
||||
|
||||
export class ExchangeKeyError extends TypedError {}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { fs } from 'mz';
|
||||
import { Server } from 'net';
|
||||
import { spy, stub } from 'sinon';
|
||||
import { SinonSpy, spy, stub, SinonStub } from 'sinon';
|
||||
|
||||
import chai = require('./lib/chai-config');
|
||||
import balenaAPI = require('./lib/mocked-balena-api');
|
||||
import prepare = require('./lib/prepare');
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
import ApiBinder from '../src/api-binder';
|
||||
import Config from '../src/config';
|
||||
import Log from '../src/lib/supervisor-console';
|
||||
import DB from '../src/db';
|
||||
import DeviceState from '../src/device-state';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
const initModels = async (obj: Dictionary<any>, filename: string) => {
|
||||
prepare();
|
||||
|
||||
@ -99,6 +99,45 @@ describe('ApiBinder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('exchanges keys if resource conflict when provisioning', async () => {
|
||||
// Get current config to extend
|
||||
const currentConfig = await components.apiBinder.config.get(
|
||||
'provisioningOptions',
|
||||
);
|
||||
// Stub config values so we have correct conditions
|
||||
const configStub = stub(Config.prototype, 'get').resolves({
|
||||
...currentConfig,
|
||||
registered_at: null,
|
||||
provisioningApiKey: '123', // Previous test case deleted the provisioningApiKey so add one
|
||||
uuid: 'not-unique', // This UUID is used in mocked-balena-api as an existing registered UUID
|
||||
});
|
||||
// If api-binder reaches this function then tests pass
|
||||
const functionToReach = stub(
|
||||
components.apiBinder,
|
||||
'exchangeKeyAndGetDeviceOrRegenerate',
|
||||
).rejects('expected-rejection'); // We throw an error so we don't have to keep stubbing
|
||||
spy(Log, 'debug');
|
||||
|
||||
try {
|
||||
await components.apiBinder.provision();
|
||||
} catch (e) {
|
||||
// Check that the error thrown is from this test
|
||||
if (e.name !== 'expected-rejection') {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
expect(functionToReach).to.be.calledOnce;
|
||||
expect((Log.debug as SinonSpy).lastCall.lastArg).to.equal(
|
||||
'UUID already registered, trying a key exchange',
|
||||
);
|
||||
|
||||
// Restore stubs
|
||||
functionToReach.restore();
|
||||
configStub.restore();
|
||||
(Log.debug as SinonStub).restore();
|
||||
});
|
||||
|
||||
it('deletes the provisioning key', async () => {
|
||||
expect(await components.config.get('apiKey')).to.be.undefined;
|
||||
});
|
||||
|
@ -29,9 +29,14 @@ api.balenaBackend = {
|
||||
registerHandler: (req, res) => {
|
||||
console.log('/device/register called with ', req.body);
|
||||
const device = req.body;
|
||||
device.id = api.balenaBackend!.currentId++;
|
||||
api.balenaBackend!.devices[device.id] = device;
|
||||
return res.status(201).json(device);
|
||||
switch (req.body.uuid) {
|
||||
case 'not-unique':
|
||||
return res.status(409).json(device);
|
||||
default:
|
||||
device.id = api.balenaBackend!.currentId++;
|
||||
api.balenaBackend!.devices[device.id] = device;
|
||||
return res.status(201).json(device);
|
||||
}
|
||||
},
|
||||
getDeviceHandler: (req, res) => {
|
||||
const uuid =
|
||||
|
Loading…
x
Reference in New Issue
Block a user