From d6c622869f6dff8226b7b28a7a2c1121b61b280e Mon Sep 17 00:00:00 2001 From: Thorarinn Sigurdsson Date: Tue, 21 Sep 2021 17:23:44 +0200 Subject: [PATCH] feat(k8s): provider-level defaults for dev mode Excludes and permission/owner/group settings can now be set on the provider level for `kubernetes` and `local-kubernetes`. Defaults specified there will be extended/overridden by corresponding settings on individual sync specs. This helps reduce clutter when the same excludes and permission settings are commonly applied across services/modules. --- core/src/plugins/container/config.ts | 78 ++++--- core/src/plugins/kubernetes/config.ts | 20 +- core/src/plugins/kubernetes/dev-mode.ts | 101 ++++++++-- core/src/plugins/kubernetes/mutagen.ts | 2 +- .../container/dev-mode/garden.yml | 2 +- .../data/test-projects/container/garden.yml | 4 + .../kubernetes/container/deployment.ts | 69 +------ .../integ/src/plugins/kubernetes/dev-mode.ts | 190 ++++++++++++++++++ .../unit/src/plugins/kubernetes/dev-mode.ts | 107 ++++++++++ docs/reference/module-types/container.md | 4 + docs/reference/module-types/helm.md | 4 + docs/reference/module-types/jib-container.md | 4 + docs/reference/module-types/kubernetes.md | 4 + .../reference/module-types/maven-container.md | 4 + docs/reference/providers/kubernetes.md | 152 +++++++++++++- docs/reference/providers/local-kubernetes.md | 152 +++++++++++++- 16 files changed, 770 insertions(+), 127 deletions(-) create mode 100644 core/test/integ/src/plugins/kubernetes/dev-mode.ts create mode 100644 core/test/unit/src/plugins/kubernetes/dev-mode.ts diff --git a/core/src/plugins/container/config.ts b/core/src/plugins/container/config.ts index 7b310b6732..78b9e16306 100644 --- a/core/src/plugins/container/config.ts +++ b/core/src/plugins/container/config.ts @@ -29,6 +29,7 @@ import { joiStringMap } from "../../config/common" import { dedent, deline } from "../../util/string" import { getModuleTypeUrl } from "../../docs/common" import { ContainerModuleOutputs } from "./container" +import { devModeGuideLink } from "../kubernetes/dev-mode" export const defaultContainerLimits: ServiceLimitSpec = { cpu: 1000, // = 1000 millicpu = 1 CPU @@ -202,13 +203,52 @@ const permissionsDocs = const ownerDocs = "Specify either an integer ID or a string name. See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#owners-and-groups) for more information." +export const syncExcludeSchema = () => + joi + .array() + .items(joi.posixPath().allowGlobs().subPathOnly()) + .description( + dedent` + Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + + \`.git\` directories and \`.garden\` directories are always ignored. + ` + ) + .example(["dist/**/*", "*.log"]) + +export const syncDefaultFileModeSchema = () => + joi + .number() + .min(0) + .max(0o777) + .description( + "The default permission bits, specified as an octal, to set on files at the sync target. Defaults to 0600 (user read/write). " + + permissionsDocs + ) + +export const syncDefaultDirectoryModeSchema = () => + joi + .number() + .min(0) + .max(0o777) + .description( + "The default permission bits, specified as an octal, to set on directories at the sync target. Defaults to 0700 (user read/write). " + + permissionsDocs + ) + +export const syncDefaultOwnerSchema = () => + joi + .alternatives(joi.number().integer(), joi.string()) + .description("Set the default owner of files and directories at the target. " + ownerDocs) + +export const syncDefaultGroupSchema = () => + joi + .alternatives(joi.number().integer(), joi.string()) + .description("Set the default group on files and directories at the target. " + ownerDocs) + const devModeSyncSchema = () => hotReloadSyncSchema().keys({ - exclude: joi - .array() - .items(joi.posixPath().allowGlobs().subPathOnly()) - .description(`Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync.`) - .example(["dist/**/*", "*.log"]), + exclude: syncExcludeSchema(), mode: joi .string() .allow("one-way", "one-way-replica", "two-way") @@ -217,28 +257,10 @@ const devModeSyncSchema = () => .description( "The sync mode to use for the given paths. Allowed options: `one-way`, `one-way-replica`, `two-way`." ), - defaultFileMode: joi - .number() - .min(0) - .max(0o777) - .description( - "The default permission bits, specified as an octal, to set on files at the sync target. Defaults to 0600 (user read/write). " + - permissionsDocs - ), - defaultDirectoryMode: joi - .number() - .min(0) - .max(0o777) - .description( - "The default permission bits, specified as an octal, to set on directories at the sync target. Defaults to 0700 (user read/write). " + - permissionsDocs - ), - defaultOwner: joi - .alternatives(joi.number().integer(), joi.string()) - .description("Set the default owner of files and directories at the target. " + ownerDocs), - defaultGroup: joi - .alternatives(joi.number().integer(), joi.string()) - .description("Set the default group on files and directories at the target. " + ownerDocs), + defaultFileMode: syncDefaultFileModeSchema(), + defaultDirectoryMode: syncDefaultDirectoryModeSchema(), + defaultOwner: syncDefaultOwnerSchema(), + defaultGroup: syncDefaultGroupSchema(), }) export interface ContainerDevModeSpec { @@ -263,7 +285,7 @@ export const containerDevModeSchema = () => Dev mode is enabled when running the \`garden dev\` command, and by setting the \`--dev\` flag on the \`garden deploy\` command. - See the [Code Synchronization guide](https://docs.garden.io/guides/code-synchronization-dev-mode) for more information. + See the [Code Synchronization guide](${devModeGuideLink}) for more information. `) export type ContainerServiceConfig = ServiceConfig diff --git a/core/src/plugins/kubernetes/config.ts b/core/src/plugins/kubernetes/config.ts index 1fb943870a..2ee5e29a49 100644 --- a/core/src/plugins/kubernetes/config.ts +++ b/core/src/plugins/kubernetes/config.ts @@ -37,6 +37,7 @@ import { baseTestSpecSchema, BaseTestSpec } from "../../config/test" import { ArtifactSpec } from "../../config/validation" import { V1Toleration } from "@kubernetes/client-node" import { runPodSpecIncludeFields } from "./run" +import { KubernetesDevModeDefaults, kubernetesDevModeDefaultsSchema } from "./dev-mode" export const DEFAULT_KANIKO_IMAGE = "gcr.io/kaniko-project/executor:v1.6.0-debug" export interface ProviderSecretRef { @@ -126,6 +127,9 @@ export interface KubernetesConfig extends BaseProviderConfig { defaultHostname?: string deploymentRegistry?: ContainerRegistryConfig deploymentStrategy?: DeploymentStrategy + devMode?: { + defaults?: KubernetesDevModeDefaults + } forceSsl: boolean imagePullSecrets: ProviderSecretRef[] ingressHttpPort: number @@ -437,14 +441,22 @@ export const kubernetesConfigBase = () => .allow("rolling", "blue-green") .description( dedent` - Defines the strategy for deploying the project services. - Default is "rolling update" and there is experimental support for "blue/green" deployment. - The feature only supports modules of type \`container\`: other types will just deploy using the default strategy. - ` + Sets the deployment strategy for \`container\` services. + + The default is \`"rolling"\`, which performs rolling updates. There is also experimental support for blue/green deployments (via the \`"blue-green"\` strategy). + + Note that this setting only applies to \`container\` services (and not, for example, \`kubernetes\` or \`helm\` services). + ` ) .meta({ experimental: true, }), + devMode: joi + .object() + .keys({ + defaults: kubernetesDevModeDefaultsSchema(), + }) + .description("Configuration options for dev mode."), forceSsl: joi .boolean() .default(false) diff --git a/core/src/plugins/kubernetes/dev-mode.ts b/core/src/plugins/kubernetes/dev-mode.ts index 85be44771f..4787489cf4 100644 --- a/core/src/plugins/kubernetes/dev-mode.ts +++ b/core/src/plugins/kubernetes/dev-mode.ts @@ -6,7 +6,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { containerDevModeSchema, ContainerDevModeSpec } from "../container/config" +import { + containerDevModeSchema, + ContainerDevModeSpec, + DevModeSyncSpec, + syncDefaultDirectoryModeSchema, + syncDefaultFileModeSchema, + syncDefaultGroupSchema, + syncDefaultOwnerSchema, + syncExcludeSchema, +} from "../container/config" import { dedent, gardenAnnotationKey } from "../../util/string" import { set } from "lodash" import { getResourceContainer, getResourcePodSpec } from "./util" @@ -16,12 +25,22 @@ import { joinWithPosix } from "../../util/fs" import chalk from "chalk" import { PluginContext } from "../../plugin-context" import { ConfigurationError } from "../../exceptions" -import { ensureMutagenSync, getKubectlExecDestination, mutagenAgentPath, mutagenConfigLock } from "./mutagen" -import { joiIdentifier } from "../../config/common" -import { KubernetesPluginContext } from "./config" +import { + ensureMutagenSync, + getKubectlExecDestination, + mutagenAgentPath, + mutagenConfigLock, + SyncConfig, +} from "./mutagen" +import { joi, joiIdentifier } from "../../config/common" +import { KubernetesPluginContext, KubernetesProvider } from "./config" const syncUtilImageName = "gardendev/k8s-sync:0.1.1" +export const builtInExcludes = ["**/*.git", "**/*.garden"] + +export const devModeGuideLink = "https://docs.garden.io/guides/code-synchronization-dev-mode" + interface ConfigureDevModeParams { target: HotReloadableResource spec: ContainerDevModeSpec @@ -32,6 +51,40 @@ export interface KubernetesDevModeSpec extends ContainerDevModeSpec { containerName?: string } +export interface KubernetesDevModeDefaults { + exclude?: string[] + fileMode?: number + directoryMode?: number + owner?: number | string + group?: number | string +} + +/** + * Provider-level dev mode settings for the local and remote k8s providers. + */ +export const kubernetesDevModeDefaultsSchema = () => + joi.object().keys({ + exclude: syncExcludeSchema().description(dedent` + Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + + Any exclusion patterns defined in individual dev mode sync specs will be applied in addition to these patterns. + + \`.git\` directories and \`.garden\` directories are always ignored. + `), + fileMode: syncDefaultFileModeSchema(), + directoryMode: syncDefaultDirectoryModeSchema(), + owner: syncDefaultOwnerSchema(), + group: syncDefaultGroupSchema(), + }).description(dedent` + Specifies default settings for dev mode syncs (e.g. for \`container\`, \`kubernetes\` and \`helm\` services). + + These are overridden/extended by the settings of any individual dev mode sync specs for a given module or service. + + Dev mode is enabled when running the \`garden dev\` command, and by setting the \`--dev\` flag on the \`garden deploy\` command. + + See the [Code Synchronization guide](${devModeGuideLink}) for more information. + `) + export const kubernetesDevModeSchema = () => containerDevModeSchema().keys({ containerName: joiIdentifier().description( @@ -44,7 +97,7 @@ export const kubernetesDevModeSchema = () => Dev mode is enabled when running the \`garden dev\` command, and by setting the \`--dev\` flag on the \`garden deploy\` command. - See the [Code Synchronization guide](https://docs.garden.io/guides/code-synchronization-dev-mode) for more information. + See the [Code Synchronization guide](${devModeGuideLink}) for more information. `) /** @@ -152,14 +205,16 @@ export async function startDevModeSync({ } const k8sCtx = ctx + const k8sProvider = k8sCtx.provider + const defaults = k8sProvider.config.devMode?.defaults || {} let i = 0 for (const s of spec.sync) { const key = `${keyBase}-${i}` - const alpha = joinWithPosix(moduleRoot, s.source) - const beta = await getKubectlExecDestination({ + const localPath = joinWithPosix(moduleRoot, s.source) + const remoteDestination = await getKubectlExecDestination({ ctx: k8sCtx, log, namespace, @@ -181,15 +236,35 @@ export async function startDevModeSync({ logSection: serviceName, sourceDescription, targetDescription, - config: { - alpha, - beta, - mode: s.mode, - ignore: s.exclude || [], - }, + config: makeSyncConfig({ defaults, spec: s, localPath, remoteDestination }), }) i++ } }) } + +export function makeSyncConfig({ + localPath, + remoteDestination, + defaults, + spec, +}: { + localPath: string + remoteDestination: string + defaults: KubernetesDevModeDefaults | null + spec: DevModeSyncSpec +}): SyncConfig { + const s = spec + const d = defaults || {} + return { + alpha: localPath, + beta: remoteDestination, + mode: s.mode, + ignore: [...builtInExcludes, ...(d["exclude"] || []), ...(s.exclude || [])], + defaultOwner: s.defaultOwner === undefined ? d["owner"] : s.defaultOwner, + defaultGroup: s.defaultGroup === undefined ? d["group"] : s.defaultGroup, + defaultDirectoryMode: s.defaultDirectoryMode === undefined ? d["directoryMode"] : s.defaultDirectoryMode, + defaultFileMode: s.defaultFileMode === undefined ? d["fileMode"] : s.defaultFileMode, + } +} diff --git a/core/src/plugins/kubernetes/mutagen.ts b/core/src/plugins/kubernetes/mutagen.ts index d1a29902f5..2240f85d75 100644 --- a/core/src/plugins/kubernetes/mutagen.ts +++ b/core/src/plugins/kubernetes/mutagen.ts @@ -35,7 +35,7 @@ export const mutagenModeMap = { "two-way": "two-way-safe", } -interface SyncConfig { +export interface SyncConfig { alpha: string beta: string mode: keyof typeof mutagenModeMap diff --git a/core/test/data/test-projects/container/dev-mode/garden.yml b/core/test/data/test-projects/container/dev-mode/garden.yml index a6fc4214a5..26538afd8f 100644 --- a/core/test/data/test-projects/container/dev-mode/garden.yml +++ b/core/test/data/test-projects/container/dev-mode/garden.yml @@ -7,7 +7,7 @@ services: command: [sh, -c, "echo Server running... && nc -l -p 8080"] devMode: sync: - - target: /tmp + - target: /tmp/ mode: two-way healthCheck: command: ["echo", "ok"] diff --git a/core/test/data/test-projects/container/garden.yml b/core/test/data/test-projects/container/garden.yml index 04b2797e16..de6e859d73 100644 --- a/core/test/data/test-projects/container/garden.yml +++ b/core/test/data/test-projects/container/garden.yml @@ -17,6 +17,10 @@ environments: providers: - name: local-kubernetes environments: [local] + devMode: + defaults: + exclude: + - "**/prefix-*" - name: local-kubernetes deploymentRegistry: &deploymentRegistry hostname: index.docker.io diff --git a/core/test/integ/src/plugins/kubernetes/container/deployment.ts b/core/test/integ/src/plugins/kubernetes/container/deployment.ts index 624ca458d1..9ef23e3a45 100644 --- a/core/test/integ/src/plugins/kubernetes/container/deployment.ts +++ b/core/test/integ/src/plugins/kubernetes/container/deployment.ts @@ -7,7 +7,6 @@ */ import { expect } from "chai" -import { join } from "path" import { Garden } from "../../../../../../src/garden" import { ConfigGraph } from "../../../../../../src/config-graph" import { emptyRuntimeContext } from "../../../../../../src/runtime-context" @@ -23,10 +22,7 @@ import { getServiceStatuses } from "../../../../../../src/tasks/base" import { expectError, grouped } from "../../../../../helpers" import stripAnsi = require("strip-ansi") import { gardenAnnotationKey } from "../../../../../../src/util/string" -import { getContainerServiceStatus } from "../../../../../../src/plugins/kubernetes/container/status" -import { sleep } from "../../../../../../src/util/util" -import { pathExists, readFile, remove, writeFile } from "fs-extra" -import { execInWorkload, kilobytesToString, millicpuToString } from "../../../../../../src/plugins/kubernetes/util" +import { kilobytesToString, millicpuToString } from "../../../../../../src/plugins/kubernetes/util" import { getResourceRequirements } from "../../../../../../src/plugins/kubernetes/container/util" describe("kubernetes container deployment handlers", () => { @@ -567,69 +563,6 @@ describe("kubernetes container deployment handlers", () => { { name: "test", mountPath: "/volume" }, ]) }) - - it("should deploy a service in dev mode and successfully set up the sync", async () => { - const service = graph.getService("dev-mode") - const module = service.module - const log = garden.log - const deployTask = new DeployTask({ - garden, - graph, - log, - service, - force: true, - forceBuild: false, - devModeServiceNames: [service.name], - hotReloadServiceNames: [], - }) - - await garden.processTasks([deployTask], { throwOnError: true }) - const status = await getContainerServiceStatus({ - ctx, - module, - service, - runtimeContext: emptyRuntimeContext, - log, - devMode: true, - hotReload: false, - }) - - // First, we create a file locally and verify that it gets synced into the pod - await writeFile(join(module.path, "made_locally"), "foo") - await sleep(300) - const execRes = await execInWorkload({ - command: ["/bin/sh", "-c", "cat /tmp/made_locally"], - ctx, - provider, - log, - namespace: provider.config.namespace!.name!, - workload: status.detail.workload!, - interactive: false, - }) - expect(execRes.output.trim()).to.eql("foo") - - // Then, we create a file in the pod and verify that it gets synced back - await execInWorkload({ - command: ["/bin/sh", "-c", "echo bar > /tmp/made_in_pod"], - ctx, - provider, - log, - namespace: provider.config.namespace!.name!, - workload: status.detail.workload!, - interactive: false, - }) - await sleep(300) - const localPath = join(module.path, "made_in_pod") - expect(await pathExists(localPath)).to.eql(true) - expect((await readFile(localPath)).toString().trim()).to.eql("bar") - - // Clean up the files we created locally - for (const filename of ["made_locally", "made_in_pod"]) { - try { - await remove(join(module.path, filename)) - } catch {} - } - }) }) grouped("cluster-docker").context("cluster-docker mode", () => { diff --git a/core/test/integ/src/plugins/kubernetes/dev-mode.ts b/core/test/integ/src/plugins/kubernetes/dev-mode.ts new file mode 100644 index 0000000000..0d5c84808e --- /dev/null +++ b/core/test/integ/src/plugins/kubernetes/dev-mode.ts @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2018-2021 Garden Technologies, Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { expect } from "chai" +import { mkdirp, pathExists, readFile, remove, writeFile } from "fs-extra" +import { join } from "path" +import { ConfigGraph } from "../../../../../src/config-graph" +import { Garden } from "../../../../../src/garden" +import { LogEntry } from "../../../../../src/logger/log-entry" +import { ContainerService } from "../../../../../src/plugins/container/config" +import { KubernetesPluginContext, KubernetesProvider } from "../../../../../src/plugins/kubernetes/config" +import { getContainerServiceStatus } from "../../../../../src/plugins/kubernetes/container/status" +import { KubernetesWorkload } from "../../../../../src/plugins/kubernetes/types" +import { execInWorkload } from "../../../../../src/plugins/kubernetes/util" +import { emptyRuntimeContext } from "../../../../../src/runtime-context" +import { DeployTask } from "../../../../../src/tasks/deploy" +import { dedent } from "../../../../../src/util/string" +import { sleep } from "../../../../../src/util/util" +import { getContainerTestGarden } from "./container/container" + +describe("dev mode deployments and sync behavior", () => { + let garden: Garden + let graph: ConfigGraph + let ctx: KubernetesPluginContext + let provider: KubernetesProvider + + const execInPod = async (command: string[], log: LogEntry, workload: KubernetesWorkload) => { + const execRes = await execInWorkload({ + command, + ctx, + provider, + log, + namespace: provider.config.namespace!.name!, + workload, + interactive: false, + }) + return execRes + } + + before(async () => { + await init("local") + }) + + beforeEach(async () => { + graph = await garden.getConfigGraph({ log: garden.log, emit: false }) + }) + + afterEach(async () => { + if (garden) { + await garden.close() + } + }) + + const init = async (environmentName: string) => { + garden = await getContainerTestGarden(environmentName) + provider = await garden.resolveProvider(garden.log, "local-kubernetes") + ctx = await garden.getPluginContext(provider) + } + + it("should deploy a service in dev mode and successfully set a two-way sync", async () => { + await init("local") + const service = graph.getService("dev-mode") + const module = service.module + const log = garden.log + const deployTask = new DeployTask({ + garden, + graph, + log, + service, + force: true, + forceBuild: false, + devModeServiceNames: [service.name], + hotReloadServiceNames: [], + }) + + await garden.processTasks([deployTask], { throwOnError: true }) + const status = await getContainerServiceStatus({ + ctx, + module, + service, + runtimeContext: emptyRuntimeContext, + log, + devMode: true, + hotReload: false, + }) + + const workload = status.detail.workload! + + // First, we create a file locally and verify that it gets synced into the pod + await writeFile(join(module.path, "made_locally"), "foo") + await sleep(300) + const execRes = await execInPod(["/bin/sh", "-c", "cat /tmp/made_locally"], log, workload) + expect(execRes.output.trim()).to.eql("foo") + + // Then, we create a file in the pod and verify that it gets synced back + await execInPod(["/bin/sh", "-c", "echo bar > /tmp/made_in_pod"], log, workload) + await sleep(500) + const localPath = join(module.path, "made_in_pod") + expect(await pathExists(localPath)).to.eql(true) + expect((await readFile(localPath)).toString().trim()).to.eql("bar") + + // This is to make sure that the two-way sync doesn't recreate the local files we're about to delete here. + const actions = await garden.getActionRouter() + await actions.deleteService({ graph, log: garden.log, service }) + + // Clean up the files we created locally + for (const filename of ["made_locally", "made_in_pod"]) { + try { + await remove(join(module.path, filename)) + } catch {} + } + }) + + it("should apply ignore rules from the sync spec and the provider-level dev mode defaults", async () => { + await init("local") + const service: ContainerService = graph.getService("dev-mode") + + // We want to ignore the following directories (all at module root) + // somedir + // prefix-a <--- matched by provider-level default excludes + // nested/prefix-b <--- matched by provider-level default excludes + + service.spec.devMode!.sync[0].mode = "one-way-replica" + service.spec.devMode!.sync[0].exclude = ["somedir"] + const module = service.module + const log = garden.log + const deployTask = new DeployTask({ + garden, + graph, + log, + service, + force: true, + forceBuild: false, + devModeServiceNames: [service.name], + hotReloadServiceNames: [], + }) + + await garden.processTasks([deployTask], { throwOnError: true }) + const status = await getContainerServiceStatus({ + ctx, + module, + service, + runtimeContext: emptyRuntimeContext, + log, + devMode: true, + hotReload: false, + }) + + const workload = status.detail.workload! + + // First, we create a non-ignored file locally + await writeFile(join(module.path, "made_locally"), "foo") + + // Then, we create files in each of the directories we intended to ignore in the `exclude` spec above, and + // verify that they didn't get synced into the pod. + await mkdirp(join(module.path, "somedir")) + await writeFile(join(module.path, "somedir", "file"), "foo") + await mkdirp(join(module.path, "prefix-a")) + await writeFile(join(module.path, "prefix-a", "file"), "foo") + await mkdirp(join(module.path, "nested", "prefix-b")) + await writeFile(join(module.path, "nested", "prefix-b", "file"), "foo") + await sleep(500) + const ignoreExecRes = await execInPod(["/bin/sh", "-c", "ls -a /tmp /tmp/nested"], log, workload) + // Clean up the files we created locally + for (const filename of ["made_locally", "somedir", "prefix-a", "nested"]) { + try { + await remove(join(module.path, filename)) + } catch {} + } + + expect(ignoreExecRes.output.trim()).to.eql(dedent` + /tmp: + . + .. + Dockerfile + garden.yml + made_locally + nested + + /tmp/nested: + . + .. + `) + }) +}) diff --git a/core/test/unit/src/plugins/kubernetes/dev-mode.ts b/core/test/unit/src/plugins/kubernetes/dev-mode.ts new file mode 100644 index 0000000000..e79a5a5a29 --- /dev/null +++ b/core/test/unit/src/plugins/kubernetes/dev-mode.ts @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018-2021 Garden Technologies, Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { expect } from "chai" +import { builtInExcludes, makeSyncConfig } from "../../../../../src/plugins/kubernetes/dev-mode" + +describe("k8s dev mode helpers", () => { + const localPath = "/path/to/module/src" + const remoteDestination = "exec:'various fun connection parameters'" + const source = "src" + const target = "/app/src" + describe("makeSyncConfig", () => { + it("should generate a simple sync config", () => { + const config = makeSyncConfig({ + localPath, + remoteDestination, + defaults: {}, + spec: { + source, + target, + mode: "one-way", + }, + }) + + expect(config).to.eql({ + alpha: localPath, + beta: remoteDestination, + ignore: [...builtInExcludes], + mode: "one-way", + defaultOwner: undefined, + defaultGroup: undefined, + defaultDirectoryMode: undefined, + defaultFileMode: undefined, + }) + }) + + it("should apply provider-level defaults", () => { + const config = makeSyncConfig({ + localPath, + remoteDestination, + defaults: { + exclude: ["**/*.log"], + owner: "node", + group: "admin", + fileMode: 600, + directoryMode: 700, + }, + spec: { + source, + target, + mode: "one-way", + }, + }) + + expect(config).to.eql({ + alpha: localPath, + beta: remoteDestination, + ignore: [...builtInExcludes, "**/*.log"], + mode: "one-way", + defaultOwner: "node", + defaultGroup: "admin", + defaultFileMode: 600, + defaultDirectoryMode: 700, + }) + }) + + it("should override/extend provider-level defaults with settings on the sync spec", () => { + const config = makeSyncConfig({ + localPath, + remoteDestination, + defaults: { + exclude: ["**/*.log"], + owner: "node", + group: "admin", + fileMode: 600, + directoryMode: 700, + }, + spec: { + source, + target, + mode: "one-way", + exclude: ["node_modules"], + defaultOwner: "owner_from_spec", + defaultGroup: "group_from_spec", + defaultFileMode: 700, + defaultDirectoryMode: 777, + }, + }) + + expect(config).to.eql({ + alpha: localPath, + beta: remoteDestination, + ignore: [...builtInExcludes, "**/*.log", "node_modules"], + mode: "one-way", + defaultOwner: "owner_from_spec", + defaultGroup: "group_from_spec", + defaultFileMode: 700, + defaultDirectoryMode: 777, + }) + }) + }) +}) diff --git a/docs/reference/module-types/container.md b/docs/reference/module-types/container.md index 5b206a5d31..ad19ab0860 100644 --- a/docs/reference/module-types/container.md +++ b/docs/reference/module-types/container.md @@ -247,6 +247,8 @@ services: target: # Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + # + # `.git` directories and `.garden` directories are always ignored. exclude: # The sync mode to use for the given paths. Allowed options: `one-way`, `one-way-replica`, `two-way`. @@ -1228,6 +1230,8 @@ services: Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. +`.git` directories and `.garden` directories are always ignored. + | Type | Required | | ------------------ | -------- | | `array[posixPath]` | No | diff --git a/docs/reference/module-types/helm.md b/docs/reference/module-types/helm.md index c62f00b3bf..2698d31623 100644 --- a/docs/reference/module-types/helm.md +++ b/docs/reference/module-types/helm.md @@ -185,6 +185,8 @@ devMode: target: # Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + # + # `.git` directories and `.garden` directories are always ignored. exclude: # The sync mode to use for the given paths. Allowed options: `one-way`, `one-way-replica`, `two-way`. @@ -946,6 +948,8 @@ devMode: Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. +`.git` directories and `.garden` directories are always ignored. + | Type | Required | | ------------------ | -------- | | `array[posixPath]` | No | diff --git a/docs/reference/module-types/jib-container.md b/docs/reference/module-types/jib-container.md index 2c0dc10940..d7ac0d6a96 100644 --- a/docs/reference/module-types/jib-container.md +++ b/docs/reference/module-types/jib-container.md @@ -265,6 +265,8 @@ services: target: # Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + # + # `.git` directories and `.garden` directories are always ignored. exclude: # The sync mode to use for the given paths. Allowed options: `one-way`, `one-way-replica`, `two-way`. @@ -1286,6 +1288,8 @@ services: Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. +`.git` directories and `.garden` directories are always ignored. + | Type | Required | | ------------------ | -------- | | `array[posixPath]` | No | diff --git a/docs/reference/module-types/kubernetes.md b/docs/reference/module-types/kubernetes.md index 2e0f773c94..d953b390f3 100644 --- a/docs/reference/module-types/kubernetes.md +++ b/docs/reference/module-types/kubernetes.md @@ -171,6 +171,8 @@ devMode: target: # Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + # + # `.git` directories and `.garden` directories are always ignored. exclude: # The sync mode to use for the given paths. Allowed options: `one-way`, `one-way-replica`, `two-way`. @@ -829,6 +831,8 @@ devMode: Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. +`.git` directories and `.garden` directories are always ignored. + | Type | Required | | ------------------ | -------- | | `array[posixPath]` | No | diff --git a/docs/reference/module-types/maven-container.md b/docs/reference/module-types/maven-container.md index bc40ec1000..8d170b753c 100644 --- a/docs/reference/module-types/maven-container.md +++ b/docs/reference/module-types/maven-container.md @@ -247,6 +247,8 @@ services: target: # Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + # + # `.git` directories and `.garden` directories are always ignored. exclude: # The sync mode to use for the given paths. Allowed options: `one-way`, `one-way-replica`, `two-way`. @@ -1238,6 +1240,8 @@ services: Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. +`.git` directories and `.garden` directories are always ignored. + | Type | Required | | ------------------ | -------- | | `array[posixPath]` | No | diff --git a/docs/reference/providers/kubernetes.md b/docs/reference/providers/kubernetes.md index fd70838e84..64f45fcd19 100644 --- a/docs/reference/providers/kubernetes.md +++ b/docs/reference/providers/kubernetes.md @@ -111,11 +111,56 @@ providers: # A default hostname to use when no hostname is explicitly configured for a service. defaultHostname: - # Defines the strategy for deploying the project services. - # Default is "rolling update" and there is experimental support for "blue/green" deployment. - # The feature only supports modules of type `container`: other types will just deploy using the default strategy. + # Sets the deployment strategy for `container` services. + # + # The default is `"rolling"`, which performs rolling updates. There is also experimental support for blue/green + # deployments (via the `"blue-green"` strategy). + # + # Note that this setting only applies to `container` services (and not, for example, `kubernetes` or `helm` + # services). deploymentStrategy: rolling + # Configuration options for dev mode. + devMode: + # Specifies default settings for dev mode syncs (e.g. for `container`, `kubernetes` and `helm` services). + # + # These are overridden/extended by the settings of any individual dev mode sync specs for a given module or + # service. + # + # Dev mode is enabled when running the `garden dev` command, and by setting the `--dev` flag on the `garden + # deploy` command. + # + # See the [Code Synchronization guide](https://docs.garden.io/guides/code-synchronization-dev-mode) for more + # information. + defaults: + # Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + # + # Any exclusion patterns defined in individual dev mode sync specs will be applied in addition to these + # patterns. + # + # `.git` directories and `.garden` directories are always ignored. + exclude: + + # The default permission bits, specified as an octal, to set on files at the sync target. Defaults to 0600 + # (user read/write). See the [Mutagen + # docs](https://mutagen.io/documentation/synchronization/permissions#permissions) for more information. + fileMode: + + # The default permission bits, specified as an octal, to set on directories at the sync target. Defaults to + # 0700 (user read/write). See the [Mutagen + # docs](https://mutagen.io/documentation/synchronization/permissions#permissions) for more information. + directoryMode: + + # Set the default owner of files and directories at the target. Specify either an integer ID or a string name. + # See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#owners-and-groups) for + # more information. + owner: + + # Set the default group on files and directories at the target. Specify either an integer ID or a string name. + # See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#owners-and-groups) for + # more information. + group: + # Require SSL on all `container` module services. If set to true, an error is raised when no certificate is # available for a configured hostname on a `container` module. forceSsl: false @@ -627,14 +672,109 @@ providers: **Experimental**: this is an experimental feature and the API might change in the future. {% endhint %} -Defines the strategy for deploying the project services. -Default is "rolling update" and there is experimental support for "blue/green" deployment. -The feature only supports modules of type `container`: other types will just deploy using the default strategy. +Sets the deployment strategy for `container` services. + +The default is `"rolling"`, which performs rolling updates. There is also experimental support for blue/green deployments (via the `"blue-green"` strategy). + +Note that this setting only applies to `container` services (and not, for example, `kubernetes` or `helm` services). | Type | Default | Required | | -------- | ----------- | -------- | | `string` | `"rolling"` | No | +### `providers[].devMode` + +[providers](#providers) > devMode + +Configuration options for dev mode. + +| Type | Required | +| -------- | -------- | +| `object` | No | + +### `providers[].devMode.defaults` + +[providers](#providers) > [devMode](#providersdevmode) > defaults + +Specifies default settings for dev mode syncs (e.g. for `container`, `kubernetes` and `helm` services). + +These are overridden/extended by the settings of any individual dev mode sync specs for a given module or service. + +Dev mode is enabled when running the `garden dev` command, and by setting the `--dev` flag on the `garden deploy` command. + +See the [Code Synchronization guide](https://docs.garden.io/guides/code-synchronization-dev-mode) for more information. + +| Type | Required | +| -------- | -------- | +| `object` | No | + +### `providers[].devMode.defaults.exclude[]` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > exclude + +Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + +Any exclusion patterns defined in individual dev mode sync specs will be applied in addition to these patterns. + +`.git` directories and `.garden` directories are always ignored. + +| Type | Required | +| ------------------ | -------- | +| `array[posixPath]` | No | + +Example: + +```yaml +providers: + - devMode: + ... + defaults: + ... + exclude: + - dist/**/* + - '*.log' +``` + +### `providers[].devMode.defaults.fileMode` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > fileMode + +The default permission bits, specified as an octal, to set on files at the sync target. Defaults to 0600 (user read/write). See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#permissions) for more information. + +| Type | Required | +| -------- | -------- | +| `number` | No | + +### `providers[].devMode.defaults.directoryMode` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > directoryMode + +The default permission bits, specified as an octal, to set on directories at the sync target. Defaults to 0700 (user read/write). See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#permissions) for more information. + +| Type | Required | +| -------- | -------- | +| `number` | No | + +### `providers[].devMode.defaults.owner` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > owner + +Set the default owner of files and directories at the target. Specify either an integer ID or a string name. See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#owners-and-groups) for more information. + +| Type | Required | +| ----------------- | -------- | +| `number | string` | No | + +### `providers[].devMode.defaults.group` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > group + +Set the default group on files and directories at the target. Specify either an integer ID or a string name. See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#owners-and-groups) for more information. + +| Type | Required | +| ----------------- | -------- | +| `number | string` | No | + ### `providers[].forceSsl` [providers](#providers) > forceSsl diff --git a/docs/reference/providers/local-kubernetes.md b/docs/reference/providers/local-kubernetes.md index 4fabd073f2..6576b61902 100644 --- a/docs/reference/providers/local-kubernetes.md +++ b/docs/reference/providers/local-kubernetes.md @@ -107,11 +107,56 @@ providers: # A default hostname to use when no hostname is explicitly configured for a service. defaultHostname: - # Defines the strategy for deploying the project services. - # Default is "rolling update" and there is experimental support for "blue/green" deployment. - # The feature only supports modules of type `container`: other types will just deploy using the default strategy. + # Sets the deployment strategy for `container` services. + # + # The default is `"rolling"`, which performs rolling updates. There is also experimental support for blue/green + # deployments (via the `"blue-green"` strategy). + # + # Note that this setting only applies to `container` services (and not, for example, `kubernetes` or `helm` + # services). deploymentStrategy: rolling + # Configuration options for dev mode. + devMode: + # Specifies default settings for dev mode syncs (e.g. for `container`, `kubernetes` and `helm` services). + # + # These are overridden/extended by the settings of any individual dev mode sync specs for a given module or + # service. + # + # Dev mode is enabled when running the `garden dev` command, and by setting the `--dev` flag on the `garden + # deploy` command. + # + # See the [Code Synchronization guide](https://docs.garden.io/guides/code-synchronization-dev-mode) for more + # information. + defaults: + # Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + # + # Any exclusion patterns defined in individual dev mode sync specs will be applied in addition to these + # patterns. + # + # `.git` directories and `.garden` directories are always ignored. + exclude: + + # The default permission bits, specified as an octal, to set on files at the sync target. Defaults to 0600 + # (user read/write). See the [Mutagen + # docs](https://mutagen.io/documentation/synchronization/permissions#permissions) for more information. + fileMode: + + # The default permission bits, specified as an octal, to set on directories at the sync target. Defaults to + # 0700 (user read/write). See the [Mutagen + # docs](https://mutagen.io/documentation/synchronization/permissions#permissions) for more information. + directoryMode: + + # Set the default owner of files and directories at the target. Specify either an integer ID or a string name. + # See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#owners-and-groups) for + # more information. + owner: + + # Set the default group on files and directories at the target. Specify either an integer ID or a string name. + # See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#owners-and-groups) for + # more information. + group: + # Require SSL on all `container` module services. If set to true, an error is raised when no certificate is # available for a configured hostname on a `container` module. forceSsl: false @@ -589,14 +634,109 @@ providers: **Experimental**: this is an experimental feature and the API might change in the future. {% endhint %} -Defines the strategy for deploying the project services. -Default is "rolling update" and there is experimental support for "blue/green" deployment. -The feature only supports modules of type `container`: other types will just deploy using the default strategy. +Sets the deployment strategy for `container` services. + +The default is `"rolling"`, which performs rolling updates. There is also experimental support for blue/green deployments (via the `"blue-green"` strategy). + +Note that this setting only applies to `container` services (and not, for example, `kubernetes` or `helm` services). | Type | Default | Required | | -------- | ----------- | -------- | | `string` | `"rolling"` | No | +### `providers[].devMode` + +[providers](#providers) > devMode + +Configuration options for dev mode. + +| Type | Required | +| -------- | -------- | +| `object` | No | + +### `providers[].devMode.defaults` + +[providers](#providers) > [devMode](#providersdevmode) > defaults + +Specifies default settings for dev mode syncs (e.g. for `container`, `kubernetes` and `helm` services). + +These are overridden/extended by the settings of any individual dev mode sync specs for a given module or service. + +Dev mode is enabled when running the `garden dev` command, and by setting the `--dev` flag on the `garden deploy` command. + +See the [Code Synchronization guide](https://docs.garden.io/guides/code-synchronization-dev-mode) for more information. + +| Type | Required | +| -------- | -------- | +| `object` | No | + +### `providers[].devMode.defaults.exclude[]` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > exclude + +Specify a list of POSIX-style paths or glob patterns that should be excluded from the sync. + +Any exclusion patterns defined in individual dev mode sync specs will be applied in addition to these patterns. + +`.git` directories and `.garden` directories are always ignored. + +| Type | Required | +| ------------------ | -------- | +| `array[posixPath]` | No | + +Example: + +```yaml +providers: + - devMode: + ... + defaults: + ... + exclude: + - dist/**/* + - '*.log' +``` + +### `providers[].devMode.defaults.fileMode` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > fileMode + +The default permission bits, specified as an octal, to set on files at the sync target. Defaults to 0600 (user read/write). See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#permissions) for more information. + +| Type | Required | +| -------- | -------- | +| `number` | No | + +### `providers[].devMode.defaults.directoryMode` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > directoryMode + +The default permission bits, specified as an octal, to set on directories at the sync target. Defaults to 0700 (user read/write). See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#permissions) for more information. + +| Type | Required | +| -------- | -------- | +| `number` | No | + +### `providers[].devMode.defaults.owner` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > owner + +Set the default owner of files and directories at the target. Specify either an integer ID or a string name. See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#owners-and-groups) for more information. + +| Type | Required | +| ----------------- | -------- | +| `number | string` | No | + +### `providers[].devMode.defaults.group` + +[providers](#providers) > [devMode](#providersdevmode) > [defaults](#providersdevmodedefaults) > group + +Set the default group on files and directories at the target. Specify either an integer ID or a string name. See the [Mutagen docs](https://mutagen.io/documentation/synchronization/permissions#owners-and-groups) for more information. + +| Type | Required | +| ----------------- | -------- | +| `number | string` | No | + ### `providers[].forceSsl` [providers](#providers) > forceSsl