diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 05735bd341..48f1bf3c3a 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -348,8 +348,9 @@ Outputs the status of your environment for debug purposes. Examples: -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) +garden get debug-info # create a zip file at the root of the project with debug information +garden get debug-info --format yaml # output provider info as YAML files (default is JSON) +garden get debug-info --include-project # include provider info for the project namespace (disabled by default) ##### Usage @@ -360,6 +361,8 @@ garden get debug-info --format yaml # output the provider info as yaml files (d | Argument | Alias | Type | Description | | -------- | ----- | ---- | ----------- | | `--format` | | `json` `yaml` | The output format for plugin-generated debug info. + | `--include-project` | | boolean | Include project-specific information from configured providers. +Note that this may include sensitive data, depending on the provider and your configuration. ### garden init diff --git a/garden-service/package-lock.json b/garden-service/package-lock.json index e13169d128..91466be199 100644 --- a/garden-service/package-lock.json +++ b/garden-service/package-lock.json @@ -163,7 +163,7 @@ "dependencies": { "chalk": { "version": "2.3.1", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { @@ -2668,7 +2668,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -3815,7 +3815,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -4517,7 +4517,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -4660,7 +4660,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -5549,7 +5549,7 @@ }, "got": { "version": "6.7.1", - "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { @@ -6241,7 +6241,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { "depd": "~1.1.2", @@ -6640,7 +6640,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -6838,7 +6838,7 @@ }, "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, "escodegen": { @@ -8558,7 +8558,7 @@ }, "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, @@ -8605,7 +8605,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -8625,7 +8625,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -8634,7 +8634,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -8650,7 +8650,7 @@ }, "yargs": { "version": "3.32.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", "dev": true, "requires": { @@ -9890,7 +9890,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } @@ -10095,7 +10095,7 @@ }, "kind-of": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=" }, "plugin-error": { @@ -10117,7 +10117,7 @@ }, "readable-stream": { "version": "2.0.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "requires": { "core-util-is": "~1.0.0", @@ -10135,12 +10135,12 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "through2": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/through2/-/through2-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz", "integrity": "sha1-OE51MU1J8y3hLuu4E2uOtrXVnak=", "requires": { "readable-stream": "~2.0.0", @@ -10528,7 +10528,7 @@ "dependencies": { "kind-of": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", "dev": true, "requires": { @@ -12646,7 +12646,7 @@ }, "xmlbuilder": { "version": "9.0.7", - "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", "dev": true }, diff --git a/garden-service/src/actions.ts b/garden-service/src/actions.ts index ba3499b74b..a13ef97819 100644 --- a/garden-service/src/actions.ts +++ b/garden-service/src/actions.ts @@ -451,9 +451,9 @@ export class ActionHelper implements TypeGuard { return { serviceStatuses, environmentStatuses } } - async getDebugInfo({ log }: { log: LogEntry }): Promise { + async getDebugInfo({ log, includeProject }: { log: LogEntry, includeProject: boolean }): Promise { const handlers = this.getActionHandlers("getDebugInfo") - return Bluebird.props(mapValues(handlers, async (h) => h({ ...await this.commonParams(h, log) }))) + return Bluebird.props(mapValues(handlers, async (h) => h({ ...await this.commonParams(h, log), includeProject }))) } //endregion diff --git a/garden-service/src/cli/cli.ts b/garden-service/src/cli/cli.ts index f9411d9680..33c20bbf0e 100644 --- a/garden-service/src/cli/cli.ts +++ b/garden-service/src/cli/cli.ts @@ -322,8 +322,7 @@ export class GardenCli { // Other exceptions are handled within the implementation of "get debug-info". if (command.name === "debug-info") { // Use default Garden dir name as fallback since Garden class hasn't been initialised - await generateBasicDebugInfoReport(root, join(root, DEFAULT_GARDEN_DIR_NAME), log) - return + await generateBasicDebugInfoReport(root, join(root, DEFAULT_GARDEN_DIR_NAME), log, parsedOpts.format) } 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 052b42599c..03f82a7279 100644 --- a/garden-service/src/commands/get/get-debug-info.ts +++ b/garden-service/src/commands/get/get-debug-info.ts @@ -11,6 +11,7 @@ import { Command, CommandParams, ChoicesParameter, + BooleanParameter, } from "../base" import { findProjectConfig } from "../../config/base" import { ensureDir, copy, remove, pathExists, writeFile } from "fs-extra" @@ -27,9 +28,10 @@ import { Garden } from "../../garden" import { zipFolder } from "../../util/archive" import chalk from "chalk" import { GitHandler } from "../../vcs/git" +import { ValidationError } from "../../exceptions" export const TEMP_DEBUG_ROOT = "tmp" -export const SYSTEM_INFO_FILENAME = "system-info.json" +export const SYSTEM_INFO_FILENAME_NO_EXT = "system-info" export const DEBUG_ZIP_FILENAME = "debug-info-TIMESTAMP.zip" export const PROVIDER_INFO_FILENAME_NO_EXT = "info" @@ -47,11 +49,9 @@ export async function collectBasicDebugInfo(root: string, gardenDirPath: string, // Find project definition const config = await findProjectConfig(root, true) if (!config) { - log.error(deline` - Couldn't find a garden.yml with a valid project definition. - Please run this command from the root of your Garden project.`) - process.exit(1) - return + throw new ValidationError(deline` + Couldn't find a garden.yml with a project definition. + Please run this command from the root of your Garden project.`, {}) } // Create temporary folder inside .garden/ at root of project @@ -77,16 +77,27 @@ export async function collectBasicDebugInfo(root: string, gardenDirPath: string, // Copy all the service configuration files for (const configPath of paths) { const servicePath = dirname(configPath) + const gardenPathLog = log.info({ + section: relative(root, servicePath) || "/", msg: "collecting info", status: "active", + }) const tempServicePath = join(tempPath, relative(root, servicePath)) await ensureDir(tempServicePath) const moduleConfigFilePath = await getConfigFilePath(servicePath) const moduleConfigFilename = basename(moduleConfigFilePath) + const gardenLog = gardenPathLog.info({ + section: moduleConfigFilename, msg: "collecting garden.yml", status: "active", + }) await copy(moduleConfigFilePath, join(tempServicePath, moduleConfigFilename)) - + gardenLog.setSuccess({ msg: chalk.green(`Done (took ${log.getDuration(1)} sec)`), append: true }) // Check if error logs exist and copy them over if they do if (await pathExists(join(servicePath, ERROR_LOG_FILENAME))) { + const errorLog = gardenPathLog.info({ + section: ERROR_LOG_FILENAME, msg: `collecting ${ERROR_LOG_FILENAME}`, status: "active", + }) await copy(join(servicePath, ERROR_LOG_FILENAME), join(tempServicePath, ERROR_LOG_FILENAME)) + errorLog.setSuccess({ msg: chalk.green(`Done (took ${log.getDuration(1)} sec)`), append: true }) } + gardenPathLog.setSuccess({ msg: chalk.green(`Done (took ${log.getDuration(1)} sec)`), append: true }) } } @@ -98,17 +109,20 @@ export async function collectBasicDebugInfo(root: string, gardenDirPath: string, * @param {string} gardenDirPath Path to the Garden cache directory * @param {LogEntry} log Logger */ -export async function collectSystemDiagnostic(gardenDirPath: string, log: LogEntry) { +export async function collectSystemDiagnostic(gardenDirPath: string, log: LogEntry, format: string) { const tempPath = join(gardenDirPath, TEMP_DEBUG_ROOT) await ensureDir(tempPath) - + const dockerLog = log.info({ section: "Docker", msg: "collecting info", status: "active" }) let dockerVersion = "" try { dockerVersion = await execa.stdout("docker", ["--version"]) + dockerLog.setSuccess({ msg: chalk.green(`Done (took ${log.getDuration(1)} sec)`), append: true }) } catch (error) { log.error("Error encountered while executing docker") log.error(error) } + const systemLog = log.info({ section: "Operating System", msg: "collecting info", status: "active" }) + const gardenLog = log.info({ section: "Garden", msg: "getting version", status: "active" }) const systemInfo = { gardenVersion: getPackageVersion(), @@ -117,8 +131,11 @@ export async function collectSystemDiagnostic(gardenDirPath: string, log: LogEnt dockerVersion, } - await writeFile(join(tempPath, SYSTEM_INFO_FILENAME), JSON.stringify(systemInfo, null, 4), "utf8") + systemLog.setSuccess({ msg: chalk.green(`Done (took ${log.getDuration(1)} sec)`), append: true }) + gardenLog.setSuccess({ msg: chalk.green(`Done (took ${log.getDuration(1)} sec)`), append: true }) + const outputFileName = `${SYSTEM_INFO_FILENAME_NO_EXT}.${format}` + await writeFile(join(tempPath, outputFileName), renderInfo(systemInfo, format), "utf8") } /** @@ -129,13 +146,14 @@ export async function collectSystemDiagnostic(gardenDirPath: string, log: LogEnt * @param {Garden} garden The Garden instance * @param {LogEntry} log Logger * @param {string} format The extension format dictating the extension of the report + * @param {string} includeProject Extended export */ -export async function collectProviderDebugInfo(garden: Garden, log: LogEntry, format: string) { +export async function collectProviderDebugInfo(garden: Garden, log: LogEntry, format: string, includeProject: boolean) { const tempPath = join(garden.gardenDirPath, TEMP_DEBUG_ROOT) await ensureDir(tempPath) // Collect debug info from providers const actions = await garden.getActionHelper() - const providersDebugInfo = await actions.getDebugInfo({ log }) + const providersDebugInfo = await actions.getDebugInfo({ log, includeProject }) // Create a provider folder and report for each provider. for (const [providerName, info] of Object.entries(providersDebugInfo)) { @@ -143,6 +161,7 @@ export async function collectProviderDebugInfo(garden: Garden, log: LogEntry, fo await ensureDir(prividerPath) const outputFileName = `${PROVIDER_INFO_FILENAME_NO_EXT}.${format}` await writeFile(join(prividerPath, outputFileName), renderInfo(info, format), "utf8") + } } @@ -156,17 +175,24 @@ export async function collectProviderDebugInfo(garden: Garden, log: LogEntry, fo * @param {string} root * @param {LogEntry} log */ -export async function generateBasicDebugInfoReport(root: string, gardenDirPath: string, log: LogEntry) { +export async function generateBasicDebugInfoReport( + root: string, gardenDirPath: string, log: LogEntry, format = "json") { + log.setWarn({ + msg: chalk.yellow( + "It looks like Garden couldn't validate your project: generating basic report.", + ), append: true, + }) + const tempPath = join(gardenDirPath, 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, gardenDirPath, log) + const projectEntry = entry.info({ section: "Project configuration", msg: "collecting info", status: "active" }) + await collectBasicDebugInfo(root, gardenDirPath, projectEntry) 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(gardenDirPath, log) + await collectSystemDiagnostic(gardenDirPath, systemEntry, format) systemEntry.setSuccess({ msg: chalk.green(`Done (took ${systemEntry.getDuration(1)} sec)`), append: true }) // Zip report folder @@ -201,11 +227,17 @@ function renderInfo(info: any, format: string) { const debugInfoArguments = {} const debugInfoOptions = { - format: new ChoicesParameter({ + "format": new ChoicesParameter({ help: "The output format for plugin-generated debug info.", choices: ["json", "yaml"], defaultValue: "json", }), + "include-project": new BooleanParameter({ + help: dedent` + Include project-specific information from configured providers. + Note that this may include sensitive data, depending on the provider and your configuration.`, + defaultValue: false, + }), } type Args = typeof debugInfoArguments @@ -226,8 +258,9 @@ export class GetDebugInfoCommand extends Command { description = dedent` Examples: - 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) + garden get debug-info # create a zip file at the root of the project with debug information + garden get debug-info --format yaml # output provider info as YAML files (default is JSON) + garden get debug-info --include-project # include provider info for the project namespace (disabled by default) ` arguments = debugInfoArguments @@ -239,19 +272,19 @@ export class GetDebugInfoCommand extends Command { 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, garden.gardenDirPath, log) + const projectEntry = entry.info({ section: "Project configuration", msg: "collecting info", status: "active" }) + await collectBasicDebugInfo(garden.projectRoot, garden.gardenDirPath, projectEntry) 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) + await collectSystemDiagnostic(garden.projectRoot, systemEntry, opts.format) 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) + await collectProviderDebugInfo(garden, providerEntry, opts.format, opts["include-project"]) providerEntry.setSuccess({ msg: chalk.green(`Done (took ${systemEntry.getDuration(1)} sec)`), append: true }) } catch (err) { // One or multiple providers threw an error while processing. @@ -269,9 +302,23 @@ export class GetDebugInfoCommand extends Command { // Cleanup temporary folders await remove(tempPath) + const success = log.placeholder() + const footer = success.placeholder() entry.setSuccess({ msg: "Done", append: true }) - log.info(`\nDone! Please find your report at ${outputFilePath}.`) + + success.setDone({ + msg: chalk.green(`\nDone! Please find your report at ${outputFilePath}.\n`), + }) + + footer.setWarn({ + msg: chalk.yellow(dedent` + NOTE: Please be aware that the output file might contain sensitive information. + If you plan to make the file available to the general public (e.g. GitHub), please review the content first. + If you need to share a file containing sensitive information with the Garden team, please contact us on + the #garden-dev channel on https://slack.k8s.io. + `), append: true, + }) return { result: 0 } } diff --git a/garden-service/src/plugins/kubernetes/kubernetes.ts b/garden-service/src/plugins/kubernetes/kubernetes.ts index 22d7b504af..98c0459d65 100644 --- a/garden-service/src/plugins/kubernetes/kubernetes.ts +++ b/garden-service/src/plugins/kubernetes/kubernetes.ts @@ -25,6 +25,7 @@ import { ConfigurationError } from "../../exceptions" import { cleanupClusterRegistry } from "./commands/cleanup-cluster-registry" import { clusterInit } from "./commands/cluster-init" import { uninstallGardenServices } from "./commands/uninstall-garden-services" +import chalk from "chalk" export const name = "kubernetes" @@ -65,20 +66,26 @@ export async function configureProvider({ projectName, config }: ConfigureProvid return { name: config.name, config } } -export async function debugInfo({ ctx, log }: GetDebugInfoParams): Promise { +export async function debugInfo({ ctx, log, includeProject }: GetDebugInfoParams): Promise { const k8sContext = ctx const { context } = k8sContext.provider.config - const appNamespace = await getAppNamespace(k8sContext, log, k8sContext.provider) - const appMetadataNamespace = await getMetadataNamespace(k8sContext, log, k8sContext.provider) - - const namespacesList = [appNamespace, appMetadataNamespace, systemNamespace, systemMetadataNamespace] + const entry = log.info({ section: ctx.provider.name, msg: "collecting provider configuration", status: "active" }) + const namespacesList = [systemNamespace, systemMetadataNamespace] + if (includeProject) { + const appNamespace = await getAppNamespace(k8sContext, log, k8sContext.provider) + const appMetadataNamespace = await getMetadataNamespace(k8sContext, log, k8sContext.provider) + namespacesList.push(appNamespace, appMetadataNamespace) + } const namespaces = await Bluebird.map(namespacesList, async (ns) => { + const nsEntry = entry.info({ section: ns, msg: "collecting namespace configuration", status: "active" }) const out = await kubectl.stdout({ log, context, args: ["get", "all", "--namespace", ns, "--output", "json"] }) + nsEntry.setSuccess({ msg: chalk.green(`Done (took ${log.getDuration(1)} sec)`), append: true }) return { namespace: ns, output: JSON.parse(out), } }) + entry.setSuccess({ msg: chalk.green(`Done (took ${log.getDuration(1)} sec)`), append: true }) const version = await kubectl.stdout({ log, context, args: ["version", "--output", "json"] }) diff --git a/garden-service/src/types/plugin/provider/getDebugInfo.ts b/garden-service/src/types/plugin/provider/getDebugInfo.ts index a4c1b94bc6..3672eeb2b1 100644 --- a/garden-service/src/types/plugin/provider/getDebugInfo.ts +++ b/garden-service/src/types/plugin/provider/getDebugInfo.ts @@ -18,13 +18,19 @@ export interface DebugInfoMap { [key: string]: DebugInfo } -export interface GetDebugInfoParams extends PluginActionParamsBase { } +export interface GetDebugInfoParams extends PluginActionParamsBase { + includeProject: boolean, +} export const getDebugInfo = { description: dedent` Collects debug info from the provider. `, - paramsSchema: actionParamsSchema, + paramsSchema: actionParamsSchema + .keys({ + includeProject: joi.boolean() + .description("If set, include project-specific information from configured providers."), + }), resultSchema: joi.object() .keys({ info: joi.any() diff --git a/garden-service/src/util/fs.ts b/garden-service/src/util/fs.ts index 426b01a1d0..657e7c931e 100644 --- a/garden-service/src/util/fs.ts +++ b/garden-service/src/util/fs.ts @@ -21,7 +21,7 @@ import { VcsHandler } from "../vcs/vcs" const VALID_CONFIG_FILENAMES = ["garden.yml", "garden.yaml"] const metadataFilename = "metadata.json" export const defaultDotIgnoreFiles = [".gitignore", ".gardenignore"] -export const fixedExcludes = [".git", ".garden", "debug-info-*"] +export const fixedExcludes = [".git", ".garden", "debug-info*/**"] /* Warning: Don't make any async calls in the loop body when using this function, since this may cause 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 8fc8c199ed..ab0905e586 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,18 +7,19 @@ */ import { expect } from "chai" +import * as yaml from "js-yaml" import { makeTestGardenA, cleanProject, withDefaultGlobalOpts } from "../../../../helpers" import { generateBasicDebugInfoReport, TEMP_DEBUG_ROOT, collectBasicDebugInfo, - SYSTEM_INFO_FILENAME, + SYSTEM_INFO_FILENAME_NO_EXT, collectSystemDiagnostic, collectProviderDebugInfo, PROVIDER_INFO_FILENAME_NO_EXT, GetDebugInfoCommand, } from "../../../../../src/commands/get/get-debug-info" -import { readdirSync, remove, pathExists, readJSONSync } from "fs-extra" +import { readdir, remove, pathExists, readJSON, readFile } from "fs-extra" import { ERROR_LOG_FILENAME } from "../../../../../src/constants" import { join, relative } from "path" import { Garden } from "../../../../../src/garden" @@ -28,7 +29,7 @@ import { getConfigFilePath } from "../../../../../src/util/fs" const debugZipFileRegex = new RegExp(/debug-info-.*?.zip/) async function cleanupTmpDebugFiles(root: string, gardenDirPath: string) { - const allFiles = readdirSync(root) + const allFiles = await readdir(root) await remove(join(gardenDirPath, TEMP_DEBUG_ROOT)) const deleteFilenames = allFiles.filter((fileName) => { return fileName.match(debugZipFileRegex) @@ -72,7 +73,7 @@ describe("GetDebugInfoCommand", () => { expect(res.result).to.eql(0) - const gardenProjectRootFiles = readdirSync(garden.projectRoot) + const gardenProjectRootFiles = await readdir(garden.projectRoot) const zipFiles = gardenProjectRootFiles.filter((fileName) => { return fileName.match(debugZipFileRegex) }) @@ -85,7 +86,7 @@ describe("GetDebugInfoCommand", () => { it("should generate a zip file containing a *basic* debug info report in the root folder of the project", async () => { await generateBasicDebugInfoReport(garden.projectRoot, garden.gardenDirPath, log) - const gardenProjectRootFiles = readdirSync(garden.projectRoot) + const gardenProjectRootFiles = await readdir(garden.projectRoot) const zipFiles = gardenProjectRootFiles.filter((fileName) => { return fileName.match(debugZipFileRegex) }) @@ -123,17 +124,37 @@ describe("GetDebugInfoCommand", () => { describe("collectSystemDiagnostic", () => { it("should create a system info report in a temporary folder", async () => { - await collectSystemDiagnostic(garden.gardenDirPath, log) + const format = "json" + await collectSystemDiagnostic(garden.gardenDirPath, log, format) + + // Check if the temporary folder exists + expect(await pathExists(gardenDebugTmp)).to.equal(true) + + // Checks if system debug file is created + const systemInfoFilePath = join(gardenDebugTmp, `${SYSTEM_INFO_FILENAME_NO_EXT}.${format}`) + expect(await pathExists(systemInfoFilePath)).to.equal(true) + + // Check structure of systemInfoFile + const systemInfoFile = await readJSON(systemInfoFilePath) + expect(systemInfoFile).to.have.property("gardenVersion") + expect(systemInfoFile).to.have.property("platform") + expect(systemInfoFile).to.have.property("platformVersion") + expect(systemInfoFile).to.have.property("dockerVersion") + }) + + it("should create a system info report in a temporary folder with yaml format", async () => { + const format = "yaml" + await collectSystemDiagnostic(garden.gardenDirPath, log, format) // Check if the temporary folder exists expect(await pathExists(gardenDebugTmp)).to.equal(true) // Checks if system debug file is created - const systemInfoFilePath = join(gardenDebugTmp, SYSTEM_INFO_FILENAME) + const systemInfoFilePath = join(gardenDebugTmp, `${SYSTEM_INFO_FILENAME_NO_EXT}.${format}`) expect(await pathExists(systemInfoFilePath)).to.equal(true) // Check structure of systemInfoFile - const systemInfoFile = readJSONSync(systemInfoFilePath) + const systemInfoFile = yaml.safeLoad(await readFile(systemInfoFilePath, "utf8")) expect(systemInfoFile).to.have.property("gardenVersion") expect(systemInfoFile).to.have.property("platform") expect(systemInfoFile).to.have.property("platformVersion") @@ -147,7 +168,7 @@ describe("GetDebugInfoCommand", () => { const expectedProviderFolderName = "test-plugin" const providerInfoFilePath = join(expectedProviderFolderName, `${PROVIDER_INFO_FILENAME_NO_EXT}.${format}`) - await collectProviderDebugInfo(garden, log, format) + await collectProviderDebugInfo(garden, log, format, false) // Check if the temporary folder exists expect(await pathExists(gardenDebugTmp)).to.equal(true) @@ -159,7 +180,7 @@ describe("GetDebugInfoCommand", () => { expect(await pathExists(join(gardenDebugTmp, providerInfoFilePath))).to.equal(true) // Check structure of provider info file - const systemInfoFile = readJSONSync(join(gardenDebugTmp, providerInfoFilePath)) + const systemInfoFile = await readJSON(join(gardenDebugTmp, providerInfoFilePath)) expect(systemInfoFile).to.have.property("info") }) diff --git a/garden-service/test/unit/src/vcs/git.ts b/garden-service/test/unit/src/vcs/git.ts index 5adf59529b..46aa839649 100644 --- a/garden-service/test/unit/src/vcs/git.ts +++ b/garden-service/test/unit/src/vcs/git.ts @@ -5,6 +5,7 @@ import { join, resolve } from "path" import { expectError } from "../../../helpers" import { getCommitIdFromRefList, parseGitUrl, GitHandler } from "../../../../src/vcs/git" +import { fixedExcludes } from "../../../../src/util/fs" // Overriding this to make sure any ignorefile name is respected const ignoreFileName = ".testignore" @@ -232,6 +233,20 @@ describe("GitHandler", () => { expect(files).to.eql([]) }) + it("should exclude files that are exclude by default", async () => { + for (const exclude of fixedExcludes) { + const name = "foo.txt" + const updatedExclude = exclude.replace("**", "a-folder").replace("*", "-a-value/sisis") + const path = resolve(join(tmpPath, updatedExclude), name) + await createFile(path) + } + + const files = (await handler.getFiles(tmpPath, undefined, [...fixedExcludes])) + .filter(f => !f.path.includes(ignoreFileName)) + + expect(files).to.eql([]) + }) + it("should exclude an untracked symlink to a directory", async () => { const tmpDir2 = await tmp.dir({ unsafeCleanup: true }) const tmpPathB = await realpath(tmpDir2.path)