Skip to content

Commit

Permalink
improvement(core): always require a namespace and simply env config
Browse files Browse the repository at this point in the history
We now always require a namespace for all environments, and make the
default simply "default". You can override the default namespace with
`environments[].defaultNamespace` as before.

This reduces code branching significantly, especially in the enterprise
UI, and simplifies our configuration as well.

BREAKING CHANGE:

The default namespace in the `kubernetes` provider is now
`<project name>.<environment namespace>` (previously it was just the
project name). Users need to override this to `"${project.name}"` if
they would like to revert to the previous default.
  • Loading branch information
edvald committed Jun 24, 2020
1 parent 7b0c6f2 commit f0a7677
Show file tree
Hide file tree
Showing 30 changed files with 168 additions and 292 deletions.
58 changes: 11 additions & 47 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,13 @@ environments:
- # The name of the environment.
name:

# Control if and how this environment should support namespaces. If set to "optional" (the default), users can
# set a namespace for the environment. This is useful for any shared environments, e.g. testing and development
# environments, where namespaces separate different users or code versions within an environment. Users then
# specify an environment with `--env <namespace>.<environment>`, e.g. `--env alice.dev` or
# `--env my-branch.testing`.
# Set the default namespace to use. This can be templated to be user-specific, or to use an environment variable
# (e.g. in CI).
#
# If set to "required", this namespace separation is enforced, and an error is thrown if a namespace is not
# specified with the `--env` parameter.
#
# If set to "disabled", an error is thrown if a namespace is specified. This makes sense for e.g. production or
# staging environments, where you don't want to split the environment between users or code versions.
#
# When specified, namespaces must be a valid DNS-style label, much like other identifiers.
namespacing: optional

# Set a default namespace to use, when `namespacing` is `required` or `optional`. This can be templated to be
# user-specific, or to use an environment variable (e.g. in CI).
#
# If this is set, users can specify `--env <environment>` and skip the namespace part, even when `namespacing` is
# `required` for the environment.
defaultNamespace:
# You can also set this to `null`, in order to require an explicit namespace to be set on usage. This may be
# advisable for shared environments, but you may also be able to achieve the desired result by templating this
# field, as mentioned above.
defaultNamespace: default

# Flag the environment as a production environment.
#
Expand Down Expand Up @@ -232,39 +218,17 @@ environments:
- name: "dev"
```

### `environments[].namespacing`

[environments](#environments) > namespacing

Control if and how this environment should support namespaces. If set to "optional" (the default), users can
set a namespace for the environment. This is useful for any shared environments, e.g. testing and development
environments, where namespaces separate different users or code versions within an environment. Users then
specify an environment with `--env <namespace>.<environment>`, e.g. `--env alice.dev` or
`--env my-branch.testing`.

If set to "required", this namespace separation is enforced, and an error is thrown if a namespace is not
specified with the `--env` parameter.

If set to "disabled", an error is thrown if a namespace is specified. This makes sense for e.g. production or
staging environments, where you don't want to split the environment between users or code versions.

When specified, namespaces must be a valid DNS-style label, much like other identifiers.

| Type | Default | Required |
| -------- | ------------ | -------- |
| `string` | `"optional"` | No |

### `environments[].defaultNamespace`

[environments](#environments) > defaultNamespace

Set a default namespace to use, when `namespacing` is `required` or `optional`. This can be templated to be user-specific, or to use an environment variable (e.g. in CI).
Set the default namespace to use. This can be templated to be user-specific, or to use an environment variable (e.g. in CI).

If this is set, users can specify `--env <environment>` and skip the namespace part, even when `namespacing` is `required` for the environment.
You can also set this to `null`, in order to require an explicit namespace to be set on usage. This may be advisable for shared environments, but you may also be able to achieve the desired result by templating this field, as mentioned above.

| Type | Required |
| -------- | -------- |
| `string` | No |
| Type | Default | Required |
| -------- | ----------- | -------- |
| `string` | `"default"` | No |

Example:

Expand Down
22 changes: 4 additions & 18 deletions docs/reference/providers/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ providers:
# A default hostname to use when no hostname is explicitly configured for a service.
defaultHostname:

# Set a default username (used for namespacing within a cluster).
defaultUsername:

# 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.
Expand Down Expand Up @@ -314,10 +311,9 @@ providers:
# Path to kubeconfig file to use instead of the system default. Must be a POSIX-style path.
kubeconfig:

# Specify which namespace to deploy services to. Defaults to the environment namespace, if specified and enabled,
# otherwise the project name.
# Specify which namespace to deploy services to. Defaults to `<project name>-<environment namespace>`.
#
# Note that the framework generates other namespaces as well with this name as a prefix.
# Note that the framework may generate other namespaces as well with this name as a prefix.
namespace:

# Set this to `nginx` to install/enable the NGINX ingress controller.
Expand Down Expand Up @@ -431,16 +427,6 @@ providers:
- defaultHostname: "api.mydomain.com"
```

