Skip to content

Commit

Permalink
feat: added --hot-reload flag to dev & deploy
Browse files Browse the repository at this point in the history
Hot reloading is now only enabled for the services explicitly specified
via the dev & deploy commands' new --hot-reload option.

When this option is used, the respective commands are run in watch mode
(i.e. assume the -w/--watch flag).

Also: Started using HotReloadScheduler to prevent possible race
conditions when multiple hot reload operations are in flight for the
same module (something that HotReloadScheduler prevents by design).
  • Loading branch information
thsig committed Oct 16, 2018
1 parent ff0001d commit c779618
Show file tree
Hide file tree
Showing 67 changed files with 1,479 additions and 620 deletions.
23 changes: 16 additions & 7 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,12 @@ Deploy service(s) to your environment.

Examples:

garden deploy # deploy all modules in the project
garden deploy my-service # only deploy my-service
garden deploy --force # force re-deploy of modules, even if they're already deployed
garden deploy --watch # watch for changes to code
garden deploy --env stage # deploy your services to an environment called stage
garden deploy # deploy all modules in the project
garden deploy my-service # only deploy my-service
garden deploy --force # force re-deploy of modules, even if they're already deployed
garden deploy --watch # watch for changes to code
garden deploy --hot-reload=my-service # deploys all services, with hot reloading enabled for my-service
garden deploy --env stage # deploy your services to an environment called stage


##### Usage
Expand All @@ -225,7 +226,7 @@ Deploy service(s) to your environment.

| Argument | Required | Description |
| -------- | -------- | ----------- |
| `service` | No | The name of the service(s) to deploy (skip to deploy all services). Use comma as separator to specify multiple services.
| `service` | No | The name(s) of the service(s) to deploy (skip to deploy all services). Use comma as separator to specify multiple services.

##### Options

Expand All @@ -234,6 +235,7 @@ Deploy service(s) to your environment.
| `--force` | | boolean | Force redeploy of service(s).
| `--force-build` | | boolean | Force rebuild of module(s).
| `--watch` | `-w` | boolean | Watch for changes in module(s) and auto-deploy.
| `--hot-reload` | | array:string | The name(s) of the service(s) to deploy with hot reloading enabled. Use comma as separator to specify multiple services. When this option is used, the command is run in watch mode (i.e. implicitly assumes the --watch/-w flag).

### garden dev

Expand All @@ -247,11 +249,18 @@ Starts the garden development console.
Examples:

garden dev
garden dev --hot-reload=foo-service,bar-service # enable hot reloading for foo-service and bar-service


##### Usage

garden dev
garden dev [options]

##### Options

| Argument | Alias | Type | Description |
| -------- | ----- | ---- | ----------- |
| `--hot-reload` | | array:string | The name(s) of the service(s) to deploy with hot reloading enabled. Use comma as separator to specify multiple services.

### garden exec

Expand Down
6 changes: 3 additions & 3 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ project:
{}

# Valid RFC1035/RFC1123 (DNS) label (may contain lowercase letters, numbers and dashes, must
# start with a letter, and cannot end with a dash) and additionally cannot contain
# start with a letter,and cannot end with a dash) and additionally cannot contain
# consecutive dashes or be longer than 63 characters.
#
# Required.
Expand Down Expand Up @@ -452,7 +452,7 @@ module:
#
# Optional.
- # Valid RFC1035/RFC1123 (DNS) label (may contain lowercase letters, numbers and dashes, must
# start with a letter, and cannot end with a dash) and additionally cannot contain
# start with a letter,and cannot end with a dash) and additionally cannot contain
# consecutive dashes or be longer than 63 characters.
#
# Required.
Expand All @@ -463,7 +463,7 @@ module:
# Optional.
dependencies:
# Valid RFC1035/RFC1123 (DNS) label (may contain lowercase letters, numbers and dashes,
# must start with a letter, and cannot end with a dash) and additionally cannot contain
# must start with a letter,and cannot end with a dash) and additionally cannot contain
# consecutive dashes or be longer than 63 characters.
#
# Optional.
Expand Down
3 changes: 2 additions & 1 deletion examples/hello-world/.garden/local-config.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
kubernetes:
username: hello
previous-usernames:
- hello
linkedModuleSources: []
linkedProjectSources: []
1 change: 0 additions & 1 deletion examples/hot-reload/garden.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ project:
environmentDefaults:
providers:
- name: container
- name: npm-package
environments:
- name: local
providers:
Expand Down
File renamed without changes.
14 changes: 14 additions & 0 deletions examples/hot-reload/node-service/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const express = require("express")

