Skip to content

Commit

Permalink
feat(core): allow .yaml endings for Garden config files
Browse files Browse the repository at this point in the history
  • Loading branch information
eysi09 committed Jun 13, 2019
1 parent 060e18b commit 3a9195a
Show file tree
Hide file tree
Showing 21 changed files with 217 additions and 65 deletions.
2 changes: 1 addition & 1 deletion examples/gatsby-hot-reload/.dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
node_modules
garden.yaml
garden.yml
.git
14 changes: 9 additions & 5 deletions garden-service/src/commands/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import Joi = require("joi")
import { GardenError, RuntimeError, InternalError } from "../exceptions"
import { GardenError, RuntimeError, InternalError, ParameterError } from "../exceptions"
import { TaskResults } from "../task-graph"
import { LoggerType } from "../logger/logger"
import { ProcessResults } from "../process"
Expand All @@ -16,8 +16,6 @@ import { LogEntry } from "../logger/log-entry"
import { printFooter } from "../logger/util"
import { GlobalOptions } from "../cli/cli"

export class ValidationError extends Error { }

export interface ParameterConstructor<T> {
help: string,
required?: boolean,
Expand Down Expand Up @@ -139,7 +137,10 @@ export class IntegerParameter extends Parameter<number> {
try {
return parseInt(input, 10)
} catch {
throw new ValidationError(`Could not parse "${input}" as integer`)
throw new ParameterError(`Could not parse "${input}" as integer`, {
expectedType: "integer",
input,
})
}
}
}
Expand All @@ -164,7 +165,10 @@ export class ChoicesParameter extends Parameter<string> {
if (this.choices.includes(input)) {
return input
} else {
throw new ValidationError(`"${input}" is not a valid argument`)
throw new ParameterError(`"${input}" is not a valid argument`, {
expectedType: `One of: ${this.choices.join(", ")}`,
input,
})
}
}