### `providers[].defaultUsername`

[providers](#providers) > defaultUsername

Set a default username (used for namespacing within a cluster).

| Type | Required |
| -------- | -------- |
| `string` | No |

### `providers[].deploymentStrategy`

[providers](#providers) > deploymentStrategy
Expand Down Expand Up @@ -1467,9 +1453,9 @@ Path to kubeconfig file to use instead of the system default. Must be a POSIX-st

[providers](#providers) > namespace

Specify which namespace to deploy services to. Defaults to the environment namespace, if specified and enabled, otherwise the project name.
Specify which namespace to deploy services to. Defaults to `<project name>-<environment namespace>`.

Note that the framework generates other namespaces as well with this name as a prefix.
Note that the framework may generate other namespaces as well with this name as a prefix.

| Type | Required |
| -------- | -------- |
Expand Down
13 changes: 0 additions & 13 deletions docs/reference/providers/local-kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,6 @@ providers:
# A default hostname to use when no hostname is explicitly configured for a service.
defaultHostname:

# Set a default username (used for namespacing within a cluster).
defaultUsername:

# 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.
Expand Down Expand Up @@ -397,16 +394,6 @@ providers:
- defaultHostname: "api.mydomain.com"
```

### `providers[].defaultUsername`

[providers](#providers) > defaultUsername

Set a default username (used for namespacing within a cluster).

| Type | Required |
| -------- | -------- |
| `string` | No |

### `providers[].deploymentStrategy`

[providers](#providers) > deploymentStrategy
Expand Down
4 changes: 2 additions & 2 deletions garden-service/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
checkForUpdates,
checkForStaticDir,
} from "./helpers"
import { defaultEnvironments, ProjectConfig } from "../config/project"
import { defaultEnvironments, ProjectConfig, defaultNamespace } from "../config/project"
import { ERROR_LOG_FILENAME, DEFAULT_API_VERSION, DEFAULT_GARDEN_DIR_NAME, LOGS_DIR_NAME } from "../constants"
import stringify = require("json-stringify-safe")
import { generateBasicDebugInfoReport } from "../commands/get/get-debug-info"
Expand All @@ -70,7 +70,7 @@ const GLOBAL_OPTIONS_GROUP_NAME = "Global options"

export async function makeDummyGarden(root: string, gardenOpts: GardenOpts = {}) {
const environments = gardenOpts.environmentName
? [{ name: gardenOpts.environmentName, variables: {} }]
? [{ name: gardenOpts.environmentName, defaultNamespace, variables: {} }]
: defaultEnvironments

const config: ProjectConfig = {
Expand Down
49 changes: 14 additions & 35 deletions garden-service/src/config/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ import { DEFAULT_API_VERSION, DOCS_BASE_URL } from "../constants"
import { defaultDotIgnoreFiles } from "../util/fs"
import { pathExists, readFile } from "fs-extra"
import { resolve } from "path"
import chalk = require("chalk")

export const defaultVarfilePath = "garden.env"
export const defaultEnvVarfilePath = (environmentName: string) => `garden.${environmentName}.env`

// These plugins are always loaded
export const defaultNamespace = "default"
export const fixedPlugins = ["exec", "container"]

export type EnvironmentNamespacing = "disabled" | "optional" | "required"
Expand All @@ -48,12 +50,9 @@ export interface ParsedEnvironment {
namespace?: string
}

const defaultEnvironmentNamespacing: EnvironmentNamespacing = "optional"

export interface EnvironmentConfig {
name: string
namespacing?: EnvironmentNamespacing
defaultNamespace?: string
defaultNamespace: string | null
providers?: ProviderConfig[] // further validated by each plugin
varfile?: string
variables: DeepPrimitiveMap
Expand All @@ -69,30 +68,14 @@ export const environmentNameSchema = () =>
export const environmentSchema = () =>
joi.object().keys({
name: environmentNameSchema(),
namespacing: joi
.string()
.allow("disabled", "optional", "required")
.default("optional").description(dedent`
Control if and how this environment should support namespaces. If set to "optional" (the default), users can
set a namespace for the environment. This is useful for any shared environments, e.g. testing and development
environments, where namespaces separate different users or code versions within an environment. Users then
specify an environment with \`--env <namespace>.<environment>\`, e.g. \`--env alice.dev\` or
\`--env my-branch.testing\`.
If set to "required", this namespace separation is enforced, and an error is thrown if a namespace is not
specified with the \`--env\` parameter.
If set to "disabled", an error is thrown if a namespace is specified. This makes sense for e.g. production or
staging environments, where you don't want to split the environment between users or code versions.
When specified, namespaces must be a valid DNS-style label, much like other identifiers.
`),
defaultNamespace: joiIdentifier()
.allow(null)
.default(defaultNamespace)
.description(
dedent`
Set a default namespace to use, when \`namespacing\` is \`required\` or \`optional\`. This can be templated to be user-specific, or to use an environment variable (e.g. in CI).
Set the default namespace to use. This can be templated to be user-specific, or to use an environment variable (e.g. in CI).
If this is set, users can specify \`--env <environment>\` and skip the namespace part, even when \`namespacing\` is \`required\` for the environment.
You can also set this to \`null\`, in order to require an explicit namespace to be set on usage. This may be advisable for shared environments, but you may also be able to achieve the desired result by templating this field, as mentioned above.
`
)
.example("user-${local.username}"),
Expand Down Expand Up @@ -224,7 +207,7 @@ export interface ProjectResource extends ProjectConfig {
export const defaultEnvironments: EnvironmentConfig[] = [
{
name: "local",
namespacing: defaultEnvironmentNamespacing,
defaultNamespace,
providers: [
{
name: "local-kubernetes",
Expand Down Expand Up @@ -572,23 +555,19 @@ export async function pickEnvironment(config: ProjectConfig, envString: string)
* Validates that the value passed for `namespace` conforms with the namespacing setting in `environmentConfig`,
* and returns `namespace` (or a default namespace, if appropriate).
*/
export function getNamespace(environmentConfig: EnvironmentConfig, namespace: string | undefined): string | undefined {
export function getNamespace(environmentConfig: EnvironmentConfig, namespace: string | undefined): string {
const envName = environmentConfig.name

if (namespace && environmentConfig.namespacing === "disabled") {
throw new ParameterError(
`Environment ${envName} does not allow namespacing, but namespace '${namespace}' was specified.`,
{ environmentConfig, namespace }
)
}

if (!namespace && environmentConfig.defaultNamespace) {
namespace = environmentConfig.defaultNamespace
}

if (!namespace && environmentConfig.namespacing === "required") {
if (!namespace) {
const envHighlight = chalk.white.bold(envName)
const exampleFlag = chalk.white(`--env=${chalk.bold("some-namespace.")}${envName}`)

throw new ParameterError(
`Environment ${envName} requires a namespace, but none was specified and no defaultNamespace is configured.`,
`Environment ${envHighlight} has defaultNamespace set to null, and no explicit namespace was specified. Please either set a defaultNamespace or explicitly set a namespace at runtime (e.g. ${exampleFlag}).`,
{
environmentConfig,
}
Expand Down
2 changes: 2 additions & 0 deletions garden-service/src/docs/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { keyBy } from "lodash"
import { writeFileSync } from "fs-extra"
import { renderModuleTypeReference, moduleTypes } from "./module-type"
import { renderProviderReference } from "./provider"
import { defaultNamespace } from "../config/project"

export async function generateDocs(targetDir: string) {
// tslint:disable: no-console
Expand Down Expand Up @@ -49,6 +50,7 @@ export async function writeConfigReferenceDocs(docsRoot: string) {
environments: [
{
name: "default",
defaultNamespace,
variables: {},
},
],
Expand Down
6 changes: 3 additions & 3 deletions garden-service/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export interface GardenParams {
dotIgnoreFiles: string[]
environmentName: string
environmentConfigs: EnvironmentConfig[]
namespace?: string
namespace: string
gardenDirPath: string
log: LogEntry
moduleIncludePatterns?: string[]
Expand Down Expand Up @@ -178,7 +178,7 @@ export class Garden {
public readonly projectName: string
public readonly environmentName: string
public readonly environmentConfigs: EnvironmentConfig[]
public readonly namespace?: string
public readonly namespace: string
public readonly variables: DeepPrimitiveMap
public readonly secrets: StringMap
public readonly projectSources: SourceConfig[]
Expand Down Expand Up @@ -1200,7 +1200,7 @@ export class DummyGarden extends Garden {
export interface ConfigDump {
environmentName: string // TODO: Remove this?
allEnvironmentNames: string[]
namespace?: string
namespace: string
providers: (Omit<Provider, "tools"> | ProviderConfig)[]
variables: DeepPrimitiveMap
moduleConfigs: ModuleConfig[]
Expand Down
3 changes: 2 additions & 1 deletion garden-service/src/plugins/conftest/conftest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ export const gardenPlugin = createGardenPlugin({
{
platform: "windows",
architecture: "amd64",
url: "https://github.com/open-policy-agent/conftest/releases/download/v0.17.1/conftest_0.17.1_Windows_x86_64.zip",
url:
"https://github.com/open-policy-agent/conftest/releases/download/v0.17.1/conftest_0.17.1_Windows_x86_64.zip",
sha256: "4c2df80420f2f148ec085bb75a8c5b92e1c665c6a041768a79924c81082527c3",
extract: {
format: "zip",
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/plugins/container/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ export interface ContainerModuleSpec extends ModuleSpec {

export interface ContainerModuleConfig extends ModuleConfig<ContainerModuleSpec> {}

export const defaultNamespace = "_"
export const defaultImageNamespace = "_"
export const defaultTag = "latest"

export const containerModuleSpecSchema = () =>
Expand Down
10 changes: 8 additions & 2 deletions garden-service/src/plugins/container/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ import isGlob from "is-glob"
import { ConfigurationError, RuntimeError } from "../../exceptions"
import { splitFirst, spawn, splitLast, SpawnOutput } from "../../util/util"
import { ModuleConfig } from "../../config/module"
import { ContainerModule, ContainerRegistryConfig, defaultTag, defaultNamespace, ContainerModuleConfig } from "./config"
import {
ContainerModule,
ContainerRegistryConfig,
defaultTag,
defaultImageNamespace,
ContainerModuleConfig,
} from "./config"
import { Writable } from "stream"
import Bluebird from "bluebird"
import { flatten, uniq, fromPairs } from "lodash"
Expand Down Expand Up @@ -200,7 +206,7 @@ const helpers = {
const name = parsed.tag ? `${parsed.repository}:${parsed.tag}` : parsed.repository

if (parsed.host) {
return `${parsed.host}/${parsed.namespace || defaultNamespace}/${name}`
return `${parsed.host}/${parsed.namespace || defaultImageNamespace}/${name}`
} else if (parsed.namespace) {
return `${parsed.namespace}/${name}`
} else {
Expand Down
6 changes: 2 additions & 4 deletions garden-service/src/plugins/kubernetes/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ export interface KubernetesConfig extends ProviderConfig {
}
context: string
defaultHostname?: string
defaultUsername?: string
deploymentRegistry?: ContainerRegistryConfig
deploymentStrategy?: DeploymentStrategy
forceSsl: boolean
Expand Down Expand Up @@ -349,7 +348,6 @@ export const kubernetesConfigBase = providerConfigBaseSchema().keys({
.string()
.description("A default hostname to use when no hostname is explicitly configured for a service.")
.example("api.mydomain.com"),
defaultUsername: joiIdentifier().description("Set a default username (used for namespacing within a cluster)."),
deploymentStrategy: joi
.string()
.default("rolling")
Expand Down Expand Up @@ -571,9 +569,9 @@ export const configSchema = kubernetesConfigBase
.posixPath()
.description("Path to kubeconfig file to use instead of the system default. Must be a POSIX-style path."),
namespace: joi.string().description(dedent`
Specify which namespace to deploy services to. Defaults to the environment namespace, if specified and enabled, otherwise the project name.
Specify which namespace to deploy services to. Defaults to \`<project name>-<environment namespace>\`.
Note that the framework generates other namespaces as well with this name as a prefix.
Note that the framework may generate other namespaces as well with this name as a prefix.
`),
setupIngressController: joi
.string()
Expand Down
Loading

0 comments on commit f0a7677

Please sign in to comment.