diff --git a/docs/faqs.md b/docs/faqs.md index 7e026a4671..46aa47cc97 100644 --- a/docs/faqs.md +++ b/docs/faqs.md @@ -58,7 +58,7 @@ We currently have a rough version of a Docker Swarm plug-in for Garden, but don' The Garden orchestrator itself doesn't care where your services are built, tested and deployed. However, the current selection of plug-ins does support local development better than remote development. For Kubernetes development in particular, it is currently much easier to set up the `local-kubernetes` plugin, and feedback loops are generally faster than with the more generic `kubernetes` plugin (see [this example](https://github.com/garden-io/garden/tree/master/examples/remote-k8s) for how to configure remote clusters). -However, we are working to bridge that gap, since we strongly believe that remote building, testing and deployment is the way of the future. You can already use our [hot reloading](./hot-reload.md) feature with remote clusters, for example. +However, we are working to bridge that gap, since we strongly believe that remote building, testing and deployment is the way of the future. You can already use our [hot reloading](./using-garden/hot-reload.md) feature with remote clusters, for example. ### Garden vs. Skaffold? diff --git a/docs/reference/module-types/maven-container.md b/docs/reference/module-types/maven-container.md new file mode 100644 index 0000000000..c0ac2a345d --- /dev/null +++ b/docs/reference/module-types/maven-container.md @@ -0,0 +1,642 @@ +# `maven-container` reference + +Below is the schema reference for the `maven-container` module type. For an introduction to configuring Garden modules, please look at our [Configuration guide](../../using-garden/configuration-files.md). + +The reference is divided into two sections. The [first section](#configuration-keys) lists and describes the available schema keys. The [second section](#complete-yaml-schema) contains the complete YAML schema. + +## Configuration keys + +### `module` + +Configuration for a container module. + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.build` +[module](#module) > build + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.build.dependencies[]` +[module](#module) > [build](#module.build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Required | +| ---- | -------- | +| `array[object]` | No + +Example: +```yaml +module: + ... + build: + ... + dependencies: + - name: some-other-module-name +``` +### `module.build.dependencies[].name` +[module](#module) > [build](#module.build) > [dependencies](#module.build.dependencies[]) > name + +Module name to build ahead of this module. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.build.dependencies[].copy[]` +[module](#module) > [build](#module.build) > [dependencies](#module.build.dependencies[]) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Required | +| ---- | -------- | +| `array[object]` | No +### `module.build.dependencies[].copy[].source` +[module](#module) > [build](#module.build) > [dependencies](#module.build.dependencies[]) > [copy](#module.build.dependencies[].copy[]) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.build.dependencies[].copy[].target` +[module](#module) > [build](#module.build) > [dependencies](#module.build.dependencies[]) > [copy](#module.build.dependencies[].copy[]) > target + +POSIX-style path or filename to copy the directory or file(s) to (defaults to same as source path). + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.build.targetImage` +[module](#module) > [build](#module.build) > targetImage + +For multi-stage Dockerfiles, specify which image to build (see https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.buildArgs` +[module](#module) > buildArgs + +Specify build arguments to use when building the container image. + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.image` +[module](#module) > image + +Specify the image name for the container. Should be a valid Docker image identifier. If specified and the module does not contain a Dockerfile, this image will be used to deploy services for this module. If specified and the module does contain a Dockerfile, this identifier is used when pushing the built image. + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.hotReload` +[module](#module) > hotReload + +Specifies which files or directories to sync to which paths inside the running containers of hot reload-enabled services when those files or directories are modified. Applies to this module's services, and to services with this module as their `sourceModule`. + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.hotReload.sync[]` +[module](#module) > [hotReload](#module.hotreload) > sync + +Specify one or more source files or directories to automatically sync into the running container. + +| Type | Required | +| ---- | -------- | +| `array[object]` | Yes +### `module.hotReload.sync[].source` +[module](#module) > [hotReload](#module.hotreload) > [sync](#module.hotreload.sync[]) > source + +POSIX-style path of the directory to sync to the target, relative to the module's top-level directory. Must be a relative path if provided. Defaults to the module's top-level directory if no value is provided. + +| Type | Required | +| ---- | -------- | +| `string` | No + +Example: +```yaml +module: + ... + hotReload: + ... + sync: + - source: "src" +``` +### `module.hotReload.sync[].target` +[module](#module) > [hotReload](#module.hotreload) > [sync](#module.hotreload.sync[]) > target + +POSIX-style absolute path to sync the directory to inside the container. The root path (i.e. "/") is not allowed. + +| Type | Required | +| ---- | -------- | +| `string` | Yes + +Example: +```yaml +module: + ... + hotReload: + ... + sync: + - target: "/app/src" +``` +### `module.dockerfile` +[module](#module) > dockerfile + +POSIX-style name of Dockerfile, relative to project root. Defaults to $MODULE_ROOT/Dockerfile. + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.services[]` +[module](#module) > services + +The list of services to deploy from this container module. + +| Type | Required | +| ---- | -------- | +| `array[object]` | No +### `module.services[].name` +[module](#module) > [services](#module.services[]) > name + +Valid RFC1035/RFC1123 (DNS) label (may contain lowercase letters, numbers and dashes, must start with a letter, and cannot end with a dash), cannot contain consecutive dashes or start with `garden`, or be longer than 63 characters. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.services[].dependencies[]` +[module](#module) > [services](#module.services[]) > dependencies + +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. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No +### `module.services[].annotations` +[module](#module) > [services](#module.services[]) > annotations + +Annotations to attach to the service (Note: May not be applicable to all providers) + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.services[].args[]` +[module](#module) > [services](#module.services[]) > args + +The arguments to run the container with when starting the service. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No +### `module.services[].daemon` +[module](#module) > [services](#module.services[]) > daemon + +Whether to run the service as a daemon (to ensure only one runs per node). + +| Type | Required | +| ---- | -------- | +| `boolean` | No +### `module.services[].ingresses[]` +[module](#module) > [services](#module.services[]) > ingresses + +List of ingress endpoints that the service exposes. + +| Type | Required | +| ---- | -------- | +| `array[object]` | No + +Example: +```yaml +module: + ... + services: + - ingresses: + - path: /api + port: http +``` +### `module.services[].ingresses[].annotations` +[module](#module) > [services](#module.services[]) > [ingresses](#module.services[].ingresses[]) > annotations + +Annotations to attach to the ingress (Note: May not be applicable to all providers) + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.services[].ingresses[].hostname` +[module](#module) > [services](#module.services[]) > [ingresses](#module.services[].ingresses[]) > hostname + +The hostname that should route to this service. Defaults to the default hostname +configured in the provider configuration. + +Note that if you're developing locally you may need to add this hostname to your hosts file. + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.services[].ingresses[].path` +[module](#module) > [services](#module.services[]) > [ingresses](#module.services[].ingresses[]) > path + +The path which should be routed to the service. + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.services[].ingresses[].port` +[module](#module) > [services](#module.services[]) > [ingresses](#module.services[].ingresses[]) > port + +The name of the container port where the specified paths should be routed. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.services[].env` +[module](#module) > [services](#module.services[]) > env + +Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with `GARDEN`) and values must be primitives. + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.services[].healthCheck` +[module](#module) > [services](#module.services[]) > healthCheck + +Specify how the service's health should be checked after deploying. + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.services[].healthCheck.httpGet` +[module](#module) > [services](#module.services[]) > [healthCheck](#module.services[].healthcheck) > httpGet + +Set this to check the service's health by making an HTTP request. + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.services[].healthCheck.httpGet.path` +[module](#module) > [services](#module.services[]) > [healthCheck](#module.services[].healthcheck) > [httpGet](#module.services[].healthcheck.httpget) > path + +The path of the service's health check endpoint. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.services[].healthCheck.httpGet.port` +[module](#module) > [services](#module.services[]) > [healthCheck](#module.services[].healthcheck) > [httpGet](#module.services[].healthcheck.httpget) > port + +The name of the port where the service's health check endpoint should be available. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.services[].healthCheck.httpGet.scheme` +[module](#module) > [services](#module.services[]) > [healthCheck](#module.services[].healthcheck) > [httpGet](#module.services[].healthcheck.httpget) > scheme + + + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.services[].healthCheck.command[]` +[module](#module) > [services](#module.services[]) > [healthCheck](#module.services[].healthcheck) > command + +Set this to check the service's health by running a command in its container. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No +### `module.services[].healthCheck.tcpPort` +[module](#module) > [services](#module.services[]) > [healthCheck](#module.services[].healthcheck) > tcpPort + +Set this to check the service's health by checking if this TCP port is accepting connections. + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.services[].hotReloadArgs[]` +[module](#module) > [services](#module.services[]) > hotReloadArgs + +If this module uses the `hotReload` field, the container will be run with these arguments instead of those in `args` when the service is deployed with hot reloading enabled. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No +### `module.services[].ports[]` +[module](#module) > [services](#module.services[]) > ports + +List of ports that the service container exposes. + +| Type | Required | +| ---- | -------- | +| `array[object]` | No +### `module.services[].ports[].name` +[module](#module) > [services](#module.services[]) > [ports](#module.services[].ports[]) > name + +The name of the port (used when referencing the port elsewhere in the service configuration). + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.services[].ports[].protocol` +[module](#module) > [services](#module.services[]) > [ports](#module.services[].ports[]) > protocol + +The protocol of the port. + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.services[].ports[].containerPort` +[module](#module) > [services](#module.services[]) > [ports](#module.services[].ports[]) > containerPort + +The port exposed on the container by the running process. This will also be the default value for `servicePort`. +`servicePort:80 -> containerPort:8080 -> process:8080` + +| Type | Required | +| ---- | -------- | +| `number` | Yes + +Example: +```yaml +module: + ... + services: + - ports: + - containerPort: "8080" +``` +### `module.services[].ports[].servicePort` +[module](#module) > [services](#module.services[]) > [ports](#module.services[].ports[]) > servicePort + +The port exposed on the service. Defaults to `containerPort` if not specified. +`servicePort:80 -> containerPort:8080 -> process:8080` + +| Type | Required | +| ---- | -------- | +| `number` | No + +Example: +```yaml +module: + ... + services: + - ports: + - servicePort: "80" +``` +### `module.services[].ports[].hostPort` +[module](#module) > [services](#module.services[]) > [ports](#module.services[].ports[]) > hostPort + + + +| Type | Required | +| ---- | -------- | +| `number` | No +### `module.services[].ports[].nodePort` +[module](#module) > [services](#module.services[]) > [ports](#module.services[].ports[]) > nodePort + +Set this to expose the service on the specified port on the host node (may not be supported by all providers). + +| Type | Required | +| ---- | -------- | +| `number` | No +### `module.services[].volumes[]` +[module](#module) > [services](#module.services[]) > volumes + +List of volumes that should be mounted when deploying the container. + +| Type | Required | +| ---- | -------- | +| `array[object]` | No +### `module.services[].volumes[].name` +[module](#module) > [services](#module.services[]) > [volumes](#module.services[].volumes[]) > name + +The name of the allocated volume. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.services[].volumes[].containerPath` +[module](#module) > [services](#module.services[]) > [volumes](#module.services[].volumes[]) > containerPath + +The path where the volume should be mounted in the container. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.services[].volumes[].hostPath` +[module](#module) > [services](#module.services[]) > [volumes](#module.services[].volumes[]) > hostPath + + + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.tests[]` +[module](#module) > tests + +A list of tests to run in the module. + +| Type | Required | +| ---- | -------- | +| `array[object]` | No +### `module.tests[].name` +[module](#module) > [tests](#module.tests[]) > name + +The name of the test. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.tests[].dependencies[]` +[module](#module) > [tests](#module.tests[]) > dependencies + +The names of any services that must be running, and the names of any tasks that must be executed, before the test is run. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No +### `module.tests[].timeout` +[module](#module) > [tests](#module.tests[]) > timeout + +Maximum duration (in seconds) of the test run. + +| Type | Required | +| ---- | -------- | +| `number` | No +### `module.tests[].args[]` +[module](#module) > [tests](#module.tests[]) > args + +The arguments used to run the test inside the container. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +module: + ... + tests: + - args: + - npm + - test +``` +### `module.tests[].env` +[module](#module) > [tests](#module.tests[]) > env + +Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with `GARDEN`) and values must be primitives. + +| Type | Required | +| ---- | -------- | +| `object` | No +### `module.tasks[]` +[module](#module) > tasks + +A list of tasks that can be run from this container module. These can be used as dependencies for services (executed before the service is deployed) or for other tasks. + +| Type | Required | +| ---- | -------- | +| `array[object]` | No +### `module.tasks[].name` +[module](#module) > [tasks](#module.tasks[]) > name + +The name of the task. + +| Type | Required | +| ---- | -------- | +| `string` | Yes +### `module.tasks[].description` +[module](#module) > [tasks](#module.tasks[]) > description + +A description of the task. + +| Type | Required | +| ---- | -------- | +| `string` | No +### `module.tasks[].dependencies[]` +[module](#module) > [tasks](#module.tasks[]) > dependencies + +The names of any tasks that must be executed, and the names of any services that must be running, before this task is executed. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No +### `module.tasks[].timeout` +[module](#module) > [tasks](#module.tasks[]) > timeout + +Maximum duration (in seconds) of the task's execution. + +| Type | Required | +| ---- | -------- | +| `number` | No +### `module.tasks[].args[]` +[module](#module) > [tasks](#module.tasks[]) > args + +The arguments used to run the task inside the container. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +module: + ... + tasks: + - args: + - rake + - 'db:migrate' +``` +### `module.jarPath` +[module](#module) > jarPath + +The path to the packaged JAR artifact, relative to the module directory. + +| Type | Required | +| ---- | -------- | +| `string` | Yes + +Example: +```yaml +module: + ... + jarPath: "target/my-module.jar" +``` +### `module.jdkVersion` +[module](#module) > jdkVersion + +The Java version to run + +| Type | Required | +| ---- | -------- | +| `number` | No + + +## Complete YAML schema +```yaml +module: + build: + dependencies: + - name: + copy: + - source: + target: '' + targetImage: + buildArgs: {} + image: + hotReload: + sync: + - source: . + target: + dockerfile: + services: + - name: + dependencies: [] + annotations: {} + args: + daemon: false + ingresses: + - annotations: {} + hostname: + path: / + port: + env: {} + healthCheck: + httpGet: + path: + port: + scheme: HTTP + command: + tcpPort: + hotReloadArgs: + ports: + - name: + protocol: TCP + containerPort: + servicePort: + hostPort: + nodePort: + volumes: + - name: + containerPath: + hostPath: + tests: + - name: + dependencies: [] + timeout: null + args: + env: {} + tasks: + - name: + description: + dependencies: [] + timeout: null + args: + jarPath: + jdkVersion: 8 +``` diff --git a/docs/reference/providers/maven-container.md b/docs/reference/providers/maven-container.md new file mode 100644 index 0000000000..e221ef6faa --- /dev/null +++ b/docs/reference/providers/maven-container.md @@ -0,0 +1,57 @@ +# `maven-container` reference + +Below is the schema reference for the `maven-container` provider. For an introduction to configuring a Garden project with providers, please look at our [configuration guide](../../using-garden/configuration-files.md). + +The reference is divided into two sections. The [first section](#configuration-keys) lists and describes the available schema keys. The [second section](#complete-yaml-schema) contains the complete YAML schema. + +## Configuration keys + +### `project` + + + +| Type | Required | +| ---- | -------- | +| `object` | No +### `project.environments[]` +[project](#project) > environments + + + +| Type | Required | +| ---- | -------- | +| `array[object]` | No +### `project.environments[].providers[]` +[project](#project) > [environments](#project.environments[]) > providers + + + +| Type | Required | +| ---- | -------- | +| `array[object]` | No +### `project.environments[].providers[].name` +[project](#project) > [environments](#project.environments[]) > [providers](#project.environments[].providers[]) > name + +The name of the provider plugin to use. + +| Type | Required | +| ---- | -------- | +| `string` | Yes + +Example: +```yaml +project: + ... + environments: + - providers: + - name: "local-kubernetes" +``` + + +## Complete YAML schema +```yaml +project: + environments: + - providers: + - name: +``` diff --git a/garden-service/package-lock.json b/garden-service/package-lock.json index 8c5f4e9065..3668cf9549 100644 --- a/garden-service/package-lock.json +++ b/garden-service/package-lock.json @@ -3801,7 +3801,8 @@ "ansi-regex": { "version": "2.1.1", "resolved": false, - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true }, "aproba": { "version": "1.2.0", @@ -3822,12 +3823,14 @@ "balanced-match": { "version": "1.0.0", "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3842,17 +3845,20 @@ "code-point-at": { "version": "1.1.0", "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3969,7 +3975,8 @@ "inherits": { "version": "2.0.3", "resolved": false, - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "optional": true }, "ini": { "version": "1.3.5", @@ -3981,6 +3988,7 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3995,6 +4003,7 @@ "version": "3.0.4", "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4002,12 +4011,14 @@ "minimist": { "version": "0.0.8", "resolved": false, - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "optional": true }, "minipass": { "version": "2.2.4", "resolved": false, "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4026,6 +4037,7 @@ "version": "0.5.1", "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4106,7 +4118,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4118,6 +4131,7 @@ "version": "1.4.0", "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, "requires": { "wrappy": "1" } @@ -4203,7 +4217,8 @@ "safe-buffer": { "version": "5.1.1", "resolved": false, - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -4239,6 +4254,7 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4258,6 +4274,7 @@ "version": "3.0.1", "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4301,12 +4318,14 @@ "wrappy": { "version": "1.0.2", "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true }, "yallist": { "version": "3.0.2", "resolved": false, - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "optional": true } } }, @@ -7487,6 +7506,7 @@ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -7856,7 +7876,8 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "is-builtin-module": { "version": "1.0.0", @@ -7952,6 +7973,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -8004,7 +8026,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true + "dev": true, + "optional": true }, "lru-cache": { "version": "4.1.3", @@ -8307,7 +8330,8 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "dev": true, + "optional": true }, "require-directory": { "version": "2.1.1", @@ -9918,8 +9942,7 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "secure-keys": { "version": "1.0.0", @@ -12123,6 +12146,14 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", diff --git a/garden-service/package.json b/garden-service/package.json index ef36e78526..8d123a9d7f 100644 --- a/garden-service/package.json +++ b/garden-service/package.json @@ -88,7 +88,8 @@ "unzipper": "^0.9.6", "uuid": "^3.3.2", "winston": "^3.1.0", - "wrap-ansi": "^4.0.0" + "wrap-ansi": "^4.0.0", + "xml-js": "^1.6.11" }, "devDependencies": { "@commitlint/cli": "^7.2.1", diff --git a/garden-service/src/config/common.ts b/garden-service/src/config/common.ts index 337df4e539..7d08363580 100644 --- a/garden-service/src/config/common.ts +++ b/garden-service/src/config/common.ts @@ -149,7 +149,7 @@ export function validateWithPath( validateOpts["ErrorClass"] = ErrorClass } - return validate(config, schema, validateOpts) + return validate(config, schema, validateOpts) } export function validate( diff --git a/garden-service/src/docs/config.ts b/garden-service/src/docs/config.ts index 3b5cc02dd6..756a70788d 100644 --- a/garden-service/src/docs/config.ts +++ b/garden-service/src/docs/config.ts @@ -30,6 +30,7 @@ import { configSchema as openfaasConfigSchema } from "../plugins/openfaas/openfa import { openfaasModuleSpecSchema } from "../plugins/openfaas/openfaas" import { helmModuleSpecSchema } from "../plugins/kubernetes/helm/config" import { joiArray } from "../config/common" +import { mavenContainerModuleSpecSchema, mavenContainerConfigSchema } from "../plugins/maven-container/maven-container" const baseProjectSchema = Joi.object().keys({ project: projectSchema, @@ -55,12 +56,14 @@ const moduleTypes = [ { name: "container", schema: populateModuleSchema(containerModuleSpecSchema) }, { name: "openfaas", schema: populateModuleSchema(openfaasModuleSpecSchema) }, { name: "helm", schema: populateModuleSchema(helmModuleSpecSchema) }, + { name: "maven-container", schema: populateModuleSchema(mavenContainerModuleSpecSchema) }, ] const providers = [ { name: "local-kubernetes", schema: populateProviderSchema(localK8sConfigSchema) }, { name: "kubernetes", schema: populateProviderSchema(k8sConfigSchema) }, { name: "openfaas", schema: populateProviderSchema(openfaasConfigSchema) }, + { name: "maven-container", schema: populateProviderSchema(mavenContainerConfigSchema) }, ] interface RenderOpts { diff --git a/garden-service/src/plugins/container/build.ts b/garden-service/src/plugins/container/build.ts new file mode 100644 index 0000000000..e3c7cbd1df --- /dev/null +++ b/garden-service/src/plugins/container/build.ts @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 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 { BuildModuleParams, GetBuildStatusParams } from "../../types/plugin/params" +import { containerHelpers } from "./helpers" +import { ContainerModule } from "./config" + +export async function getContainerBuildStatus({ module, log }: GetBuildStatusParams) { + const identifier = await containerHelpers.imageExistsLocally(module) + + if (identifier) { + log.debug({ + section: module.name, + msg: `Image ${identifier} already exists`, + symbol: "info", + }) + } + + return { ready: !!identifier } +} + +export async function buildContainerModule({ module, log }: BuildModuleParams) { + const buildPath = module.buildPath + const image = module.spec.image + + if (!!image && !(await containerHelpers.hasDockerfile(module))) { + if (await containerHelpers.imageExistsLocally(module)) { + return { fresh: false } + } + log.setState(`Pulling image ${image}...`) + await containerHelpers.pullImage(module) + return { fetched: true } + } + + const identifier = await containerHelpers.getLocalImageId(module) + + // build doesn't exist, so we create it + log.setState(`Building ${identifier}...`) + + const cmdOpts = ["build", "-t", identifier] + + for (const [key, value] of Object.entries(module.spec.buildArgs)) { + cmdOpts.push("--build-arg", `${key}=${value}`) + } + + if (module.spec.build.targetImage) { + cmdOpts.push("--target", module.spec.build.targetImage) + } + + if (module.spec.dockerfile) { + cmdOpts.push("--file", containerHelpers.getDockerfilePathFromModule(module)) + } + + // TODO: log error if it occurs + // TODO: stream output to log if at debug log level + await containerHelpers.dockerCli(module, [...cmdOpts, buildPath]) + + return { fresh: true, details: { identifier } } +} diff --git a/garden-service/src/plugins/container/container.ts b/garden-service/src/plugins/container/container.ts index 0039c51467..5b06835033 100644 --- a/garden-service/src/plugins/container/container.ts +++ b/garden-service/src/plugins/container/container.ts @@ -12,16 +12,11 @@ import { validateWithPath } from "../../config/common" import { pathExists } from "fs-extra" import { ConfigurationError } from "../../exceptions" import { GardenPlugin } from "../../types/plugin/plugin" -import { - BuildModuleParams, - GetBuildStatusParams, - ConfigureModuleParams, - HotReloadServiceParams, - PublishModuleParams, -} from "../../types/plugin/params" +import { ConfigureModuleParams, HotReloadServiceParams, PublishModuleParams } from "../../types/plugin/params" import { keyBy } from "lodash" import { containerHelpers } from "./helpers" import { ContainerModule, containerModuleSpecSchema } from "./config" +import { buildContainerModule, getContainerBuildStatus } from "./build" export async function configureContainerModule({ ctx, moduleConfig }: ConfigureModuleParams) { moduleConfig.spec = validateWithPath({ @@ -149,63 +144,8 @@ export const gardenPlugin = (): GardenPlugin => ({ moduleActions: { container: { configure: configureContainerModule, - - async getBuildStatus({ module, log }: GetBuildStatusParams) { - const identifier = await containerHelpers.imageExistsLocally(module) - - if (identifier) { - log.debug({ - section: module.name, - msg: `Image ${identifier} already exists`, - symbol: "info", - }) - } - - return { ready: !!identifier } - }, - - async build({ module, log }: BuildModuleParams) { - const buildPath = module.buildPath - const image = module.spec.image - - if (!!image && !(await containerHelpers.hasDockerfile(module))) { - if (await containerHelpers.imageExistsLocally(module)) { - return { fresh: false } - } - log.setState(`Pulling image ${image}...`) - await containerHelpers.pullImage(module) - return { fetched: true } - } - - const identifier = await containerHelpers.getLocalImageId(module) - - // build doesn't exist, so we create it - log.setState(`Building ${identifier}...`) - - const cmdOpts = ["build", "-t", identifier] - const buildArgs = Object.entries(module.spec.buildArgs).map(([key, value]) => { - // TODO: may need to escape this - return `--build-arg ${key}=${value}` - }).join(" ") - - if (buildArgs) { - cmdOpts.push(buildArgs) - } - - if (module.spec.build.targetImage) { - cmdOpts.push("--target", module.spec.build.targetImage) - } - - if (module.spec.dockerfile) { - cmdOpts.push("--file", containerHelpers.getDockerfilePathFromModule(module)) - } - - // TODO: log error if it occurs - // TODO: stream output to log if at debug log level - await containerHelpers.dockerCli(module, [...cmdOpts, buildPath]) - - return { fresh: true, details: { identifier } } - }, + getBuildStatus: getContainerBuildStatus, + build: buildContainerModule, async publishModule({ module, log }: PublishModuleParams) { if (!(await containerHelpers.hasDockerfile(module))) { diff --git a/garden-service/src/plugins/maven-container/maven-container.ts b/garden-service/src/plugins/maven-container/maven-container.ts new file mode 100644 index 0000000000..9859d9fcf9 --- /dev/null +++ b/garden-service/src/plugins/maven-container/maven-container.ts @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2018 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 * as execa from "execa" +import * as Joi from "joi" +import { omit, pick, get } from "lodash" +import { copy, pathExists, readFile } from "fs-extra" +import { GardenPlugin } from "../../types/plugin/plugin" +import { + ContainerModuleSpec, + ContainerServiceSpec, + ContainerTestSpec, + ContainerModuleConfig, +} from "../container/config" +import { validateWithPath } from "../../config/common" +import { BuildModuleParams, ConfigureModuleParams, GetBuildStatusParams } from "../../types/plugin/params" +import { Module } from "../../types/module" +import { configureContainerModule, gardenPlugin as containerPlugin } from "../container/container" +import { buildContainerModule, getContainerBuildStatus } from "../container/build" +import { resolve } from "path" +import { RuntimeError, ConfigurationError } from "../../exceptions" +import { containerHelpers } from "../container/helpers" +import { STATIC_DIR } from "../../constants" +import { xml2json } from "xml-js" +import { containerModuleSpecSchema } from "../container/config" +import { providerConfigBaseSchema } from "../../config/project" + +const defaultDockerfilePath = resolve(STATIC_DIR, "maven-container", "Dockerfile") + +interface MavenContainerModuleSpec extends ContainerModuleSpec { + jarPath: string + jdkVersion: number + mvnArgs: string[] +} + +// type MavenContainerModuleConfig = ModuleConfig + +interface MavenContainerModule< + M extends ContainerModuleSpec = MavenContainerModuleSpec, + S extends ContainerServiceSpec = ContainerServiceSpec, + T extends ContainerTestSpec = ContainerTestSpec + > extends Module { } + +const mavenKeys = { + jarPath: Joi.string() + .required() + .description("The path to the packaged JAR artifact, relative to the module directory.") + .example("target/my-module.jar"), + jdkVersion: Joi.number() + .integer() + .min(8) + .default(8) + .description("The Java version to run"), +} + +const mavenFieldsSchema = Joi.object() + .keys(mavenKeys) + +export const mavenContainerModuleSpecSchema = containerModuleSpecSchema.keys(mavenKeys) + +export const mavenContainerConfigSchema = providerConfigBaseSchema + +export const gardenPlugin = (): GardenPlugin => { + const basePlugin = containerPlugin() + + return { + ...basePlugin, + moduleActions: { + "maven-container": { + ...basePlugin.moduleActions!.container, + configure, + getBuildStatus, + build, + }, + }, + } +} + +async function configure(params: ConfigureModuleParams) { + const { ctx, moduleConfig } = params + + const mavenFields = validateWithPath({ + config: pick(params.moduleConfig.spec, Object.keys(mavenKeys)), + schema: mavenFieldsSchema, + name: moduleConfig.name, + path: moduleConfig.path, + projectRoot: ctx.projectRoot, + }) + + let containerConfig: ContainerModuleConfig = { ...moduleConfig } + containerConfig.spec = omit(moduleConfig.spec, Object.keys(mavenKeys)) + + containerConfig.spec.buildArgs = { + JAR_PATH: mavenFields.jarPath!, + JDK_VERSION: mavenFields.jdkVersion!.toString(), + } + + const configured = await configureContainerModule({ ...params, moduleConfig: containerConfig }) + + return { + ...configured, + spec: { + ...configured.spec, + ...mavenFields, + }, + } +} + +async function getBuildStatus(params: GetBuildStatusParams) { + const { module, log } = params + + // Copy the default Dockerfile to the build directory, if the module doesn't provide one + // Note: Doing this here so that the build status check works as expected. + if (!(await containerHelpers.hasDockerfile(module))) { + log.debug(`Using default Dockerfile`) + await copy(defaultDockerfilePath, resolve(module.buildPath, "Dockerfile")) + } + + return getContainerBuildStatus(params) +} + +async function build(params: BuildModuleParams) { + // Run the maven build + const { ctx, module, log } = params + let { jarPath } = module.spec + + const pom = await loadPom(module.path) + const artifactId = get(pom, ["project", "artifactId", "_text"]) + + if (!artifactId) { + throw new ConfigurationError(`Could not read artifact ID from pom.xml in ${module.path}`, { path: module.path }) + } + + const mvnArgs = [ + "package", + "--batch-mode", + "-DskipTests", + "--projects", ":" + artifactId, + "--also-make", + ] + const mvnCmdStr = "mvn " + mvnArgs.join(" ") + + log.setState(`Creating jar artifact...`) + await mvn(ctx.projectRoot, mvnArgs) + + // Copy the artifact to the module build directory + const resolvedJarPath = resolve(module.path, jarPath) + + if (!(await pathExists(resolvedJarPath))) { + throw new RuntimeError( + `Could not find artifact at ${resolvedJarPath} after running '${mvnCmdStr}'`, + { jarPath, mvnArgs }, + ) + } + + await copy(resolvedJarPath, resolve(module.buildPath, "app.jar")) + + // Build the container + return buildContainerModule(params) +} + +async function mvn(cwd: string, args: string[]) { + return execa.stdout("mvn", args, { cwd, maxBuffer: 10 * 1024 * 1024 }) +} + +async function loadPom(dir: string) { + try { + const pomPath = resolve(dir, "pom.xml") + const pomData = await readFile(pomPath) + return JSON.parse(xml2json(pomData.toString(), { compact: true })) + } catch (err) { + throw new ConfigurationError(`Could not load pom.xml from directory ${dir}`, { dir }) + } +} + +// TODO: see if we could make this perform adequately, or perhaps use this only on Linux... +// +// async function mvn(jdkVersion: number, projectRoot: string, args: string[]) { +// const mvnImage = `maven:3.6.0-jdk-${jdkVersion}-slim` +// const m2Path = resolve(homedir(), ".m2") + +// const dockerArgs = [ +// "run", +// "--rm", +// "--interactive", +// "--volume", `${m2Path}:/root/.m2`, +// "--volume", `${projectRoot}:/project`, +// "--workdir", "/project", +// mvnImage, +// "--", +// ...args, +// ] + +// console.log(dockerArgs.join(" ")) + +// return execa.stdout("docker", dockerArgs, { maxBuffer: 10 * 1024 * 1024 }) +// } diff --git a/garden-service/src/plugins/plugins.ts b/garden-service/src/plugins/plugins.ts index 0708f3647e..ba1b9d4bde 100644 --- a/garden-service/src/plugins/plugins.ts +++ b/garden-service/src/plugins/plugins.ts @@ -17,6 +17,7 @@ const localKubernetes = require("./kubernetes/local/local") const npmPackage = require("./npm-package") const gae = require("./google/google-app-engine") const openfaas = require("./openfaas/openfaas") +const mavenContainer = require("./maven-container/maven-container") // These plugins are always registered export const builtinPlugins = mapValues({ @@ -29,6 +30,7 @@ export const builtinPlugins = mapValues({ "npm-package": npmPackage, "google-app-engine": gae, openfaas, + "maven-container": mavenContainer, }, (m => m.gardenPlugin)) // These plugins are always loaded diff --git a/garden-service/static/maven-container/Dockerfile b/garden-service/static/maven-container/Dockerfile new file mode 100644 index 0000000000..5d682e705e --- /dev/null +++ b/garden-service/static/maven-container/Dockerfile @@ -0,0 +1,14 @@ +ARG JDK_VERSION=8 +FROM openjdk:${JDK_VERSION}-jdk-alpine + +RUN addgroup -g 2000 app && \ + adduser -D -u 2000 -G app -h /var/lib/app -s /bin/sh app +USER 2000:2000 + +EXPOSE 8080 + +ENV JVM_OPTS "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1" + +ENTRYPOINT ["java", "-server", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/usr/local/bin/app.jar"] + +COPY app.jar /usr/local/bin/app.jar