From 63cca8f1cc5fbad32627ed417edce482884456d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BE=C3=B3r=20Magn=C3=BAsson?= Date: Mon, 19 Nov 2018 16:35:17 +0100 Subject: [PATCH] fix(create-command): add project key to generated config and fix tests The "project" key was missing at the top of the project level garden.yml file generated by the create command. Furthermore, the tests were validating the generated config against the projectSchema instead of the entire configSchema. --- .../src/commands/create/config-templates.ts | 62 +++++++++---------- garden-service/src/commands/create/helpers.ts | 24 ++----- garden-service/src/commands/create/module.ts | 16 ++--- garden-service/src/commands/create/project.ts | 57 ++++++++++------- .../src/commands/create/config-templates.ts | 13 ++-- .../test/src/commands/create/project.ts | 12 ++-- 6 files changed, 93 insertions(+), 91 deletions(-) diff --git a/garden-service/src/commands/create/config-templates.ts b/garden-service/src/commands/create/config-templates.ts index c614b54c63..84239f2a3b 100644 --- a/garden-service/src/commands/create/config-templates.ts +++ b/garden-service/src/commands/create/config-templates.ts @@ -7,13 +7,12 @@ */ import { capitalize, camelCase, uniq } from "lodash" -import * as Joi from "joi" import { DeepPartial } from "../../util/util" import { ContainerModuleSpec } from "../../plugins/container" import { GcfModuleSpec } from "../../plugins/google/google-cloud-functions" import { ProjectConfig } from "../../config/project" -import { BaseModuleSpec, ModuleConfig, baseModuleSpecSchema } from "../../config/module" +import { ModuleConfig } from "../../config/module" /** * Ideally there would be some mechanism to discover available module types, @@ -32,23 +31,12 @@ export const availableModuleTypes = Object.keys(MODULE_PROVIDER_MA export type ModuleType = keyof typeof MODULE_PROVIDER_MAP -export const moduleSchema = Joi.object().keys({ - module: baseModuleSpecSchema, -}) - -export interface ConfigOpts { - name: string - path: string - config: { module: Partial } | Partial -} - -export interface ModuleConfigOpts extends ConfigOpts { - type: ModuleType - config: { module: Partial } +export interface ProjectTemplate { + project: Partial } -export interface ProjectConfigOpts extends ConfigOpts { - config: Partial +export interface ModuleTemplate { + module: Partial } const noCase = (str: string) => str.replace(/-|_/g, " ") @@ -85,22 +73,34 @@ export function npmPackageTemplate(_moduleName: string): any { return {} } -export const projectTemplate = (name: string, moduleTypes: ModuleType[]): Partial => { +export const projectTemplate = (name: string, moduleTypes: ModuleType[]): ProjectTemplate => { const providers = uniq(moduleTypes).map(type => ({ name: MODULE_PROVIDER_MAP[type] })) return { - name, - environments: [ - { - name: "local", - providers, - variables: {}, - }, - ], + project: { + name, + environments: [ + { + name: "local", + providers, + variables: {}, + }, + ], + }, } } -export const moduleTemplate = (name: string, type: ModuleType): Partial => ({ - name, - type, - description: `${titleize(name)} ${noCase(type)}`, -}) +export const moduleTemplate = (name: string, type: ModuleType): ModuleTemplate => { + const moduleTypeTemplate = { + container: containerTemplate, + "google-cloud-function": googleCloudFunctionTemplate, + "npm-package": npmPackageTemplate, + }[type] + return { + module: { + name, + type, + description: `${titleize(name)} ${noCase(type)}`, + ...moduleTypeTemplate(name), + }, + } +} diff --git a/garden-service/src/commands/create/helpers.ts b/garden-service/src/commands/create/helpers.ts index e2732aacac..509471d7f2 100644 --- a/garden-service/src/commands/create/helpers.ts +++ b/garden-service/src/commands/create/helpers.ts @@ -8,13 +8,8 @@ import * as Joi from "joi" import { - containerTemplate, - googleCloudFunctionTemplate, - npmPackageTemplate, - ModuleConfigOpts, ModuleType, moduleTemplate, - ConfigOpts, } from "./config-templates" import { join } from "path" import { pathExists } from "fs-extra" @@ -22,28 +17,19 @@ import { validate } from "../../config/common" import { dumpYaml } from "../../util/util" import { MODULE_CONFIG_FILENAME } from "../../constants" import { LogNode } from "../../logger/log-node" +import { NewModuleOpts, CommonOpts } from "./project" -export function prepareNewModuleConfig(name: string, type: ModuleType, path: string): ModuleConfigOpts { - const moduleTypeTemplate = { - container: containerTemplate, - "google-cloud-function": googleCloudFunctionTemplate, - "npm-package": npmPackageTemplate, - }[type] +export function prepareNewModuleOpts(name: string, type: ModuleType, path: string): NewModuleOpts { return { name, type, path, - config: { - module: { - ...moduleTemplate(name, type), - ...moduleTypeTemplate(name), - }, - }, + config: moduleTemplate(name, type), } } -export async function dumpConfig(configOpts: ConfigOpts, schema: Joi.Schema, logger: LogNode) { - const { config, name, path } = configOpts +export async function dumpConfig(opts: CommonOpts, schema: Joi.Schema, logger: LogNode) { + const { config, name, path } = opts const yamlPath = join(path, MODULE_CONFIG_FILENAME) const task = logger.info({ msg: `Writing config for ${name}`, diff --git a/garden-service/src/commands/create/module.ts b/garden-service/src/commands/create/module.ts index 51c62ca1e0..7a9dfd5d21 100644 --- a/garden-service/src/commands/create/module.ts +++ b/garden-service/src/commands/create/module.ts @@ -8,6 +8,7 @@ import { basename, join } from "path" import dedent = require("dedent") +import { ensureDir } from "fs-extra" import { Command, @@ -17,14 +18,15 @@ import { CommandParams, } from "../base" import { ParameterError, GardenBaseError } from "../../exceptions" -import { availableModuleTypes, ModuleType, moduleSchema, ModuleConfigOpts } from "./config-templates" +import { availableModuleTypes, ModuleType } from "./config-templates" import { - prepareNewModuleConfig, + prepareNewModuleOpts, dumpConfig, } from "./helpers" import { prompts } from "./prompts" import { validate, joiIdentifier } from "../../config/common" -import { ensureDir } from "fs-extra" +import { NewModuleOpts } from "./project" +import { configSchema } from "../../config/base" const createModuleOptions = { name: new StringParameter({ @@ -47,7 +49,7 @@ type Opts = typeof createModuleOptions interface CreateModuleResult extends CommandResult { result: { - module?: ModuleConfigOpts, + module?: NewModuleOpts, } } @@ -102,14 +104,14 @@ export class CreateModuleCommand extends Command { } } - const module = prepareNewModuleConfig(moduleName, type, moduleRoot) + const moduleOpts = prepareNewModuleOpts(moduleName, type, moduleRoot) try { - await dumpConfig(module, moduleSchema, garden.log) + await dumpConfig(moduleOpts, configSchema, garden.log) } catch (err) { errors.push(err) } return { - result: { module }, + result: { module: moduleOpts }, errors, } } diff --git a/garden-service/src/commands/create/project.ts b/garden-service/src/commands/create/project.ts index a78c694d34..d846aef8b6 100644 --- a/garden-service/src/commands/create/project.ts +++ b/garden-service/src/commands/create/project.ts @@ -21,19 +21,21 @@ import { } from "../base" import { GardenBaseError } from "../../exceptions" import { - prepareNewModuleConfig, + prepareNewModuleOpts, dumpConfig, } from "./helpers" import { prompts } from "./prompts" import { projectTemplate, - ModuleConfigOpts, - ProjectConfigOpts, - moduleSchema, + ModuleTemplate, + ModuleType, + ProjectTemplate, } from "./config-templates" import { getChildDirNames } from "../../util/util" import { validate, joiIdentifier } from "../../config/common" -import { projectSchema } from "../../config/project" +import { configSchema } from "../../config/base" + +const flatten = (acc, val) => acc.concat(val) const createProjectOptions = { "module-dirs": new PathsParameter({ @@ -53,12 +55,25 @@ const createProjectArguments = { type Args = typeof createProjectArguments type Opts = typeof createProjectOptions -const flatten = (acc, val) => acc.concat(val) +export interface CommonOpts { + name: string + path: string + config: ModuleTemplate | ProjectTemplate +} + +export interface NewModuleOpts extends CommonOpts { + type: ModuleType + config: ModuleTemplate +} + +export interface NewProjectOpts extends CommonOpts { + config: ProjectTemplate +} interface CreateProjectResult extends CommandResult { result: { - projectConfig: ProjectConfigOpts, - moduleConfigs: ModuleConfigOpts[], + project: NewProjectOpts, + modules: NewModuleOpts[], } } @@ -86,7 +101,7 @@ export class CreateProjectCommand extends Command { options = createProjectOptions async action({ garden, args, opts }: CommandParams): Promise { - let moduleConfigs: ModuleConfigOpts[] = [] + let moduleOpts: NewModuleOpts[] = [] let errors: GardenBaseError[] = [] const projectRoot = args["project-dir"] ? join(garden.projectRoot, args["project-dir"].trim()) : garden.projectRoot @@ -107,13 +122,13 @@ export class CreateProjectCommand extends Command { if (moduleParentDirs.length > 0) { // If module-dirs option provided we scan for modules in the parent dir(s) and add them one by one - moduleConfigs = (await Bluebird.mapSeries(moduleParentDirs, async parentDir => { + moduleOpts = (await Bluebird.mapSeries(moduleParentDirs, async parentDir => { const moduleNames = await getChildDirNames(parentDir) - return Bluebird.reduce(moduleNames, async (acc: ModuleConfigOpts[], moduleName: string) => { + return Bluebird.reduce(moduleNames, async (acc: NewModuleOpts[], moduleName: string) => { const { type } = await prompts.addConfigForModule(moduleName) if (type) { - acc.push(prepareNewModuleConfig(moduleName, type, join(parentDir, moduleName))) + acc.push(prepareNewModuleOpts(moduleName, type, join(parentDir, moduleName))) } return acc }, []) @@ -122,30 +137,30 @@ export class CreateProjectCommand extends Command { .filter(m => m) } else { // Otherwise we prompt the user for modules to add - moduleConfigs = (await prompts.repeatAddModule()) - .map(({ name, type }) => prepareNewModuleConfig(name, type, join(projectRoot, name))) + moduleOpts = (await prompts.repeatAddModule()) + .map(({ name, type }) => prepareNewModuleOpts(name, type, join(projectRoot, name))) } garden.log.info("---------") const taskLog = garden.log.info({ msg: "Setting up project", status: "active" }) - for (const module of moduleConfigs) { + for (const module of moduleOpts) { await ensureDir(module.path) try { - await dumpConfig(module, moduleSchema, garden.log) + await dumpConfig(module, configSchema, garden.log) } catch (err) { errors.push(err) } } - const projectConfig: ProjectConfigOpts = { + const projectOpts: NewProjectOpts = { path: projectRoot, name: projectName, - config: projectTemplate(projectName, moduleConfigs.map(module => module.type)), + config: projectTemplate(projectName, moduleOpts.map(module => module.type)), } try { - await dumpConfig(projectConfig, projectSchema, garden.log) + await dumpConfig(projectOpts, configSchema, garden.log) } catch (err) { errors.push(err) } @@ -161,8 +176,8 @@ export class CreateProjectCommand extends Command { return { result: { - moduleConfigs, - projectConfig, + modules: moduleOpts, + project: projectOpts, }, errors, } diff --git a/garden-service/test/src/commands/create/config-templates.ts b/garden-service/test/src/commands/create/config-templates.ts index fe62eaf3e6..9995ad4b7c 100644 --- a/garden-service/test/src/commands/create/config-templates.ts +++ b/garden-service/test/src/commands/create/config-templates.ts @@ -6,35 +6,34 @@ import { moduleTemplate, } from "../../../../src/commands/create/config-templates" import { validate } from "../../../../src/config/common" -import { baseModuleSpecSchema } from "../../../../src/config/module" -import { projectSchema } from "../../../../src/config/project" +import { configSchema } from "../../../../src/config/base" describe("ConfigTemplates", () => { describe("projectTemplate", () => { for (const moduleType of availableModuleTypes) { it(`should be valid for module type ${moduleType}`, async () => { const config = projectTemplate("my-project", [moduleType]) - expect(() => validate(config, projectSchema)).to.not.throw() + expect(() => validate(config, configSchema)).to.not.throw() }) } it("should be valid for multiple module types", async () => { const config = projectTemplate("my-project", availableModuleTypes) - expect(() => validate(config, projectSchema)).to.not.throw() + expect(() => validate(config, configSchema)).to.not.throw() }) it("should be valid for multiple modules of same type", async () => { const config = projectTemplate("my-project", [availableModuleTypes[0], availableModuleTypes[0]]) - expect(() => validate(config, projectSchema)).to.not.throw() + expect(() => validate(config, configSchema)).to.not.throw() }) it("should be valid if no modules", async () => { const config = projectTemplate("my-project", []) - expect(() => validate(config, projectSchema)).to.not.throw() + expect(() => validate(config, configSchema)).to.not.throw() }) }) describe("moduleTemplate", () => { for (const moduleType of availableModuleTypes) { it(`should be valid for module type ${moduleType}`, async () => { const config = moduleTemplate("my-module", moduleType) - expect(() => validate(config, baseModuleSpecSchema)).to.not.throw() + expect(() => validate(config, configSchema)).to.not.throw() }) } }) diff --git a/garden-service/test/src/commands/create/project.ts b/garden-service/test/src/commands/create/project.ts index f340ca9978..9bb7b8aff9 100644 --- a/garden-service/test/src/commands/create/project.ts +++ b/garden-service/test/src/commands/create/project.ts @@ -54,8 +54,8 @@ describe("CreateProjectCommand", () => { args: { "project-dir": "" }, opts: { name: "", "module-dirs": [] }, }) - const modules = result.moduleConfigs.map(m => pick(m, ["name", "type", "path"])) - const project = pick(result.projectConfig, ["name", "path"]) + const modules = result.modules.map(m => pick(m, ["name", "type", "path"])) + const project = pick(result.project, ["name", "path"]) expect({ modules, project }).to.eql({ modules: [ @@ -77,7 +77,7 @@ describe("CreateProjectCommand", () => { args: { "project-dir": "new-project" }, opts: { name: "", "module-dirs": [] }, }) - expect(pick(result.projectConfig, ["name", "path"])).to.eql({ + expect(pick(result.project, ["name", "path"])).to.eql({ name: "new-project", path: join(garden.projectRoot, "new-project"), }) @@ -91,7 +91,7 @@ describe("CreateProjectCommand", () => { args: { "project-dir": "" }, opts: { name: "my-project", "module-dirs": [] }, }) - expect(pick(result.projectConfig, ["name", "path"])).to.eql({ + expect(pick(result.project, ["name", "path"])).to.eql({ name: "my-project", path: join(garden.projectRoot), }) @@ -105,7 +105,7 @@ describe("CreateProjectCommand", () => { args: { "project-dir": "" }, opts: { name: "", "module-dirs": ["."] }, }) - expect(result.moduleConfigs.map(m => pick(m, ["name", "type", "path"]))).to.eql([ + expect(result.modules.map(m => pick(m, ["name", "type", "path"]))).to.eql([ { type: "container", name: "module-a", path: join(garden.projectRoot, "module-a") }, { type: "container", name: "module-b", path: join(garden.projectRoot, "module-b") }, ]) @@ -119,7 +119,7 @@ describe("CreateProjectCommand", () => { args: { "project-dir": "" }, opts: { name: "", "module-dirs": ["module-a", "module-b"] }, }) - expect(result.moduleConfigs.map(m => pick(m, ["name", "type", "path"]))).to.eql([ + expect(result.modules.map(m => pick(m, ["name", "type", "path"]))).to.eql([ { type: "container", name: "child-module-a", path: join(garden.projectRoot, "module-a", "child-module-a") }, { type: "container", name: "child-module-b", path: join(garden.projectRoot, "module-b", "child-module-b") }, ])