Skip to content

Commit

Permalink
Merge pull request #81 from garden-io/fix-hello-world
Browse files Browse the repository at this point in the history
Fix hello world
  • Loading branch information
thsig authored Apr 22, 2018
2 parents 290d1f3 + 3f2ee33 commit d8219fb
Show file tree
Hide file tree
Showing 29 changed files with 321 additions and 232 deletions.
2 changes: 1 addition & 1 deletion bin/integ
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ export GARDEN_LOGGER_TYPE=basic

cd ${garden_root}/examples/hello-world

${garden_bin} build
${garden_bin} build --force
3 changes: 3 additions & 0 deletions examples/hello-world/libraries/hello-npm-package/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function(whatAmI) {
return `Hello there, I'm a ${whatAmI}`
}
6 changes: 6 additions & 0 deletions examples/hello-world/services/hello-function/garden.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ module:
test:
unit:
command: [npm, test]
build:
dependencies:
- name: hello-npm-package
copy:
- source: "*"
target: libraries/hello-npm-package/
4 changes: 3 additions & 1 deletion examples/hello-world/services/hello-function/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const hello = require("./libraries/hello-npm-package")

/**
* HTTP Cloud Function.
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
exports.helloFunction = (req, res) => res.send("Hello there, I'm a function!")
exports.helloFunction = (req, res) => res.send(hello("function"))
1 change: 1 addition & 0 deletions examples/hello-world/services/hello-function/libraries
4 changes: 3 additions & 1 deletion examples/hello-world/services/hello-function/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
},
"author": "",
"license": "ISC",
"dependencies": {}
"dependencies": {
"hello-npm-package": "file:libraries/hello-npm-package"
}
}
18 changes: 0 additions & 18 deletions examples/multi-container/services/.vscode/launch.json

This file was deleted.

18 changes: 0 additions & 18 deletions examples/multi-container/services/result/.vscode/launch.json

This file was deleted.

50 changes: 34 additions & 16 deletions src/build-dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

import { map as bluebirdMap } from "bluebird"
import {
dirname,
isAbsolute,
join,
parse,
resolve,
sep,
} from "path"
import {
emptyDir,
Expand All @@ -20,8 +20,13 @@ import {
} from "fs-extra"
import * as Rsync from "rsync"
import { GARDEN_DIR_NAME } from "./constants"
import { ConfigurationError } from "./exceptions"
import { PluginContext } from "./plugin-context"
import { execRsyncCmd } from "./util"
import { Module } from "./types/module"
import {
BuildCopySpec,
Module,
} from "./types/module"

// Lazily construct a directory of modules inside which all build steps are performed.

Expand All @@ -41,26 +46,40 @@ export class BuildDir {

async syncFromSrc<T extends Module>(module: T) {
await this.sync(
resolve(this.projectRoot, module.path),
this.buildDirPath)
resolve(this.projectRoot, module.path, "*"),
await this.buildPath(module),
)
}

async syncDependencyProducts<T extends Module>(module: T) {
async syncDependencyProducts<T extends Module>(ctx: PluginContext, module: T) {
await this.syncFromSrc(module)
const buildPath = await this.buildPath(module)
const config = await module.getConfig()

await bluebirdMap(config.build.dependencies || [], (depConfig) => {
await bluebirdMap(config.build.dependencies || [], async (depConfig) => {
if (!depConfig.copy) {
return []
}

// Sync to the module's top-level dir by default.
const destinationDir = depConfig.copyDestination || ""
const sourceModule = await ctx.getModule(depConfig.name)
const sourceBuildPath = await this.buildPath(sourceModule)

return bluebirdMap(depConfig.copy, (relSourcePath) => {
const sourcePath = resolve(this.buildDirPath, depConfig.name, relSourcePath)
const destinationPath = dirname(resolve(buildPath, destinationDir, relSourcePath)) + sep
// Sync to the module's top-level dir by default.
return bluebirdMap(depConfig.copy, (copy: BuildCopySpec) => {
if (isAbsolute(copy.source)) {
throw new ConfigurationError(`Source path in build dependency copy spec must be a relative path`, {
copySpec: copy,
})
}

if (isAbsolute(copy.target)) {
throw new ConfigurationError(`Target path in build dependency copy spec must be a relative path`, {
copySpec: copy,
})
}

const sourcePath = join(sourceBuildPath, copy.source)
const destinationPath = join(buildPath, copy.target)
return this.sync(sourcePath, destinationPath)
})
})
Expand All @@ -77,15 +96,14 @@ export class BuildDir {
}

private async sync(sourcePath: string, destinationPath: string): Promise<void> {

await ensureDir(destinationPath)
const destinationDir = parse(destinationPath).dir
await ensureDir(destinationDir)

const syncCmd = new Rsync()
.flags(["a"])
.flags(["r", "p", "t", "g", "o"])
.source(sourcePath)
.destination(destinationPath)

await execRsyncCmd(syncCmd)
}

}
8 changes: 7 additions & 1 deletion src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import chalk from "chalk"
import { PluginContext } from "../plugin-context"
import { BooleanParameter, Command, ParameterValues, StringParameter } from "./base"
import { BuildTask } from "../tasks/build"
Expand Down Expand Up @@ -44,6 +45,11 @@ export class BuildCommand extends Command<typeof buildArguments, typeof buildOpt

ctx.log.header({ emoji: "hammer", command: "build" })

return await ctx.processTasks()
const result = await ctx.processTasks()

ctx.log.info("")
ctx.log.info({ emoji: "heavy_check_mark", msg: chalk.green("Done!\n") })

return result
}
}
57 changes: 39 additions & 18 deletions src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ export class Garden {
private readonly environment: string,
private readonly namespace: string,
public readonly config: EnvironmentConfig,
plugins: RegisterPluginParam[],
logger?: RootLogNode,
) {
this.modulesScanned = false
Expand All @@ -173,21 +172,6 @@ export class Garden {

this.pluginContext = createPluginContext(this)
this.taskGraph = new TaskGraph(this.pluginContext)

// Register plugins
for (const plugin of builtinPlugins.concat(plugins)) {
this.registerPlugin(plugin)
}

for (const plugin of fixedPlugins) {
this.loadPlugin(plugin, {})
}

// Load configured plugins
// Validate configuration
for (const [providerName, provider] of Object.entries(config.providers)) {
this.loadPlugin(providerName, provider)
}
}

static async factory(projectRoot: string, { env, config, logger, plugins = [] }: ContextOpts = {}) {
Expand Down Expand Up @@ -254,7 +238,24 @@ export class Garden {
// Resolve the project configuration based on selected environment
const projectConfig = merge({}, globalConfig, envConfig)

return new Garden(projectRoot, projectName, environment, namespace, projectConfig, plugins, logger)
const garden = new Garden(projectRoot, projectName, environment, namespace, projectConfig, logger)

// Register plugins
for (const plugin of builtinPlugins.concat(plugins)) {
garden.registerPlugin(plugin)
}

for (const plugin of fixedPlugins) {
await garden.loadPlugin(plugin, {})
}

// Load configured plugins
// Validate configuration
for (const [providerName, provider] of Object.entries(projectConfig.providers)) {
await garden.loadPlugin(providerName, provider)
}

return garden
}

getEnvironment(): Environment {
Expand Down Expand Up @@ -336,7 +337,7 @@ export class Garden {
this.registeredPlugins[name] = factory
}

private loadPlugin(pluginName: string, config: object) {
private async loadPlugin(pluginName: string, config: object) {
const factory = this.registeredPlugins[pluginName]

if (!factory) {
Expand Down Expand Up @@ -369,6 +370,19 @@ export class Garden {
this.config.providers[pluginName] = plugin.config
}

for (const modulePath of plugin.modules || []) {
const module = await this.resolveModule(modulePath)
if (!module) {
throw new PluginError(`Could not load module "${modulePath}" specified in plugin "${pluginName}"`, {
pluginName,
modulePath,
})
}
module.name = `${pluginName}.${module.name}`
module.updateConfig("name", module.name)
await this.addModule(module)
}

const actions = plugin.actions || {}

for (const actionType of pluginActionNames) {
Expand Down Expand Up @@ -472,6 +486,13 @@ export class Garden {
return output
}

/**
* Returns the module with the specified name. Throws error if it doesn't exist.
*/
async getModule(name: string, noScan?: boolean): Promise<Module<any>> {
return (await this.getModules([name], noScan))[name]
}