Expand Down
17 changes: 9 additions & 8 deletions garden-service/src/commands/get/get-debug-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@ 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, relative } from "path"
import { join, relative, basename } from "path"
import execa = require("execa")
import { LogEntry } from "../../logger/log-entry"
import { deline } from "../../util/string"
import { getModulesPathsFromPath } from "../../util/fs"
import {
CONFIG_FILENAME,
ERROR_LOG_FILENAME,
} from "../../constants"
import { getModulesPathsFromPath, getConfigFilePath } from "../../util/fs"
import { ERROR_LOG_FILENAME } from "../../constants"
import dedent = require("dedent")
import { Garden } from "../../garden"
import { zipFolder } from "../../util/archive"
Expand Down Expand Up @@ -61,7 +58,9 @@ export async function collectBasicDebugInfo(root: string, gardenDirPath: string,
await ensureDir(tempPath)

// Copy project definition in tmp folder
await copy(join(root, CONFIG_FILENAME), join(tempPath, CONFIG_FILENAME))
const projectConfigFilePath = await getConfigFilePath(root)
const projectConfigFilename = basename(projectConfigFilePath)
await copy(projectConfigFilePath, join(tempPath, projectConfigFilename))
// Check if error logs exist and copy it over if it does
if (await pathExists(join(root, ERROR_LOG_FILENAME))) {
await copy(join(root, ERROR_LOG_FILENAME), join(tempPath, ERROR_LOG_FILENAME))
Expand All @@ -74,7 +73,9 @@ export async function collectBasicDebugInfo(root: string, gardenDirPath: string,
for (const servicePath of paths) {
const tempServicePath = join(tempPath, relative(root, servicePath))
await ensureDir(tempServicePath)
await copy(join(servicePath, CONFIG_FILENAME), join(tempServicePath, CONFIG_FILENAME))
const moduleConfigFilePath = await getConfigFilePath(servicePath)
const moduleConfigFilename = basename(moduleConfigFilePath)
await copy(moduleConfigFilePath, join(tempServicePath, moduleConfigFilename))

// Check if error logs exist and copy them over if they do
if (await pathExists(join(servicePath, ERROR_LOG_FILENAME))) {
Expand Down
9 changes: 5 additions & 4 deletions garden-service/src/config/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { join, sep, resolve, relative } from "path"
import { sep, resolve, relative, basename } from "path"
import * as yaml from "js-yaml"
import { readFile } from "fs-extra"
import { omit, flatten, isPlainObject, find } from "lodash"
import { baseModuleSpecSchema, ModuleResource } from "./module"
import { ConfigurationError } from "../exceptions"
import { CONFIG_FILENAME, DEFAULT_API_VERSION } from "../constants"
import { DEFAULT_API_VERSION } from "../constants"
import { ProjectResource } from "../config/project"
import { getConfigFilePath } from "../util/fs"

export interface GardenResource {
apiVersion: string
Expand All @@ -26,7 +27,7 @@ const baseModuleSchemaKeys = Object.keys(baseModuleSpecSchema.describe().childre

export async function loadConfig(projectRoot: string, path: string): Promise<GardenResource[]> {
// TODO: nicer error messages when load/validation fails
const absPath = join(path, CONFIG_FILENAME)
const absPath = await getConfigFilePath(path)
let fileData: Buffer
let rawSpecs: any[]

Expand All @@ -40,7 +41,7 @@ export async function loadConfig(projectRoot: string, path: string): Promise<Gar
try {
rawSpecs = yaml.safeLoadAll(fileData.toString()) || []
} catch (err) {
throw new ConfigurationError(`Could not parse ${CONFIG_FILENAME} in directory ${path} as valid YAML`, err)
throw new ConfigurationError(`Could not parse ${basename(absPath)} in directory ${path} as valid YAML`, err)
}

const resources: GardenResource[] = flatten(rawSpecs.map(s => prepareResources(s, path, projectRoot)))
Expand Down
1 change: 0 additions & 1 deletion garden-service/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { resolve, join } from "path"

export const isPkg = !!(<any>process).pkg

export const CONFIG_FILENAME = "garden.yml"
export const LOCAL_CONFIG_FILENAME = "local-config.yml"
export const GARDEN_SERVICE_ROOT = isPkg ? resolve(process.execPath, "..") : resolve(__dirname, "..", "..")
export const STATIC_DIR = join(GARDEN_SERVICE_ROOT, "static")
Expand Down
30 changes: 14 additions & 16 deletions garden-service/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import Bluebird = require("bluebird")
import { parse, relative, resolve, sep, join } from "path"
import { parse, relative, resolve, sep } from "path"
import { flatten, isString, cloneDeep, sortBy, set, zip } from "lodash"
const AsyncLock = require("async-lock")

Expand Down Expand Up @@ -35,12 +35,12 @@ import { BuildDependencyConfig, ModuleConfig, baseModuleSpecSchema, ModuleResour
import { ModuleConfigContext, ContextResolveOpts } from "./config/config-context"
import { createPluginContext } from "./plugin-context"
import { ModuleAndRuntimeActions, Plugins, RegisterPluginParam } from "./types/plugin/plugin"
import { SUPPORTED_PLATFORMS, SupportedPlatform, CONFIG_FILENAME, DEFAULT_GARDEN_DIR_NAME } from "./constants"
import { SUPPORTED_PLATFORMS, SupportedPlatform, DEFAULT_GARDEN_DIR_NAME } from "./constants"
import { platform, arch } from "os"
import { LogEntry } from "./logger/log-entry"
import { EventBus } from "./events"
import { Watcher } from "./watch"
import { getIgnorer, Ignorer, getModulesPathsFromPath } from "./util/fs"
import { getIgnorer, Ignorer, getModulesPathsFromPath, getConfigFilePath } from "./util/fs"
import { Provider, ProviderConfig, getProviderDependencies } from "./config/provider"
import { ResolveProviderTask } from "./tasks/resolve-provider"
import { ActionHelper } from "./actions"
Expand Down Expand Up @@ -407,13 +407,13 @@ export class Garden {

this.resolvedProviders = Object.values(taskResults).map(result => result.output)

for (const provider of this.resolvedProviders) {
for (const moduleConfig of provider.moduleConfigs) {
await Bluebird.map(this.resolvedProviders, async (provider) =>
Bluebird.map(provider.moduleConfigs, async (moduleConfig) => {
// Make sure module and all nested entities are scoped to the plugin
moduleConfig.plugin = provider.name
this.addModule(moduleConfig)
}
}
return this.addModule(moduleConfig)
}),
)
})

return this.resolvedProviders
Expand Down Expand Up @@ -622,9 +622,7 @@ export class Garden {
}
})

for (const config of rawConfigs) {
this.addModule(config)
}
await Bluebird.map(rawConfigs, async (config) => this.addModule(config))

this.modulesScanned = true
})
Expand All @@ -641,14 +639,14 @@ export class Garden {
* Add a module config to the context, after validating and calling the appropriate configure plugin handler.
* Template strings should be resolved on the config before calling this.
*/
private addModule(config: ModuleConfig) {
private async addModule(config: ModuleConfig) {
const key = getModuleKey(config.name, config.plugin)

if (this.moduleConfigs[key]) {
const [pathA, pathB] = [
relative(this.projectRoot, join(this.moduleConfigs[key].path, CONFIG_FILENAME)),
relative(this.projectRoot, join(config.path, CONFIG_FILENAME)),
].sort()
const paths = [this.moduleConfigs[key].path, config.path]
const [pathA, pathB] = (await Bluebird
.map(paths, async (path) => relative(this.projectRoot, await getConfigFilePath(path))))
.sort()

throw new ConfigurationError(
`Module ${key} is declared multiple times (in '${pathA}' and '${pathB}')`,
Expand Down
5 changes: 2 additions & 3 deletions garden-service/src/types/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { resolve } from "path"
import { flatten, uniq, cloneDeep, keyBy } from "lodash"
import { getNames } from "../util/util"
import { TestSpec } from "../config/test"
Expand All @@ -20,7 +19,7 @@ import * as Joi from "joi"
import { joiArray, joiIdentifier, joiIdentifierMap } from "../config/common"
import { ConfigGraph } from "../config-graph"
import * as Bluebird from "bluebird"
import { CONFIG_FILENAME } from "../constants"
import { getConfigFilePath } from "../util/fs"

export interface FileCopySpec {
source: string
Expand Down Expand Up @@ -96,7 +95,7 @@ export interface ModuleConfigMap<T extends ModuleConfig = ModuleConfig> {
}

export async function moduleFromConfig(garden: Garden, graph: ConfigGraph, config: ModuleConfig): Promise<Module> {
const configPath = resolve(config.path, CONFIG_FILENAME)
const configPath = await getConfigFilePath(config.path)
const version = await garden.resolveVersion(config.name, config.build.dependencies)

// Always include configuration file
Expand Down
41 changes: 39 additions & 2 deletions garden-service/src/util/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
import klaw = require("klaw")
import * as _spawn from "cross-spawn"
import { pathExists, readFile } from "fs-extra"
import * as Bluebird from "bluebird"
import minimatch = require("minimatch")
import { some } from "lodash"
import { join, basename, win32, posix, relative, parse } from "path"
import { CONFIG_FILENAME } from "../constants"
import { ValidationError } from "../exceptions"
// NOTE: Importing from ignore/ignore doesn't work on Windows
const ignore = require("ignore")

const VALID_CONFIG_FILENAMES = ["garden.yml", "garden.yaml"]

/*
Warning: Don't make any async calls in the loop body when using this function, since this may cause
funky concurrency behavior.
Expand Down Expand Up @@ -50,6 +53,40 @@ export async function* scanDirectory(path: string, opts?: klaw.Options): AsyncIt
}
}

/**
* Returns the expected full path to the Garden config filename.
*
* If a valid config filename is found at the given path, it returns the full path to it.
* If no config file is found, it returns the path joined with the first value from the VALID_CONFIG_FILENAMES list.
* (The check for whether or not a project or a module has a valid config file at all is handled elsewehere.)
*
* Throws an error if there are more than one valid config filenames at the given path.
*/
export async function getConfigFilePath(path: string) {
const configFilePaths = await Bluebird
.map(VALID_CONFIG_FILENAMES, async (filename) => {
const configFilePath = join(path, filename)
return (await pathExists(configFilePath)) ? configFilePath : undefined
})
.filter(Boolean)

if (configFilePaths.length > 1) {
throw new ValidationError(`Found more than one Garden config file at ${path}.`, {
path,
configFilenames: configFilePaths.map(filePath => basename(filePath || "")).join(", "),
})
}

return configFilePaths[0] || join(path, VALID_CONFIG_FILENAMES[0])
}

/**
* Helper function to check whether a given filename is a valid Garden config filename
*/
export function isConfigFilename(filename: string) {
return VALID_CONFIG_FILENAMES.includes(filename)
}

export async function getChildDirNames(parentDir: string): Promise<string[]> {
let dirNames: string[] = []
// Filter on hidden dirs by default. We could make the filter function a param if needed later
Expand Down Expand Up @@ -125,7 +162,7 @@ export async function getModulesPathsFromPath(dir: string, gardenDirPath: string

const parsedPath = parse(item.path)

if (parsedPath.base !== CONFIG_FILENAME) {
if (!isConfigFilename(parsedPath.base)) {
continue
}

Expand Down
8 changes: 4 additions & 4 deletions garden-service/src/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import { watch, FSWatcher } from "chokidar"
import { parse, relative } from "path"
import { pathToCacheContext } from "./cache"
import { Module } from "./types/module"
import { CONFIG_FILENAME } from "./constants"
import { Garden } from "./garden"
import { LogEntry } from "./logger/log-entry"
import * as klaw from "klaw"
import { registerCleanupFunction } from "./util/util"
import * as Bluebird from "bluebird"
import { some } from "lodash"
import { isConfigFilename } from "./util/fs"

export type ChangeHandler = (module: Module | null, configChanged: boolean) => Promise<void>

Expand Down Expand Up @@ -117,12 +117,12 @@ export class Watcher {
const parsed = parse(path)
const filename = parsed.base

if (filename === CONFIG_FILENAME || filename === ".gitignore" || filename === ".gardenignore") {
if (isConfigFilename(filename) || filename === ".gitignore" || filename === ".gardenignore") {
this.invalidateCached(modules)

if (changedModuleNames.length > 0) {
this.garden.events.emit("moduleConfigChanged", { names: changedModuleNames, path })
} else if (filename === CONFIG_FILENAME) {
} else if (isConfigFilename(filename)) {
if (parsed.dir === this.garden.projectRoot) {
this.garden.events.emit("projectConfigChanged", {})
} else {
Expand Down Expand Up @@ -160,7 +160,7 @@ export class Watcher {
klaw(path, scanOpts)
.on("data", (item) => {
const parsed = parse(item.path)
if (item.path !== path && parsed.base === CONFIG_FILENAME) {
if (item.path !== path && isConfigFilename(parsed.base)) {
configChanged = true
this.garden.events.emit("configAdded", { path: item.path })
}
Expand Down
7 changes: 5 additions & 2 deletions garden-service/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { Garden, GardenOpts } from "../src/garden"
import { ModuleConfig } from "../src/config/module"
import { mapValues, fromPairs } from "lodash"
import { ModuleVersion } from "../src/vcs/vcs"
import { CONFIG_FILENAME, GARDEN_SERVICE_ROOT } from "../src/constants"
import { GARDEN_SERVICE_ROOT } from "../src/constants"
import { EventBus, Events } from "../src/events"
import { ValueOf } from "../src/util/util"
import { Ignorer } from "../src/util/fs"
Expand Down Expand Up @@ -400,7 +400,10 @@ export function stubExtSources(garden: Garden) {
}

export function getExampleProjects() {
const names = readdirSync(examplesDir).filter(n => existsSync(join(examplesDir, n, CONFIG_FILENAME)))
const names = readdirSync(examplesDir).filter(n => {
const basePath = join(examplesDir, n)
return existsSync(join(basePath, "garden.yml")) || existsSync(join(basePath, "garden.yaml"))
})
return fromPairs(names.map(n => [n, join(examplesDir, n)]))
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project:
name: test-project-a
environmentDefaults:
variables:
some: variable
environments:
- name: local
providers:
- name: test-plugin
- name: test-plugin-b
- name: other
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project:
name: test-project-a
environmentDefaults:
variables:
some: variable
environments:
- name: local
providers:
- name: test-plugin
- name: test-plugin-b
- name: other
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project:
name: test-project-a
environmentDefaults:
variables:
some: variable
environments:
- name: local
providers:
- name: test-plugin
- name: test-plugin-b
- name: other
Loading

0 comments on commit 3a9195a

Please sign in to comment.