const app = express()

app.get("/hello", (req, res) => {
res.json({message: "Hello from Node!"})
})

// This is the path GAE uses for health checks
app.get("/_ah/health", (req, res) => {
res.sendStatus(200)
})

module.exports = { app }
21 changes: 21 additions & 0 deletions examples/hot-reload/node-service/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module:
description: Node greeting service
name: node-service
type: container
hotReload:
sync:
- target: /app/
services:
- name: node-service
command: [npm, start]
hotReloadCommand: [npm, run, dev]
ports:
- name: http
containerPort: 8080
ingresses:
- path: /hello
port: http
healthCheck:
httpGet:
path: /_ah/health
port: http
File renamed without changes.
5 changes: 3 additions & 2 deletions garden-service/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ static/**/.garden-version
support/**/*.js
support/**/*.map
support/**/*.d.ts
test/**/*.js
test/**/*.map
test/*.js
test/src/**/*.js
test/integ/**/*.js
5 changes: 5 additions & 0 deletions garden-service/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions garden-service/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ import { mapValues, values, keyBy, omit } from "lodash"
import { Omit } from "./util/util"
import { RuntimeContext } from "./types/service"
import { processServices, ProcessResults } from "./process"
import { getDeployTasks } from "./tasks/deploy"
import { getDeployTasks } from "./tasks/helpers"
import { LogEntry } from "./logger/log-entry"
import { createPluginContext } from "./plugin-context"
import { CleanupEnvironmentParams } from "./types/plugin/params"
Expand Down Expand Up @@ -229,7 +229,9 @@ export class ActionHelper implements TypeGuard {

async hotReload<T extends Module>(params: ModuleActionHelperParams<HotReloadParams<T>>)
: Promise<HotReloadResult> {
return this.callModuleHandler(({ params, actionType: "hotReload" }))
return this.garden.hotReload(params.module.name, async () => {
return this.callModuleHandler(({ params, actionType: "hotReload" }))
})
}

async testModule<T extends Module>(params: ModuleActionHelperParams<TestModuleParams<T>>): Promise<TestResult> {
Expand Down Expand Up @@ -333,9 +335,9 @@ export class ActionHelper implements TypeGuard {
garden: this.garden,
module,
serviceNames,
hotReloadServiceNames: [],
force,
forceBuild,
includeDependants: false,
}),
})
}
Expand Down
5 changes: 3 additions & 2 deletions garden-service/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import dedent = require("dedent")
import { processModules } from "../process"
import { computeAutoReloadDependants, withDependants } from "../watch"
import { Module } from "../types/module"
import { hotReloadAndLog } from "./deploy"
import { hotReloadAndLog } from "./helpers"