/*
Returns all services that are registered in this context.
Scans for modules and services in the project root if it hasn't already been done.
Expand Down
17 changes: 13 additions & 4 deletions src/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export type WrappedFromGarden = Pick<Garden,
"getEnvironment" |
"resolveModule" |
"getModules" |
"getModule" |
"getServices" |
"getService" |
"getTemplateContext" |
Expand All @@ -82,7 +83,9 @@ export interface PluginContext extends PluginContextGuard, WrappedFromGarden {
parseModule: <T extends Module>(moduleConfig: T["_ConfigType"]) => Promise<T>
getModuleBuildPath: <T extends Module>(module: T) => Promise<string>
getModuleBuildStatus: <T extends Module>(module: T, logEntry?: LogEntry) => Promise<BuildStatus>
buildModule: <T extends Module>(module: T, logEntry?: LogEntry) => Promise<BuildResult>
buildModule: <T extends Module>(
module: T, buildContext: PrimitiveMap, logEntry?: LogEntry,
) => Promise<BuildResult>
pushModule: <T extends Module>(module: T, logEntry?: LogEntry) => Promise<PushResult>
testModule: <T extends Module>(module: T, testSpec: TestSpec, logEntry?: LogEntry) => Promise<TestResult>
getTestResult: <T extends Module>(module: T, version: TreeVersion, logEntry?: LogEntry) => Promise<TestResult | null>
Expand All @@ -102,6 +105,7 @@ export interface PluginContext extends PluginContextGuard, WrappedFromGarden {
setConfig: (key: string[], value: string) => Promise<void>
deleteConfig: (key: string[]) => Promise<DeleteConfigResult>

stageBuild: <T extends Module>(module: T) => Promise<void>
getStatus: () => Promise<ContextStatus>
deployServices: (
params: { names?: string[], force?: boolean, forceBuild?: boolean, logEntry?: LogEntry },
Expand Down Expand Up @@ -140,6 +144,7 @@ export function createPluginContext(garden: Garden): PluginContext {
clearBuilds: wrap(garden.clearBuilds),
getEnvironment: wrap(garden.getEnvironment),
getModules: wrap(garden.getModules),
getModule: wrap(garden.getModule),
getServices: wrap(garden.getServices),
getService: wrap(garden.getService),
getTemplateContext: wrap(garden.getTemplateContext),
Expand All @@ -162,11 +167,15 @@ export function createPluginContext(garden: Garden): PluginContext {
return handler({ ...commonParams(handler), module, logEntry })
},

buildModule: async <T extends Module>(module: T, logEntry?: LogEntry) => {
await garden.buildDir.syncDependencyProducts(module)
buildModule: async <T extends Module>(module: T, buildContext: PrimitiveMap, logEntry?: LogEntry) => {
await ctx.stageBuild(module)
const defaultHandler = garden.getModuleActionHandler("buildModule", "generic")
const handler = garden.getModuleActionHandler("buildModule", module.type, defaultHandler)
return handler({ ...commonParams(handler), module, logEntry })
return handler({ ...commonParams(handler), module, buildContext, logEntry })
},

stageBuild: async <T extends Module>(module: T) => {
await garden.buildDir.syncDependencyProducts(ctx, module)
},

pushModule: async <T extends Module>(module: T, logEntry?: LogEntry) => {
Expand Down
23 changes: 18 additions & 5 deletions src/plugins/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ export interface ServiceEndpointSpec {
port: string
}

export type ServicePortProtocol = "TCP" | "UDP"

export interface ServicePortSpec {
protocol: "TCP" | "UDP"
protocol: ServicePortProtocol
containerPort: number
hostPort?: number
nodePort?: number
Expand Down Expand Up @@ -257,8 +259,14 @@ export const gardenPlugin = (): GardenPlugin => ({
return { ready: !!identifier }
},

async buildModule({ ctx, module, logEntry }: BuildModuleParams<ContainerModule>) {
if (!!module.image && !module.hasDockerfile()) {
async buildModule({ ctx, module, buildContext, logEntry }: BuildModuleParams<ContainerModule>) {
const buildPath = await module.getBuildPath()
const dockerfilePath = join(buildPath, "Dockerfile")

if (!!module.image && !existsSync(dockerfilePath)) {
if (await module.imageExistsLocally()) {
return { fresh: false }
}
logEntry && logEntry.setState(`Pulling image ${module.image}...`)
await module.pullImage(ctx)
return { fetched: true }
Expand All @@ -269,11 +277,16 @@ export const gardenPlugin = (): GardenPlugin => ({
// build doesn't exist, so we create it
logEntry && logEntry.setState(`Building ${identifier}...`)

const buildArgs = Object.entries(buildContext).map(([key, value]) => {
// TODO: may need to escape this
return `--build-arg ${key}=${value}`
}).join(" ")

// TODO: log error if it occurs
// TODO: stream output to log if at debug log level
await module.dockerCli(`build -t ${identifier} ${await module.getBuildPath()}`)
await module.dockerCli(`build ${buildArgs} -t ${identifier} ${buildPath}`)

return { fresh: true }
return { fresh: true, details: { identifier } }
},

async pushModule({ module, logEntry }: PushModuleParams<ContainerModule>) {
Expand Down
Loading

0 comments on commit d8219fb

Please sign in to comment.