Merge pull request #1136 from balena-io/extract_backup

Extract backup logic from device state
This commit is contained in:
Theodor Gherzan 2019-11-16 18:04:46 +00:00 committed by GitHub
commit cf94feb88b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 367 additions and 454 deletions

357
package-lock.json generated
View File

@ -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": {

View File

@ -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",

View File

@ -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<void> {
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<DeviceApplicationState> {
public async getTargetState(): Promise<TargetState> {
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<void> {
// 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,

View File

@ -111,7 +111,7 @@ export function generateStep<T extends CompositionStepAction>(
}
type Executors<T extends CompositionStepAction> = {
[key in T]: (step: CompositionStep<key>) => Promise<unknown>
[key in T]: (step: CompositionStep<key>) => Promise<unknown>;
};
type LockingFn = (
// TODO: Once the entire codebase is typescript, change

View File

@ -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<unknown>,
);
}
private format(image: MaybeImage): Image {

View File

@ -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<void> {
// 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(() => {

View File

@ -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

View File

@ -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') {

View File

@ -30,9 +30,7 @@ function remountAndWriteAtomic(file: string, data: string): Promise<void> {
// 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(

View File

@ -27,10 +27,10 @@ interface ConfigOpts {
}
export type ConfigMap<T extends SchemaTypeKey> = {
[key in T]: SchemaReturn<key>
[key in T]: SchemaReturn<key>;
};
export type ConfigChangeMap<T extends SchemaTypeKey> = {
[key in T]?: SchemaReturn<key>
[key in T]?: SchemaReturn<key>;
};
// 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;
},
);
}
});
};

View File

@ -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<string>,
);
},
};
}
@ -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();

View File

@ -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 =>

View File

@ -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<any>;
public triggerApplyTarget(opts: any): Promise<any>;
public reportCurrentState(state: any);
public getCurrentForComparison(): Promise<any>;
public getStatus(): Promise<any>;
public async init();
}

View File

@ -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) {

View File

@ -104,3 +104,4 @@ export class ContractViolationError extends TypedError {
export class AppsJsonParseError extends TypedError {}
export class DatabaseParseError extends TypedError {}
export class BackupError extends TypedError {}

View File

@ -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<void> {
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);
}
}

View File

@ -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,

View File

@ -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(

View File

@ -178,9 +178,9 @@ describe('LocalModeManager', () => {
const stubEngineObjectMethods = (
removeThrows: boolean,
): Array<sinon.SinonStubbedInstance<EngineStubbedObject>> => {
const resArray: Array<
sinon.SinonStubbedInstance<EngineStubbedObject>
> = [];
const resArray: Array<sinon.SinonStubbedInstance<
EngineStubbedObject
>> = [];
const stub = <T>(
c: sinon.StubbableType<EngineStubbedObject>,