const buildArguments = {
module: new StringsParameter({
Expand Down Expand Up @@ -73,8 +73,9 @@ export class BuildCommand extends Command<BuildArguments, BuildOptions> {
watch: opts.watch,
handler: async (module) => [new BuildTask({ garden, module, force: opts.force })],
changeHandler: async (module: Module) => {

if (module.spec.hotReload) {
hotReloadAndLog(module, garden)
await hotReloadAndLog(garden, module)
}

return (await withDependants(garden, [module], autoReloadDependants))
Expand Down
102 changes: 41 additions & 61 deletions garden-service/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import chalk from "chalk"
import deline = require("deline")
import {
BooleanParameter,
Command,
Expand All @@ -15,27 +15,29 @@ import {
handleTaskResults,
StringsParameter,
} from "./base"
import { Garden } from "../garden"
import { Module } from "../types/module"
import { getDeployTasks } from "../tasks/deploy"
import { hotReloadAndLog, validateHotReloadOpt } from "./helpers"
import { getDeployTasks, getTasksForHotReload, getHotReloadModuleNames } from "../tasks/helpers"
import { TaskResults } from "../task-graph"
import { processServices } from "../process"
import { getNames } from "../util/util"
import { prepareRuntimeContext } from "../types/service"
import { uniq } from "lodash"
import { flatten } from "underscore"

const deployArgs = {
service: new StringsParameter({
help: "The name of the service(s) to deploy (skip to deploy all services). " +
"Use comma as separator to specify multiple services.",
help: deline`The name(s) of the service(s) to deploy (skip to deploy all services).
Use comma as separator to specify multiple services.`,
}),
}

const deployOpts = {
force: new BooleanParameter({ help: "Force redeploy of service(s)." }),
"force-build": new BooleanParameter({ help: "Force rebuild of module(s)." }),
watch: new BooleanParameter({ help: "Watch for changes in module(s) and auto-deploy.", alias: "w" }),
"hot-reload": new StringsParameter({
help: deline`The name(s) of the service(s) to deploy with hot reloading enabled.
Use comma as separator to specify multiple services. When this option is used,
the command is run in watch mode (i.e. implicitly assumes the --watch/-w flag).
`,
}),
}

type Args = typeof deployArgs
Expand All @@ -54,11 +56,12 @@ export class DeployCommand extends Command<Args, Opts> {
Examples:
garden deploy # deploy all modules in the project
garden deploy my-service # only deploy my-service
garden deploy --force # force re-deploy of modules, even if they're already deployed
garden deploy --watch # watch for changes to code
garden deploy --env stage # deploy your services to an environment called stage
garden deploy # deploy all modules in the project
garden deploy my-service # only deploy my-service
garden deploy --force # force re-deploy of modules, even if they're already deployed
garden deploy --watch # watch for changes to code
garden deploy --hot-reload=my-service # deploys all services, with hot reloading enabled for my-service
garden deploy --env stage # deploy your services to an environment called stage
`

arguments = deployArgs
Expand All @@ -69,10 +72,23 @@ export class DeployCommand extends Command<Args, Opts> {
const serviceNames = getNames(services)

if (services.length === 0) {
garden.log.warn({ msg: "No services found. Aborting." })
garden.log.error({ msg: "No services found. Aborting." })
return { result: {} }
}

let watch
const hotReloadServiceNames = opts["hot-reload"] || []
const hotReloadModuleNames = await getHotReloadModuleNames(garden, hotReloadServiceNames)

if (opts["hot-reload"]) {
if (!validateHotReloadOpt(garden, hotReloadServiceNames)) {
return { result: {} }
}
watch = true
} else {
watch = opts.watch
}

garden.log.header({ emoji: "rocket", command: "Deploy" })

// TODO: make this a task
Expand All @@ -81,64 +97,28 @@ export class DeployCommand extends Command<Args, Opts> {
const results = await processServices({
garden,
services,
watch: opts.watch,
watch,
handler: async (module) => getDeployTasks({
garden,
module,
serviceNames,
watch,
hotReloadServiceNames,
force: opts.force,
forceBuild: opts["force-build"],
watch: opts.watch,
includeDependants: false,
}),
changeHandler: async (module) => {

const hotReload = !!module.spec.hotReload

if (hotReload) {
hotReloadAndLog(module, garden)
if (hotReloadModuleNames.has(module.name)) {
await hotReloadAndLog(garden, module)
return getTasksForHotReload({ garden, module, hotReloadServiceNames, serviceNames })
} else {
return getDeployTasks({
garden, module, serviceNames, hotReloadServiceNames, force: true, forceBuild: true, watch: true,
})
}

return getDeployTasks({
garden,
module,
serviceNames,
force: true,
forceBuild: true,
watch: true,
includeDependants: true,
skipDeployTaskForModule: hotReload,
})

},
})

return handleTaskResults(garden, "deploy", results)
}
}

export async function hotReloadAndLog(module: Module, garden: Garden) {

const logEntry = garden.log.info({
section: module.name,
msg: "Hot reloading",
status: "active",
})

const serviceDependencyNames = uniq(flatten(module.services.map(s => s.config.dependencies)))
const runtimeContext = await prepareRuntimeContext(garden, module, await garden.getServices(serviceDependencyNames))

try {
await garden.actions.hotReload({ module, runtimeContext })
} catch (err) {
logEntry.setError()
throw err
}

const msec = logEntry.getDuration(5) * 1000
logEntry.setSuccess({
msg: chalk.green(`Done (took ${msec} ms)`),
append: true,
})

}
Loading

0 comments on commit c779618

Please sign in to comment.