From 14063c061a5edca91a4af517a153a3936c317c63 Mon Sep 17 00:00:00 2001 From: Emanuele Libralato Date: Wed, 22 May 2019 13:47:57 +0200 Subject: [PATCH] fix: include fixes, docs and refactoring for #778 Includes all change requests for PR #778 plus fixes 404 on tmp-promise release 2.0.0 dependency and some tests which broke after rebasing. --- docs/reference/commands.md | 4 +- garden-service/package-lock.json | 729 +++++++++++++++++- garden-service/package.json | 2 +- garden-service/src/actions.ts | 4 +- garden-service/src/cli/cli.ts | 23 +- .../src/commands/get/get-debug-info.ts | 181 +++-- garden-service/src/garden.ts | 27 +- garden-service/src/util/fs.ts | 38 +- .../unit/src/commands/get/get-debug-info.ts | 39 +- 9 files changed, 913 insertions(+), 134 deletions(-) diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 585652b3a6..ec7413d314 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -330,8 +330,8 @@ Outputs the status of your environment for debug purposes. Examples: -garden get debug-info # create a zip file on the root of the project with debug information -garden get debug-info --format yaml # outputs the provider info as yaml files (default as json) +garden get debug-info # create a zip file at the root of the project with debug information +garden get debug-info --format yaml # output the provider info as yaml files (default as json) ##### Usage diff --git a/garden-service/package-lock.json b/garden-service/package-lock.json index 914a238d0d..93b97eeb9a 100644 --- a/garden-service/package-lock.json +++ b/garden-service/package-lock.json @@ -476,7 +476,7 @@ }, "@types/accepts": { "version": "1.3.5", - "resolved": "http://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", "requires": { "@types/node": "*" @@ -662,7 +662,713 @@ "dev": true, "requires": { "@types/undertaker": "*", - "@types/vinyl-fs": "*" + "@types/vinyl-fs": "*", + "chokidar": "^2.1.2" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, "@types/has-ansi": { @@ -1733,7 +2439,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { "readable-stream": "^2.3.5", @@ -3465,7 +4171,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "requires": { "core-util-is": "~1.0.0", @@ -6824,12 +7530,12 @@ }, "resolve": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" }, "source-map": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", "optional": true, "requires": { @@ -7884,7 +8590,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { @@ -11543,12 +12249,11 @@ } }, "tmp-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.1.0.tgz", - "integrity": "sha512-8+Ah9aB1IRXCnIOxXZ0uFozV1nMU5xiu7hhFVUSxZ3bYu+psD4TzagCzVbexUCgNNGJnsmNDQlS4nG3mTyoNkw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-2.0.1.tgz", + "integrity": "sha512-2A0lDPWiuu+92AVO19p5D6eq2egvCPiVUkzR11jE+GG4QMjj4YQ4glr2RqzYT0TMeMs2tIXLPDYo4wWh2/WoSg==", "dev": true, "requires": { - "bluebird": "^3.5.0", "tmp": "0.1.0" }, "dependencies": { @@ -12080,7 +12785,7 @@ "dependencies": { "bluebird": { "version": "3.4.7", - "resolved": "http://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" }, "duplexer2": { diff --git a/garden-service/package.json b/garden-service/package.json index c2b7a7dc90..4f8d626982 100644 --- a/garden-service/package.json +++ b/garden-service/package.json @@ -163,7 +163,7 @@ "testdouble": "^3.11.0", "testdouble-chai": "^0.5.0", "timekeeper": "^2.2.0", - "tmp-promise": "^1.1.0", + "tmp-promise": "^2.0.1", "touch": "^3.1.0", "ts-node": "^8.1.0", "tslint": "^5.16.0", diff --git a/garden-service/src/actions.ts b/garden-service/src/actions.ts index b74e178ddb..6e6ddcc396 100644 --- a/garden-service/src/actions.ts +++ b/garden-service/src/actions.ts @@ -422,9 +422,7 @@ export class ActionHelper implements TypeGuard { async getDebugInfo({ log }: { log: LogEntry }): Promise { const handlers = this.getActionHandlers("getDebugInfo") - const res = await Bluebird.props(mapValues(handlers, h => h({ ...this.commonParams(h, log) }))) - log.setSuccess("Ready") - return res + return await Bluebird.props(mapValues(handlers, h => h({ ...this.commonParams(h, log) }))) } //endregion diff --git a/garden-service/src/cli/cli.ts b/garden-service/src/cli/cli.ts index 36a33a29dd..d79a68bf83 100644 --- a/garden-service/src/cli/cli.ts +++ b/garden-service/src/cli/cli.ts @@ -51,7 +51,7 @@ import { GardenConfig } from "../config/base" import { defaultEnvironments } from "../config/project" import { ERROR_LOG_FILENAME } from "../constants" import stringify = require("json-stringify-safe") -import { collectBasicDebugInfo } from "../commands/get/get-debug-info" +import { generateBasicDebugInfoReport } from "../commands/get/get-debug-info" const OUTPUT_RENDERERS = { json: (data: DeepPrimitiveMap) => { @@ -284,23 +284,6 @@ export class GardenCli { }) do { - garden = await Garden.factory(root, contextOpts) - - // Register log file writers. We need to do this after the Garden class is initialised because - // the file writers depend on the project root. - await this.initFileWriters(logger, garden.projectRoot) - - // TODO: enforce that commands always output DeepPrimitiveMap - result = await command.action({ - garden, - log, - logFooter, - args: parsedArgs, - opts: parsedOpts, - }) - - await garden.close() - try { garden = await Garden.factory(root, contextOpts) @@ -320,8 +303,10 @@ export class GardenCli { await garden.close() } catch (err) { + // Generate a basic report in case Garden.factory(...) fails and command is "get debug-info". + // Other exceptions are handled within the implementation of "get debug-info". if (command.name === "debug-info") { - await collectBasicDebugInfo(root, log) + await generateBasicDebugInfoReport(root, log) return } throw err diff --git a/garden-service/src/commands/get/get-debug-info.ts b/garden-service/src/commands/get/get-debug-info.ts index 1a9c121812..fafad78895 100644 --- a/garden-service/src/commands/get/get-debug-info.ts +++ b/garden-service/src/commands/get/get-debug-info.ts @@ -16,11 +16,11 @@ import { findProjectConfig } from "../../config/base" import { ensureDir, copy, remove, pathExists, writeFile } from "fs-extra" import { getPackageVersion } from "../../util/util" import { platform, release } from "os" -import { join, parse, relative } from "path" +import { join, relative } from "path" import execa = require("execa") import { LogEntry } from "../../logger/log-entry" import { deline } from "../../util/string" -import { scanDirectory, getIgnorer } from "../../util/fs" +import { getModulesPathsFromPath } from "../../util/fs" import { CONFIG_FILENAME, ERROR_LOG_FILENAME, @@ -29,26 +29,23 @@ import { import dedent = require("dedent") import { Garden } from "../../garden" import { zipFolder } from "../../util/archive" +import chalk from "chalk" export const TEMP_DEBUG_ROOT = "tmp" -export const SYSTEM_INFO_FILENAME = "systemInfo.json" +export const SYSTEM_INFO_FILENAME = "system-info.json" export const DEBUG_ZIP_FILENAME = "debug-info-TIMESTAMP.zip" export const PROVIDER_INFO_FILENAME_NO_EXT = "info" -export async function generateBasicDebugInfoReport(root: string, log: LogEntry) { - const tempPath = join(root, GARDEN_DIR_NAME, TEMP_DEBUG_ROOT) - await collectBasicDebugInfo(root, log) - - log.info("Preparing archive.") - const outputFilename = DEBUG_ZIP_FILENAME.replace("TIMESTAMP", new Date().toISOString()) - await zipFolder(tempPath, join(root, outputFilename), log) - - await remove(tempPath) - log.info(`Done! Please find your report under ${root}.`) -} - +/** + * Collects project and modules configuration files and error logs (in case they exist). + * The files are copied over a temporary folder and mantain the folder structure from where + * they are copied from. + * + * @export + * @param {string} root Project root path + * @param {LogEntry} log Logger + */ export async function collectBasicDebugInfo(root: string, log: LogEntry) { - log.info("Collecting project configuration files.") // Find project definition const config = await findProjectConfig(root, true) if (!config) { @@ -71,31 +68,7 @@ export async function collectBasicDebugInfo(root: string, log: LogEntry) { } // Find all services paths - const ignorer = await getIgnorer(root) - const scanOpts = { - filter: (path) => { - const relPath = relative(root, path) - return !ignorer.ignores(relPath) - }, - } - const paths: string[] = [] - for await (const item of scanDirectory(root, scanOpts)) { - if (!item) { - continue - } - - const parsedPath = parse(item.path) - - if (parsedPath.dir === root) { - continue - } - - if (parsedPath.base !== CONFIG_FILENAME) { - continue - } - - paths.push(parsedPath.dir) - } + const paths = await getModulesPathsFromPath(root) // Copy all the service configuration files for (const servicePath of paths) { @@ -108,16 +81,17 @@ export async function collectBasicDebugInfo(root: string, log: LogEntry) { await copy(join(servicePath, ERROR_LOG_FILENAME), join(tempServicePath, ERROR_LOG_FILENAME)) } } - - // Run system diagnostic - await collectSystemDiagnostic(root, log) - } +/** + * Collects informations about garden, the OS and docker. + * Saves all the informations as json in a temporary folder. + * + * @export + * @param {string} root Project root path + * @param {LogEntry} log Logger + */ export async function collectSystemDiagnostic(root: string, log: LogEntry) { - - log.info("Collecting OS basic information.") - const tempPath = join(root, GARDEN_DIR_NAME, TEMP_DEBUG_ROOT) await ensureDir(tempPath) @@ -140,12 +114,22 @@ export async function collectSystemDiagnostic(root: string, log: LogEntry) { } -export async function collectProviderDebugInfo(garden: Garden, log: LogEntry, tempPath: string, format: string) { - +/** + * Generates a report with debug information for each provider which implements the action + * The reports are saved in a temporary and follows the structure "tmp/provider-name/info.json". + * + * @export + * @param {Garden} garden The Garden instance + * @param {LogEntry} log Logger + * @param {string} format The extension format dictating the extension of the report + */ +export async function collectProviderDebugInfo(garden: Garden, log: LogEntry, format: string) { + const tempPath = join(garden.projectRoot, GARDEN_DIR_NAME, TEMP_DEBUG_ROOT) await ensureDir(tempPath) - + // Collect debug info from providers const providersDebugInfo = await garden.actions.getDebugInfo({ log }) + // Create a provider folder and report for each provider. for (const [providerName, info] of Object.entries(providersDebugInfo)) { const prividerPath = join(tempPath, providerName) await ensureDir(prividerPath) @@ -154,6 +138,50 @@ export async function collectProviderDebugInfo(garden: Garden, log: LogEntry, te } } +/** + * Collects information about the project and the system running garden. + * Creates a zip file with the debug information at the root of the project. + * Accepts an invalid project and it will always generate a report. + * THIS SHOULD ONLY BE CALLED FROM `cli.ts`. + * + * @export + * @param {string} root + * @param {LogEntry} log + */ +export async function generateBasicDebugInfoReport(root: string, log: LogEntry) { + const tempPath = join(root, GARDEN_DIR_NAME, TEMP_DEBUG_ROOT) + const entry = log.info({ msg: "Collecting basic debug info", status: "active" }) + // Collect project info + const projectEntry = entry.info({ section: "Project", msg: "collecting info", status: "active" }) + await collectBasicDebugInfo(root, log) + projectEntry.setSuccess({ msg: chalk.green(`Done (took ${projectEntry.getDuration(1)} sec)`), append: true }) + + // Run system diagnostic + const systemEntry = entry.info({ section: "System", msg: "collecting info", status: "active" }) + await collectSystemDiagnostic(root, log) + systemEntry.setSuccess({ msg: chalk.green(`Done (took ${systemEntry.getDuration(1)} sec)`), append: true }) + + // Zip report folder + entry.setState("Preparing archive") + const outputFilename = DEBUG_ZIP_FILENAME.replace("TIMESTAMP", new Date().toISOString()) + const outputFilePath = join(root, outputFilename) + await zipFolder(tempPath, outputFilePath, log) + + // Cleanup temporary folders + await remove(tempPath) + + entry.setSuccess({ msg: "Done", append: true }) + log.info(`\nDone! Please find your report at ${outputFilePath}.`) +} + +/** + * Returns the input object as json or yaml string + * Defaults to yaml. + * + * @param {*} info The input data + * @param {string} format The format of the output. Default is yaml. + * @returns The info rendered in either json or yaml + */ function renderInfo(info: any, format: string) { if (format === "json") { return JSON.stringify(info, null, 4) @@ -168,13 +196,21 @@ const debugInfoOptions = { format: new ChoicesParameter({ help: "The output format for plugin-generated debug info.", choices: ["json", "yaml"], - defaultValue: "yaml", + defaultValue: "json", }), } type Args = typeof debugInfoArguments type Opts = typeof debugInfoOptions +/** + * Collects information about the project, the system running garden and the providers. + * Creates a zip file with the debug information at the root of the project. + * + * @export + * @class GetDebugInfoCommand + * @extends {Command} + */ export class GetDebugInfoCommand extends Command { name = "debug-info" help = "Outputs the status of your environment for debug purposes." @@ -182,27 +218,52 @@ export class GetDebugInfoCommand extends Command { description = dedent` Examples: - garden get debug-info # create a zip file on the root of the project with debug information - garden get debug-info --format yaml # outputs the provider info as yaml files (default as json) + garden get debug-info # create a zip file at the root of the project with debug information + garden get debug-info --format yaml # output the provider info as yaml files (default as json) ` arguments = debugInfoArguments options = debugInfoOptions async action({ garden, log, opts }: CommandParams) { - const tempPath = join(garden.projectRoot, GARDEN_DIR_NAME, TEMP_DEBUG_ROOT) - await collectBasicDebugInfo(garden.projectRoot, log) - await collectProviderDebugInfo(garden, log, tempPath, opts.format) + const entry = log.info({ msg: "Collecting debug info", status: "active" }) + + // Collect project info + const projectEntry = entry.info({ section: "Project", msg: "collecting info", status: "active" }) + await collectBasicDebugInfo(garden.projectRoot, log) + projectEntry.setSuccess({ msg: chalk.green(`Done (took ${projectEntry.getDuration(1)} sec)`), append: true }) + + // Run system diagnostic + const systemEntry = entry.info({ section: "System", msg: "collecting info", status: "active" }) + await collectSystemDiagnostic(garden.projectRoot, log) + systemEntry.setSuccess({ msg: chalk.green(`Done (took ${systemEntry.getDuration(1)} sec)`), append: true }) + + // Collect providers info + const providerEntry = entry.info({ section: "Providers", msg: "collecting info", status: "active" }) + try { + await collectProviderDebugInfo(garden, log, opts.format) + providerEntry.setSuccess({ msg: chalk.green(`Done (took ${systemEntry.getDuration(1)} sec)`), append: true }) + } catch (err) { + // One or multiple providers threw an error while processing. + // Skip the step but still create a report. + providerEntry.setWarn({ + msg: chalk.yellow(`Failed to collect providers info. Skipping this step.`), append: true, + }) + } - log.info("Preparing archive.") + // Zip report folder + entry.setState("Preparing archive") const outputFilename = DEBUG_ZIP_FILENAME.replace("TIMESTAMP", new Date().toISOString()) - await zipFolder(tempPath, join(garden.projectRoot, outputFilename), log) + const outputFilePath = join(garden.projectRoot, outputFilename) + await zipFolder(tempPath, outputFilePath, log) + // Cleanup temporary folders await remove(tempPath) - log.info(`Done! Please find your report under ${garden.projectRoot}.`) + entry.setSuccess({ msg: "Done", append: true }) + log.info(`\nDone! Please find your report at ${outputFilePath}.`) return { result: 0 } } diff --git a/garden-service/src/garden.ts b/garden-service/src/garden.ts index 30c54c9b1c..d8b0c27295 100644 --- a/garden-service/src/garden.ts +++ b/garden-service/src/garden.ts @@ -67,7 +67,7 @@ import { platform, arch } from "os" import { LogEntry } from "./logger/log-entry" import { EventBus } from "./events" import { Watcher } from "./watch" -import { getIgnorer, Ignorer, scanDirectory } from "./util/fs" +import { getIgnorer, Ignorer, getModulesPathsFromPath } from "./util/fs" export interface ActionHandlerMap { [actionName: string]: PluginActions[T] @@ -607,30 +607,7 @@ export class Garden { const dirsToScan = [this.projectRoot, ...extSourcePaths] const modulePaths = flatten(await Bluebird.map(dirsToScan, async dir => { - const ignorer = await getIgnorer(dir) - const scanOpts = { - filter: (path) => { - const relPath = relative(dir, path) - return !ignorer.ignores(relPath) - }, - } - const paths: string[] = [] - - for await (const item of scanDirectory(dir, scanOpts)) { - if (!item) { - continue - } - - const parsedPath = parse(item.path) - - if (parsedPath.base !== CONFIG_FILENAME) { - continue - } - - paths.push(parsedPath.dir) - } - - return paths + return await getModulesPathsFromPath(dir) })).filter(Boolean) const rawConfigs: ModuleConfig[] = [...this.pluginModuleConfigs] diff --git a/garden-service/src/util/fs.ts b/garden-service/src/util/fs.ts index 9dfa7a2bb9..f013f0fb4c 100644 --- a/garden-service/src/util/fs.ts +++ b/garden-service/src/util/fs.ts @@ -11,8 +11,8 @@ import * as _spawn from "cross-spawn" import { pathExists, readFile } from "fs-extra" import minimatch = require("minimatch") import { some } from "lodash" -import { join, basename, win32, posix } from "path" -import { GARDEN_DIR_NAME } from "../constants" +import { join, basename, win32, posix, relative, parse } from "path" +import { GARDEN_DIR_NAME, CONFIG_FILENAME } from "../constants" // NOTE: Importing from ignore/ignore doesn't work on Windows const ignore = require("ignore") @@ -97,6 +97,40 @@ export async function getIgnorer(rootPath: string): Promise { return ig } +/** + * Given a path returns an array of module paths for the project. + * + * @export + * @param {string} dir Root of the project + * @returns + */ +export async function getModulesPathsFromPath(dir: string) { + const ignorer = await getIgnorer(dir) + const scanOpts = { + filter: (path) => { + const relPath = relative(dir, path) + return !ignorer.ignores(relPath) + }, + } + const paths: string[] = [] + + for await (const item of scanDirectory(dir, scanOpts)) { + if (!item) { + continue + } + + const parsedPath = parse(item.path) + + if (parsedPath.base !== CONFIG_FILENAME) { + continue + } + + paths.push(parsedPath.dir) + } + + return paths +} + /** * Converts a Windows-style path to a cygwin style path (e.g. C:\some\folder -> /cygdrive/c/some/folder). */ diff --git a/garden-service/test/unit/src/commands/get/get-debug-info.ts b/garden-service/test/unit/src/commands/get/get-debug-info.ts index f0a6841399..95d382097d 100644 --- a/garden-service/test/unit/src/commands/get/get-debug-info.ts +++ b/garden-service/test/unit/src/commands/get/get-debug-info.ts @@ -7,7 +7,7 @@ */ import { expect } from "chai" -import { makeTestGardenA, cleanProject } from "../../../../helpers" +import { makeTestGardenA, cleanProject, withDefaultGlobalOpts } from "../../../../helpers" import { generateBasicDebugInfoReport, TEMP_DEBUG_ROOT, @@ -16,6 +16,7 @@ import { collectSystemDiagnostic, collectProviderDebugInfo, PROVIDER_INFO_FILENAME_NO_EXT, + GetDebugInfoCommand, } from "../../../../../src/commands/get/get-debug-info" import { readdirSync, remove, pathExists, readJSONSync } from "fs-extra" import { GARDEN_DIR_NAME, CONFIG_FILENAME, ERROR_LOG_FILENAME } from "../../../../../src/constants" @@ -32,7 +33,6 @@ async function cleanupTmpDebugFiles(root: string) { return fileName.match(debugZipFileRegex) }) for await (const name of deleteFilenames) { - console.log(join(root, name)) await remove(join(root, name)) } } @@ -56,8 +56,31 @@ describe("GetDebugInfoCommand", () => { await cleanProject(garden.projectRoot) }) - describe("collectBasicDebugInfo", () => { - it("should generate a zip file containing a basic debug info report in the root folder of the project", + describe("generateDebugInfoReport", () => { + it("should generate a zip file containing a debug info report in the root folder of the project", + async () => { + const command = new GetDebugInfoCommand() + const res = await command.action({ + garden, + log, + logFooter: log, + args: {}, + opts: withDefaultGlobalOpts({ format: "json" }), + }) + + expect(res.result).to.eql(0) + + const gardenProjectRootFiles = readdirSync(garden.projectRoot) + const zipFiles = gardenProjectRootFiles.filter((fileName) => { + return fileName.match(debugZipFileRegex) + }) + expect(zipFiles.length).to.equal(1) + }, + ) + }) + + describe("generateBasicDebugInfoReport", () => { + it("should generate a zip file containing a *basic* debug info report in the root folder of the project", async () => { await generateBasicDebugInfoReport(garden.projectRoot, log) const gardenProjectRootFiles = readdirSync(garden.projectRoot) @@ -69,7 +92,7 @@ describe("GetDebugInfoCommand", () => { ) }) - describe("generateBasicDebugInfo", () => { + describe("collectBasicDebugInfo", () => { it("should create a basic debug info report in a temporary folder", async () => { await collectBasicDebugInfo(garden.projectRoot, log) @@ -93,10 +116,6 @@ describe("GetDebugInfoCommand", () => { expect(await pathExists(join(gardenDebugTmp, moduleRelativePath, ERROR_LOG_FILENAME))).to.equal(true) } } - - // Checks if system debug file is created - expect(await pathExists(join(gardenDebugTmp, SYSTEM_INFO_FILENAME))).to.equal(true) - }) }) @@ -126,7 +145,7 @@ describe("GetDebugInfoCommand", () => { const expectedProviderFolderName = "test-plugin" const providerInfoFilePath = join(expectedProviderFolderName, `${PROVIDER_INFO_FILENAME_NO_EXT}.${format}`) - await collectProviderDebugInfo(garden, log, gardenDebugTmp, format) + await collectProviderDebugInfo(garden, log, format) // Check if the temporary folder exists expect(await pathExists(gardenDebugTmp)).to.equal(true)