Skip to content

Commit

Permalink
feat(k8s): make hot reloading work for remote clusters
Browse files Browse the repository at this point in the history
  • Loading branch information
edvald committed Jan 11, 2019
1 parent 68bd1a6 commit 7ca3dc3
Show file tree
Hide file tree
Showing 14 changed files with 210 additions and 245 deletions.
4 changes: 1 addition & 3 deletions garden-service/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,7 @@ export class ActionHelper implements TypeGuard {

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

async testModule<T extends Module>(params: ModuleActionHelperParams<TestModuleParams<T>>): Promise<TestResult> {
Expand Down
13 changes: 8 additions & 5 deletions garden-service/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import {
handleTaskResults,
StringsParameter,
} from "./base"
import { hotReloadAndLog, validateHotReloadOpt } from "./helpers"
import { validateHotReloadOpt } from "./helpers"
import { getDependantTasksForModule, getHotReloadModuleNames } from "../tasks/helpers"
import { TaskResults } from "../task-graph"
import { processServices } from "../process"
import { logHeader } from "../logger/util"
import { HotReloadTask } from "../tasks/hot-reload"
import { BaseTask } from "../tasks/base"

const deployArgs = {
services: new StringsParameter({
Expand Down Expand Up @@ -118,13 +120,14 @@ export class DeployCommand extends Command<Args, Opts> {
forceBuild: opts["force-build"],
}),
changeHandler: async (module) => {
if (hotReloadModuleNames.has(module.name)) {
await hotReloadAndLog(garden, log, module)
}
return getDependantTasksForModule({
const tasks: BaseTask[] = await getDependantTasksForModule({
garden, log, module, hotReloadServiceNames, force: true, forceBuild: opts["force-build"],
fromWatch: true, includeDependants: true,
})
if (hotReloadModuleNames.has(module.name)) {
tasks.push(new HotReloadTask({ garden, log, module, force: true }))
}
return tasks
},
})

Expand Down
15 changes: 10 additions & 5 deletions garden-service/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import moment = require("moment")
import { join } from "path"

import { BaseTask } from "../tasks/base"
import { hotReloadAndLog, validateHotReloadOpt } from "./helpers"
import { validateHotReloadOpt } from "./helpers"
import { getDependantTasksForModule, getHotReloadModuleNames } from "../tasks/helpers"
import {
Command,
Expand All @@ -29,6 +29,7 @@ import { STATIC_DIR } from "../constants"
import { processModules } from "../process"
import { Module } from "../types/module"
import { getTestTasks } from "../tasks/test"
import { HotReloadTask } from "../tasks/hot-reload"

const ansiBannerPath = join(STATIC_DIR, "garden-banner-2.txt")

Expand Down Expand Up @@ -96,21 +97,23 @@ export class DevCommand extends Command<Args, Opts> {

const tasksForModule = (watch: boolean) => {
return async (module: Module) => {
const tasks: BaseTask[] = []

const hotReload = hotReloadModuleNames.has(module.name)

if (watch && hotReload) {
await hotReloadAndLog(garden, log, module)
tasks.push(new HotReloadTask({ garden, log, module, force: true }))
}

const testModules: Module[] = watch
? (await dependencyGraph.withDependantModules([module]))
: [module]

const testTasks: BaseTask[] = flatten(await Bluebird.map(
testModules, m => getTestTasks({ garden, log, module: m })))
tasks.push(...flatten(
await Bluebird.map(testModules, m => getTestTasks({ garden, log, module: m })),
))

return testTasks.concat(await getDependantTasksForModule({
tasks.push(...await getDependantTasksForModule({
garden,
log,
module,
Expand All @@ -120,6 +123,8 @@ export class DevCommand extends Command<Args, Opts> {
forceBuild: false,
includeDependants: watch,
}))

return tasks
}
}

Expand Down
32 changes: 1 addition & 31 deletions garden-service/src/commands/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import chalk from "chalk"
import dedent = require("dedent")
import { uniq, flatten } from "lodash"
import { Garden } from "../garden"
import { Module } from "../types/module"
import { prepareRuntimeContext, Service } from "../types/service"
import { Service } from "../types/service"
import { LogEntry } from "../logger/log-entry"

// Returns true if validation succeeded, false otherwise.
Expand Down Expand Up @@ -47,30 +44,3 @@ export async function validateHotReloadOpt(
}

}

export async function hotReloadAndLog(garden: Garden, log: LogEntry, module: Module) {
const logEntry = 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, logEntry, module, await garden.getServices(serviceDependencyNames),
)

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

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

}
7 changes: 0 additions & 7 deletions garden-service/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ import {
import { VcsHandler, ModuleVersion } from "./vcs/base"
import { GitHandler } from "./vcs/git"
import { BuildDir } from "./build-dir"
import { HotReloadHandler, HotReloadScheduler } from "./hotReloadScheduler"
import { DependencyGraph } from "./dependency-graph"
import {
TaskGraph,
Expand Down Expand Up @@ -154,7 +153,6 @@ export class Garden {
private readonly registeredPlugins: { [key: string]: PluginFactory }
private readonly serviceNameIndex: { [key: string]: string } // service name -> module name
private readonly taskNameIndex: { [key: string]: string } // task name -> module name
private readonly hotReloadScheduler: HotReloadScheduler
private readonly taskGraph: TaskGraph
private readonly watcher: Watcher

Expand Down Expand Up @@ -211,7 +209,6 @@ export class Garden {

this.taskGraph = new TaskGraph(this, this.log)
this.actions = new ActionHelper(this)
this.hotReloadScheduler = new HotReloadScheduler()
this.events = new EventBus()
this.watcher = new Watcher(this, this.log)
}
Expand Down Expand Up @@ -349,10 +346,6 @@ export class Garden {
return this.taskGraph.processTasks()
}

async hotReload(moduleName: string, hotReloadHandler: HotReloadHandler) {
return this.hotReloadScheduler.requestHotReload(moduleName, hotReloadHandler)
}

/**
* Enables the file watcher for the project.
* Make sure to stop it using `.close()` when cleaning up or when watching is no longer needed.
Expand Down
101 changes: 0 additions & 101 deletions garden-service/src/hotReloadScheduler.ts

This file was deleted.

51 changes: 2 additions & 49 deletions garden-service/src/plugins/kubernetes/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import * as Bluebird from "bluebird"
import * as execa from "execa"
import { includes } from "lodash"

import { DeploymentError, ConfigurationError } from "../../exceptions"
import { HotReloadResult, RunResult, TestResult } from "../../types/plugin/outputs"
import { RunResult, TestResult } from "../../types/plugin/outputs"
import {
ExecInServiceParams,
GetServiceOutputsParams,
GetTestResultParams,
HotReloadParams,
RunModuleParams,
TestModuleParams,
DeleteServiceParams,
Expand All @@ -30,18 +26,10 @@ import { KubeApi } from "./api"
import { getAppNamespace, getMetadataNamespace } from "./namespace"
import { kubectl } from "./kubectl"
import { DEFAULT_TEST_TIMEOUT } from "../../constants"
import {
getContainerServiceStatus,
deleteContainerService,
rsyncSourcePath,
rsyncTargetPath,
} from "./deployment"
import { getContainerServiceStatus, deleteContainerService } from "./deployment"
import { KubernetesProvider } from "./kubernetes"
import { getIngresses } from "./ingress"
import { rsyncPortName } from "./service"
import { ServiceStatus } from "../../types/service"
import { ValidateModuleParams } from "../../types/plugin/params"
import { waitForServices } from "./status"

export async function validate(params: ValidateModuleParams<ContainerModule>) {
const config = await validateContainerModule(params)
Expand Down Expand Up @@ -137,41 +125,6 @@ export async function execInService(params: ExecInServiceParams<ContainerModule>
return { code: res.code, output: res.output }
}

export async function hotReload(
{ ctx, log, runtimeContext, module, buildDependencies }: HotReloadParams<ContainerModule>,
): Promise<HotReloadResult> {
const hotReloadConfig = module.spec.hotReload!

const services = module.services

if (!await waitForServices(ctx, log, runtimeContext, services, buildDependencies)) {
// Service deployment timed out, skip hot reload
return {}
}

const api = new KubeApi(ctx.provider)

const namespace = await getAppNamespace(ctx, ctx.provider)

await Bluebird.map(services, async (service) => {

const hostname = (await getIngresses(service, api))[0].hostname

const rsyncNodePort = (await api.core
.readNamespacedService(service.name + "-nodeport", namespace))
.body.spec.ports.find(p => p.name === rsyncPortName(service.name))!
.nodePort

await Bluebird.map(hotReloadConfig.sync, async ({ source, target }) => {
const src = rsyncSourcePath(module.path, source)
const destination = `rsync://${hostname}:${rsyncNodePort}/volume/${rsyncTargetPath(target)}`
await execa("rsync", ["-vrptgo", src, destination])
})
})

return {}
}

export async function runModule(
{
ctx, module, command, ignoreError = true, interactive, runtimeContext, timeout,
Expand Down
10 changes: 8 additions & 2 deletions garden-service/src/plugins/kubernetes/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import { DeployServiceParams, GetServiceStatusParams, PushModuleParams } from ".
import { RuntimeContext, Service, ServiceStatus } from "../../types/service"
import { helpers, ContainerModule, ContainerService, SyncSpec } from "../container"
import { createIngresses, getIngresses } from "./ingress"
import { createServices, RSYNC_PORT, RSYNC_PORT_NAME } from "./service"
import { RSYNC_PORT, RSYNC_PORT_NAME } from "./hot-reload"
import { createServices } from "./service"
import { waitForObjects, compareDeployedObjects } from "./status"
import { applyMany, deleteObjectsByLabel } from "./kubectl"
import { getAppNamespace } from "./namespace"
Expand Down Expand Up @@ -113,7 +114,7 @@ export async function createContainerObjects(
const api = new KubeApi(ctx.provider)
const ingresses = await createIngresses(api, namespace, service)
const deployment = await createDeployment(ctx.provider, service, runtimeContext, namespace, enableHotReload)
const kubeservices = await createServices(service, namespace, enableHotReload)
const kubeservices = await createServices(service, namespace)

const objects = [deployment, ...kubeservices, ...ingresses]

Expand Down Expand Up @@ -446,6 +447,11 @@ function configureHotReload(deployment, container, serviceSpec, moduleSpec, env,
name: "garden-rsync",
image: "eugenmayer/rsync",
imagePullPolicy: "IfNotPresent",
env: [
// This makes sure the server is accessible on any IP address, because CIDRs can be different across clusters.
// K8s can be trusted to secure the port. - JE
{ name: "ALLOW", value: "0.0.0.0/0" },
],
volumeMounts: [{
name: syncVolumeName,
/**
Expand Down
Loading

0 comments on commit 7ca3dc3

Please sign in to comment.