diff --git a/package-lock.json b/package-lock.json index 499d3e57..720518e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,18 +5,18 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", "dev": true, "requires": { "@babel/highlight": "^7.0.0" } }, "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -24,40 +24,11 @@ "js-tokens": "^4.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -469,9 +440,9 @@ "dev": true }, "@types/prettier": { - "version": "1.16.4", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.16.4.tgz", - "integrity": "sha512-MG7ExKBo7AQ5UrL1awyYLNinNM/kyXgE4iP4Ul9fB+T7n768Z5Xem8IZeP6Bna0xze8gkDly49Rgge2HOEw4xA==", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.18.3.tgz", + "integrity": "sha512-48rnerQdcZ26odp+HOvDGX8IcUkYOCuMc2BodWYTe956MqkHlOGAG4oFQ83cjZ0a4GAgj7mb4GUClxYd2Hlodg==", "dev": true }, "@types/range-parser": { @@ -492,6 +463,16 @@ "@types/tough-cookie": "*" } }, + "@types/rimraf": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.3.tgz", + "integrity": "sha512-dZfyfL/u9l/oi984hEXdmAjX3JHry7TLWw43u1HQ8HhPv6KtfxnrZ3T/bleJ0GEvnk9t5sM7eePkgMqz3yBcGg==", + "dev": true, + "requires": { + "@types/glob": "*", + "@types/node": "*" + } + }, "@types/rwlock": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/rwlock/-/rwlock-5.0.2.tgz", @@ -1088,6 +1069,15 @@ "ansi-wrap": "0.1.0" } }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "ansi-underline": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-underline/-/ansi-underline-0.1.1.tgz", @@ -1604,6 +1594,15 @@ "resolved": "https://registry.npmjs.org/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz", "integrity": "sha512-qu4mXWf4wus4idBIN/kVH+XSer8IZ9CwHP+Pd7DL7TuKNC1hP7ykon4kkBjwJF3EMX2WsFp4hH7gU7CyL7ucXw==", "dev": true + }, + "node-web-streams": { + "version": "github:resin-io-modules/node-web-streams#46f98300b69090bde3f6b4983877ccfe283a892c", + "from": "github:resin-io-modules/node-web-streams#emit-errors", + "dev": true, + "requires": { + "is-stream": "^1.1.0", + "web-streams-polyfill": "^1.3.2" + } } } }, @@ -1775,6 +1774,17 @@ "tar-stream": "^1.5.2" } }, + "resin-discoverable-services": { + "version": "git+https://github.com/resin-io-modules/resin-discoverable-services.git#afca9e4700ec5ef82aa897f14bd5a46f06518061", + "from": "git+https://github.com/resin-io-modules/resin-discoverable-services.git#find-on-all-interfaces", + "dev": true, + "requires": { + "bluebird": "^3.0.0", + "bonjour": "git+https://github.com/resin-io/bonjour.git#e018851dc823b4b3f670f658f71d0c1c7f3e637c", + "ip": "^1.1.4", + "lodash": "^4.17.4" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -1970,7 +1980,7 @@ "deep-equal": "^1.0.1", "dns-equal": "^1.0.0", "dns-txt": "^2.0.2", - "multicast-dns": "git+https://github.com/resin-io-modules/multicast-dns.git#listen-on-all-interfaces", + "multicast-dns": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc", "multicast-dns-service-types": "^1.1.0" }, "dependencies": { @@ -2283,6 +2293,17 @@ "chai": "^3.5.0" } }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -2443,17 +2464,6 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, "clone": { @@ -3210,9 +3220,9 @@ "dev": true }, "deprecate": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/deprecate/-/deprecate-1.1.0.tgz", - "integrity": "sha512-b5dDNQYdy2vW9WXUD8+RQlfoxvqztLLhDE+T7Gd37I5E8My7nJkKu6FmhdDeRWJ8B+yjZKuwjCta8pgi8kgSqA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deprecate/-/deprecate-1.1.1.tgz", + "integrity": "sha512-ZGDXefq1xknT292LnorMY5s8UVU08/WKdzDZCUT6t9JzsiMSP4uzUhgpqugffNVcT5WC6wMBiSQ+LFjlv3v7iQ==", "dev": true }, "deps-regex": { @@ -4520,8 +4530,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -4542,14 +4551,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4564,20 +4571,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -4694,8 +4698,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -4707,7 +4710,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4722,7 +4724,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4730,14 +4731,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4756,7 +4755,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -4837,8 +4835,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -4850,7 +4847,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4936,8 +4932,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -4973,7 +4968,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4993,7 +4987,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5037,14 +5030,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -5079,9 +5070,9 @@ } }, "nan": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true, "optional": true }, @@ -5197,9 +5188,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.6.tgz", - "integrity": "sha512-vfmKZp3XPM36DNF0qhW+Cdxk7xm7gTEHY1clv1Xq1arwRQuKZgAhw+NZNWbJBtuaNxzNXwhfdPYRrvIbjfS33A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "dev": true, "optional": true }, @@ -8493,15 +8484,6 @@ "integrity": "sha512-W0SgGKaB9qSCfFfNj2uQZ/5BlVumaNHjVCAPdEoXrkEJ3ynSf/806LEz1rbDFbJ4+PL9G8IxRkJJTvZndd5D9g==", "dev": true }, - "node-web-streams": { - "version": "github:resin-io-modules/node-web-streams#46f98300b69090bde3f6b4983877ccfe283a892c", - "from": "github:resin-io-modules/node-web-streams#46f98300b69090bde3f6b4983877ccfe283a892c", - "dev": true, - "requires": { - "is-stream": "^1.1.0", - "web-streams-polyfill": "^1.3.2" - } - }, "noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", @@ -9183,9 +9165,9 @@ "dev": true }, "prettier": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz", - "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", "dev": true }, "pretty-format": { @@ -9746,21 +9728,10 @@ "lodash": "^4.0.0" } }, - "resin-discoverable-services": { - "version": "git+https://github.com/resin-io-modules/resin-discoverable-services.git#afca9e4700ec5ef82aa897f14bd5a46f06518061", - "from": "git+https://github.com/resin-io-modules/resin-discoverable-services.git#afca9e4700ec5ef82aa897f14bd5a46f06518061", - "dev": true, - "requires": { - "bluebird": "^3.0.0", - "bonjour": "git+https://github.com/resin-io/bonjour.git#fixed-mdns", - "ip": "^1.1.4", - "lodash": "^4.17.4" - } - }, "resin-lint": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/resin-lint/-/resin-lint-3.1.0.tgz", - "integrity": "sha512-bipsVrhMBtoegrBdJf/6NMQke4g8xmZENSu0fBU1KvxLXNhGPQkmobY7vVmP47BeD0m0Zdv9yrEc43w2S+kRWA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/resin-lint/-/resin-lint-3.1.1.tgz", + "integrity": "sha512-BgIsrj9fvWcELoqfiu0dGflqkysByn7m/XVgbv19YdnnVToEtyQkFzfF9oY+h6nnr45pRYkorE6NAFYaVaYhLQ==", "dev": true, "requires": { "@types/bluebird": "^3.5.26", @@ -9768,7 +9739,7 @@ "@types/glob": "^5.0.35", "@types/node": "^8.10.45", "@types/optimist": "0.0.29", - "@types/prettier": "^1.16.1", + "@types/prettier": "^1.18.3", "bluebird": "^3.5.4", "coffee-script": "^1.10.0", "coffeelint": "^1.15.0", @@ -9777,7 +9748,7 @@ "glob": "^7.0.3", "merge": "^1.2.0", "optimist": "^0.6.1", - "prettier": "^1.16.4", + "prettier": "^1.19.1", "tslint": "^5.15.0", "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", @@ -9785,21 +9756,21 @@ }, "dependencies": { "@types/bluebird": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.27.tgz", - "integrity": "sha512-6BmYWSBea18+tSjjSC3QIyV93ZKAeNWGM7R6aYt1ryTZXrlHF+QLV0G2yV0viEGVyRkyQsWfMoJ0k/YghBX5sQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.28.tgz", + "integrity": "sha512-0Vk/kqkukxPKSzP9c8WJgisgGDx5oZDbsLLWIP5t70yThO/YleE+GEm2S1GlRALTaack3O7U5OS5qEm7q2kciA==", "dev": true }, "@types/node": { - "version": "8.10.48", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.48.tgz", - "integrity": "sha512-c35YEBTkL4rzXY2ucpSKy+UYHjUBIIkuJbWYbsGIrKLEWU5dgJMmLkkIb3qeC3O3Tpb1ZQCwecscvJTDjDjkRw==", + "version": "8.10.59", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", + "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==", "dev": true }, "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", "dev": true }, "coffee-script": { @@ -9807,18 +9778,6 @@ "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", "dev": true - }, - "prettier": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.1.tgz", - "integrity": "sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg==", - "dev": true - }, - "typescript": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", - "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", - "dev": true } } }, @@ -10778,6 +10737,15 @@ "is-regexp": "^1.0.0" } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -11309,18 +11277,18 @@ "dev": true }, "tslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", - "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", "commander": "^2.12.1", - "diff": "^3.2.0", + "diff": "^4.0.1", "glob": "^7.1.1", - "js-yaml": "^3.13.0", + "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "resolve": "^1.3.2", @@ -11329,25 +11297,11 @@ "tsutils": "^2.29.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true }, "js-yaml": { "version": "3.13.1", @@ -11360,22 +11314,13 @@ } }, "resolve": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", - "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "dev": true, "requires": { "path-parse": "^1.0.6" } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -11395,9 +11340,9 @@ }, "dependencies": { "tsutils": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.10.0.tgz", - "integrity": "sha512-q20XSMq7jutbGB8luhKKsQldRKWvyBO2BGqni3p4yq8Ys9bEP/xQw3KepKmMRt9gJ4lvQSScrihJrcKdKoSU7Q==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -11895,8 +11840,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -11917,14 +11861,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11939,20 +11881,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -12069,8 +12008,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -12082,7 +12020,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -12097,7 +12034,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -12105,14 +12041,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -12131,7 +12065,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -12212,8 +12145,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -12225,7 +12157,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -12311,8 +12242,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -12348,7 +12278,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -12368,7 +12297,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -12412,14 +12340,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -12454,9 +12380,9 @@ } }, "nan": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true, "optional": true }, @@ -12913,17 +12839,6 @@ "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" - }, - "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, "wrappy": { diff --git a/package.json b/package.json index d7cebbe4..0e323685 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@types/mz": "0.0.32", "@types/node": "^10.12.17", "@types/request": "^2.48.1", + "@types/rimraf": "^2.0.3", "@types/rwlock": "^5.0.2", "@types/shell-quote": "^1.6.0", "@types/sinon": "^7.0.13", @@ -101,10 +102,10 @@ "mz": "^2.7.0", "network-checker": "^0.1.1", "pinejs-client-request": "^5.2.0", - "prettier": "1.17.0", + "prettier": "1.19.1", "pretty-ms": "^4.0.0", "request": "^2.51.0", - "resin-lint": "^3.1.0", + "resin-lint": "^3.1.1", "resin-register-device": "^3.0.0", "resumable-request": "^2.0.0", "rimraf": "^2.6.2", diff --git a/src/api-binder.ts b/src/api-binder.ts index 99f4fc3b..b7ebd2ea 100644 --- a/src/api-binder.ts +++ b/src/api-binder.ts @@ -4,17 +4,15 @@ import * as express from 'express'; import { isLeft } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; import * as _ from 'lodash'; -import * as Path from 'path'; import { PinejsClientRequest, StatusError } from 'pinejs-client-request'; import * as deviceRegister from 'resin-register-device'; import * as url from 'url'; import Config, { ConfigType } from './config'; import Database from './db'; -import DeviceConfig from './device-config'; import { EventTracker } from './event-tracker'; +import { loadBackupFromMigration } from './lib/migration'; -import * as constants from './lib/constants'; import { ContractValidationError, ContractViolationError, @@ -22,10 +20,9 @@ import { ExchangeKeyError, InternalInconsistencyError, } from './lib/errors'; -import { pathExistsOnHost } from './lib/fs-utils'; import * as request from './lib/request'; import { writeLock } from './lib/update-lock'; -import { DeviceApplicationState } from './types/state'; +import { DeviceApplicationState, TargetState } from './types/state'; import log from './lib/supervisor-console'; @@ -73,14 +70,12 @@ export class APIBinder { public router: express.Router; private config: Config; - private deviceState: { - deviceConfig: DeviceConfig; - [key: string]: any; - }; + private deviceState: DeviceState; private eventTracker: EventTracker; private logger: Logger; public balenaApi: PinejsClientRequest | null = null; + // TODO{type}: Retype me when all types are sorted private cachedBalenaApi: PinejsClientRequest | null = null; private lastReportedState: DeviceApplicationState = { local: {}, @@ -90,7 +85,7 @@ export class APIBinder { local: {}, dependent: {}, }; - private lastTarget: DeviceApplicationState = {}; + private lastTarget: TargetState; private lastTargetStateFetch = process.hrtime(); private reportPending = false; private stateReportErrors = 0; @@ -143,9 +138,15 @@ export class APIBinder { } public async initClient() { - const { unmanaged, apiEndpoint, currentApiKey } = await this.config.getMany( - ['unmanaged', 'apiEndpoint', 'currentApiKey'], - ); + const { + unmanaged, + apiEndpoint, + currentApiKey, + } = await this.config.getMany([ + 'unmanaged', + 'apiEndpoint', + 'currentApiKey', + ]); if (unmanaged) { log.debug('Unmanaged mode is set, skipping API client initialization'); @@ -164,25 +165,6 @@ export class APIBinder { this.cachedBalenaApi = this.balenaApi.clone({}, { cache: {} }); } - public async loadBackupFromMigration(retryDelay: number): Promise { - try { - const exists = await pathExistsOnHost( - Path.join('mnt/data', constants.migrationBackupFile), - ); - if (!exists) { - return; - } - log.info('Migration backup detected'); - const targetState = await this.getTargetState(); - await this.deviceState.restoreBackup(targetState); - } catch (err) { - log.error(`Error restoring migration backup, retrying: ${err}`); - - await Bluebird.delay(retryDelay); - return this.loadBackupFromMigration(retryDelay); - } - } - public async start() { const conf = await this.config.getMany([ 'apiEndpoint', @@ -222,7 +204,11 @@ export class APIBinder { log.debug('Starting current state report'); await this.startCurrentStateReport(); - await this.loadBackupFromMigration(bootstrapRetryDelay); + await loadBackupFromMigration( + this.deviceState, + await this.getTargetState(), + bootstrapRetryDelay, + ); this.readyForUpdates = true; log.debug('Starting target state poll'); @@ -327,11 +313,10 @@ export class APIBinder { return (await this.balenaApi .post({ resource: 'device', body: device }) - // TODO: Remove the `as number` when we fix the config typings .timeout(conf.apiTimeout)) as Device; } - public async getTargetState(): Promise { + public async getTargetState(): Promise { const { uuid, apiEndpoint, apiTimeout } = await this.config.getMany([ 'uuid', 'apiEndpoint', @@ -355,9 +340,9 @@ export class APIBinder { this.cachedBalenaApi.passthrough, ); - return await this.cachedBalenaApi + return (await this.cachedBalenaApi ._request(requestParams) - .timeout(apiTimeout); + .timeout(apiTimeout)) as TargetState; } // TODO: Once 100% typescript, change this to a native promise @@ -535,9 +520,11 @@ export class APIBinder { // the watchdog to kill the supervisor - and killing the supervisor will // not help in this situation log.error( - `Non-200 response from the API! Status code: ${ - e.statusCode - } - message:`, + `Non-200 response from the API! Status code: ${e.statusCode} - message:`, + e, + ); + log.error( + `Non-200 response from the API! Status code: ${e.statusCode} - message:`, e, ); } else { @@ -614,9 +601,10 @@ export class APIBinder { private async pollTargetState(isInitialCall: boolean = false): Promise { // TODO: Remove the checkInt here with the config changes - const { appUpdatePollInterval, instantUpdates } = await this.config.getMany( - ['appUpdatePollInterval', 'instantUpdates'], - ); + const { + appUpdatePollInterval, + instantUpdates, + } = await this.config.getMany(['appUpdatePollInterval', 'instantUpdates']); // We add jitter to the poll interval so that it's between 0.5 and 1.5 times // the configured interval @@ -885,9 +873,7 @@ export class APIBinder { 'Attempting to provision a device without an initialized API client', ); } - this.balenaApi.passthrough.headers.Authorization = `Bearer ${ - opts.deviceApiKey - }`; + this.balenaApi.passthrough.headers.Authorization = `Bearer ${opts.deviceApiKey}`; const configToUpdate = { registered_at: opts.registered_at, diff --git a/src/compose/composition-steps.ts b/src/compose/composition-steps.ts index 4478895a..3285a869 100644 --- a/src/compose/composition-steps.ts +++ b/src/compose/composition-steps.ts @@ -111,7 +111,7 @@ export function generateStep( } type Executors = { - [key in T]: (step: CompositionStep) => Promise + [key in T]: (step: CompositionStep) => Promise; }; type LockingFn = ( // TODO: Once the entire codebase is typescript, change diff --git a/src/compose/images.ts b/src/compose/images.ts index 3bfdc503..ba67d836 100644 --- a/src/compose/images.ts +++ b/src/compose/images.ts @@ -580,9 +580,11 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) { image = this.format(image); // TODO: Get rid of this janky cast once the database is // more strongly typed - await this.db.upsertModel('image', image, (image as unknown) as Dictionary< - unknown - >); + await this.db.upsertModel( + 'image', + image, + (image as unknown) as Dictionary, + ); } private format(image: MaybeImage): Image { diff --git a/src/compose/service-manager.ts b/src/compose/service-manager.ts index 6aab62ce..3dfeaf77 100644 --- a/src/compose/service-manager.ts +++ b/src/compose/service-manager.ts @@ -96,9 +96,9 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve public async get(service: Service) { // Get the container ids for special network handling const containerIds = await this.getContainerIdMap(service.appId!); - const services = (await this.getAll( - `service-id=${service.serviceId}`, - )).filter(currentService => + const services = ( + await this.getAll(`service-id=${service.serviceId}`) + ).filter(currentService => currentService.isEqualConfig(service, containerIds), ); @@ -158,9 +158,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve const svc = await this.get(service); if (svc.containerId == null) { throw new InternalInconsistencyError( - `No containerId provided for service ${ - service.serviceName - } in ServiceManager.updateMetadata. Service: ${service}`, + `No containerId provided for service ${service.serviceName} in ServiceManager.updateMetadata. Service: ${service}`, ); } @@ -184,9 +182,9 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve public async killAllLegacy(): Promise { // Containers haven't been normalized (this is an updated supervisor) // so we need to stop and remove them - const supervisorImageId = (await this.docker - .getImage(constants.supervisorImage) - .inspect()).Id; + const supervisorImageId = ( + await this.docker.getImage(constants.supervisorImage).inspect() + ).Id; for (const container of await this.docker.listContainers({ all: true })) { if (container.ImageID !== supervisorImageId) { @@ -212,9 +210,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve if (existingService.containerId == null) { throw new InternalInconsistencyError( - `No containerId provided for service ${ - service.serviceName - } in ServiceManager.updateMetadata. Service: ${service}`, + `No containerId provided for service ${service.serviceName} in ServiceManager.updateMetadata. Service: ${service}`, ); } @@ -248,9 +244,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve const existing = await this.get(service); if (existing.containerId == null) { throw new InternalInconsistencyError( - `No containerId provided for service ${ - service.serviceName - } in ServiceManager.updateMetadata. Service: ${service}`, + `No containerId provided for service ${service.serviceName} in ServiceManager.updateMetadata. Service: ${service}`, ); } return this.docker.getContainer(existing.containerId); @@ -371,9 +365,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve const imageId = service.imageId; if (serviceId == null || imageId == null) { throw new InternalInconsistencyError( - `serviceId and imageId not defined for service: ${ - service.serviceName - } in ServiceManager.start`, + `serviceId and imageId not defined for service: ${service.serviceName} in ServiceManager.start`, ); } @@ -439,9 +431,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve const imageId = service.imageId; if (serviceId == null || imageId == null) { throw new InternalInconsistencyError( - `serviceId and imageId not defined for service: ${ - service.serviceName - } in ServiceManager.listenToEvents`, + `serviceId and imageId not defined for service: ${service.serviceName} in ServiceManager.listenToEvents`, ); } this.logger.attach(this.docker, data.id, { @@ -488,17 +478,13 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve const imageId = service.imageId; if (serviceId == null || imageId == null) { throw new InternalInconsistencyError( - `serviceId and imageId not defined for service: ${ - service.serviceName - } in ServiceManager.start`, + `serviceId and imageId not defined for service: ${service.serviceName} in ServiceManager.start`, ); } if (service.containerId == null) { throw new InternalInconsistencyError( - `containerId not defined for service: ${ - service.serviceName - } in ServiceManager.attachToRunning`, + `containerId not defined for service: ${service.serviceName} in ServiceManager.attachToRunning`, ); } this.logger.attach(this.docker, service.containerId, { @@ -638,17 +624,13 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve const svc = await this.get(service); if (svc.containerId == null) { throw new InternalInconsistencyError( - `No containerId provided for service ${ - service.serviceName - } in ServiceManager.prepareForHandover. Service: ${service}`, + `No containerId provided for service ${service.serviceName} in ServiceManager.prepareForHandover. Service: ${service}`, ); } const container = this.docker.getContainer(svc.containerId); await container.update({ RestartPolicy: {} }); return await container.rename({ - name: `old_${service.serviceName}_${service.imageId}_${service.imageId}_${ - service.releaseId - }`, + name: `old_${service.serviceName}_${service.imageId}_${service.imageId}_${service.releaseId}`, }); } @@ -670,17 +652,13 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve return wait(); } else { log.info( - `Handover timeout has passed, assuming handover was completed for service ${ - service.serviceName - }`, + `Handover timeout has passed, assuming handover was completed for service ${service.serviceName}`, ); } }); log.info( - `Waiting for handover to be completed for service: ${ - service.serviceName - }`, + `Waiting for handover to be completed for service: ${service.serviceName}`, ); return wait().then(() => { diff --git a/src/compose/service.ts b/src/compose/service.ts index be0a44a1..349deaef 100644 --- a/src/compose/service.ts +++ b/src/compose/service.ts @@ -337,9 +337,7 @@ export class Service { config.cpus = Math.round(Number(config.cpus) * 10 ** 9); if (_.isNaN(config.cpus)) { log.warn( - `config.cpus value cannot be parsed. Ignoring.\n Value:${ - config.cpus - }`, + `config.cpus value cannot be parsed. Ignoring.\n Value:${config.cpus}`, ); config.cpus = undefined; } @@ -713,9 +711,7 @@ export class Service { // Add some console output for why a service is not matching // so that if we end up in a restart loop, we know exactly why log.debug( - `Replacing container for service ${ - this.serviceName - } because of config changes:`, + `Replacing container for service ${this.serviceName} because of config changes:`, ); if (!nonArrayEquals) { // Try not to leak any sensitive information diff --git a/src/compose/utils.ts b/src/compose/utils.ts index 04defa9b..d20f3766 100644 --- a/src/compose/utils.ts +++ b/src/compose/utils.ts @@ -343,9 +343,9 @@ export function addFeaturesFromLabels( `${constants.dockerSocket}:${constants.dockerSocket}`, ); if (service.config.environment['DOCKER_HOST'] == null) { - service.config.environment['DOCKER_HOST'] = `unix://${ - constants.dockerSocket - }`; + service.config.environment[ + 'DOCKER_HOST' + ] = `unix://${constants.dockerSocket}`; } // We keep balena.sock for backwards compatibility if (constants.dockerSocket !== '/var/run/balena.sock') { diff --git a/src/config/backend.ts b/src/config/backend.ts index 20eb2d20..fa13c910 100644 --- a/src/config/backend.ts +++ b/src/config/backend.ts @@ -30,9 +30,7 @@ function remountAndWriteAtomic(file: string, data: string): Promise { // Here's the dangerous part: return Promise.resolve( childProcess.execAsync( - `mount -t vfat -o remount,rw ${ - constants.bootBlockDevice - } ${bootMountPoint}`, + `mount -t vfat -o remount,rw ${constants.bootBlockDevice} ${bootMountPoint}`, ), ) .then(() => { @@ -76,9 +74,7 @@ export abstract class DeviceConfigBackend { } export class RPiConfigBackend extends DeviceConfigBackend { - private static bootConfigVarPrefix = `${ - constants.hostConfigVarPrefix - }CONFIG_`; + private static bootConfigVarPrefix = `${constants.hostConfigVarPrefix}CONFIG_`; private static bootConfigPath = `${bootMountPoint}/config.txt`; public static bootConfigVarRegex = new RegExp( @@ -206,9 +202,7 @@ export class RPiConfigBackend extends DeviceConfigBackend { } export class ExtlinuxConfigBackend extends DeviceConfigBackend { - private static bootConfigVarPrefix = `${ - constants.hostConfigVarPrefix - }EXTLINUX_`; + private static bootConfigVarPrefix = `${constants.hostConfigVarPrefix}EXTLINUX_`; private static bootConfigPath = `${bootMountPoint}/extlinux/extlinux.conf`; public static bootConfigVarRegex = new RegExp( diff --git a/src/config/index.ts b/src/config/index.ts index 4a0cf5a6..555eed8f 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -27,10 +27,10 @@ interface ConfigOpts { } export type ConfigMap = { - [key in T]: SchemaReturn + [key in T]: SchemaReturn; }; export type ConfigChangeMap = { - [key in T]?: SchemaReturn + [key in T]?: SchemaReturn; }; // Export this type renamed, for storing config keys @@ -173,9 +173,11 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) { }) .then(() => { if (!_.isEmpty(configJsonVals)) { - return this.configJsonBackend.set(configJsonVals as { - [key in Schema.SchemaKey]: unknown - }); + return this.configJsonBackend.set( + configJsonVals as { + [key in Schema.SchemaKey]: unknown; + }, + ); } }); }; diff --git a/src/device-config.ts b/src/device-config.ts index e15f6328..ffe93ff4 100644 --- a/src/device-config.ts +++ b/src/device-config.ts @@ -208,9 +208,10 @@ export class DeviceConfig { 'Non-dictionary passed to DeviceConfig.setBootConfig', ); } - await this.setBootConfig(configBackend, step.target as Dictionary< - string - >); + await this.setBootConfig( + configBackend, + step.target as Dictionary, + ); }, }; } @@ -276,9 +277,9 @@ export class DeviceConfig { } public async getCurrent() { - const conf = await this.config.getMany(['deviceType'].concat( - _.keys(DeviceConfig.configKeys), - ) as SchemaTypeKey[]); + const conf = await this.config.getMany( + ['deviceType'].concat(_.keys(DeviceConfig.configKeys)) as SchemaTypeKey[], + ); const configBackend = await this.getConfigBackend(); diff --git a/src/device-state.coffee b/src/device-state.coffee index cfbc707c..0ed2c218 100644 --- a/src/device-state.coffee +++ b/src/device-state.coffee @@ -1,27 +1,18 @@ Promise = require 'bluebird' _ = require 'lodash' EventEmitter = require 'events' -fs = Promise.promisifyAll(require('fs')) express = require 'express' bodyParser = require 'body-parser' prettyMs = require 'pretty-ms' hostConfig = require './host-config' network = require './network' -execAsync = Promise.promisify(require('child_process').exec) -mkdirp = Promise.promisify(require('mkdirp')) -path = require 'path' -rimraf = Promise.promisify(require('rimraf')) constants = require './lib/constants' validation = require './lib/validation' systemd = require './lib/systemd' updateLock = require './lib/update-lock' { loadTargetFromFile } = require './device-state/preload' -{ singleToMulticontainerApp } = require './lib/migration' -{ - NotFoundError, - UpdatesLockedError -} = require './lib/errors' +{ UpdatesLockedError } = require './lib/errors' { DeviceConfig } = require './device-config' ApplicationManager = require './application-manager' @@ -323,44 +314,6 @@ module.exports = class DeviceState extends EventEmitter _.assign(@_currentVolatile, newState) @emitAsync('change') - restoreBackup: (targetState) => - @setTarget(targetState) - .then => - appId = _.keys(targetState.local.apps)[0] - if !appId? - throw new Error('No appId in target state') - volumes = targetState.local.apps[appId].volumes - backupPath = path.join(constants.rootMountPoint, 'mnt/data/backup') - rimraf(backupPath) # We clear this path in case it exists from an incomplete run of this function - .then -> - mkdirp(backupPath) - .then -> - execAsync("tar -xzf backup.tgz -C #{backupPath}", cwd: path.join(constants.rootMountPoint, 'mnt/data')) - .then -> - fs.readdirAsync(backupPath) - .then (dirContents) => - Promise.mapSeries dirContents, (volumeName) => - fs.statAsync(path.join(backupPath, volumeName)) - .then (s) => - if !s.isDirectory() - throw new Error("Invalid backup: #{volumeName} is not a directory") - if volumes[volumeName]? - log.debug("Creating volume #{volumeName} from backup") - # If the volume exists (from a previous incomplete run of this restoreBackup), we delete it first - @applications.volumes.get({ appId, name: volumeName }) - .then => - @applications.volumes.get({ appId, name: volumeName }).then (volume) -> - volume.remove() - .catch(NotFoundError, _.noop) - .then => - @applications.volumes.createFromPath({ appId, name: volumeName }, volumes[volumeName], path.join(backupPath, volumeName)) - else - throw new Error("Invalid backup: #{volumeName} is present in backup but not in target state") - .then -> - rimraf(backupPath) - .then -> - rimraf(path.join(constants.rootMountPoint, 'mnt/data', constants.migrationBackupFile)) - reboot: (force, skipLock) => @applications.stopAll({ force, skipLock }) .then => diff --git a/src/device-state.d.ts b/src/device-state.d.ts index 737a00f8..d9e9307d 100644 --- a/src/device-state.d.ts +++ b/src/device-state.d.ts @@ -17,6 +17,9 @@ class DeviceState extends EventEmitter { public config: Config; public eventTracker: EventTracker; + // FIXME: I should be removed once device-state is refactored + public connected: boolean; + public constructor(args: { config: Config; db: Database; @@ -31,6 +34,8 @@ class DeviceState extends EventEmitter { public setTarget(target: any): Promise; public triggerApplyTarget(opts: any): Promise; public reportCurrentState(state: any); + public getCurrentForComparison(): Promise; + public getStatus(): Promise; public async init(); } diff --git a/src/lib/docker-utils.ts b/src/lib/docker-utils.ts index b50b83ca..2d742779 100644 --- a/src/lib/docker-utils.ts +++ b/src/lib/docker-utils.ts @@ -84,9 +84,7 @@ export class DockerUtils extends DockerToolbelt { if (!_.includes([2, 3], deltaOpts.deltaVersion)) { logFn( - `Unsupported delta version: ${ - deltaOpts.deltaVersion - }. Failling back to regular pull`, + `Unsupported delta version: ${deltaOpts.deltaVersion}. Failling back to regular pull`, ); return await this.fetchImageWithProgress(imgDest, deltaOpts, onProgress); } @@ -117,9 +115,7 @@ export class DockerUtils extends DockerToolbelt { }, }; - const url = `${deltaOpts.deltaEndpoint}/api/v${ - deltaOpts.deltaVersion - }/delta?src=${deltaOpts.deltaSource}&dest=${imgDest}`; + const url = `${deltaOpts.deltaEndpoint}/api/v${deltaOpts.deltaVersion}/delta?src=${deltaOpts.deltaSource}&dest=${imgDest}`; const [res, data] = await (await request.getRequestInstance()).getAsync( url, @@ -140,9 +136,7 @@ export class DockerUtils extends DockerToolbelt { ) ) { throw new Error( - `Got ${ - res.statusCode - } when requesting an image from delta server.`, + `Got ${res.statusCode} when requesting an image from delta server.`, ); } const deltaUrl = res.headers['location']; @@ -164,9 +158,7 @@ export class DockerUtils extends DockerToolbelt { case 3: if (res.statusCode !== 200) { throw new Error( - `Got ${ - res.statusCode - } when requesting v3 delta from delta server.`, + `Got ${res.statusCode} when requesting v3 delta from delta server.`, ); } let name; @@ -340,16 +332,11 @@ export class DockerUtils extends DockerToolbelt { }, json: true, }; - const tokenUrl = `${tokenEndpoint}?service=${ - dstInfo.registry - }&scope=repository:${dstInfo.imageName}:pull&scope=repository:${ - srcInfo.imageName - }:pull`; + const tokenUrl = `${tokenEndpoint}?service=${dstInfo.registry}&scope=repository:${dstInfo.imageName}:pull&scope=repository:${srcInfo.imageName}:pull`; - const tokenResponseBody = (await (await request.getRequestInstance()).getAsync( - tokenUrl, - tokenOpts, - ))[1]; + const tokenResponseBody = ( + await (await request.getRequestInstance()).getAsync(tokenUrl, tokenOpts) + )[1]; const token = tokenResponseBody != null ? tokenResponseBody.token : null; if (token == null) { diff --git a/src/lib/errors.ts b/src/lib/errors.ts index 947bf71d..2e9672a3 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -104,3 +104,4 @@ export class ContractViolationError extends TypedError { export class AppsJsonParseError extends TypedError {} export class DatabaseParseError extends TypedError {} +export class BackupError extends TypedError {} diff --git a/src/lib/migration.ts b/src/lib/migration.ts index 7d9098a6..b2096792 100644 --- a/src/lib/migration.ts +++ b/src/lib/migration.ts @@ -1,15 +1,27 @@ +import * as Bluebird from 'bluebird'; import * as _ from 'lodash'; +import * as mkdirp from 'mkdirp'; +import { child_process, fs } from 'mz'; +import * as path from 'path'; import { PinejsClientRequest } from 'pinejs-client-request'; +import * as rimraf from 'rimraf'; + +const mkdirpAsync = Bluebird.promisify(mkdirp); +const rimrafAsync = Bluebird.promisify(rimraf); import ApplicationManager from '../application-manager'; import Config from '../config'; import Database, { Transaction } from '../db'; -import { DatabaseParseError, NotFoundError } from '../lib/errors'; +import DeviceState = require('../device-state'); +import * as constants from '../lib/constants'; +import { BackupError, DatabaseParseError, NotFoundError } from '../lib/errors'; +import { pathExistsOnHost } from '../lib/fs-utils'; import { log } from '../lib/supervisor-console'; import { ApplicationDatabaseFormat, AppsJsonFormat, TargetApplication, + TargetState, } from '../types/state'; export const defaultLegacyVolume = () => 'resin-data'; @@ -152,9 +164,7 @@ export async function normaliseLegacyDatabase( if (releases.length === 0) { log.warn( - `No compatible releases found in API, removing ${ - app.appId - } from target state`, + `No compatible releases found in API, removing ${app.appId} from target state`, ); await db .models('app') @@ -171,20 +181,18 @@ export async function normaliseLegacyDatabase( : `${image.is_stored_at__image_location}@${image.content_hash}`; log.debug( - `Found a release with releaseId ${release.id}, imageId ${ - image.id - }, serviceId ${serviceId}\nImage location is ${imageUrl}`, + `Found a release with releaseId ${release.id}, imageId ${image.id}, serviceId ${serviceId}\nImage location is ${imageUrl}`, ); const imageFromDocker = await application.docker .getImage(service.image) .inspect() - .catch(e => { - if (e instanceof NotFoundError) { + .catch(error => { + if (error instanceof NotFoundError) { return; } - throw e; + throw error; }); const imagesFromDatabase = await db .models('image') @@ -256,3 +264,88 @@ export async function normaliseLegacyDatabase( legacyAppsPresent: false, }); } + +export async function loadBackupFromMigration( + deviceState: DeviceState, + targetState: TargetState, + retryDelay: number, +): Promise { + try { + const exists = await pathExistsOnHost( + path.join('mnt/data', constants.migrationBackupFile), + ); + if (!exists) { + return; + } + log.info('Migration backup detected'); + + await deviceState.setTarget(targetState); + + // multi-app warning! + const appId = parseInt(_.keys(targetState.local?.apps)[0], 10); + + if (isNaN(appId)) { + throw new BackupError('No appId in target state'); + } + + const volumes = targetState.local?.apps?.[appId].volumes; + + const backupPath = path.join(constants.rootMountPoint, 'mnt/data/backup'); + // We clear this path in case it exists from an incomplete run of this function + await rimrafAsync(backupPath); + await mkdirpAsync(backupPath); + await child_process.exec(`tar -xzf backup.tgz -C ${backupPath}`, { + cwd: path.join(constants.rootMountPoint, 'mnt/data'), + }); + + for (const volumeName of await fs.readdir(backupPath)) { + const statInfo = await fs.stat(path.join(backupPath, volumeName)); + + if (!statInfo.isDirectory()) { + throw new BackupError( + `Invalid backup: ${volumeName} is not a directory`, + ); + } + + if (volumes[volumeName] != null) { + log.debug(`Creating volume ${volumeName} from backup`); + // If the volume exists (from a previous incomplete run of this restoreBackup), we delete it first + await deviceState.applications.volumes + .get({ appId, name: volumeName }) + .then(volume => { + return volume.remove(); + }) + .catch(error => { + if (error instanceof NotFoundError) { + return; + } + throw error; + }); + + await deviceState.applications.volumes.createFromPath( + { appId, name: volumeName }, + volumes[volumeName], + path.join(backupPath, volumeName), + ); + } else { + throw new BackupError( + `Invalid backup: ${volumeName} is present in backup but not in target state`, + ); + } + } + + await rimrafAsync(backupPath); + await rimrafAsync( + path.join( + constants.rootMountPoint, + 'mnt/data', + constants.migrationBackupFile, + ), + ); + } catch (err) { + log.error(`Error restoring migration backup, retrying: ${err}`); + + await Bluebird.delay(retryDelay); + return loadBackupFromMigration(deviceState, targetState, retryDelay); + } +} diff --git a/src/logging/balena-backend.ts b/src/logging/balena-backend.ts index ceeb6999..b2faf446 100644 --- a/src/logging/balena-backend.ts +++ b/src/logging/balena-backend.ts @@ -60,9 +60,7 @@ export class BalenaLogBackend extends LogBackend { this.flush(); if (this.dropCount > 0) { this.write({ - message: `Warning: Suppressed ${ - this.dropCount - } message(s) due to high load`, + message: `Warning: Suppressed ${this.dropCount} message(s) due to high load`, timestamp: Date.now(), isSystem: true, isStdErr: true, diff --git a/src/logging/container.ts b/src/logging/container.ts index d4278616..717026c6 100644 --- a/src/logging/container.ts +++ b/src/logging/container.ts @@ -38,34 +38,35 @@ export class ContainerLogs extends (EventEmitter as new () => LogsEventEmitter) const stdoutStream = await container.logs(stdoutLogOpts); const stderrStream = await container.logs(stderrLogOpts); - [[stdoutStream, true], [stderrStream, false]].forEach( - ([stream, isStdout]: [Stream.Readable, boolean]) => { - stream - .on('error', err => { - this.emit( - 'error', - new Error(`Error on container logs: ${err} ${err.stack}`), - ); - }) - .pipe(es.split()) - .on('data', (logBuf: Buffer | string) => { - if (_.isString(logBuf)) { - logBuf = Buffer.from(logBuf); - } - const logMsg = ContainerLogs.extractMessage(logBuf); - if (logMsg != null) { - this.emit('log', { isStdout, ...logMsg }); - } - }) - .on('error', err => { - this.emit( - 'error', - new Error(`Error on container logs: ${err} ${err.stack}`), - ); - }) - .on('end', () => this.emit('closed')); - }, - ); + [ + [stdoutStream, true], + [stderrStream, false], + ].forEach(([stream, isStdout]: [Stream.Readable, boolean]) => { + stream + .on('error', err => { + this.emit( + 'error', + new Error(`Error on container logs: ${err} ${err.stack}`), + ); + }) + .pipe(es.split()) + .on('data', (logBuf: Buffer | string) => { + if (_.isString(logBuf)) { + logBuf = Buffer.from(logBuf); + } + const logMsg = ContainerLogs.extractMessage(logBuf); + if (logMsg != null) { + this.emit('log', { isStdout, ...logMsg }); + } + }) + .on('error', err => { + this.emit( + 'error', + new Error(`Error on container logs: ${err} ${err.stack}`), + ); + }) + .on('end', () => this.emit('closed')); + }); } private static extractMessage( diff --git a/test/23-local-mode.ts b/test/23-local-mode.ts index 991da111..bf17ed8d 100644 --- a/test/23-local-mode.ts +++ b/test/23-local-mode.ts @@ -178,9 +178,9 @@ describe('LocalModeManager', () => { const stubEngineObjectMethods = ( removeThrows: boolean, ): Array> => { - const resArray: Array< - sinon.SinonStubbedInstance - > = []; + const resArray: Array> = []; const stub = ( c: sinon.StubbableType,