Skip to content

Commit

Permalink
fix(core): certain template strings could not be resolved in configs
Browse files Browse the repository at this point in the history
This addresses a design issue, where runtime values were being computed
to interpolate module configurations. Because service names and outputs
cannot really be resolved until after all module configs are resolved,
we needed to remove the `services` template string context, as well
as fix the module resolution flow.

BREAKING CHANGE:

Module configurations using the `services` template key need to be
updated to use `modules` instead.

The (admittedly poorly supported) google-cloud-function module type has
been changed to include only one function per module. This is more
consistent with other module types, and avoids complex refactoring
to fit with the changes in the templating context.
  • Loading branch information
edvald committed Feb 1, 2019
1 parent a50f2fd commit 3d582c4
Show file tree
Hide file tree
Showing 59 changed files with 352 additions and 443 deletions.
11 changes: 9 additions & 2 deletions docs/examples/hello-world.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,19 @@ module:
name: hello-container
services:
...
env:
FUNCTION_ENDPOINT: ${services.hello-function.outputs.endpoint}
dependencies:
- hello-function
env:
FUNCTION_ENDPOINT: ${modules.hello-function.outputs.endpoint}
```

Note the added `dependencies` key, which specified `hello-function` as a runtime dependency for the `hello-container`
service.

Here we also reference the module outputs from the OpenFaaS module using a templated string, in order to inform the
container service where the endpoint for the function is. Please refer to individual provider docs on which values
are exposed under the `outputs` key on different module types.

Test dependencies will be covered further ahead.

# Defining ports, endpoints, and health checks
Expand Down
9 changes: 0 additions & 9 deletions docs/reference/module-types/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,6 @@ The names of any services that this service depends on at runtime, and the names
| Type | Required |
| ---- | -------- |
| `array[string]` | No
### `module.services[].outputs`
[module](#module) > [services](#module.services[]) > outputs

Key/value map. Keys must be valid identifiers.

| Type | Required |
| ---- | -------- |
| `object` | No
### `module.services[].args[]`
[module](#module) > [services](#module.services[]) > args

Expand Down Expand Up @@ -493,7 +485,6 @@ module:
services:
- name:
dependencies: []
outputs: {}
args:
daemon: false
ingresses:
Expand Down
11 changes: 0 additions & 11 deletions docs/reference/template-strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,6 @@ environment:
modules:
{}
# Retrieve information about services that are defined in the project.
#
# Example:
# my-service:
# outputs:
# ingress: 'http://my-service/path/to/endpoint'
# version: v17ad4cb3fd
#
services:
{}
# A map of all configured plugins/providers for this environment and their configuration.
#
# Example:
Expand Down
4 changes: 2 additions & 2 deletions examples/hello-world/services/hello-container/garden.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module:
dependencies:
- hello-function
env:
FUNCTION_ENDPOINT: ${services.hello-function.outputs.endpoint}
FUNCTION_ENDPOINT: ${modules.hello-function.outputs.endpoint}
build:
dependencies:
- name: hello-npm-package
Expand All @@ -31,6 +31,6 @@ module:
- name: integ
args: [npm, run, integ]
env:
FUNCTION_ENDPOINT: ${services.hello-function.outputs.endpoint}
FUNCTION_ENDPOINT: ${modules.hello-function.outputs.endpoint}
dependencies:
- hello-function
14 changes: 2 additions & 12 deletions garden-service/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import Bluebird = require("bluebird")
import chalk from "chalk"
import { Garden } from "./garden"
import { PrimitiveMap } from "./config/common"
import { Module } from "./types/module"
import { ModuleActions, ServiceActions, PluginActions, TaskActions } from "./types/plugin/plugin"
import {
Expand Down Expand Up @@ -41,7 +40,6 @@ import {
GetSecretParams,
GetBuildStatusParams,
GetServiceLogsParams,
GetServiceOutputsParams,
GetServiceStatusParams,
GetTestResultParams,
ModuleActionParams,
Expand Down Expand Up @@ -295,14 +293,6 @@ export class ActionHelper implements TypeGuard {
})
}

async getServiceOutputs(params: ServiceActionHelperParams<GetServiceOutputsParams>): Promise<PrimitiveMap> {
return this.callServiceHandler({
params,
actionType: "getServiceOutputs",
defaultHandler: async () => ({}),
})
}

async execInService(params: ServiceActionHelperParams<ExecInServiceParams>): Promise<ExecInServiceResult> {
return this.callServiceHandler({ params, actionType: "execInService" })
}
Expand Down Expand Up @@ -337,7 +327,7 @@ export class ActionHelper implements TypeGuard {

const serviceStatus = await Bluebird.props(mapValues(services, async (service: Service) => {
const serviceDependencies = await this.garden.getServices(service.config.dependencies)
const runtimeContext = await prepareRuntimeContext(this.garden, log, service.module, serviceDependencies)
const runtimeContext = await prepareRuntimeContext(this.garden, service.module, serviceDependencies)
// TODO: The status will be reported as "outdated" if the service was deployed with hot-reloading enabled.
// Once hot-reloading is a toggle, as opposed to an API/CLI flag, we can resolve that issue.
return this.getServiceStatus({ log, service, runtimeContext, hotReload: false })
Expand Down Expand Up @@ -440,7 +430,7 @@ export class ActionHelper implements TypeGuard {

// TODO: figure out why this doesn't compile without the casts
const deps = await this.garden.getServices(service.config.dependencies)
const runtimeContext = ((<any>params).runtimeContext || await prepareRuntimeContext(this.garden, log, module, deps))
const runtimeContext = ((<any>params).runtimeContext || await prepareRuntimeContext(this.garden, module, deps))

const handlerParams: any = {
...this.commonParams(handler, log),
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/run/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class RunModuleCommand extends Command<Args, Opts> {
const depNames = uniq(flatten(module.serviceConfigs.map(s => s.dependencies)))
const deps = await garden.getServices(depNames)

const runtimeContext = await prepareRuntimeContext(garden, log, module, deps)
const runtimeContext = await prepareRuntimeContext(garden, module, deps)

printRuntimeContext(log, runtimeContext)

Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/run/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class RunServiceCommand extends Command<Args, Opts> {
await garden.processTasks()

const dependencies = await garden.getServices(module.serviceDependencyNames)
const runtimeContext = await prepareRuntimeContext(garden, log, module, dependencies)
const runtimeContext = await prepareRuntimeContext(garden, module, dependencies)

printRuntimeContext(log, runtimeContext)

Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/run/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class RunTestCommand extends Command<Args, Opts> {

const interactive = opts.interactive
const deps = await garden.getServices(testConfig.dependencies)
const runtimeContext = await prepareRuntimeContext(garden, log, module, deps)
const runtimeContext = await prepareRuntimeContext(garden, module, deps)

printRuntimeContext(log, runtimeContext)

Expand Down
1 change: 1 addition & 0 deletions garden-service/src/config/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export async function loadConfig(projectRoot: string, path: string): Promise<Gar
build: moduleConfig.build,
description: moduleConfig.description,
name: moduleConfig.name,
outputs: {},
path,
repositoryUrl: moduleConfig.repositoryUrl,
serviceConfigs: [],
Expand Down
74 changes: 18 additions & 56 deletions garden-service/src/config/config-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { isString, flatten } from "lodash"
import { isString } from "lodash"
import { Module } from "../types/module"
import { PrimitiveMap, isPrimitive, Primitive, joiIdentifierMap, joiStringMap, joiPrimitive } from "./common"
import { Provider, Environment, providerConfigBaseSchema } from "./project"
import { ModuleConfig } from "./module"
import { ConfigurationError } from "../exceptions"
import { Service } from "../types/service"
import { resolveTemplateString } from "../template-string"
import * as Joi from "joi"
import { Garden } from "../garden"
import { LogEntry } from "../logger/log-entry"

export type ContextKey = string[]

Expand Down Expand Up @@ -188,49 +186,36 @@ class EnvironmentContext extends ConfigContext {
}
}

const exampleOutputs = { endpoint: "http://my-service/path/to/endpoint" }
const exampleVersion = "v17ad4cb3fd"

class ModuleContext extends ConfigContext {
@schema(Joi.string().description("The local path of the module.").example("/home/me/code/my-project/my-module"))
public path: string

@schema(Joi.string().description("The current version of the module.").example(exampleVersion))
public version: string

@schema(
Joi.string()
.description("The build path of the module.")
.example("/home/me/code/my-project/.garden/build/my-module"),
)
public buildPath: string

constructor(root: ConfigContext, module: Module) {
super(root)
this.path = module.path
this.version = module.version.versionString
this.buildPath = module.buildPath
}
}

const exampleOutputs = { ingress: "http://my-service/path/to/endpoint" }

class ServiceContext extends ConfigContext {
@schema(
joiIdentifierMap(joiPrimitive()
.description("The outputs defined by the service (see individual plugins for details)."),
).example(exampleOutputs),
joiIdentifierMap(joiPrimitive())
.description("The outputs defined by the module (see individual plugins for details).")
.example(exampleOutputs),
)
public outputs: PrimitiveMap

@schema(Joi.string().description("The current version of the service.").example(exampleVersion))
public version: string
@schema(Joi.string().description("The local path of the module.").example("/home/me/code/my-project/my-module"))
public path: string

// TODO: add ingresses
@schema(Joi.string().description("The current version of the module.").example(exampleVersion))
public version: string

constructor(root: ConfigContext, service: Service, outputs: PrimitiveMap) {
constructor(root: ConfigContext, module: Module) {
super(root)
this.outputs = outputs
this.version = service.module.version.versionString
this.buildPath = module.buildPath
this.outputs = module.outputs
this.path = module.path
this.version = module.version.versionString
}
}

Expand All @@ -252,26 +237,13 @@ export class ModuleConfigContext extends ProjectConfigContext {
)
public modules: Map<string, () => Promise<ModuleContext>>

@schema(
joiIdentifierMap(ServiceContext.getSchema())
.description("Retrieve information about services that are defined in the project.")
.example({ "my-service": { outputs: exampleOutputs, version: exampleVersion } }),
)
public services: Map<string, () => Promise<ServiceContext>>

@schema(
joiIdentifierMap(providerConfigBaseSchema)
.description("A map of all configured plugins/providers for this environment and their configuration.")
.example({ kubernetes: { name: "local-kubernetes", context: "my-kube-context" } }),
)
public providers: Map<string, Provider>

// NOTE: This has some negative performance implications and may not be something we want to support,
// so I'm disabling this feature for now.
//
// @description("Use this to look up values that are configured in the current environment.")
// public config: RemoteConfigContext

@schema(
joiIdentifierMap(joiPrimitive())
.description("A map of all variables defined in the project configuration.")
Expand All @@ -281,7 +253,6 @@ export class ModuleConfigContext extends ProjectConfigContext {

constructor(
garden: Garden,
log: LogEntry,
environment: Environment,
moduleConfigs: ModuleConfig[],
) {
Expand All @@ -293,24 +264,15 @@ export class ModuleConfigContext extends ProjectConfigContext {

this.modules = new Map(moduleConfigs.map((config) =>
<[string, () => Promise<ModuleContext>]>[config.name, async () => {
// NOTE: This is a temporary hacky solution until we implement module resolution as a TaskGraph task
if (!garden.hasModule(config.name)) {
await garden.addModule(config)
}
const module = await garden.getModule(config.name)
return new ModuleContext(_this, module)
}],
))

const serviceNames = flatten(moduleConfigs.map(m => m.serviceConfigs)).map(s => s.name)

this.services = new Map(serviceNames.map((name) =>
<[string, () => Promise<ServiceContext>]>[name, async () => {
const service = await garden.getService(name)
const outputs = {
...service.config.outputs,
...await garden.actions.getServiceOutputs({ log, service }),
}
return new ServiceContext(_this, service, outputs)
}],
))

this.providers = new Map(environment.providers.map(p => <[string, Provider]>[p.name, p]))

// this.config = new SecretsContextNode(ctx)
Expand Down
6 changes: 6 additions & 0 deletions garden-service/src/config/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
joiIdentifier,
joiRepositoryUrl,
joiUserIdentifier,
PrimitiveMap,
joiIdentifierMap,
joiPrimitive,
} from "./common"
import { TestConfig, TestSpec } from "./test"
import { TaskConfig, TaskSpec } from "./task"
Expand All @@ -36,6 +39,8 @@ const copySchema = Joi.object()
),
})

export const moduleOutputsSchema = joiIdentifierMap(joiPrimitive())

export interface BuildDependencyConfig {
name: string
plugin?: string
Expand Down Expand Up @@ -121,6 +126,7 @@ export interface ModuleConfig

plugin?: string // used to identify modules that are bundled as part of a plugin

outputs: PrimitiveMap
serviceConfigs: ServiceConfig<S>[]
testConfigs: TestConfig<T>[]
taskConfigs: TaskConfig<W>[]
Expand Down
1 change: 0 additions & 1 deletion garden-service/src/config/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export const baseServiceSchema = Joi.object()
The names of any services that this service depends on at runtime, and the names of any
tasks that should be executed before this service is deployed.
`),
outputs: serviceOutputsSchema,
})
.unknown(true)
.meta({ extendable: true })
Expand Down
Loading

0 comments on commit 3d582c4

Please sign in to comment.