diff --git a/docs/module-types/conftest.md b/docs/module-types/conftest.md index 45aefd3945..2ef14cc73f 100644 --- a/docs/module-types/conftest.md +++ b/docs/module-types/conftest.md @@ -38,45 +38,50 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -95,8 +100,7 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' @@ -112,8 +116,7 @@ policyPath: # The policy namespace in which to find _deny_ and _warn_ rules. namespace: main -# A list of files to test with the given policy. Must be POSIX-style paths, and may include -# wildcards. +# A list of files to test with the given policy. Must be POSIX-style paths, and may include wildcards. files: ``` @@ -167,6 +170,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this diff --git a/docs/module-types/container.md b/docs/module-types/container.md index 0d17803b95..abac660fd7 100644 --- a/docs/module-types/container.md +++ b/docs/module-types/container.md @@ -39,18 +39,30 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. # @@ -62,29 +74,22 @@ description: # specifies a remote image, Garden automatically sets `include` to `[]`. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -103,14 +108,12 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' # 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). + # https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). targetImage: # Maximum time in seconds to wait for build to finish. @@ -119,29 +122,26 @@ build: # Specify build arguments to use when building the container image. buildArgs: {} -# Specify extra flags to use when building the container image. Note that arguments may not be -# portable across implementations. +# Specify extra flags to use when building the container image. Note that arguments may not be portable across +# implementations. extraFlags: -# 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. +# 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. image: -# 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`. +# 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`. hotReload: - # Specify one or more source files or directories to automatically sync into the running - # container. + # Specify one or more source files or directories to automatically sync into the running container. sync: - # 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. + # 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. - source: . - # POSIX-style absolute path to sync the directory to inside the container. The root path - # (i.e. "/") is not allowed. + # POSIX-style absolute path to sync the directory to inside the container. The root path (i.e. "/") is not + # allowed. target: # An optional command to run inside the container after syncing. @@ -152,21 +152,33 @@ dockerfile: # The list of services to deploy from this container module. services: - # 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. + # 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. - name: - # 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. + # 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. dependencies: [] + # Set this to `true` to disable the service. You can use this with conditional template strings to + # enable/disable services based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This can be handy when you only need certain services for + # specific environments, e.g. only for development. + # + # Disabling a service means that it will not be deployed, and will also be ignored if it is declared as a + # runtime dependency for another service, test or task. + # + # Note however that template strings referencing the service's outputs (i.e. runtime outputs) will fail to + # resolve when the service is disabled, so you need to make sure to provide alternate values for those if + # you're using them, using conditional expressions. + disabled: false # Annotations to attach to the service (Note: May not be applicable to all providers). annotations: {} # The command/entrypoint to run the container with when starting the service. command: # The arguments to run the container with when starting the service. args: - # Whether to run the service as a daemon (to ensure exactly one instance runs per node). May - # not be supported by all providers. + # Whether to run the service as a daemon (to ensure exactly one instance runs per node). May not be supported by + # all providers. daemon: false # List of ingress endpoints that the service exposes. ingresses: @@ -175,8 +187,7 @@ services: # 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. + # Note that if you're developing locally you may need to add this hostname to your hosts file. hostname: # The link URL for the ingress to show in the console and on the dashboard. # Also used when calling the service with the `call` command. @@ -190,8 +201,8 @@ services: path: / # The name of the container port where the specified paths should be routed. port: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives or references to secrets. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives or references to secrets. env: {} # Specify how the service's health should be checked after deploying. healthCheck: @@ -208,14 +219,13 @@ services: # Set this to check the service's health by running a command in its container. command: - # Set this to check the service's health by checking if this TCP port is accepting - # connections. + # Set this to check the service's health by checking if this TCP port is accepting connections. tcpPort: - # If this module uses the `hotReload` field, the container will be run with this - # command/entrypoint when the service is deployed with hot reloading enabled. + # If this module uses the `hotReload` field, the container will be run with this command/entrypoint when the + # service is deployed with hot reloading enabled. hotReloadCommand: - # If this module uses the `hotReload` field, the container will be run with these arguments - # when the service is deployed with hot reloading enabled. + # If this module uses the `hotReload` field, the container will be run with these arguments when the service is + # deployed with hot reloading enabled. hotReloadArgs: # Specify resource limits for the service. limits: @@ -226,39 +236,37 @@ services: memory: 1024 # List of ports that the service container exposes. ports: - # The name of the port (used when referencing the port elsewhere in the service - # configuration). + # The name of the port (used when referencing the port elsewhere in the service configuration). - name: # The protocol of the port. protocol: TCP - # The port exposed on the container by the running process. This will also be the default - # value for `servicePort`. - # This is the port you would expose in your Dockerfile and that your process listens on. - # This is commonly a non-priviledged port like 8080 for security reasons. + # The port exposed on the container by the running process. This will also be the default value for + # `servicePort`. + # This is the port you would expose in your Dockerfile and that your process listens on. This is commonly a + # non-priviledged port like 8080 for security reasons. # The service port maps to the container port: # `servicePort:80 -> containerPort:8080 -> process:8080` containerPort: # The port exposed on the service. Defaults to `containerPort` if not specified. - # This is the port you use when calling a service from another service within the cluster. - # For example, if your service name is my-service and the service port is 8090, you would - # call it with: http://my-service:8090/some-endpoint. - # It is common to use port 80, the default port number, so that you can call the service - # directly with http://my-service/some-endpoint. + # This is the port you use when calling a service from another service within the cluster. For example, if + # your service name is my-service and the service port is 8090, you would call it with: + # http://my-service:8090/some-endpoint. + # It is common to use port 80, the default port number, so that you can call the service directly with + # http://my-service/some-endpoint. # The service port maps to the container port: # `servicePort:80 -> containerPort:8080 -> process:8080` servicePort: hostPort: - # Set this to expose the service on the specified port on the host node (may not be - # supported by all providers). Set to `true` to have the cluster pick a port - # automatically, which is most often advisable if the cluster is shared by multiple users. - # This allows you to call the service from the outside by the node's IP address and the - # port number set in this field. + # Set this to expose the service on the specified port on the host node (may not be supported by all + # providers). Set to `true` to have the cluster pick a port automatically, which is most often advisable if + # the cluster is shared by multiple users. + # This allows you to call the service from the outside by the node's IP address and the port number set in + # this field. nodePort: - # The number of instances of the service to deploy. Defaults to 3 for environments configured - # with `production: true`, otherwise 1. - # Note: This setting may be overridden or ignored in some cases. For example, when running - # with `daemon: true`, with hot-reloading enabled, or if the provider doesn't support multiple - # replicas. + # The number of instances of the service to deploy. Defaults to 3 for environments configured with `production: + # true`, otherwise 1. + # Note: This setting may be overridden or ignored in some cases. For example, when running with `daemon: true`, + # with hot-reloading enabled, or if the provider doesn't support multiple replicas. replicas: # List of volumes that should be mounted when deploying the container. volumes: @@ -266,12 +274,10 @@ services: - name: # The path where the volume should be mounted in the container. containerPath: - # _NOTE: Usage of hostPath is generally discouraged, since it doesn't work reliably across - # different platforms + # _NOTE: Usage of hostPath is generally discouraged, since it doesn't work reliably across different platforms # and providers. Some providers may not support it at all._ # - # A local path or path on the node that's running the container, to mount in the - # container, relative to the + # A local path or path on the node that's running the container, to mount in the container, relative to the # module source path (or absolute). hostPath: @@ -279,55 +285,70 @@ services: tests: # The name of the test. - name: - # The names of any services that must be running, and the names of any tasks that must be - # executed, before the test is run. + # The names of any services that must be running, and the names of any tasks that must be executed, before the + # test is run. dependencies: [] + # Set this to `true` to disable the test. You can use this with conditional template strings to + # enable/disable tests based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in + # specific environments, e.g. only during CI. + disabled: false # Maximum duration (in seconds) of the test run. timeout: null # The arguments used to run the test inside the container. args: # Specify artifacts to copy out of the container after the run. - # Note: Depending on the provider, this may require the container image to include `sh` `tar`, - # in order to enable the file transfer. + # Note: Depending on the provider, this may require the container image to include `sh` `tar`, in order to enable + # the file transfer. artifacts: # A POSIX-style path or glob to copy. Must be an absolute path. May contain wildcards. - source: - # A POSIX-style path to copy the artifacts to, relative to the project artifacts - # directory. + # A POSIX-style path to copy the artifacts to, relative to the project artifacts directory. target: . # The command/entrypoint used to run the test inside the container. command: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives or references to secrets. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives or references to secrets. env: {} -# 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. +# 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. tasks: # The name of the task. - name: # A description of the task. description: - # The names of any tasks that must be executed, and the names of any services that must be - # running, before this task is executed. + # The names of any tasks that must be executed, and the names of any services that must be running, before this + # task is executed. dependencies: [] + # Set this to `true` to disable the task. You can use this with conditional template strings to + # enable/disable tasks based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This can be handy when you only want certain tasks to run in + # specific environments, e.g. only for development. + # + # Disabling a task means that it will not be run, and will also be ignored if it is declared as a + # runtime dependency for another service, test or task. + # + # Note however that template strings referencing the task's outputs (i.e. runtime outputs) will fail to + # resolve when the task is disabled, so you need to make sure to provide alternate values for those if + # you're using them, using conditional expressions. + disabled: false # Maximum duration (in seconds) of the task's execution. timeout: null # The arguments used to run the task inside the container. args: # Specify artifacts to copy out of the container after the run. - # Note: Depending on the provider, this may require the container image to include `sh` `tar`, - # in order to enable the file transfer. + # Note: Depending on the provider, this may require the container image to include `sh` `tar`, in order to enable + # the file transfer. artifacts: # A POSIX-style path or glob to copy. Must be an absolute path. May contain wildcards. - source: - # A POSIX-style path to copy the artifacts to, relative to the project artifacts - # directory. + # A POSIX-style path to copy the artifacts to, relative to the project artifacts directory. target: . # The command/entrypoint used to run the task inside the container. command: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives or references to secrets. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives or references to secrets. env: {} ``` @@ -381,6 +402,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this @@ -686,6 +728,26 @@ The names of any services that this service depends on at runtime, and the names | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `services[].disabled` + +[services](#services) > disabled + +Set this to `true` to disable the service. You can use this with conditional template strings to +enable/disable services based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This can be handy when you only need certain services for +specific environments, e.g. only for development. + +Disabling a service means that it will not be deployed, and will also be ignored if it is declared as a +runtime dependency for another service, test or task. + +Note however that template strings referencing the service's outputs (i.e. runtime outputs) will fail to +resolve when the service is disabled, so you need to make sure to provide alternate values for those if +you're using them, using conditional expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `services[].annotations` [services](#services) > annotations @@ -1184,6 +1246,19 @@ The names of any services that must be running, and the names of any tasks that | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `tests[].disabled` + +[tests](#tests) > disabled + +Set this to `true` to disable the test. You can use this with conditional template strings to +enable/disable tests based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in +specific environments, e.g. only during CI. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `tests[].timeout` [tests](#tests) > timeout @@ -1350,6 +1425,26 @@ The names of any tasks that must be executed, and the names of any services that | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `tasks[].disabled` + +[tasks](#tasks) > disabled + +Set this to `true` to disable the task. You can use this with conditional template strings to +enable/disable tasks based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This can be handy when you only want certain tasks to run in +specific environments, e.g. only for development. + +Disabling a task means that it will not be run, and will also be ignored if it is declared as a +runtime dependency for another service, test or task. + +Note however that template strings referencing the task's outputs (i.e. runtime outputs) will fail to +resolve when the task is disabled, so you need to make sure to provide alternate values for those if +you're using them, using conditional expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `tasks[].timeout` [tasks](#tasks) > timeout diff --git a/docs/module-types/exec.md b/docs/module-types/exec.md index 3cd16d2740..a151718f0d 100644 --- a/docs/module-types/exec.md +++ b/docs/module-types/exec.md @@ -42,45 +42,50 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -99,30 +104,25 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' # The command to run to perform the build. # - # By default, the command is run inside the Garden build directory (under - # .garden/build/). - # If the top level `local` directive is set to `true`, the command runs in the module source - # directory instead. + # By default, the command is run inside the Garden build directory (under .garden/build/). + # If the top level `local` directive is set to `true`, the command runs in the module source directory instead. command: [] -# If set to true, Garden will run the build command, tests, and tasks in the module source -# directory, +# If set to true, Garden will run the build command, tests, and tasks in the module source directory, # instead of in the Garden build directory (under .garden/build/). # -# Garden will therefore not stage the build for local exec modules. This means that -# include/exclude filters +# Garden will therefore not stage the build for local exec modules. This means that include/exclude filters # and ignore files are not applied to local exec modules. local: false -# Key/value map of environment variables. Keys must be valid POSIX environment variable names -# (must not start with `GARDEN`) and values must be primitives. +# Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with +# `GARDEN`) and values must be primitives. env: {} # A list of tasks that can be run in this module. @@ -131,9 +131,21 @@ tasks: - name: # A description of the task. description: - # The names of any tasks that must be executed, and the names of any services that must be - # running, before this task is executed. + # The names of any tasks that must be executed, and the names of any services that must be running, before this + # task is executed. dependencies: [] + # Set this to `true` to disable the task. You can use this with conditional template strings to + # enable/disable tasks based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This can be handy when you only want certain tasks to run in + # specific environments, e.g. only for development. + # + # Disabling a task means that it will not be run, and will also be ignored if it is declared as a + # runtime dependency for another service, test or task. + # + # Note however that template strings referencing the task's outputs (i.e. runtime outputs) will fail to + # resolve when the task is disabled, so you need to make sure to provide alternate values for those if + # you're using them, using conditional expressions. + disabled: false # Maximum duration (in seconds) of the task's execution. timeout: null # A list of artifacts to copy after the task run. @@ -144,33 +156,34 @@ tasks: target: . # The command to run. # - # By default, the command is run inside the Garden build directory (under - # .garden/build/). - # If the top level `local` directive is set to `true`, the command runs in the module source - # directory instead. + # By default, the command is run inside the Garden build directory (under .garden/build/). + # If the top level `local` directive is set to `true`, the command runs in the module source directory instead. command: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives. env: {} # A list of tests to run in the module. tests: # The name of the test. - name: - # The names of any services that must be running, and the names of any tasks that must be - # executed, before the test is run. + # The names of any services that must be running, and the names of any tasks that must be executed, before the + # test is run. dependencies: [] + # Set this to `true` to disable the test. You can use this with conditional template strings to + # enable/disable tests based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in + # specific environments, e.g. only during CI. + disabled: false # Maximum duration (in seconds) of the test run. timeout: null # The command to run to test the module. # - # By default, the command is run inside the Garden build directory (under - # .garden/build/). - # If the top level `local` directive is set to `true`, the command runs in the module source - # directory instead. + # By default, the command is run inside the Garden build directory (under .garden/build/). + # If the top level `local` directive is set to `true`, the command runs in the module source directory instead. command: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives. env: {} # A list of artifacts to copy after the test run. artifacts: @@ -230,6 +243,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this @@ -455,6 +489,26 @@ The names of any tasks that must be executed, and the names of any services that | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `tasks[].disabled` + +[tasks](#tasks) > disabled + +Set this to `true` to disable the task. You can use this with conditional template strings to +enable/disable tasks based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This can be handy when you only want certain tasks to run in +specific environments, e.g. only for development. + +Disabling a task means that it will not be run, and will also be ignored if it is declared as a +runtime dependency for another service, test or task. + +Note however that template strings referencing the task's outputs (i.e. runtime outputs) will fail to +resolve when the task is disabled, so you need to make sure to provide alternate values for those if +you're using them, using conditional expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `tasks[].timeout` [tasks](#tasks) > timeout @@ -546,6 +600,19 @@ The names of any services that must be running, and the names of any tasks that | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `tests[].disabled` + +[tests](#tests) > disabled + +Set this to `true` to disable the test. You can use this with conditional template strings to +enable/disable tests based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in +specific environments, e.g. only during CI. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `tests[].timeout` [tests](#tests) > timeout diff --git a/docs/module-types/hadolint.md b/docs/module-types/hadolint.md index 86992248b3..e2ef58fadd 100644 --- a/docs/module-types/hadolint.md +++ b/docs/module-types/hadolint.md @@ -41,45 +41,50 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -98,8 +103,7 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' @@ -157,6 +161,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this diff --git a/docs/module-types/helm.md b/docs/module-types/helm.md index 5ef073e9ca..5531922f63 100644 --- a/docs/module-types/helm.md +++ b/docs/module-types/helm.md @@ -34,18 +34,30 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. # @@ -56,29 +68,22 @@ description: # automatically sets `ìnclude` to `[]`. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -97,26 +102,23 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' -# The name of another `helm` module to use as a base for this one. Use this to re-use a Helm chart -# across multiple services. For example, you might have an organization-wide base chart for -# certain types of services. -# If set, this module will by default inherit the following properties from the base module: -# `serviceResource`, `values` -# Each of those can be overridden in this module. They will be merged with a JSON Merge Patch (RFC -# 7396). +# The name of another `helm` module to use as a base for this one. Use this to re-use a Helm chart across multiple +# services. For example, you might have an organization-wide base chart for certain types of services. +# If set, this module will by default inherit the following properties from the base module: `serviceResource`, +# `values` +# Each of those can be overridden in this module. They will be merged with a JSON Merge Patch (RFC 7396). base: -# A valid Helm chart name or URI (same as you'd input to `helm install`). Required if the module -# doesn't contain the Helm chart itself. +# A valid Helm chart name or URI (same as you'd input to `helm install`). Required if the module doesn't contain the +# Helm chart itself. chart: -# The path, relative to the module path, to the chart sources (i.e. where the Chart.yaml file is, -# if any). Not used when `base` is specified. +# The path, relative to the module path, to the chart sources (i.e. where the Chart.yaml file is, if any). Not used +# when `base` is specified. chartPath: . # List of names of services that should be deployed before this chart. @@ -128,42 +130,39 @@ releaseName: # The repository URL to fetch the chart from. repo: -# The Deployment, DaemonSet or StatefulSet that Garden should regard as the _Garden service_ in -# this module (not to be confused with Kubernetes Service resources). Because a Helm chart can -# contain any number of Kubernetes resources, this needs to be specified for certain Garden -# features and commands to work, such as hot-reloading. -# We currently map a Helm chart to a single Garden service, because all the resources in a Helm -# chart are deployed at once. +# The Deployment, DaemonSet or StatefulSet that Garden should regard as the _Garden service_ in this module (not to be +# confused with Kubernetes Service resources). Because a Helm chart can contain any number of Kubernetes resources, +# this needs to be specified for certain Garden features and commands to work, such as hot-reloading. +# We currently map a Helm chart to a single Garden service, because all the resources in a Helm chart are deployed at +# once. serviceResource: # The type of Kubernetes resource to sync files to. kind: Deployment - # The name of the resource to sync to. If the chart contains a single resource of the specified - # Kind, this can be omitted. - # This can include a Helm template string, e.g. '{{ template "my-chart.fullname" . }}'. This - # allows you to easily match the dynamic names given by Helm. In most cases you should copy this - # directly from the template in question in order to match it. Note that you may need to add - # single quotes around the string for the YAML to be parsed correctly. + # The name of the resource to sync to. If the chart contains a single resource of the specified Kind, this can be + # omitted. + # This can include a Helm template string, e.g. '{{ template "my-chart.fullname" . }}'. This allows you to easily + # match the dynamic names given by Helm. In most cases you should copy this directly from the template in question + # in order to match it. Note that you may need to add single quotes around the string for the YAML to be parsed + # correctly. name: - # The name of a container in the target. Specify this if the target contains more than one - # container and the main container is not the first container in the spec. + # The name of a container in the target. Specify this if the target contains more than one container and the main + # container is not the first container in the spec. containerName: - # The Garden module that contains the sources for the container. This needs to be specified - # under `serviceResource` in order to enable hot-reloading for the chart, but is not necessary - # for tasks and tests. - # Must be a `container` module, and for hot-reloading to work you must specify the `hotReload` - # field on the container module. - # Note: If you specify a module here, you don't need to specify it additionally under - # `build.dependencies` + # The Garden module that contains the sources for the container. This needs to be specified under `serviceResource` + # in order to enable hot-reloading for the chart, but is not necessary for tasks and tests. + # Must be a `container` module, and for hot-reloading to work you must specify the `hotReload` field on the + # container module. + # Note: If you specify a module here, you don't need to specify it additionally under `build.dependencies` containerModule: # If specified, overrides the arguments for the main container when running in hot-reload mode. hotReloadArgs: -# Set this to true if the chart should only be built, but not deployed as a service. Use this, for -# example, if the chart should only be used as a base for other modules. +# Set this to true if the chart should only be built, but not deployed as a service. Use this, for example, if the +# chart should only be used as a base for other modules. skipDeploy: false # The task definitions for this module. @@ -172,136 +171,137 @@ tasks: - name: # A description of the task. description: - # The names of any tasks that must be executed, and the names of any services that must be - # running, before this task is executed. + # The names of any tasks that must be executed, and the names of any services that must be running, before this + # task is executed. dependencies: [] + # Set this to `true` to disable the task. You can use this with conditional template strings to + # enable/disable tasks based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This can be handy when you only want certain tasks to run in + # specific environments, e.g. only for development. + # + # Disabling a task means that it will not be run, and will also be ignored if it is declared as a + # runtime dependency for another service, test or task. + # + # Note however that template strings referencing the task's outputs (i.e. runtime outputs) will fail to + # resolve when the task is disabled, so you need to make sure to provide alternate values for those if + # you're using them, using conditional expressions. + disabled: false # Maximum duration (in seconds) of the task's execution. timeout: null - # The Deployment, DaemonSet or StatefulSet that Garden should use to execute this task. If not - # specified, the `serviceResource` configured on the module will be used. If neither is - # specified, an error will be thrown. + # The Deployment, DaemonSet or StatefulSet that Garden should use to execute this task. If not specified, the + # `serviceResource` configured on the module will be used. If neither is specified, an error will be thrown. resource: # The type of Kubernetes resource to sync files to. kind: Deployment - # The name of the resource to sync to. If the chart contains a single resource of the - # specified Kind, this can be omitted. - # This can include a Helm template string, e.g. '{{ template "my-chart.fullname" . }}'. This - # allows you to easily match the dynamic names given by Helm. In most cases you should copy - # this directly from the template in question in order to match it. Note that you may need - # to add single quotes around the string for the YAML to be parsed correctly. + # The name of the resource to sync to. If the chart contains a single resource of the specified Kind, this can + # be omitted. + # This can include a Helm template string, e.g. '{{ template "my-chart.fullname" . }}'. This allows you to + # easily match the dynamic names given by Helm. In most cases you should copy this directly from the template in + # question in order to match it. Note that you may need to add single quotes around the string for the YAML to + # be parsed correctly. name: - # The name of a container in the target. Specify this if the target contains more than one - # container and the main container is not the first container in the spec. + # The name of a container in the target. Specify this if the target contains more than one container and the + # main container is not the first container in the spec. containerName: - # The Garden module that contains the sources for the container. This needs to be specified - # under `serviceResource` in order to enable hot-reloading for the chart, but is not - # necessary for tasks and tests. - # Must be a `container` module, and for hot-reloading to work you must specify the - # `hotReload` field on the container module. - # Note: If you specify a module here, you don't need to specify it additionally under - # `build.dependencies` + # The Garden module that contains the sources for the container. This needs to be specified under + # `serviceResource` in order to enable hot-reloading for the chart, but is not necessary for tasks and tests. + # Must be a `container` module, and for hot-reloading to work you must specify the `hotReload` field on the + # container module. + # Note: If you specify a module here, you don't need to specify it additionally under `build.dependencies` containerModule: - # If specified, overrides the arguments for the main container when running in hot-reload - # mode. + # If specified, overrides the arguments for the main container when running in hot-reload mode. hotReloadArgs: # The command/entrypoint used to run the task inside the container. command: # The arguments to pass to the pod used for execution. args: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives or references to secrets. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives or references to secrets. env: {} # Specify artifacts to copy out of the container after the task is complete. artifacts: # A POSIX-style path or glob to copy. Must be an absolute path. May contain wildcards. - source: - # A POSIX-style path to copy the artifacts to, relative to the project artifacts - # directory. + # A POSIX-style path to copy the artifacts to, relative to the project artifacts directory. target: . # The test suite definitions for this module. tests: # The name of the test. - name: - # The names of any services that must be running, and the names of any tasks that must be - # executed, before the test is run. + # The names of any services that must be running, and the names of any tasks that must be executed, before the + # test is run. dependencies: [] + # Set this to `true` to disable the test. You can use this with conditional template strings to + # enable/disable tests based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in + # specific environments, e.g. only during CI. + disabled: false # Maximum duration (in seconds) of the test run. timeout: null - # The Deployment, DaemonSet or StatefulSet that Garden should use to execute this test suite. - # If not specified, the `serviceResource` configured on the module will be used. If neither is - # specified, an error will be thrown. + # The Deployment, DaemonSet or StatefulSet that Garden should use to execute this test suite. If not specified, + # the `serviceResource` configured on the module will be used. If neither is specified, an error will be thrown. resource: # The type of Kubernetes resource to sync files to. kind: Deployment - # The name of the resource to sync to. If the chart contains a single resource of the - # specified Kind, this can be omitted. - # This can include a Helm template string, e.g. '{{ template "my-chart.fullname" . }}'. This - # allows you to easily match the dynamic names given by Helm. In most cases you should copy - # this directly from the template in question in order to match it. Note that you may need - # to add single quotes around the string for the YAML to be parsed correctly. + # The name of the resource to sync to. If the chart contains a single resource of the specified Kind, this can + # be omitted. + # This can include a Helm template string, e.g. '{{ template "my-chart.fullname" . }}'. This allows you to + # easily match the dynamic names given by Helm. In most cases you should copy this directly from the template in + # question in order to match it. Note that you may need to add single quotes around the string for the YAML to + # be parsed correctly. name: - # The name of a container in the target. Specify this if the target contains more than one - # container and the main container is not the first container in the spec. + # The name of a container in the target. Specify this if the target contains more than one container and the + # main container is not the first container in the spec. containerName: - # The Garden module that contains the sources for the container. This needs to be specified - # under `serviceResource` in order to enable hot-reloading for the chart, but is not - # necessary for tasks and tests. - # Must be a `container` module, and for hot-reloading to work you must specify the - # `hotReload` field on the container module. - # Note: If you specify a module here, you don't need to specify it additionally under - # `build.dependencies` + # The Garden module that contains the sources for the container. This needs to be specified under + # `serviceResource` in order to enable hot-reloading for the chart, but is not necessary for tasks and tests. + # Must be a `container` module, and for hot-reloading to work you must specify the `hotReload` field on the + # container module. + # Note: If you specify a module here, you don't need to specify it additionally under `build.dependencies` containerModule: - # If specified, overrides the arguments for the main container when running in hot-reload - # mode. + # If specified, overrides the arguments for the main container when running in hot-reload mode. hotReloadArgs: # The command/entrypoint used to run the test inside the container. command: # The arguments to pass to the pod used for testing. args: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives or references to secrets. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives or references to secrets. env: {} # Specify artifacts to copy out of the container after the test is complete. artifacts: # A POSIX-style path or glob to copy. Must be an absolute path. May contain wildcards. - source: - # A POSIX-style path to copy the artifacts to, relative to the project artifacts - # directory. + # A POSIX-style path to copy the artifacts to, relative to the project artifacts directory. target: . -# Time in seconds to wait for Helm to complete any individual Kubernetes operation (like Jobs for -# hooks). +# Time in seconds to wait for Helm to complete any individual Kubernetes operation (like Jobs for hooks). timeout: 300 # The chart version to deploy. version: -# Map of values to pass to Helm when rendering the templates. May include arrays and nested -# objects. When specified, these take precedence over the values in the `values.yaml` file (or the -# files specified in `valueFiles`). +# Map of values to pass to Helm when rendering the templates. May include arrays and nested objects. When specified, +# these take precedence over the values in the `values.yaml` file (or the files specified in `valueFiles`). values: {} -# Specify value files to use when rendering the Helm chart. These will take precedence over the -# `values.yaml` file -# bundled in the Helm chart, and should be specified in ascending order of precedence. Meaning, -# the last file in +# Specify value files to use when rendering the Helm chart. These will take precedence over the `values.yaml` file +# bundled in the Helm chart, and should be specified in ascending order of precedence. Meaning, the last file in # this list will have the highest precedence. # -# If you _also_ specify keys under the `values` field, those will effectively be added as another -# file at the end +# If you _also_ specify keys under the `values` field, those will effectively be added as another file at the end # of this list, so they will take precedence over other files listed here. # -# Note that the paths here should be relative to the _module_ root, and the files should be -# contained in +# Note that the paths here should be relative to the _module_ root, and the files should be contained in # your module directory. valueFiles: [] ``` @@ -356,6 +356,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this @@ -693,6 +714,26 @@ The names of any tasks that must be executed, and the names of any services that | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `tasks[].disabled` + +[tasks](#tasks) > disabled + +Set this to `true` to disable the task. You can use this with conditional template strings to +enable/disable tasks based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This can be handy when you only want certain tasks to run in +specific environments, e.g. only for development. + +Disabling a task means that it will not be run, and will also be ignored if it is declared as a +runtime dependency for another service, test or task. + +Note however that template strings referencing the task's outputs (i.e. runtime outputs) will fail to +resolve when the task is disabled, so you need to make sure to provide alternate values for those if +you're using them, using conditional expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `tasks[].timeout` [tasks](#tasks) > timeout @@ -921,6 +962,19 @@ The names of any services that must be running, and the names of any tasks that | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `tests[].disabled` + +[tests](#tests) > disabled + +Set this to `true` to disable the test. You can use this with conditional template strings to +enable/disable tests based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in +specific environments, e.g. only during CI. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `tests[].timeout` [tests](#tests) > timeout diff --git a/docs/module-types/kubernetes.md b/docs/module-types/kubernetes.md index 44dfb3c489..a938a69652 100644 --- a/docs/module-types/kubernetes.md +++ b/docs/module-types/kubernetes.md @@ -42,18 +42,30 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. # @@ -61,29 +73,22 @@ description: # `files` directive so that only the Kubernetes manifests get included. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -102,17 +107,16 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' -# 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. +# 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. dependencies: [] -# List of Kubernetes resource manifests to deploy. Use this instead of the `files` field if you -# need to resolve template strings in any of the manifests. +# List of Kubernetes resource manifests to deploy. Use this instead of the `files` field if you need to resolve +# template strings in any of the manifests. manifests: # The API version of the resource. - apiVersion: @@ -176,6 +180,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this diff --git a/docs/module-types/maven-container.md b/docs/module-types/maven-container.md index e66fe36863..04d7de646f 100644 --- a/docs/module-types/maven-container.md +++ b/docs/module-types/maven-container.md @@ -44,45 +44,50 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -101,14 +106,12 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' # 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). + # https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). targetImage: # Maximum time in seconds to wait for build to finish. @@ -117,29 +120,26 @@ build: # Specify build arguments to use when building the container image. buildArgs: {} -# Specify extra flags to use when building the container image. Note that arguments may not be -# portable across implementations. +# Specify extra flags to use when building the container image. Note that arguments may not be portable across +# implementations. extraFlags: -# 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. +# 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. image: -# 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`. +# 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`. hotReload: - # Specify one or more source files or directories to automatically sync into the running - # container. + # Specify one or more source files or directories to automatically sync into the running container. sync: - # 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. + # 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. - source: . - # POSIX-style absolute path to sync the directory to inside the container. The root path - # (i.e. "/") is not allowed. + # POSIX-style absolute path to sync the directory to inside the container. The root path (i.e. "/") is not + # allowed. target: # An optional command to run inside the container after syncing. @@ -150,21 +150,33 @@ dockerfile: # The list of services to deploy from this container module. services: - # 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. + # 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. - name: - # 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. + # 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. dependencies: [] + # Set this to `true` to disable the service. You can use this with conditional template strings to + # enable/disable services based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This can be handy when you only need certain services for + # specific environments, e.g. only for development. + # + # Disabling a service means that it will not be deployed, and will also be ignored if it is declared as a + # runtime dependency for another service, test or task. + # + # Note however that template strings referencing the service's outputs (i.e. runtime outputs) will fail to + # resolve when the service is disabled, so you need to make sure to provide alternate values for those if + # you're using them, using conditional expressions. + disabled: false # Annotations to attach to the service (Note: May not be applicable to all providers). annotations: {} # The command/entrypoint to run the container with when starting the service. command: # The arguments to run the container with when starting the service. args: - # Whether to run the service as a daemon (to ensure exactly one instance runs per node). May - # not be supported by all providers. + # Whether to run the service as a daemon (to ensure exactly one instance runs per node). May not be supported by + # all providers. daemon: false # List of ingress endpoints that the service exposes. ingresses: @@ -173,8 +185,7 @@ services: # 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. + # Note that if you're developing locally you may need to add this hostname to your hosts file. hostname: # The link URL for the ingress to show in the console and on the dashboard. # Also used when calling the service with the `call` command. @@ -188,8 +199,8 @@ services: path: / # The name of the container port where the specified paths should be routed. port: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives or references to secrets. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives or references to secrets. env: {} # Specify how the service's health should be checked after deploying. healthCheck: @@ -206,14 +217,13 @@ services: # Set this to check the service's health by running a command in its container. command: - # Set this to check the service's health by checking if this TCP port is accepting - # connections. + # Set this to check the service's health by checking if this TCP port is accepting connections. tcpPort: - # If this module uses the `hotReload` field, the container will be run with this - # command/entrypoint when the service is deployed with hot reloading enabled. + # If this module uses the `hotReload` field, the container will be run with this command/entrypoint when the + # service is deployed with hot reloading enabled. hotReloadCommand: - # If this module uses the `hotReload` field, the container will be run with these arguments - # when the service is deployed with hot reloading enabled. + # If this module uses the `hotReload` field, the container will be run with these arguments when the service is + # deployed with hot reloading enabled. hotReloadArgs: # Specify resource limits for the service. limits: @@ -224,39 +234,37 @@ services: memory: 1024 # List of ports that the service container exposes. ports: - # The name of the port (used when referencing the port elsewhere in the service - # configuration). + # The name of the port (used when referencing the port elsewhere in the service configuration). - name: # The protocol of the port. protocol: TCP - # The port exposed on the container by the running process. This will also be the default - # value for `servicePort`. - # This is the port you would expose in your Dockerfile and that your process listens on. - # This is commonly a non-priviledged port like 8080 for security reasons. + # The port exposed on the container by the running process. This will also be the default value for + # `servicePort`. + # This is the port you would expose in your Dockerfile and that your process listens on. This is commonly a + # non-priviledged port like 8080 for security reasons. # The service port maps to the container port: # `servicePort:80 -> containerPort:8080 -> process:8080` containerPort: # The port exposed on the service. Defaults to `containerPort` if not specified. - # This is the port you use when calling a service from another service within the cluster. - # For example, if your service name is my-service and the service port is 8090, you would - # call it with: http://my-service:8090/some-endpoint. - # It is common to use port 80, the default port number, so that you can call the service - # directly with http://my-service/some-endpoint. + # This is the port you use when calling a service from another service within the cluster. For example, if + # your service name is my-service and the service port is 8090, you would call it with: + # http://my-service:8090/some-endpoint. + # It is common to use port 80, the default port number, so that you can call the service directly with + # http://my-service/some-endpoint. # The service port maps to the container port: # `servicePort:80 -> containerPort:8080 -> process:8080` servicePort: hostPort: - # Set this to expose the service on the specified port on the host node (may not be - # supported by all providers). Set to `true` to have the cluster pick a port - # automatically, which is most often advisable if the cluster is shared by multiple users. - # This allows you to call the service from the outside by the node's IP address and the - # port number set in this field. + # Set this to expose the service on the specified port on the host node (may not be supported by all + # providers). Set to `true` to have the cluster pick a port automatically, which is most often advisable if + # the cluster is shared by multiple users. + # This allows you to call the service from the outside by the node's IP address and the port number set in + # this field. nodePort: - # The number of instances of the service to deploy. Defaults to 3 for environments configured - # with `production: true`, otherwise 1. - # Note: This setting may be overridden or ignored in some cases. For example, when running - # with `daemon: true`, with hot-reloading enabled, or if the provider doesn't support multiple - # replicas. + # The number of instances of the service to deploy. Defaults to 3 for environments configured with `production: + # true`, otherwise 1. + # Note: This setting may be overridden or ignored in some cases. For example, when running with `daemon: true`, + # with hot-reloading enabled, or if the provider doesn't support multiple replicas. replicas: # List of volumes that should be mounted when deploying the container. volumes: @@ -264,12 +272,10 @@ services: - name: # The path where the volume should be mounted in the container. containerPath: - # _NOTE: Usage of hostPath is generally discouraged, since it doesn't work reliably across - # different platforms + # _NOTE: Usage of hostPath is generally discouraged, since it doesn't work reliably across different platforms # and providers. Some providers may not support it at all._ # - # A local path or path on the node that's running the container, to mount in the - # container, relative to the + # A local path or path on the node that's running the container, to mount in the container, relative to the # module source path (or absolute). hostPath: @@ -277,59 +283,73 @@ services: tests: # The name of the test. - name: - # The names of any services that must be running, and the names of any tasks that must be - # executed, before the test is run. + # The names of any services that must be running, and the names of any tasks that must be executed, before the + # test is run. dependencies: [] + # Set this to `true` to disable the test. You can use this with conditional template strings to + # enable/disable tests based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in + # specific environments, e.g. only during CI. + disabled: false # Maximum duration (in seconds) of the test run. timeout: null # The arguments used to run the test inside the container. args: # Specify artifacts to copy out of the container after the run. - # Note: Depending on the provider, this may require the container image to include `sh` `tar`, - # in order to enable the file transfer. + # Note: Depending on the provider, this may require the container image to include `sh` `tar`, in order to enable + # the file transfer. artifacts: # A POSIX-style path or glob to copy. Must be an absolute path. May contain wildcards. - source: - # A POSIX-style path to copy the artifacts to, relative to the project artifacts - # directory. + # A POSIX-style path to copy the artifacts to, relative to the project artifacts directory. target: . # The command/entrypoint used to run the test inside the container. command: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives or references to secrets. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives or references to secrets. env: {} -# 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. +# 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. tasks: # The name of the task. - name: # A description of the task. description: - # The names of any tasks that must be executed, and the names of any services that must be - # running, before this task is executed. + # The names of any tasks that must be executed, and the names of any services that must be running, before this + # task is executed. dependencies: [] + # Set this to `true` to disable the task. You can use this with conditional template strings to + # enable/disable tasks based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This can be handy when you only want certain tasks to run in + # specific environments, e.g. only for development. + # + # Disabling a task means that it will not be run, and will also be ignored if it is declared as a + # runtime dependency for another service, test or task. + # + # Note however that template strings referencing the task's outputs (i.e. runtime outputs) will fail to + # resolve when the task is disabled, so you need to make sure to provide alternate values for those if + # you're using them, using conditional expressions. + disabled: false # Maximum duration (in seconds) of the task's execution. timeout: null # The arguments used to run the task inside the container. args: # Specify artifacts to copy out of the container after the run. - # Note: Depending on the provider, this may require the container image to include `sh` `tar`, - # in order to enable the file transfer. + # Note: Depending on the provider, this may require the container image to include `sh` `tar`, in order to enable + # the file transfer. artifacts: # A POSIX-style path or glob to copy. Must be an absolute path. May contain wildcards. - source: - # A POSIX-style path to copy the artifacts to, relative to the project artifacts - # directory. + # A POSIX-style path to copy the artifacts to, relative to the project artifacts directory. target: . # The command/entrypoint used to run the task inside the container. command: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives or references to secrets. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives or references to secrets. env: {} -# Set this to override the default OpenJDK container image version. Make sure the image version -# matches the +# Set this to override the default OpenJDK container image version. Make sure the image version matches the # configured `jdkVersion`. Ignored if you provide your own Dockerfile. imageVersion: @@ -393,6 +413,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this @@ -691,6 +732,26 @@ The names of any services that this service depends on at runtime, and the names | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `services[].disabled` + +[services](#services) > disabled + +Set this to `true` to disable the service. You can use this with conditional template strings to +enable/disable services based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This can be handy when you only need certain services for +specific environments, e.g. only for development. + +Disabling a service means that it will not be deployed, and will also be ignored if it is declared as a +runtime dependency for another service, test or task. + +Note however that template strings referencing the service's outputs (i.e. runtime outputs) will fail to +resolve when the service is disabled, so you need to make sure to provide alternate values for those if +you're using them, using conditional expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `services[].annotations` [services](#services) > annotations @@ -1189,6 +1250,19 @@ The names of any services that must be running, and the names of any tasks that | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `tests[].disabled` + +[tests](#tests) > disabled + +Set this to `true` to disable the test. You can use this with conditional template strings to +enable/disable tests based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in +specific environments, e.g. only during CI. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `tests[].timeout` [tests](#tests) > timeout @@ -1355,6 +1429,26 @@ The names of any tasks that must be executed, and the names of any services that | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `tasks[].disabled` + +[tasks](#tasks) > disabled + +Set this to `true` to disable the task. You can use this with conditional template strings to +enable/disable tasks based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This can be handy when you only want certain tasks to run in +specific environments, e.g. only for development. + +Disabling a task means that it will not be run, and will also be ignored if it is declared as a +runtime dependency for another service, test or task. + +Note however that template strings referencing the task's outputs (i.e. runtime outputs) will fail to +resolve when the task is disabled, so you need to make sure to provide alternate values for those if +you're using them, using conditional expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `tasks[].timeout` [tasks](#tasks) > timeout diff --git a/docs/module-types/openfaas.md b/docs/module-types/openfaas.md index 98c0c3e2e7..ead9f975f2 100644 --- a/docs/module-types/openfaas.md +++ b/docs/module-types/openfaas.md @@ -34,45 +34,50 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -91,16 +96,15 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' # The names of services/functions that this function depends on at runtime. dependencies: [] -# Key/value map of environment variables. Keys must be valid POSIX environment variable names -# (must not start with `GARDEN`) and values must be primitives. +# Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with +# `GARDEN`) and values must be primitives. env: {} # Specify which directory under the module contains the handler file/function. @@ -116,15 +120,20 @@ lang: tests: # The name of the test. - name: - # The names of any services that must be running, and the names of any tasks that must be - # executed, before the test is run. + # The names of any services that must be running, and the names of any tasks that must be executed, before the + # test is run. dependencies: [] + # Set this to `true` to disable the test. You can use this with conditional template strings to + # enable/disable tests based on, for example, the current environment or other variables (e.g. + # `enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in + # specific environments, e.g. only during CI. + disabled: false # Maximum duration (in seconds) of the test run. timeout: null # The command to run in the module build context in order to test it. command: - # Key/value map of environment variables. Keys must be valid POSIX environment variable names - # (must not start with `GARDEN`) and values must be primitives. + # Key/value map of environment variables. Keys must be valid POSIX environment variable names (must not start with + # `GARDEN`) and values must be primitives. env: {} ``` @@ -178,6 +187,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this @@ -389,6 +419,19 @@ The names of any services that must be running, and the names of any tasks that | --------------- | -------- | ------- | | `array[string]` | No | `[]` | +#### `tests[].disabled` + +[tests](#tests) > disabled + +Set this to `true` to disable the test. You can use this with conditional template strings to +enable/disable tests based on, for example, the current environment or other variables (e.g. +`enabled: \${environment.name != "prod"}`). This is handy when you only want certain tests to run in +specific environments, e.g. only during CI. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `tests[].timeout` [tests](#tests) > timeout diff --git a/docs/module-types/terraform.md b/docs/module-types/terraform.md index 4b08e52ea7..318eff722e 100644 --- a/docs/module-types/terraform.md +++ b/docs/module-types/terraform.md @@ -48,45 +48,50 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -105,29 +110,26 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' -# If set to true, Garden will automatically run `terraform apply -auto-approve` when the stack is -# not up-to-date. Otherwise, a warning is logged if the stack is out-of-date, and an error thrown -# if it is missing entirely. +# If set to true, Garden will automatically run `terraform apply -auto-approve` when the stack is not up-to-date. +# Otherwise, a warning is logged if the stack is out-of-date, and an error thrown if it is missing entirely. # Defaults to the value set in the provider config. autoApply: null -# 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. +# 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. dependencies: [] -# Specify the path to the working directory root—i.e. where your Terraform files are—relative to -# the module root. +# Specify the path to the working directory root—i.e. where your Terraform files are—relative to the module root. root: . -# A map of variables to use when applying the stack. You can define these here or you can place a -# `terraform.tfvars` file in the working directory root. -# If you specified `variables` in the `terraform` provider config, those will be included but the -# variables specified here take precedence. +# A map of variables to use when applying the stack. You can define these here or you can place a `terraform.tfvars` +# file in the working directory root. +# If you specified `variables` in the `terraform` provider config, those will be included but the variables specified +# here take precedence. variables: # The version of Terraform to use. Defaults to the version set in the provider config. @@ -184,6 +186,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this diff --git a/docs/providers/conftest-container.md b/docs/providers/conftest-container.md index a75b9a3280..eb7ea8bd79 100644 --- a/docs/providers/conftest-container.md +++ b/docs/providers/conftest-container.md @@ -26,16 +26,14 @@ The values in the schema below are the default values. providers: # The name of the provider plugin to use. - name: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. environments: # Path to the default policy directory or rego file to use for `conftest` modules. policyPath: ./policy # Default policy namespace to use for `conftest` modules. namespace: - # Set this to `"warn"` if you'd like tests to be marked as failed if one or more _warn_ rules - # are matched. + # Set this to `"warn"` if you'd like tests to be marked as failed if one or more _warn_ rules are matched. # Set to `"none"` to always mark the tests as successful. testFailureThreshold: error ``` diff --git a/docs/providers/conftest-kubernetes.md b/docs/providers/conftest-kubernetes.md index a77d72eb0a..9fea339288 100644 --- a/docs/providers/conftest-kubernetes.md +++ b/docs/providers/conftest-kubernetes.md @@ -28,16 +28,14 @@ The values in the schema below are the default values. providers: # The name of the provider plugin to use. - name: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. environments: # Path to the default policy directory or rego file to use for `conftest` modules. policyPath: ./policy # Default policy namespace to use for `conftest` modules. namespace: - # Set this to `"warn"` if you'd like tests to be marked as failed if one or more _warn_ rules - # are matched. + # Set this to `"warn"` if you'd like tests to be marked as failed if one or more _warn_ rules are matched. # Set to `"none"` to always mark the tests as successful. testFailureThreshold: error ``` diff --git a/docs/providers/conftest.md b/docs/providers/conftest.md index 9d939c3185..5706f535b8 100644 --- a/docs/providers/conftest.md +++ b/docs/providers/conftest.md @@ -35,16 +35,14 @@ The values in the schema below are the default values. providers: # The name of the provider plugin to use. - name: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. environments: # Path to the default policy directory or rego file to use for `conftest` modules. policyPath: ./policy # Default policy namespace to use for `conftest` modules. namespace: - # Set this to `"warn"` if you'd like tests to be marked as failed if one or more _warn_ rules - # are matched. + # Set this to `"warn"` if you'd like tests to be marked as failed if one or more _warn_ rules are matched. # Set to `"none"` to always mark the tests as successful. testFailureThreshold: error ``` diff --git a/docs/providers/hadolint.md b/docs/providers/hadolint.md index 2b8158539e..36d4fe3f95 100644 --- a/docs/providers/hadolint.md +++ b/docs/providers/hadolint.md @@ -30,16 +30,13 @@ The values in the schema below are the default values. providers: # The name of the provider plugin to use. - name: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. environments: - # By default, the provider automatically creates a `hadolint` module for every `container` - # module in your + # By default, the provider automatically creates a `hadolint` module for every `container` module in your # project. Set this to `false` to disable this behavior. autoInject: true - # Set this to `"warning"` if you'd like tests to be marked as failed if one or more warnings - # are returned. + # Set this to `"warning"` if you'd like tests to be marked as failed if one or more warnings are returned. # Set to `"none"` to always mark the tests as successful. testFailureThreshold: error ``` diff --git a/docs/providers/kubernetes.md b/docs/providers/kubernetes.md index 1198b45461..dba080dad1 100644 --- a/docs/providers/kubernetes.md +++ b/docs/providers/kubernetes.md @@ -28,41 +28,29 @@ The values in the schema below are the default values. ```yaml providers: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. - environments: - # Choose the mechanism for building container images before deploying. By default it uses the - # local Docker - # daemon, but you can set it to `cluster-docker` or `kaniko` to sync files to a remote Docker - # daemon, - # installed in the cluster, and build container images there. This removes the need to run - # Docker or - # Kubernetes locally, and allows you to share layer and image caches between multiple - # developers, as well + # Choose the mechanism for building container images before deploying. By default it uses the local Docker + # daemon, but you can set it to `cluster-docker` or `kaniko` to sync files to a remote Docker daemon, + # installed in the cluster, and build container images there. This removes the need to run Docker or + # Kubernetes locally, and allows you to share layer and image caches between multiple developers, as well # as between your development and CI workflows. # - # This is currently experimental and sometimes not desired, so it's not enabled by default. - # For example when using - # the `local-kubernetes` provider with Docker for Desktop and Minikube, we directly use the - # in-cluster docker - # daemon when building. You might also be deploying to a remote cluster that isn't intended as - # a development + # This is currently experimental and sometimes not desired, so it's not enabled by default. For example when using + # the `local-kubernetes` provider with Docker for Desktop and Minikube, we directly use the in-cluster docker + # daemon when building. You might also be deploying to a remote cluster that isn't intended as a development # environment, so you'd want your builds to happen elsewhere. # - # Functionally, both `cluster-docker` and `kaniko` do the same thing, but use different - # underlying mechanisms - # to build. The former uses a normal Docker daemon in the cluster. Because this has to run in - # privileged mode, + # Functionally, both `cluster-docker` and `kaniko` do the same thing, but use different underlying mechanisms + # to build. The former uses a normal Docker daemon in the cluster. Because this has to run in privileged mode, # this is less secure than Kaniko, but in turn it is generally faster. See the - # [Kaniko docs](https://github.com/GoogleContainerTools/kaniko) for more information on - # Kaniko. + # [Kaniko docs](https://github.com/GoogleContainerTools/kaniko) for more information on Kaniko. buildMode: local-docker # Configuration options for the `cluster-docker` build mode. clusterDocker: - # Enable [BuildKit](https://github.com/moby/buildkit) support. This should in most cases - # work well and be more performant, but we're opting to keep it optional until it's enabled - # by default in Docker. + # Enable [BuildKit](https://github.com/moby/buildkit) support. This should in most cases work well and be more + # performant, but we're opting to keep it optional until it's enabled by default in Docker. enableBuildKit: false # A default hostname to use when no hostname is explicitly configured for a service. defaultHostname: @@ -70,37 +58,30 @@ providers: 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. + # The feature only supports modules of type `container`: other types will just deploy using the default strategy. deploymentStrategy: rolling - # 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. + # 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 - # References to `docker-registry` secrets to use for authenticating with remote registries - # when pulling - # images. This is necessary if you reference private images in your module configuration, and - # is required + # References to `docker-registry` secrets to use for authenticating with remote registries when pulling + # images. This is necessary if you reference private images in your module configuration, and is required # when configuring a remote Kubernetes environment with buildMode=local. imagePullSecrets: # The name of the Kubernetes secret. - name: - # The namespace where the secret is stored. If necessary, the secret may be copied to the - # appropriate namespace before use. + # The namespace where the secret is stored. If necessary, the secret may be copied to the appropriate + # namespace before use. namespace: default - # Resource requests and limits for the in-cluster builder, container registry and code sync - # service. (which are automatically installed and used when `buildMode` is `cluster-docker` or - # `kaniko`). + # Resource requests and limits for the in-cluster builder, container registry and code sync service. (which are + # automatically installed and used when `buildMode` is `cluster-docker` or `kaniko`). resources: # Resource requests and limits for the in-cluster builder. # - # When `buildMode` is `cluster-docker`, this refers to the Docker Daemon that is installed - # and run - # cluster-wide. This is shared across all users and builds, so it should be resourced - # accordingly, factoring + # When `buildMode` is `cluster-docker`, this refers to the Docker Daemon that is installed and run + # cluster-wide. This is shared across all users and builds, so it should be resourced accordingly, factoring # in how many concurrent builds you expect and how heavy your builds tend to be. # - # When `buildMode` is `kaniko`, this refers to _each instance_ of Kaniko, so you'd generally - # use lower + # When `buildMode` is `kaniko`, this refers to _each instance_ of Kaniko, so you'd generally use lower # limits/requests, but you should evaluate based on your needs. builder: limits: @@ -117,12 +98,10 @@ providers: # Memory request in megabytes. memory: 512 - # Resource requests and limits for the in-cluster image registry. Built images are pushed to - # this registry, + # Resource requests and limits for the in-cluster image registry. Built images are pushed to this registry, # so that they are available to all the nodes in your cluster. # - # This is shared across all users and builds, so it should be resourced accordingly, - # factoring + # This is shared across all users and builds, so it should be resourced accordingly, factoring # in how many concurrent builds you expect and how large your images tend to be. registry: limits: @@ -139,10 +118,8 @@ providers: # Memory request in megabytes. memory: 512 - # Resource requests and limits for the code sync service, which we use to sync build - # contexts to the cluster - # ahead of building images. This generally is not resource intensive, but you might want to - # adjust the + # Resource requests and limits for the code sync service, which we use to sync build contexts to the cluster + # ahead of building images. This generally is not resource intensive, but you might want to adjust the # defaults if you have many concurrent users. sync: limits: @@ -158,15 +135,11 @@ providers: # Memory request in megabytes. memory: 64 - # Storage parameters to set for the in-cluster builder, container registry and code sync - # persistent volumes - # (which are automatically installed and used when `buildMode` is `cluster-docker` or - # `kaniko`). + # Storage parameters to set for the in-cluster builder, container registry and code sync persistent volumes + # (which are automatically installed and used when `buildMode` is `cluster-docker` or `kaniko`). # - # These are all shared cluster-wide across all users and builds, so they should be resourced - # accordingly, - # factoring in how many concurrent builds you expect and how large your images and build - # contexts tend to be. + # These are all shared cluster-wide across all users and builds, so they should be resourced accordingly, + # factoring in how many concurrent builds you expect and how large your images and build contexts tend to be. storage: # Storage parameters for the data volume for the in-cluster Docker Daemon. # @@ -178,8 +151,7 @@ providers: # Storage class to use for the volume. storageClass: null - # Storage parameters for the NFS provisioner, which we automatically create for the sync - # volume, _unless_ + # Storage parameters for the NFS provisioner, which we automatically create for the sync volume, _unless_ # you specify a `storageClass` for the sync volume. See the below `sync` parameter for more. # # Only applies when `buildMode` is set to `cluster-docker` or `kaniko`, ignored otherwise. @@ -187,8 +159,7 @@ providers: # Storage class to use as backing storage for NFS . storageClass: null - # Storage parameters for the in-cluster Docker registry volume. Built images are stored - # here, so that they + # Storage parameters for the in-cluster Docker registry volume. Built images are stored here, so that they # are available to all the nodes in your cluster. # # Only applies when `buildMode` is set to `cluster-docker` or `kaniko`, ignored otherwise. @@ -199,8 +170,7 @@ providers: # Storage class to use for the volume. storageClass: null - # Storage parameters for the code sync volume, which build contexts are synced to ahead of - # running + # Storage parameters for the code sync volume, which build contexts are synced to ahead of running # in-cluster builds. # # Important: The storage class configured here has to support _ReadWriteMany_ access. @@ -218,30 +188,26 @@ providers: tlsCertificates: # A unique identifier for this certificate. - name: - # A list of hostnames that this certificate should be used for. If you don't specify - # these, they will be automatically read from the certificate. + # A list of hostnames that this certificate should be used for. If you don't specify these, they will be + # automatically read from the certificate. hostnames: - # A reference to the Kubernetes secret that contains the TLS certificate and key for the - # domain. + # A reference to the Kubernetes secret that contains the TLS certificate and key for the domain. secretRef: # The name of the Kubernetes secret. name: - # The namespace where the secret is stored. If necessary, the secret may be copied to - # the appropriate namespace before use. + # The namespace where the secret is stored. If necessary, the secret may be copied to the appropriate + # namespace before use. namespace: default - # Set to `cert-manager` to configure - # [cert-manager](https://github.com/jetstack/cert-manager) to manage this + # Set to `cert-manager` to configure [cert-manager](https://github.com/jetstack/cert-manager) to manage this # certificate. See our - # [cert-manager integration guide](https://docs.garden.io/guides/cert-manager-integration) - # for details. + # [cert-manager integration guide](https://docs.garden.io/guides/cert-manager-integration) for details. managedBy: # cert-manager configuration, for creating and managing TLS certificates. See the # [cert-manager guide](https://docs.garden.io/guides/cert-manager-integration) for details. certManager: # Automatically install `cert-manager` on initialization. See the - # [cert-manager integration guide](https://docs.garden.io/guides/cert-manager-integration) - # for details. + # [cert-manager integration guide](https://docs.garden.io/guides/cert-manager-integration) for details. install: false # The email to use when requesting Let's Encrypt certificates. @@ -250,52 +216,44 @@ providers: # The type of issuer for the certificate (only ACME is supported for now). issuer: acme - # Specify which ACME server to request certificates from. Currently Let's Encrypt staging - # and prod servers are supported. + # Specify which ACME server to request certificates from. Currently Let's Encrypt staging and prod servers are + # supported. acmeServer: letsencrypt-staging - # The type of ACME challenge used to validate hostnames and generate the certificates (only - # HTTP-01 is supported for now). + # The type of ACME challenge used to validate hostnames and generate the certificates (only HTTP-01 is supported + # for now). acmeChallengeType: HTTP-01 # For setting tolerations on the registry-proxy when using in-cluster building. - # The registry-proxy is a DaemonSet that proxies connections to the docker registry service on - # each node. + # The registry-proxy is a DaemonSet that proxies connections to the docker registry service on each node. # # Use this only if you're doing in-cluster building and the nodes in your cluster # have [taints](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/). registryProxyTolerations: - # "Effect" indicates the taint effect to match. Empty means match all taint effects. When - # specified, + # "Effect" indicates the taint effect to match. Empty means match all taint effects. When specified, # allowed values are "NoSchedule", "PreferNoSchedule" and "NoExecute". - effect: # "Key" is the taint key that the toleration applies to. Empty means match all taint keys. - # If the key is empty, operator must be "Exists"; this combination means to match all - # values and all keys. + # If the key is empty, operator must be "Exists"; this combination means to match all values and all keys. key: - # "Operator" represents a key's relationship to the value. Valid operators are "Exists" - # and "Equal". Defaults to - # "Equal". "Exists" is equivalent to wildcard for value, so that a pod can tolerate all - # taints of a + # "Operator" represents a key's relationship to the value. Valid operators are "Exists" and "Equal". Defaults + # to + # "Equal". "Exists" is equivalent to wildcard for value, so that a pod can tolerate all taints of a # particular category. operator: Equal - # "TolerationSeconds" represents the period of time the toleration (which must be of - # effect "NoExecute", - # otherwise this field is ignored) tolerates the taint. By default, it is not set, which - # means tolerate - # the taint forever (do not evict). Zero and negative values will be treated as 0 (evict - # immediately) + # "TolerationSeconds" represents the period of time the toleration (which must be of effect "NoExecute", + # otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate + # the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) # by the system. tolerationSeconds: - # "Value" is the taint value the toleration matches to. If the operator is "Exists", the - # value should be empty, + # "Value" is the taint value the toleration matches to. If the operator is "Exists", the value should be + # empty, # otherwise just a regular string. value: # The name of the provider plugin to use. name: kubernetes # The kubectl context to use to connect to the Kubernetes cluster. context: - # The registry where built containers should be pushed to, and then pulled to the cluster when - # deploying services. + # The registry where built containers should be pushed to, and then pulled to the cluster when deploying services. deploymentRegistry: # The hostname (and optionally port, if not the default port) of the registry. hostname: @@ -305,10 +263,8 @@ providers: # The namespace in the registry where images should be pushed. namespace: _ - # The ingress class to use on configured Ingresses (via the `kubernetes.io/ingress.class` - # annotation) - # when deploying `container` services. Use this if you have multiple ingress controllers in - # your cluster. + # The ingress class to use on configured Ingresses (via the `kubernetes.io/ingress.class` annotation) + # when deploying `container` services. Use this if you have multiple ingress controllers in your cluster. ingressClass: # The external HTTP port of the cluster's ingress controller. ingressHttpPort: 80 @@ -316,8 +272,8 @@ providers: ingressHttpsPort: 443 # 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 ). Note that the - # framework generates other namespaces as well with this name as a prefix. + # Specify which namespace to deploy services to (defaults to ). Note that the framework generates + # other namespaces as well with this name as a prefix. namespace: # Set this to `nginx` to install/enable the NGINX ingress controller. setupIngressController: false diff --git a/docs/providers/local-kubernetes.md b/docs/providers/local-kubernetes.md index 6d1eb88b26..e845f8030b 100644 --- a/docs/providers/local-kubernetes.md +++ b/docs/providers/local-kubernetes.md @@ -26,41 +26,29 @@ The values in the schema below are the default values. ```yaml providers: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. - environments: - # Choose the mechanism for building container images before deploying. By default it uses the - # local Docker - # daemon, but you can set it to `cluster-docker` or `kaniko` to sync files to a remote Docker - # daemon, - # installed in the cluster, and build container images there. This removes the need to run - # Docker or - # Kubernetes locally, and allows you to share layer and image caches between multiple - # developers, as well + # Choose the mechanism for building container images before deploying. By default it uses the local Docker + # daemon, but you can set it to `cluster-docker` or `kaniko` to sync files to a remote Docker daemon, + # installed in the cluster, and build container images there. This removes the need to run Docker or + # Kubernetes locally, and allows you to share layer and image caches between multiple developers, as well # as between your development and CI workflows. # - # This is currently experimental and sometimes not desired, so it's not enabled by default. - # For example when using - # the `local-kubernetes` provider with Docker for Desktop and Minikube, we directly use the - # in-cluster docker - # daemon when building. You might also be deploying to a remote cluster that isn't intended as - # a development + # This is currently experimental and sometimes not desired, so it's not enabled by default. For example when using + # the `local-kubernetes` provider with Docker for Desktop and Minikube, we directly use the in-cluster docker + # daemon when building. You might also be deploying to a remote cluster that isn't intended as a development # environment, so you'd want your builds to happen elsewhere. # - # Functionally, both `cluster-docker` and `kaniko` do the same thing, but use different - # underlying mechanisms - # to build. The former uses a normal Docker daemon in the cluster. Because this has to run in - # privileged mode, + # Functionally, both `cluster-docker` and `kaniko` do the same thing, but use different underlying mechanisms + # to build. The former uses a normal Docker daemon in the cluster. Because this has to run in privileged mode, # this is less secure than Kaniko, but in turn it is generally faster. See the - # [Kaniko docs](https://github.com/GoogleContainerTools/kaniko) for more information on - # Kaniko. + # [Kaniko docs](https://github.com/GoogleContainerTools/kaniko) for more information on Kaniko. buildMode: local-docker # Configuration options for the `cluster-docker` build mode. clusterDocker: - # Enable [BuildKit](https://github.com/moby/buildkit) support. This should in most cases - # work well and be more performant, but we're opting to keep it optional until it's enabled - # by default in Docker. + # Enable [BuildKit](https://github.com/moby/buildkit) support. This should in most cases work well and be more + # performant, but we're opting to keep it optional until it's enabled by default in Docker. enableBuildKit: false # A default hostname to use when no hostname is explicitly configured for a service. defaultHostname: @@ -68,37 +56,30 @@ providers: 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. + # The feature only supports modules of type `container`: other types will just deploy using the default strategy. deploymentStrategy: rolling - # 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. + # 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 - # References to `docker-registry` secrets to use for authenticating with remote registries - # when pulling - # images. This is necessary if you reference private images in your module configuration, and - # is required + # References to `docker-registry` secrets to use for authenticating with remote registries when pulling + # images. This is necessary if you reference private images in your module configuration, and is required # when configuring a remote Kubernetes environment with buildMode=local. imagePullSecrets: # The name of the Kubernetes secret. - name: - # The namespace where the secret is stored. If necessary, the secret may be copied to the - # appropriate namespace before use. + # The namespace where the secret is stored. If necessary, the secret may be copied to the appropriate + # namespace before use. namespace: default - # Resource requests and limits for the in-cluster builder, container registry and code sync - # service. (which are automatically installed and used when `buildMode` is `cluster-docker` or - # `kaniko`). + # Resource requests and limits for the in-cluster builder, container registry and code sync service. (which are + # automatically installed and used when `buildMode` is `cluster-docker` or `kaniko`). resources: # Resource requests and limits for the in-cluster builder. # - # When `buildMode` is `cluster-docker`, this refers to the Docker Daemon that is installed - # and run - # cluster-wide. This is shared across all users and builds, so it should be resourced - # accordingly, factoring + # When `buildMode` is `cluster-docker`, this refers to the Docker Daemon that is installed and run + # cluster-wide. This is shared across all users and builds, so it should be resourced accordingly, factoring # in how many concurrent builds you expect and how heavy your builds tend to be. # - # When `buildMode` is `kaniko`, this refers to _each instance_ of Kaniko, so you'd generally - # use lower + # When `buildMode` is `kaniko`, this refers to _each instance_ of Kaniko, so you'd generally use lower # limits/requests, but you should evaluate based on your needs. builder: limits: @@ -115,12 +96,10 @@ providers: # Memory request in megabytes. memory: 512 - # Resource requests and limits for the in-cluster image registry. Built images are pushed to - # this registry, + # Resource requests and limits for the in-cluster image registry. Built images are pushed to this registry, # so that they are available to all the nodes in your cluster. # - # This is shared across all users and builds, so it should be resourced accordingly, - # factoring + # This is shared across all users and builds, so it should be resourced accordingly, factoring # in how many concurrent builds you expect and how large your images tend to be. registry: limits: @@ -137,10 +116,8 @@ providers: # Memory request in megabytes. memory: 512 - # Resource requests and limits for the code sync service, which we use to sync build - # contexts to the cluster - # ahead of building images. This generally is not resource intensive, but you might want to - # adjust the + # Resource requests and limits for the code sync service, which we use to sync build contexts to the cluster + # ahead of building images. This generally is not resource intensive, but you might want to adjust the # defaults if you have many concurrent users. sync: limits: @@ -156,15 +133,11 @@ providers: # Memory request in megabytes. memory: 64 - # Storage parameters to set for the in-cluster builder, container registry and code sync - # persistent volumes - # (which are automatically installed and used when `buildMode` is `cluster-docker` or - # `kaniko`). + # Storage parameters to set for the in-cluster builder, container registry and code sync persistent volumes + # (which are automatically installed and used when `buildMode` is `cluster-docker` or `kaniko`). # - # These are all shared cluster-wide across all users and builds, so they should be resourced - # accordingly, - # factoring in how many concurrent builds you expect and how large your images and build - # contexts tend to be. + # These are all shared cluster-wide across all users and builds, so they should be resourced accordingly, + # factoring in how many concurrent builds you expect and how large your images and build contexts tend to be. storage: # Storage parameters for the data volume for the in-cluster Docker Daemon. # @@ -176,8 +149,7 @@ providers: # Storage class to use for the volume. storageClass: null - # Storage parameters for the NFS provisioner, which we automatically create for the sync - # volume, _unless_ + # Storage parameters for the NFS provisioner, which we automatically create for the sync volume, _unless_ # you specify a `storageClass` for the sync volume. See the below `sync` parameter for more. # # Only applies when `buildMode` is set to `cluster-docker` or `kaniko`, ignored otherwise. @@ -185,8 +157,7 @@ providers: # Storage class to use as backing storage for NFS . storageClass: null - # Storage parameters for the in-cluster Docker registry volume. Built images are stored - # here, so that they + # Storage parameters for the in-cluster Docker registry volume. Built images are stored here, so that they # are available to all the nodes in your cluster. # # Only applies when `buildMode` is set to `cluster-docker` or `kaniko`, ignored otherwise. @@ -197,8 +168,7 @@ providers: # Storage class to use for the volume. storageClass: null - # Storage parameters for the code sync volume, which build contexts are synced to ahead of - # running + # Storage parameters for the code sync volume, which build contexts are synced to ahead of running # in-cluster builds. # # Important: The storage class configured here has to support _ReadWriteMany_ access. @@ -216,30 +186,26 @@ providers: tlsCertificates: # A unique identifier for this certificate. - name: - # A list of hostnames that this certificate should be used for. If you don't specify - # these, they will be automatically read from the certificate. + # A list of hostnames that this certificate should be used for. If you don't specify these, they will be + # automatically read from the certificate. hostnames: - # A reference to the Kubernetes secret that contains the TLS certificate and key for the - # domain. + # A reference to the Kubernetes secret that contains the TLS certificate and key for the domain. secretRef: # The name of the Kubernetes secret. name: - # The namespace where the secret is stored. If necessary, the secret may be copied to - # the appropriate namespace before use. + # The namespace where the secret is stored. If necessary, the secret may be copied to the appropriate + # namespace before use. namespace: default - # Set to `cert-manager` to configure - # [cert-manager](https://github.com/jetstack/cert-manager) to manage this + # Set to `cert-manager` to configure [cert-manager](https://github.com/jetstack/cert-manager) to manage this # certificate. See our - # [cert-manager integration guide](https://docs.garden.io/guides/cert-manager-integration) - # for details. + # [cert-manager integration guide](https://docs.garden.io/guides/cert-manager-integration) for details. managedBy: # cert-manager configuration, for creating and managing TLS certificates. See the # [cert-manager guide](https://docs.garden.io/guides/cert-manager-integration) for details. certManager: # Automatically install `cert-manager` on initialization. See the - # [cert-manager integration guide](https://docs.garden.io/guides/cert-manager-integration) - # for details. + # [cert-manager integration guide](https://docs.garden.io/guides/cert-manager-integration) for details. install: false # The email to use when requesting Let's Encrypt certificates. @@ -248,52 +214,45 @@ providers: # The type of issuer for the certificate (only ACME is supported for now). issuer: acme - # Specify which ACME server to request certificates from. Currently Let's Encrypt staging - # and prod servers are supported. + # Specify which ACME server to request certificates from. Currently Let's Encrypt staging and prod servers are + # supported. acmeServer: letsencrypt-staging - # The type of ACME challenge used to validate hostnames and generate the certificates (only - # HTTP-01 is supported for now). + # The type of ACME challenge used to validate hostnames and generate the certificates (only HTTP-01 is supported + # for now). acmeChallengeType: HTTP-01 # For setting tolerations on the registry-proxy when using in-cluster building. - # The registry-proxy is a DaemonSet that proxies connections to the docker registry service on - # each node. + # The registry-proxy is a DaemonSet that proxies connections to the docker registry service on each node. # # Use this only if you're doing in-cluster building and the nodes in your cluster # have [taints](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/). registryProxyTolerations: - # "Effect" indicates the taint effect to match. Empty means match all taint effects. When - # specified, + # "Effect" indicates the taint effect to match. Empty means match all taint effects. When specified, # allowed values are "NoSchedule", "PreferNoSchedule" and "NoExecute". - effect: # "Key" is the taint key that the toleration applies to. Empty means match all taint keys. - # If the key is empty, operator must be "Exists"; this combination means to match all - # values and all keys. + # If the key is empty, operator must be "Exists"; this combination means to match all values and all keys. key: - # "Operator" represents a key's relationship to the value. Valid operators are "Exists" - # and "Equal". Defaults to - # "Equal". "Exists" is equivalent to wildcard for value, so that a pod can tolerate all - # taints of a + # "Operator" represents a key's relationship to the value. Valid operators are "Exists" and "Equal". Defaults + # to + # "Equal". "Exists" is equivalent to wildcard for value, so that a pod can tolerate all taints of a # particular category. operator: Equal - # "TolerationSeconds" represents the period of time the toleration (which must be of - # effect "NoExecute", - # otherwise this field is ignored) tolerates the taint. By default, it is not set, which - # means tolerate - # the taint forever (do not evict). Zero and negative values will be treated as 0 (evict - # immediately) + # "TolerationSeconds" represents the period of time the toleration (which must be of effect "NoExecute", + # otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate + # the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) # by the system. tolerationSeconds: - # "Value" is the taint value the toleration matches to. If the operator is "Exists", the - # value should be empty, + # "Value" is the taint value the toleration matches to. If the operator is "Exists", the value should be + # empty, # otherwise just a regular string. value: # The name of the provider plugin to use. name: local-kubernetes # The kubectl context to use to connect to the Kubernetes cluster. context: - # Specify which namespace to deploy services to (defaults to the project name). Note that the - # framework generates other namespaces as well with this name as a prefix. + # Specify which namespace to deploy services to (defaults to the project name). Note that the framework generates + # other namespaces as well with this name as a prefix. namespace: # Set this to null or false to skip installing/enabling the `nginx` ingress controller. setupIngressController: nginx diff --git a/docs/providers/maven-container.md b/docs/providers/maven-container.md index 90a076d600..4223d541c7 100644 --- a/docs/providers/maven-container.md +++ b/docs/providers/maven-container.md @@ -24,9 +24,8 @@ The values in the schema below are the default values. providers: # The name of the provider plugin to use. - name: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. environments: ``` ### Configuration Keys diff --git a/docs/providers/openfaas.md b/docs/providers/openfaas.md index ce7d1d9cdf..18175b53f4 100644 --- a/docs/providers/openfaas.md +++ b/docs/providers/openfaas.md @@ -26,41 +26,31 @@ The values in the schema below are the default values. ```yaml providers: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. - environments: # The name of the provider plugin to use. name: openfaas - # The external URL to the function gateway, after installation. This is required if you set - # `faasNetes.values` + # The external URL to the function gateway, after installation. This is required if you set `faasNetes.values` # or `faastNetes.install: false`, so that Garden can know how to reach the gateway. gatewayUrl: - # The ingress hostname to configure for the function gateway, when `faasNetes.install: true` - # and not - # overriding `faasNetes.values`. Defaults to the default hostname of the configured Kubernetes - # provider. + # The ingress hostname to configure for the function gateway, when `faasNetes.install: true` and not + # overriding `faasNetes.values`. Defaults to the default hostname of the configured Kubernetes provider. # - # Important: If you have other types of services, this should be different from their ingress - # hostnames, - # or the other services should not expose paths under /function and /system to avoid routing - # conflicts. + # Important: If you have other types of services, this should be different from their ingress hostnames, + # or the other services should not expose paths under /function and /system to avoid routing conflicts. hostname: faasNetes: # Set to false if you'd like to install and configure faas-netes yourself. - # See the [official instructions](https://docs.openfaas.com/deployment/kubernetes/) for - # details. + # See the [official instructions](https://docs.openfaas.com/deployment/kubernetes/) for details. install: true # Override the values passed to the faas-netes Helm chart. Ignored if `install: false`. - # See the [chart docs](https://github.com/openfaas/faas-netes/tree/master/chart/openfaas) - # for the available + # See the [chart docs](https://github.com/openfaas/faas-netes/tree/master/chart/openfaas) for the available # options. # - # Note that this completely replaces the values Garden assigns by default, including - # `functionNamespace`, - # ingress configuration etc. so you need to make sure those are correctly configured for - # your use case. + # Note that this completely replaces the values Garden assigns by default, including `functionNamespace`, + # ingress configuration etc. so you need to make sure those are correctly configured for your use case. values: ``` ### Configuration Keys diff --git a/docs/providers/terraform.md b/docs/providers/terraform.md index 1302b5390b..40c9bdff99 100644 --- a/docs/providers/terraform.md +++ b/docs/providers/terraform.md @@ -21,23 +21,19 @@ The values in the schema below are the default values. providers: # The name of the provider plugin to use. - name: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. environments: - # If set to true, Garden will automatically run `terraform apply -auto-approve` when a stack - # is not up-to-date. Otherwise, a warning is logged if the stack is out-of-date, and an error - # thrown if it is missing entirely. + # If set to true, Garden will automatically run `terraform apply -auto-approve` when a stack is not up-to-date. + # Otherwise, a warning is logged if the stack is out-of-date, and an error thrown if it is missing entirely. autoApply: false - # Specify the path to a Terraform config directory, that should be resolved when initializing - # the provider. + # Specify the path to a Terraform config directory, that should be resolved when initializing the provider. # This is useful when other providers need to be able to reference the outputs from the stack. # # See the [Terraform guide](../guides/terraform.md) for more information. initRoot: - # A map of variables to use when applying Terraform stacks. You can define these here, in - # individual `terraform` module configs, or you can place a `terraform.tfvars` file in each - # working directory. + # A map of variables to use when applying Terraform stacks. You can define these here, in individual `terraform` + # module configs, or you can place a `terraform.tfvars` file in each working directory. variables: # The version of Terraform to use. version: 0.12.7 diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 792d6a799d..60de61c50b 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -608,6 +608,7 @@ Examples: | Argument | Alias | Type | Description | | -------- | ----- | ---- | ----------- | + | `--force` | | boolean | Run the service even if it's disabled for the environment. | `--force-build` | | boolean | Force rebuild of module. ### garden run task @@ -634,6 +635,7 @@ Examples: | Argument | Alias | Type | Description | | -------- | ----- | ---- | ----------- | + | `--force` | | boolean | Run the task even if it's disabled for the environment. | `--force-build` | | boolean | Force rebuild of module before running. ### garden run test @@ -663,6 +665,7 @@ Examples: | Argument | Alias | Type | Description | | -------- | ----- | ---- | ----------- | | `--interactive` | | boolean | Set to false to skip interactive mode and just output the command result. + | `--force` | | boolean | Run the test even if it's disabled for the environment. | `--force-build` | | boolean | Force rebuild of module before running. ### garden scan diff --git a/docs/reference/config.md b/docs/reference/config.md index fe0f677fab..e2df20cfcd 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -31,20 +31,17 @@ kind: Project # The name of the project. name: -# The default environment to use when calling commands without the `--env` parameter. Defaults to -# the first configured environment. +# The default environment to use when calling commands without the `--env` parameter. Defaults to the first configured +# environment. defaultEnvironment: '' -# Specify a list of filenames that should be used as ".ignore" files across the project, using the -# same syntax and semantics as `.gitignore` files. By default, patterns matched in `.gitignore` -# and `.gardenignore` files, found anywhere in the project, are ignored when scanning for modules -# and module sources. -# Note that these take precedence over the project `module.include` field, and module `include` -# fields, so any paths matched by the .ignore files will be ignored even if they are explicitly -# specified in those fields. +# Specify a list of filenames that should be used as ".ignore" files across the project, using the same syntax and +# semantics as `.gitignore` files. By default, patterns matched in `.gitignore` and `.gardenignore` files, found +# anywhere in the project, are ignored when scanning for modules and module sources. +# Note that these take precedence over the project `module.include` field, and module `include` fields, so any paths +# matched by the .ignore files will be ignored even if they are explicitly specified in those fields. # See the [Configuration Files guide] -# (https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# (https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. dotIgnoreFiles: - .gitignore - .gardenignore @@ -52,31 +49,24 @@ dotIgnoreFiles: modules: # Specify a list of POSIX-style paths or globs that should be scanned for Garden modules. # - # Note that you can also _exclude_ path using the `exclude` field or by placing `.gardenignore` - # files in your + # Note that you can also _exclude_ path using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files - # guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) - # for details. + # guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # - # Unlike the `exclude` field, the paths/globs specified here have _no effect_ on which files and - # directories - # Garden watches for changes. Use the `exclude` field to affect those, if you have large - # directories that + # Unlike the `exclude` field, the paths/globs specified here have _no effect_ on which files and directories + # Garden watches for changes. Use the `exclude` field to affect those, if you have large directories that # should not be watched for changes. # # Also note that specifying an empty list here means _no paths_ should be included. include: - # Specify a list of POSIX-style paths or glob patterns that should be excluded when scanning for - # modules. + # Specify a list of POSIX-style paths or glob patterns that should be excluded when scanning for modules. # - # The filters here also affect which files and directories are watched for changes. So if you - # have a large number + # The filters here also affect which files and directories are watched for changes. So if you have a large number # of directories in your project that should not be watched, you should specify them here. # - # For example, you might want to exclude large vendor directories in your project from being - # scanned and + # For example, you might want to exclude large vendor directories in your project from being scanned and # watched: # # modules: @@ -84,46 +74,40 @@ modules: # - node_modules/**/* # - vendor/**/* # - # Note that you can also explicitly _include_ files using the `include` field. If you also - # specify the - # `include` field, the paths/patterns specified here are filtered from the files matched by - # `include`. + # Note that you can also explicitly _include_ files using the `include` field. If you also specify the + # `include` field, the paths/patterns specified here are filtered from the files matched by `include`. # # The `include` field does _not_ affect which files are watched. # # See the [Configuration Files - # guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) - # for details. + # guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. exclude: -# A list of providers that should be used for this project, and their configuration. Please refer -# to individual plugins/providers for details on how to configure them. +# A list of providers that should be used for this project, and their configuration. Please refer to individual +# plugins/providers for details on how to configure them. providers: # The name of the provider plugin to use. - name: - # If specified, this provider will only be used in the listed environments. Note that an empty - # array effectively disables the provider. To use a provider in all environments, omit this - # field. + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. environments: # A list of remote sources to import into project. sources: # The name of the source to import - name: - # A remote repository URL. Currently only supports git servers. Must contain a hash suffix - # pointing to a specific branch or tag, with the format: # + # A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific + # branch or tag, with the format: # repositoryUrl: -# Specify a path (relative to the project root) to a file containing variables, that we apply on -# top of the +# Specify a path (relative to the project root) to a file containing variables, that we apply on top of the # project-wide `variables` field. The file should be in a standard "dotenv" format, specified # [here](https://github.com/motdotla/dotenv#rules). # # If you don't set the field and the `garden.env` file does not exist, we simply ignore it. # If you do override the default value and the file doesn't exist, an error will be thrown. # -# _Note that in many cases it is advisable to only use environment-specific var files, instead of -# combining +# _Note that in many cases it is advisable to only use environment-specific var files, instead of combining # multiple ones. See the `environments[].varfile` field for this option._ varfile: garden.env @@ -131,36 +115,30 @@ varfile: garden.env variables: {} environments: - # DEPRECATED - Please use the top-level `providers` field instead, and if needed use the - # `environments` key on the provider configurations to limit them to specific environments. + # DEPRECATED - Please use the top-level `providers` field instead, and if needed use the `environments` key on the + # provider configurations to limit them to specific environments. - providers: # The name of the provider plugin to use. - name: - # If specified, this provider will only be used in the listed environments. Note that an - # empty array effectively disables the provider. To use a provider in all environments, - # omit this field. + # If specified, this provider will only be used in the listed environments. Note that an empty array + # effectively disables the provider. To use a provider in all environments, omit this field. environments: - # Specify a path (relative to the project root) to a file containing variables, that we apply - # on top of the - # _environment-specific_ `variables` field. The file should be in a standard "dotenv" format, - # specified + # Specify a path (relative to the project root) to a file containing variables, that we apply on top of the + # _environment-specific_ `variables` field. The file should be in a standard "dotenv" format, specified # [here](https://github.com/motdotla/dotenv#rules). # # If you don't set the field and the `garden..env` file does not exist, - # we simply ignore it. If you do override the default value and the file doesn't exist, an - # error will be thrown. + # we simply ignore it. If you do override the default value and the file doesn't exist, an error will be thrown. varfile: - # A key/value map of variables that modules can reference when using this environment. These - # take precedence over variables defined in the top-level `variables` field. + # A key/value map of variables that modules can reference when using this environment. These take precedence over + # variables defined in the top-level `variables` field. variables: {} # The name of the environment. name: # Flag the environment as a production environment. # - # Setting this flag to `true` will activate the protection on the `deploy`, `test`, `task`, - # `build`, - # and `dev` commands. A protected command will ask for a user confirmation every time is run - # agains + # Setting this flag to `true` will activate the protection on the `deploy`, `test`, `task`, `build`, + # and `dev` commands. A protected command will ask for a user confirmation every time is run agains # an environment marked as production. # Run the command with the "--yes" flag to skip the check (e.g. when running Garden in CI). # @@ -519,45 +497,50 @@ name: description: -# Specify a list of POSIX-style paths or globs that should be regarded as the source files for -# this -# module. Files that do *not* match these paths or globs are excluded when computing the version -# of the module, +# Set this to `true` to disable the module. You can use this with conditional template strings to +# disable modules based on, for example, the current environment or other variables (e.g. +# `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +# specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +# It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +# module (in which case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +# will automatically ignore those dependency declarations. Note however that template strings referencing the +# module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +# so you need to make sure to provide alternate values for those if you're using them, using conditional +# expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this +# module. Files that do *not* match these paths or globs are excluded when computing the version of the module, # when responding to filesystem watch events, and when staging builds. # -# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` -# files in your +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your # source tree, which use the same format as `.gitignore` files. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) -# for details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories) for details. # # Also note that specifying an empty list here means _no sources_ should be included. include: -# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. -# Files that -# match these paths or globs are excluded when computing the version of the module, when -# responding to filesystem +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that +# match these paths or globs are excluded when computing the version of the module, when responding to filesystem # watch events, and when staging builds. # -# Note that you can also explicitly _include_ files using the `include` field. If you also specify -# the -# `include` field, the files/patterns specified here are filtered from the files matched by -# `include`. See the +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the +# `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the # [Configuration Files -# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for -# details. +# guide](https://docs.garden.io/guides/configuration-files#including-excluding-files-and-directories)for details. # -# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on -# which files -# and directories are watched for changes. Use the project `modules.exclude` field to affect -# those, if you have +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files +# and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have # large directories that should not be watched for changes. exclude: -# A remote repository URL. Currently only supports git servers. Must contain a hash suffix -# pointing to a specific branch or tag, with the format: # +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # # # Garden will import the repository source code into this module, but read the module's # config from the local garden.yml file. @@ -576,8 +559,7 @@ build: copy: # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - # POSIX-style path or filename to copy the directory or file(s), relative to the build - # directory. + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. # Defaults to to same as source path. target: '' ``` @@ -633,6 +615,27 @@ name: "my-sweet-module" | -------- | -------- | | `string` | No | +#### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to +disable modules based on, for example, the current environment or other variables (e.g. +`disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for +specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. +It also means that the module is not built _unless_ it is declared as a build dependency by another enabled +module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden +will automatically ignore those dependency declarations. Note however that template strings referencing the +module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, +so you need to make sure to provide alternate values for those if you're using them, using conditional +expressions. + +| Type | Required | Default | +| --------- | -------- | ------- | +| `boolean` | No | `false` | + #### `include` Specify a list of POSIX-style paths or globs that should be regarded as the source files for this diff --git a/docs/reference/template-strings.md b/docs/reference/template-strings.md index 4289984e4e..2f3cce10ad 100644 --- a/docs/reference/template-strings.md +++ b/docs/reference/template-strings.md @@ -30,8 +30,7 @@ The following keys are available in any template strings within project definiti # Type: object # local: - # The absolute path to the directory where exported artifacts from test and task runs are - # stored. + # The absolute path to the directory where exported artifacts from test and task runs are stored. # # Type: string # @@ -39,8 +38,7 @@ local: # artifactsPath: - # A map of all local environment variables (see - # https://nodejs.org/api/process.html#process_process_env). + # A map of all local environment variables (see https://nodejs.org/api/process.html#process_process_env). # # Type: object # @@ -75,8 +73,7 @@ in `garden.yml` project config files: # Type: object # local: - # The absolute path to the directory where exported artifacts from test and task runs are - # stored. + # The absolute path to the directory where exported artifacts from test and task runs are stored. # # Type: string # @@ -84,8 +81,7 @@ local: # artifactsPath: - # A map of all local environment variables (see - # https://nodejs.org/api/process.html#process_process_env). + # A map of all local environment variables (see https://nodejs.org/api/process.html#process_process_env). # # Type: object # @@ -172,8 +168,7 @@ The following keys are available in template strings with module definitions in # Type: object # local: - # The absolute path to the directory where exported artifacts from test and task runs are - # stored. + # The absolute path to the directory where exported artifacts from test and task runs are stored. # # Type: string # @@ -181,8 +176,7 @@ local: # artifactsPath: - # A map of all local environment variables (see - # https://nodejs.org/api/process.html#process_process_env). + # A map of all local environment variables (see https://nodejs.org/api/process.html#process_process_env). # # Type: object # @@ -271,8 +265,8 @@ var: {} # modules: {} -# Runtime outputs and information from services and tasks (only resolved at runtime when deploying -# services and running tasks). +# Runtime outputs and information from services and tasks (only resolved at runtime when deploying services and +# running tasks). # # Type: object # diff --git a/docs/using-garden/adding-modules.md b/docs/using-garden/adding-modules.md index b28e803933..561cabe92c 100644 --- a/docs/using-garden/adding-modules.md +++ b/docs/using-garden/adding-modules.md @@ -105,6 +105,24 @@ type: container image: postgres:11.4-alpine ``` +## Advanced + +### Disabling Modules + +You can disable modules by setting `disabled: true` in the module config file. You can also disable it conditionally using template strings. For example, to disable a particular module for a specific environment, you could do something like this: + +```yaml +kind: Module +name: backend +description: Postgres DB container +type: container +disabled: ${environment.name == "prod"} +image: postgres:11.4-alpine +``` + +Disabling a module disables all services, tasks and tests defined in the module. +Note however, that if a disabled module is referenced as a build dependency of another module, the module will still be built when needed, to ensure the dependant module can be built as expected. + ## Further Reading * [Module type reference docs](../module-types/README.md). diff --git a/docs/using-garden/adding-services.md b/docs/using-garden/adding-services.md index 8e128928b8..14ac39d2ed 100644 --- a/docs/using-garden/adding-services.md +++ b/docs/using-garden/adding-services.md @@ -98,6 +98,24 @@ services: ## Advanced +### Disabling Services + +Module types that allow you to configure services generally also allow you to disable services by setting `disabled: true` in the service configuration. You can also disable them conditionally using template strings. For example, to disable a `container` module service for a specific environment, you could do something like this: + +```yaml +kind: Module +type: container +... +services: + - name: frontend + disabled: ${environment.name == "prod"} + ... +``` + +Services are also implicitly disabled when the parent module is disabled. + +### How Services Map to Kubernetes Resources + A container service maps to a Kubernetes [Deployment object](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/). If you specify a `port`, Garden will create a [Service object](https://kubernetes.io/docs/concepts/services-networking/service/) for the Deployment. And if you specify an `ingress`, Garden will create a corresponding Kubernetes [Ingress object](https://kubernetes.io/docs/concepts/services-networking/ingress/). By default the Kubernetes provider does a rolling update for deployments. diff --git a/docs/using-garden/running-tasks.md b/docs/using-garden/running-tasks.md index b901acb9ea..96c88d2bfd 100644 --- a/docs/using-garden/running-tasks.md +++ b/docs/using-garden/running-tasks.md @@ -43,7 +43,6 @@ Tasks correspond to a **run** action in the Stack Graph. ## Examples - ### Database Migration Below is an example of a Helm module that uses the `postgresql` Helm chart. The module has a task for initializing the database and another one for clearing it. In the example we use environment variables to set the password. Notice also that the tasks depend on the `postgres` service being deployed. @@ -112,6 +111,22 @@ After running `my-task`, you can find the contents of the `report` directory in Please look at individual [module type references](../module-types/README.md) to see how to configure each module type's tasks to extract artifacts after running them. +### Disabling Tasks + +Module types that allow you to configure tasks generally also allow you to disable tasks by setting `disabled: true` in the task configuration. You can also disable them conditionally using template strings. For example, to disable a `container` module task for a specific environment, you could do something like this: + +```yaml +kind: Module +type: container +... +tasks: + - name: database-reset + disabled: ${environment.name == "prod"} + ... +``` + +Tasks are also implicitly disabled when the parent module is disabled. + ### Kubernetes Provider The Kubernetes providers execute each task in its own Pod inside the project namespace. The Pod is removed once the task has finished running. diff --git a/docs/using-garden/running-tests.md b/docs/using-garden/running-tests.md index 868d6ebe32..bfa5b9bea1 100644 --- a/docs/using-garden/running-tests.md +++ b/docs/using-garden/running-tests.md @@ -109,6 +109,22 @@ After running `my-test`, you can find the contents of the `report` directory in Please look at individual [module type references](../module-types/README.md) to see how to configure each module type's tests to extract artifacts after running them. +### Disabling Tests + +Module types that allow you to configure tests generally also allow you to disable tests by setting `disabled: true` in the test configuration. You can also disable them conditionally using template strings. For example, to disable a `container` module test for a specific environment, you could do something like this: + +```yaml +kind: Module +type: container +... +tests: + - name: e2e + disabled: ${environment.name == "prod"} + ... +``` + +Tests are also implicitly disabled when the parent module is disabled. + ### Kubernetes Provider Tests are executed in their own Pod inside the project namespace. The Pod is removed once the test has finished running. diff --git a/garden-service/src/actions.ts b/garden-service/src/actions.ts index de70facd77..7a20745fa7 100644 --- a/garden-service/src/actions.ts +++ b/garden-service/src/actions.ts @@ -21,8 +21,6 @@ import { defaultProvider } from "./config/provider" import { ParameterError, PluginError, ConfigurationError, InternalError, RuntimeError } from "./exceptions" import { Garden } from "./garden" import { LogEntry } from "./logger/log-entry" -import { ProcessResults, processServices } from "./process" -import { getDependantTasksForModule } from "./tasks/helpers" import { Module } from "./types/module" import { PluginActionContextParams, @@ -98,6 +96,7 @@ import { realpath, writeFile } from "fs-extra" import { relative, join } from "path" import { getArtifactKey } from "./util/artifacts" import { AugmentGraphResult, AugmentGraphParams } from "./types/plugin/provider/augmentGraph" +import { DeployTask } from "./tasks/deploy" const maxArtifactLogLines = 5 // max number of artifacts to list in console after task+test runs @@ -533,7 +532,7 @@ export class ActionRouter implements TypeGuard { serviceNames?: string[] }): Promise { const graph = await this.garden.getConfigGraph(log) - const services = await graph.getServices(serviceNames) + const services = await graph.getServices({ names: serviceNames }) const tasks = services.map( (service) => @@ -550,32 +549,25 @@ export class ActionRouter implements TypeGuard { return getServiceStatuses(results) } - async deployServices({ - serviceNames, - force = false, - forceBuild = false, - log, - }: DeployServicesParams): Promise { + async deployServices({ serviceNames, force = false, forceBuild = false, log }: DeployServicesParams) { const graph = await this.garden.getConfigGraph(log) - const services = await graph.getServices(serviceNames) + const services = await graph.getServices({ names: serviceNames }) - return processServices({ - services, - garden: this.garden, - graph, - log, - watch: false, - handler: async (_, module) => - getDependantTasksForModule({ + const tasks = services.map( + (service) => + new DeployTask({ garden: this.garden, log, graph, - module, - hotReloadServiceNames: [], + service, force, forceBuild, - }), - }) + fromWatch: false, + hotReloadServiceNames: [], + }) + ) + + return this.garden.processTasks(tasks) } /** @@ -586,7 +578,7 @@ export class ActionRouter implements TypeGuard { const servicesLog = log.info({ msg: chalk.white("Deleting services..."), status: "active" }) - const services = await graph.getServices(names) + const services = await graph.getServices({ names }) const deleteResults = await this.garden.processTasks( services.map((service) => { diff --git a/garden-service/src/build-dir.ts b/garden-service/src/build-dir.ts index 8d92ff17d3..066a855147 100644 --- a/garden-service/src/build-dir.ts +++ b/garden-service/src/build-dir.ts @@ -121,7 +121,7 @@ export class BuildDir { return } - const sourceModule = await graph.getModule(getModuleKey(buildDepConfig.name, buildDepConfig.plugin)) + const sourceModule = await graph.getModule(getModuleKey(buildDepConfig.name, buildDepConfig.plugin), true) const sourceBuildPath = await this.buildPath(sourceModule) // Sync to the module's top-level dir by default. diff --git a/garden-service/src/commands/build.ts b/garden-service/src/commands/build.ts index ba1dfc42e4..9dfbf372f4 100644 --- a/garden-service/src/commands/build.ts +++ b/garden-service/src/commands/build.ts @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import Bluebird from "bluebird" import { BooleanParameter, Command, @@ -83,9 +84,13 @@ export class BuildCommand extends Command { await garden.clearBuilds() const graph = await garden.getConfigGraph(log) - const modules = await graph.getModules(args.modules) + const modules = await graph.getModules({ names: args.modules }) const moduleNames = modules.map((m) => m.name) + const initialTasks = flatten( + await Bluebird.map(modules, (module) => BuildTask.factory({ garden, log, module, force: opts.force })) + ) + const results = await processModules({ garden, graph: await garden.getConfigGraph(log), @@ -93,9 +98,9 @@ export class BuildCommand extends Command { footerLog, modules, watch: opts.watch, - handler: async (_, module) => BuildTask.factory({ garden, log, module, force: opts.force }), + initialTasks, changeHandler: async (newGraph, module) => { - const deps = await newGraph.getDependants("build", module.name, true) + const deps = await newGraph.getDependants({ nodeType: "build", name: module.name, recursive: true }) const tasks = [module] .concat(deps.build) .filter((m) => moduleNames.includes(m.name)) diff --git a/garden-service/src/commands/delete.ts b/garden-service/src/commands/delete.ts index 8ff27afca3..b093d6a935 100644 --- a/garden-service/src/commands/delete.ts +++ b/garden-service/src/commands/delete.ts @@ -132,7 +132,7 @@ export class DeleteServiceCommand extends Command { async action({ garden, log, headerLog, args }: CommandParams): Promise { const graph = await garden.getConfigGraph(log) - const services = await graph.getServices(args.services) + const services = await graph.getServices({ names: args.services }) if (services.length === 0) { log.warn({ msg: "No services found. Aborting." }) diff --git a/garden-service/src/commands/deploy.ts b/garden-service/src/commands/deploy.ts index 7a70fc9e92..cd64d21516 100644 --- a/garden-service/src/commands/deploy.ts +++ b/garden-service/src/commands/deploy.ts @@ -18,14 +18,15 @@ import { StringsParameter, PrepareParams, } from "./base" -import { getDependantTasksForModule } from "../tasks/helpers" +import { getModuleWatchTasks } from "../tasks/helpers" import { TaskResults } from "../task-graph" -import { processServices } from "../process" +import { processModules } from "../process" import { printHeader } from "../logger/util" -import { HotReloadTask } from "../tasks/hot-reload" import { BaseTask } from "../tasks/base" import { getHotReloadServiceNames, validateHotReloadServiceNames } from "./helpers" import { startServer, GardenServer } from "../server/server" +import { DeployTask } from "../tasks/deploy" +import { naturalList } from "../util/string" const deployArgs = { services: new StringsParameter({ @@ -103,18 +104,27 @@ export class DeployCommand extends Command { } const initGraph = await garden.getConfigGraph(log) - const services = await initGraph.getServices(args.services) + let services = await initGraph.getServices({ names: args.services, includeDisabled: true }) + + const disabled = services.filter((s) => s.disabled).map((s) => s.name) + + if (disabled.length > 0) { + log.info({ symbol: "info", msg: `Services ${naturalList(disabled)} are disabled` }) + } + + services = services.filter((s) => !s.disabled) if (services.length === 0) { - log.error({ msg: "No services found. Aborting." }) + log.error({ msg: "No services to deploy. Aborting." }) return { result: {} } } + const modules = Array.from(new Set(services.map((s) => s.module))) const hotReloadServiceNames = await getHotReloadServiceNames(opts["hot-reload"], initGraph) let watch: boolean if (hotReloadServiceNames.length > 0) { - await initGraph.getServices(hotReloadServiceNames) // validate the existence of these services + await initGraph.getServices({ names: hotReloadServiceNames }) // validate the existence of these services const errMsg = await validateHotReloadServiceNames(hotReloadServiceNames, initGraph) if (errMsg) { log.error({ msg: errMsg }) @@ -125,44 +135,40 @@ export class DeployCommand extends Command { watch = opts.watch } - const results = await processServices({ + const force = opts.force + const forceBuild = opts["force-build"] + + const initialTasks = services.map( + (service) => + new DeployTask({ + garden, + log, + graph: initGraph, + service, + force, + forceBuild, + fromWatch: false, + hotReloadServiceNames, + }) + ) + + const results = await processModules({ garden, graph: initGraph, log, footerLog, - services, + modules, + initialTasks, watch, - handler: async (graph, module) => - getDependantTasksForModule({ - garden, - graph, - log, - module, - fromWatch: false, - hotReloadServiceNames, - force: opts.force, - forceBuild: opts["force-build"], - }), changeHandler: async (graph, module) => { - const tasks: BaseTask[] = await getDependantTasksForModule({ + const tasks: BaseTask[] = await getModuleWatchTasks({ garden, graph, log, module, hotReloadServiceNames, - force: true, - forceBuild: opts["force-build"], - fromWatch: true, - includeDependants: true, }) - const hotReloadServices = await graph.getServices(hotReloadServiceNames) - const hotReloadTasks = hotReloadServices - .filter((service) => service.module.name === module.name || service.sourceModule.name === module.name) - .map((service) => new HotReloadTask({ garden, graph, log, service, force: true })) - - tasks.push(...hotReloadTasks) - return tasks }, }) diff --git a/garden-service/src/commands/dev.ts b/garden-service/src/commands/dev.ts index 3c0f03186c..adb3c7e918 100644 --- a/garden-service/src/commands/dev.ts +++ b/garden-service/src/commands/dev.ts @@ -15,8 +15,7 @@ import { flatten } from "lodash" import moment = require("moment") import { join } from "path" -import { BaseTask } from "../tasks/base" -import { getDependantTasksForModule } from "../tasks/helpers" +import { getModuleWatchTasks } from "../tasks/helpers" import { Command, CommandResult, @@ -30,10 +29,11 @@ import { STATIC_DIR } from "../constants" import { processModules } from "../process" import { Module } from "../types/module" import { getTestTasks } from "../tasks/test" -import { HotReloadTask } from "../tasks/hot-reload" import { ConfigGraph } from "../config-graph" import { getHotReloadServiceNames, validateHotReloadServiceNames } from "./helpers" import { GardenServer, startServer } from "../server/server" +import { BuildTask } from "../tasks/build" +import { DeployTask } from "../tasks/deploy" const ansiBannerPath = join(STATIC_DIR, "garden-banner-2.txt") @@ -59,11 +59,11 @@ const devOpts = { }), } -type Args = typeof devArgs -type Opts = typeof devOpts +export type DevCommandArgs = typeof devArgs +export type DevCommandOpts = typeof devOpts // TODO: allow limiting to certain modules and/or services -export class DevCommand extends Command { +export class DevCommand extends Command { name = "dev" help = "Starts the garden development console." protected = true @@ -90,7 +90,7 @@ export class DevCommand extends Command { private server: GardenServer - async prepare({ log, footerLog }: PrepareParams) { + async prepare({ log, footerLog }: PrepareParams) { // print ANSI banner image const data = await readFile(ansiBannerPath) log.info(data.toString()) @@ -102,15 +102,22 @@ export class DevCommand extends Command { return { persistent: true } } - async action({ garden, log, footerLog, opts }: CommandParams): Promise { + async action({ + garden, + log, + footerLog, + opts, + }: CommandParams): Promise { this.server.setGarden(garden) const graph = await garden.getConfigGraph(log) const modules = await graph.getModules() + const skipTests = opts["skip-tests"] + if (modules.length === 0) { footerLog && footerLog.setState({ msg: "" }) - log.info({ msg: "No modules found in project." }) + log.info({ msg: "No enabled modules found in project." }) log.info({ msg: "Aborting..." }) return {} } @@ -124,32 +131,70 @@ export class DevCommand extends Command { } } - const tasksForModule = (watch: boolean) => { - return async (updatedGraph: ConfigGraph, module: Module) => { - const tasks: BaseTask[] = [] - - if (watch) { - const hotReloadServices = await updatedGraph.getServices(hotReloadServiceNames) - const hotReloadTasks = hotReloadServices - .filter((service) => service.module.name === module.name || service.sourceModule.name === module.name) - .map( - (service) => - new HotReloadTask({ - garden, - graph: updatedGraph, - log, - service, - force: true, - }) - ) - - tasks.push(...hotReloadTasks) - } + const initialTasks = flatten( + await Bluebird.map(modules, async (module) => { + // Build the module (in case there are no tests, tasks or services here that need to be run) + const buildTasks = await BuildTask.factory({ + garden, + log, + module, + force: false, + }) + + // Run all tests in module + const testTasks = skipTests + ? [] + : await getTestTasks({ + garden, + graph, + log, + module, + force: false, + forceBuild: false, + }) + + // Deploy all enabled services in module + const services = await graph.getServices({ names: module.serviceNames, includeDisabled: true }) + const deployTasks = services + .filter((s) => !s.disabled) + .map( + (service) => + new DeployTask({ + garden, + log, + graph, + service, + force: false, + forceBuild: false, + fromWatch: false, + hotReloadServiceNames, + }) + ) - const testModules: Module[] = watch ? await updatedGraph.withDependantModules([module]) : [module] + return [...buildTasks, ...testTasks, ...deployTasks] + }) + ) - if (!opts["skip-tests"]) { + const results = await processModules({ + garden, + graph, + log, + footerLog, + modules, + watch: true, + initialTasks, + changeHandler: async (updatedGraph: ConfigGraph, module: Module) => { + const tasks = await getModuleWatchTasks({ + garden, + log, + graph: updatedGraph, + module, + hotReloadServiceNames, + }) + + if (!skipTests) { const filterNames = opts["test-names"] + const testModules: Module[] = await updatedGraph.withDependantModules([module]) tasks.push( ...flatten( await Bluebird.map(testModules, (m) => @@ -165,33 +210,8 @@ export class DevCommand extends Command { ) } - tasks.push( - ...(await getDependantTasksForModule({ - garden, - log, - graph: updatedGraph, - module, - fromWatch: watch, - hotReloadServiceNames, - force: watch, - forceBuild: false, - includeDependants: watch, - })) - ) - return tasks - } - } - - const results = await processModules({ - garden, - graph, - log, - footerLog, - modules, - watch: true, - handler: tasksForModule(false), - changeHandler: tasksForModule(true), + }, }) return handleTaskResults(footerLog, "dev", results) diff --git a/garden-service/src/commands/get/get-tasks.ts b/garden-service/src/commands/get/get-tasks.ts index 40307d2e05..6b84768f75 100644 --- a/garden-service/src/commands/get/get-tasks.ts +++ b/garden-service/src/commands/get/get-tasks.ts @@ -62,9 +62,9 @@ export class GetTasksCommand extends Command { async action({ args, garden, log }: CommandParams): Promise { const graph = await garden.getConfigGraph(log) - const tasks = await graph.getTasks(args.tasks) + const tasks = await graph.getTasks({ names: args.tasks }) const taskModuleNames = uniq(tasks.map((t) => t.module.name)) - const modules = sortBy(await graph.getModules(taskModuleNames), (m) => m.name) + const modules = sortBy(await graph.getModules({ names: taskModuleNames }), (m) => m.name) const taskListing: any[] = [] let logStr = "" diff --git a/garden-service/src/commands/helpers.ts b/garden-service/src/commands/helpers.ts index b6a4b501cf..bea22b7768 100644 --- a/garden-service/src/commands/helpers.ts +++ b/garden-service/src/commands/helpers.ts @@ -26,13 +26,19 @@ export async function validateHotReloadServiceNames( serviceNames: string[], configGraph: ConfigGraph ): Promise { - const services = await configGraph.getServices(serviceNames) - const invalidNames = services.filter((s) => !supportsHotReloading(s)).map((s) => s.name) - if (invalidNames.length > 0) { - return `The following requested services are not configured for hot reloading: ${invalidNames.join(", ")}` - } else { - return null + const services = await configGraph.getServices({ names: serviceNames, includeDisabled: true }) + + const notHotreloadable = services.filter((s) => !supportsHotReloading(s)).map((s) => s.name) + if (notHotreloadable.length > 0) { + return `The following requested services are not configured for hot reloading: ${notHotreloadable.join(", ")}` } + + const disabled = services.filter((s) => s.config.disabled).map((s) => s.name) + if (disabled.length > 0) { + return `The following requested services are disabled for the specified environment: ${disabled.join(", ")}` + } + + return null } function supportsHotReloading(service: Service) { diff --git a/garden-service/src/commands/logs.ts b/garden-service/src/commands/logs.ts index adb0b1b5aa..908fde883e 100644 --- a/garden-service/src/commands/logs.ts +++ b/garden-service/src/commands/logs.ts @@ -65,7 +65,7 @@ export class LogsCommand extends Command { async action({ garden, log, args, opts }: CommandParams): Promise> { const { follow, tail } = opts const graph = await garden.getConfigGraph(log) - const services = await graph.getServices(args.services) + const services = await graph.getServices({ names: args.services }) const serviceNames = services.map((s) => s.name).filter(Boolean) const maxServiceName = (maxBy(serviceNames, (serviceName) => serviceName.length) || "").length diff --git a/garden-service/src/commands/publish.ts b/garden-service/src/commands/publish.ts index 49e312b350..4c026134e5 100644 --- a/garden-service/src/commands/publish.ts +++ b/garden-service/src/commands/publish.ts @@ -65,7 +65,7 @@ export class PublishCommand extends Command { printHeader(headerLog, "Publish modules", "rocket") const graph = await garden.getConfigGraph(log) - const modules = await graph.getModules(args.modules) + const modules = await graph.getModules({ names: args.modules }) const results = await publishModules(garden, log, modules, !!opts["force-build"], !!opts["allow-dirty"]) diff --git a/garden-service/src/commands/run/module.ts b/garden-service/src/commands/run/module.ts index c89cebbd0c..5b0c079d8e 100644 --- a/garden-service/src/commands/run/module.ts +++ b/garden-service/src/commands/run/module.ts @@ -93,7 +93,7 @@ export class RunModuleCommand extends Command { }) await garden.processTasks(buildTasks) - const dependencies = await graph.getDependencies("build", module.name, false) + const dependencies = await graph.getDependencies({ nodeType: "build", name: module.name, recursive: false }) const runtimeContext = await prepareRuntimeContext({ garden, diff --git a/garden-service/src/commands/run/service.ts b/garden-service/src/commands/run/service.ts index 32923960d6..cd192d3dc9 100644 --- a/garden-service/src/commands/run/service.ts +++ b/garden-service/src/commands/run/service.ts @@ -15,6 +15,8 @@ import { printHeader } from "../../logger/util" import { DeployTask } from "../../tasks/deploy" import { getServiceStatuses, getRunTaskResults } from "../../tasks/base" import { prepareRuntimeContext } from "../../runtime-context" +import { deline } from "../../util/string" +import { CommandError } from "../../exceptions" const runArgs = { service: new StringParameter({ @@ -24,7 +26,12 @@ const runArgs = { } const runOpts = { - "force-build": new BooleanParameter({ help: "Force rebuild of module." }), + "force": new BooleanParameter({ + help: "Run the service even if it's disabled for the environment.", + }), + "force-build": new BooleanParameter({ + help: "Force rebuild of module.", + }), } type Args = typeof runArgs @@ -51,9 +58,20 @@ export class RunServiceCommand extends Command { async action({ garden, log, headerLog, args, opts }: CommandParams): Promise> { const serviceName = args.service const graph = await garden.getConfigGraph(log) - const service = await graph.getService(serviceName) + const service = await graph.getService(serviceName, true) const module = service.module + if (service.disabled && !opts.force) { + throw new CommandError( + chalk.red(deline` + Service ${chalk.redBright(service.name)} is disabled for the ${chalk.redBright(garden.environmentName)} + environment. If you're sure you want to run it anyway, please run the command again with the + ${chalk.redBright("--force")} flag. + `), + { serviceName: service.name, environmentName: garden.environmentName } + ) + } + printHeader(headerLog, `Running service ${chalk.cyan(serviceName)} in module ${chalk.cyan(module.name)}`, "runner") const actions = await garden.getActionRouter() @@ -69,7 +87,7 @@ export class RunServiceCommand extends Command { }) const dependencyResults = await garden.processTasks(await deployTask.getDependencies()) - const dependencies = await graph.getDependencies("deploy", serviceName, false) + const dependencies = await graph.getDependencies({ nodeType: "deploy", name: serviceName, recursive: false }) const serviceStatuses = getServiceStatuses(dependencyResults) const taskResults = getRunTaskResults(dependencyResults) diff --git a/garden-service/src/commands/run/task.ts b/garden-service/src/commands/run/task.ts index 3f2507132f..63fba35c58 100644 --- a/garden-service/src/commands/run/task.ts +++ b/garden-service/src/commands/run/task.ts @@ -8,10 +8,11 @@ import chalk from "chalk" import { BooleanParameter, Command, CommandParams, StringParameter, CommandResult } from "../base" -import dedent = require("dedent") import { TaskTask } from "../../tasks/task" import { TaskResult } from "../../task-graph" import { printHeader, printFooter } from "../../logger/util" +import { CommandError } from "../../exceptions" +import { dedent, deline } from "../../util/string" const runArgs = { task: new StringParameter({ @@ -21,7 +22,12 @@ const runArgs = { } const runOpts = { - "force-build": new BooleanParameter({ help: "Force rebuild of module before running." }), + "force": new BooleanParameter({ + help: "Run the task even if it's disabled for the environment.", + }), + "force-build": new BooleanParameter({ + help: "Force rebuild of module before running.", + }), } type Args = typeof runArgs @@ -52,7 +58,18 @@ export class RunTaskCommand extends Command { opts, }: CommandParams): Promise> { const graph = await garden.getConfigGraph(log) - const task = await graph.getTask(args.task) + const task = await graph.getTask(args.task, true) + + if (task.disabled && !opts.force) { + throw new CommandError( + chalk.red(deline` + Task ${chalk.redBright(task.name)} is disabled for the ${chalk.redBright(garden.environmentName)} + environment. If you're sure you want to run it anyway, please run the command again with the + ${chalk.redBright("--force")} flag. + `), + { moduleName: task.module.name, taskName: task.name, environmentName: garden.environmentName } + ) + } const msg = `Running task ${chalk.white(task.name)}` diff --git a/garden-service/src/commands/run/test.ts b/garden-service/src/commands/run/test.ts index 66e4d9adba..eaefbf4850 100644 --- a/garden-service/src/commands/run/test.ts +++ b/garden-service/src/commands/run/test.ts @@ -7,16 +7,17 @@ */ import chalk from "chalk" -import { ParameterError } from "../../exceptions" +import { ParameterError, CommandError } from "../../exceptions" import { RunResult } from "../../types/plugin/base" import { findByName, getNames } from "../../util/util" import { BooleanParameter, Command, CommandParams, CommandResult, StringParameter } from "../base" import { printRuntimeContext } from "./run" -import dedent = require("dedent") import { prepareRuntimeContext } from "../../runtime-context" import { printHeader } from "../../logger/util" -import { getTestVersion, TestTask } from "../../tasks/test" +import { TestTask } from "../../tasks/test" import { getRunTaskResults, getServiceStatuses } from "../../tasks/base" +import { dedent, deline } from "../../util/string" +import { testFromConfig } from "../../types/test" const runArgs = { module: new StringParameter({ @@ -36,6 +37,9 @@ const runOpts = { cliDefault: true, cliOnly: true, }), + "force": new BooleanParameter({ + help: "Run the test even if it's disabled for the environment.", + }), "force-build": new BooleanParameter({ help: "Force rebuild of module before running.", }), @@ -65,7 +69,7 @@ export class RunTestCommand extends Command { const testName = args.test const graph = await garden.getConfigGraph(log) - const module = await graph.getModule(moduleName) + const module = await graph.getModule(moduleName, true) const testConfig = findByName(module.testConfigs, testName) @@ -77,13 +81,25 @@ export class RunTestCommand extends Command { }) } + const test = testFromConfig(module, testConfig) + + if ((module.disabled || test.disabled) && !opts.force) { + throw new CommandError( + chalk.red(deline` + Test ${chalk.redBright(`${module.name}.${test.name}`)} is disabled for the + ${chalk.redBright(garden.environmentName)} environment. If you're sure you want to run it anyway, + please run the command again with the ${chalk.redBright("--force")} flag. + `), + { moduleName: module.name, testName: test.name, environmentName: garden.environmentName } + ) + } + printHeader(headerLog, `Running test ${chalk.cyan(testName)} in module ${chalk.cyan(moduleName)}`, "runner") const actions = await garden.getActionRouter() - const version = await getTestVersion(garden, graph, module, testConfig) // Make sure all dependencies are ready and collect their outputs for the runtime context - const testTask = new TestTask({ + const testTask = await TestTask.factory({ force: true, forceBuild: opts["force-build"], garden, @@ -91,12 +107,11 @@ export class RunTestCommand extends Command { log, module, testConfig, - version, }) const dependencyResults = await garden.processTasks(await testTask.getDependencies()) const interactive = opts.interactive - const dependencies = await graph.getDependencies("test", testConfig.name, false) + const dependencies = await graph.getDependencies({ nodeType: "test", name: test.name, recursive: false }) const serviceStatuses = getServiceStatuses(dependencyResults) const taskResults = getRunTaskResults(dependencyResults) @@ -119,7 +134,7 @@ export class RunTestCommand extends Command { runtimeContext, silent: false, testConfig, - testVersion: version, + testVersion: testTask.version, }) return { result } diff --git a/garden-service/src/commands/test.ts b/garden-service/src/commands/test.ts index b1f18805eb..b84b34fbfd 100644 --- a/garden-service/src/commands/test.ts +++ b/garden-service/src/commands/test.ts @@ -103,35 +103,37 @@ export class TestCommand extends Command { const graph = await garden.getConfigGraph(log) - let modules: Module[] - if (args.modules) { - modules = await graph.withDependantModules(await graph.getModules(args.modules)) - } else { - // All modules are included in this case, so there's no need to compute dependants. - modules = await graph.getModules() - } + const modules: Module[] = args.modules + ? await graph.withDependantModules(await graph.getModules({ names: args.modules })) + : // All modules are included in this case, so there's no need to compute dependants. + await graph.getModules() const filterNames = opts.name ? [opts.name] : [] const force = opts.force const forceBuild = opts["force-build"] - const results = await processModules({ - garden, - graph, - log, - footerLog, - modules, - watch: opts.watch, - handler: async (updatedGraph, module) => + const initialTasks = flatten( + await Bluebird.map(modules, (module) => getTestTasks({ garden, log, - graph: updatedGraph, + graph, module, filterNames, force, forceBuild, - }), + }) + ) + ) + + const results = await processModules({ + garden, + graph, + log, + footerLog, + modules, + initialTasks, + watch: opts.watch, changeHandler: async (updatedGraph, module) => { const modulesToProcess = await updatedGraph.withDependantModules([module]) return flatten( diff --git a/garden-service/src/commands/update-remote/modules.ts b/garden-service/src/commands/update-remote/modules.ts index 47e97dbc0a..d6e6ccba1d 100644 --- a/garden-service/src/commands/update-remote/modules.ts +++ b/garden-service/src/commands/update-remote/modules.ts @@ -59,7 +59,7 @@ export async function updateRemoteModules({ }): Promise> { const { modules: moduleNames } = args const graph = await garden.getConfigGraph(log) - const modules = await graph.getModules(moduleNames) + const modules = await graph.getModules({ names: moduleNames }) const moduleSources = ( modules.filter(hasRemoteSource).filter((src) => (moduleNames ? moduleNames.includes(src.name) : true)) diff --git a/garden-service/src/config-graph.ts b/garden-service/src/config-graph.ts index 72deeb3ced..f71cef0d55 100644 --- a/garden-service/src/config-graph.ts +++ b/garden-service/src/config-graph.ts @@ -8,7 +8,7 @@ import Bluebird from "bluebird" import toposort from "toposort" -import { flatten, pick, uniq, find, sortBy } from "lodash" +import { flatten, pick, uniq, find, sortBy, pickBy } from "lodash" import { Garden } from "./garden" import { BuildDependencyConfig, ModuleConfig } from "./config/module" import { Module, getModuleKey, moduleFromConfig, moduleNeedsBuild } from "./types/module" @@ -61,6 +61,14 @@ export interface RenderedNode { type DepNodeTaskTypeMap = { [key in DependencyGraphNodeType]: TaskType } +type EntityConfig = ServiceConfig | TaskConfig | TestConfig + +interface EntityConfigEntry { + type: T + moduleKey: string + config: C +} + /** * A graph data structure that facilitates querying (recursive or non-recursive) of the project's dependency and * dependant relationships. @@ -72,13 +80,13 @@ export class ConfigGraph { private moduleConfigs: { [key: string]: ModuleConfig } private serviceConfigs: { - [key: string]: { moduleKey: string; config: ServiceConfig } + [key: string]: EntityConfigEntry<"service", ServiceConfig> } private taskConfigs: { - [key: string]: { moduleKey: string; config: TaskConfig } + [key: string]: EntityConfigEntry<"task", TaskConfig> } private testConfigs: { - [key: string]: { moduleKey: string; config: TestConfig } + [key: string]: EntityConfigEntry<"test", TestConfig> } constructor(private garden: Garden, moduleConfigs: ModuleConfig[], moduleTypes: ModuleTypeMap) { @@ -89,6 +97,7 @@ export class ConfigGraph { this.taskConfigs = {} this.testConfigs = {} + // Add nodes to graph and validate for (const moduleConfig of moduleConfigs) { const moduleKey = this.keyForModule(moduleConfig) this.moduleConfigs[moduleKey] = moduleConfig @@ -125,7 +134,7 @@ export class ConfigGraph { }) } - this.serviceConfigs[serviceName] = { moduleKey, config: serviceConfig } + this.serviceConfigs[serviceName] = { type: "service", moduleKey, config: serviceConfig } } // Add tasks @@ -151,12 +160,13 @@ export class ConfigGraph { ) } - this.taskConfigs[taskName] = { moduleKey, config: taskConfig } + this.taskConfigs[taskName] = { type: "task", moduleKey, config: taskConfig } } } this.validateDependencies() + // Add relations between nodes for (const moduleConfig of moduleConfigs) { const type = moduleTypes[moduleConfig.type] const needsBuild = moduleNeedsBuild(moduleConfig, type) @@ -167,7 +177,12 @@ export class ConfigGraph { const addBuildDeps = (node: DependencyGraphNode) => { for (const buildDep of moduleConfig.build.dependencies) { const buildDepKey = getModuleKey(buildDep.name, buildDep.plugin) - this.addRelation(node, "build", buildDepKey, buildDepKey) + this.addRelation({ + dependant: node, + dependencyType: "build", + dependencyName: buildDepKey, + dependencyModuleName: buildDepKey, + }) } } @@ -181,18 +196,19 @@ export class ConfigGraph { if (needsBuild) { // The service needs its own module to be built - this.addRelation(serviceNode, "build", moduleKey, moduleKey) + this.addRelation({ + dependant: serviceNode, + dependencyType: "build", + dependencyName: moduleKey, + dependencyModuleName: moduleKey, + }) } else { // No build needed for the module, but the service needs the module's build dependencies to be built (if any). addBuildDeps(serviceNode) } for (const depName of serviceConfig.dependencies) { - if (this.serviceConfigs[depName]) { - this.addRelation(serviceNode, "deploy", depName, this.serviceConfigs[depName].moduleKey) - } else { - this.addRelation(serviceNode, "run", depName, this.taskConfigs[depName].moduleKey) - } + this.addRuntimeRelation(serviceNode, depName) } } @@ -202,18 +218,19 @@ export class ConfigGraph { if (needsBuild) { // The task needs its own module to be built - this.addRelation(taskNode, "build", moduleKey, moduleKey) + this.addRelation({ + dependant: taskNode, + dependencyType: "build", + dependencyName: moduleKey, + dependencyModuleName: moduleKey, + }) } else { // No build needed for the module, but the task needs the module's build dependencies to be built (if any). addBuildDeps(taskNode) } for (const depName of taskConfig.dependencies) { - if (this.serviceConfigs[depName]) { - this.addRelation(taskNode, "deploy", depName, this.serviceConfigs[depName].moduleKey) - } else { - this.addRelation(taskNode, "run", depName, this.taskConfigs[depName].moduleKey) - } + this.addRuntimeRelation(taskNode, depName) } } @@ -221,24 +238,25 @@ export class ConfigGraph { for (const testConfig of moduleConfig.testConfigs) { const testConfigName = makeTestTaskName(moduleConfig.name, testConfig.name) - this.testConfigs[testConfigName] = { moduleKey, config: testConfig } + this.testConfigs[testConfigName] = { type: "test", moduleKey, config: testConfig } const testNode = this.getNode("test", testConfigName, moduleKey) if (needsBuild) { // The test needs its own module to be built - this.addRelation(testNode, "build", moduleKey, moduleKey) + this.addRelation({ + dependant: testNode, + dependencyType: "build", + dependencyName: moduleKey, + dependencyModuleName: moduleKey, + }) } else { // No build needed for the module, but the test needs the module's build dependencies to be built (if any). addBuildDeps(testNode) } for (const depName of testConfig.dependencies) { - if (this.serviceConfigs[depName]) { - this.addRelation(testNode, "deploy", depName, this.serviceConfigs[depName].moduleKey) - } else { - this.addRelation(testNode, "run", depName, this.taskConfigs[depName].moduleKey) - } + this.addRuntimeRelation(testNode, depName) } } } @@ -257,32 +275,56 @@ export class ConfigGraph { ) } + private addRuntimeRelation(node: DependencyGraphNode, depName: string) { + const dep = this.serviceConfigs[depName] || this.taskConfigs[depName] + + // Ignore runtime dependencies on disabled tasks/services + if (this.isDisabled(dep)) { + return + } + + const depType = dep.type === "service" ? "deploy" : "run" + + this.addRelation({ + dependant: node, + dependencyType: depType, + dependencyName: depName, + dependencyModuleName: dep.moduleKey, + }) + } + + private isDisabled(dep: EntityConfigEntry) { + const moduleConfig = this.moduleConfigs[dep.moduleKey] + return moduleConfig.disabled || dep.config.disabled + } + /** * Returns the Service with the specified name. Throws error if it doesn't exist. */ - async getModule(name: string): Promise { - return (await this.getModules([name]))[0] + async getModule(name: string, includeDisabled?: boolean): Promise { + return (await this.getModules({ names: [name], includeDisabled }))[0] } /** * Returns the Service with the specified name. Throws error if it doesn't exist. */ - async getService(name: string): Promise { - return (await this.getServices([name]))[0] + async getService(name: string, includeDisabled?: boolean): Promise { + return (await this.getServices({ names: [name], includeDisabled }))[0] } /** * Returns the Task with the specified name. Throws error if it doesn't exist. */ - async getTask(name: string): Promise { - return (await this.getTasks([name]))[0] + async getTask(name: string, includeDisabled?: boolean): Promise { + return (await this.getTasks({ names: [name], includeDisabled }))[0] } /* Returns all modules defined in this configuration graph, or the ones specified. */ - async getModules(names?: string[]): Promise { - const configs = Object.values(names ? pickKeys(this.moduleConfigs, names, "module") : this.moduleConfigs) + async getModules({ names, includeDisabled = false }: { names?: string[]; includeDisabled?: boolean } = {}) { + const moduleConfigs = includeDisabled ? this.moduleConfigs : pickBy(this.moduleConfigs, (c) => !c.disabled) + const configs = Object.values(names ? pickKeys(moduleConfigs, names, "module") : moduleConfigs) return Bluebird.map(configs, (config) => moduleFromConfig(this.garden, this, config)) } @@ -290,19 +332,26 @@ export class ConfigGraph { /* Returns all services defined in this configuration graph, or the ones specified. */ - async getServices(names?: string[]): Promise { - const entries = Object.values(names ? pickKeys(this.serviceConfigs, names, "service") : this.serviceConfigs) + async getServices({ names, includeDisabled = false }: { names?: string[]; includeDisabled?: boolean } = {}) { + const serviceConfigs = includeDisabled + ? this.serviceConfigs + : pickBy(this.serviceConfigs, (s) => !this.isDisabled(s)) + + const configs = Object.values(names ? pickKeys(serviceConfigs, names, "service") : serviceConfigs) - return Bluebird.map(entries, async (e) => serviceFromConfig(this, await this.getModule(e.moduleKey), e.config)) + return Bluebird.map(configs, async (c) => + serviceFromConfig(this, await this.getModule(c.moduleKey, true), c.config) + ) } /* Returns all tasks defined in this configuration graph, or the ones specified. */ - async getTasks(names?: string[]): Promise { - const entries = Object.values(names ? pickKeys(this.taskConfigs, names, "task") : this.taskConfigs) + async getTasks({ names, includeDisabled = false }: { names?: string[]; includeDisabled?: boolean } = {}) { + const taskConfigs = includeDisabled ? this.taskConfigs : pickBy(this.taskConfigs, (t) => !this.isDisabled(t)) + const configs = Object.values(names ? pickKeys(taskConfigs, names, "task") : taskConfigs) - return Bluebird.map(entries, async (e) => taskFromConfig(await this.getModule(e.moduleKey), e.config)) + return Bluebird.map(configs, async (c) => taskFromConfig(await this.getModule(c.moduleKey, true), c.config)) } /* @@ -313,24 +362,20 @@ export class ConfigGraph { /** * Returns the set union of modules with the set union of their dependants (across all dependency types, recursively). */ - async withDependantModules(modules: Module[], filterFn?: DependencyRelationFilterFn): Promise { - const dependants = flatten(await Bluebird.map(modules, (m) => this.getDependantsForModule(m, filterFn))) + async withDependantModules(modules: Module[]): Promise { + const dependants = flatten(await Bluebird.map(modules, (m) => this.getDependantsForModule(m, true))) // We call getModules to ensure that the returned modules have up-to-date versions. const dependantModules = await this.modulesForRelations(await this.mergeRelations(...dependants)) - return this.getModules(uniq(modules.concat(dependantModules).map((m) => m.name))) + return this.getModules({ names: uniq(modules.concat(dependantModules).map((m) => m.name)), includeDisabled: true }) } /** - * Returns all build and runtime dependants of module and its services & tasks (recursively). + * Returns all build and runtime dependants of a module and its services & tasks (recursively). + * Includes the services and tasks contained in the given module, but does _not_ contain the build node for the + * module itself. */ - async getDependantsForModule(module: Module, filterFn?: DependencyRelationFilterFn): Promise { - return this.mergeRelations( - ...(await Bluebird.all([ - this.getDependants("build", module.name, true, filterFn), - this.getDependantsForMany("deploy", module.serviceNames, true, filterFn), - this.getDependantsForMany("run", module.taskNames, true, filterFn), - ])) - ) + async getDependantsForModule(module: Module, recursive: boolean): Promise { + return this.getDependants({ nodeType: "build", name: module.name, recursive }) } /** @@ -340,13 +385,18 @@ export class ConfigGraph { * * If recursive = true, also includes those dependencies' dependencies, etc. */ - async getDependencies( - nodeType: DependencyGraphNodeType, - name: string, - recursive: boolean, + async getDependencies({ + nodeType, + name, + recursive, + filterFn, + }: { + nodeType: DependencyGraphNodeType + name: string + recursive: boolean filterFn?: DependencyRelationFilterFn - ): Promise { - return this.toRelations(this.getDependencyNodes(nodeType, name, recursive, filterFn)) + }): Promise { + return this.toRelations(this.getDependencyNodes({ nodeType, name, recursive, filterFn })) } /** @@ -356,39 +406,58 @@ export class ConfigGraph { * * If recursive = true, also includes those dependants' dependants, etc. */ - async getDependants( - nodeType: DependencyGraphNodeType, - name: string, - recursive: boolean, + async getDependants({ + nodeType, + name, + recursive, + filterFn, + }: { + nodeType: DependencyGraphNodeType + name: string + recursive: boolean filterFn?: DependencyRelationFilterFn - ): Promise { - return this.toRelations(this.getDependantNodes(nodeType, name, recursive, filterFn)) + }): Promise { + return this.toRelations(this.getDependantNodes({ nodeType, name, recursive, filterFn })) } /** * Same as getDependencies above, but returns the set union of the dependencies of the nodes in the graph * having type = nodeType and name = name (computed recursively or shallowly for all). */ - async getDependenciesForMany( - nodeType: DependencyGraphNodeType, - names: string[], - recursive: boolean, + async getDependenciesForMany({ + nodeType, + names, + recursive, + filterFn, + }: { + nodeType: DependencyGraphNodeType + names: string[] + recursive: boolean filterFn?: DependencyRelationFilterFn - ): Promise { - return this.toRelations(flatten(names.map((name) => this.getDependencyNodes(nodeType, name, recursive, filterFn)))) + }): Promise { + return this.toRelations( + flatten(names.map((name) => this.getDependencyNodes({ nodeType, name, recursive, filterFn }))) + ) } /** * Same as getDependants above, but returns the set union of the dependants of the nodes in the graph * having type = nodeType and name = name (computed recursively or shallowly for all). */ - async getDependantsForMany( - nodeType: DependencyGraphNodeType, - names: string[], - recursive: boolean, + async getDependantsForMany({ + nodeType, + names, + recursive, + filterFn, + }: { + nodeType: DependencyGraphNodeType + names: string[] + recursive: boolean filterFn?: DependencyRelationFilterFn - ): Promise { - return this.toRelations(flatten(names.map((name) => this.getDependantNodes(nodeType, name, recursive, filterFn)))) + }): Promise { + return this.toRelations( + flatten(names.map((name) => this.getDependantNodes({ nodeType, name, recursive, filterFn }))) + ) } /** @@ -411,17 +480,17 @@ export class ConfigGraph { /** * Returns the (unique by name) list of modules represented in relations. */ - async modulesForRelations(relations: DependencyRelations): Promise { + private async modulesForRelations(relations: DependencyRelations): Promise { const moduleNames = uniq( flatten([ relations.build, relations.deploy.map((s) => s.module), relations.run.map((w) => w.module), - await this.getModules(relations.test.map((t) => this.testConfigs[t.name].moduleKey)), + await this.getModules({ names: relations.test.map((t) => this.testConfigs[t.name].moduleKey) }), ]).map((m) => m.name) ) // We call getModules to ensure that the returned modules have up-to-date versions. - return this.getModules(moduleNames) + return this.getModules({ names: moduleNames, includeDisabled: true }) } /** @@ -433,15 +502,17 @@ export class ConfigGraph { runtimeDependencies: string[] ): Promise { const moduleNames = buildDependencies.map((d) => getModuleKey(d.name, d.plugin)) - const serviceNames = runtimeDependencies.filter((d) => this.serviceConfigs[d]) - const taskNames = runtimeDependencies.filter((d) => this.taskConfigs[d]) + const serviceNames = runtimeDependencies.filter( + (d) => this.serviceConfigs[d] && !this.isDisabled(this.serviceConfigs[d]) + ) + const taskNames = runtimeDependencies.filter((d) => this.taskConfigs[d] && !this.isDisabled(this.taskConfigs[d])) - const buildDeps = await this.getDependenciesForMany("build", moduleNames, true) - const serviceDeps = await this.getDependenciesForMany("deploy", serviceNames, true) - const taskDeps = await this.getDependenciesForMany("run", taskNames, true) + const buildDeps = await this.getDependenciesForMany({ nodeType: "build", names: moduleNames, recursive: true }) + const serviceDeps = await this.getDependenciesForMany({ nodeType: "deploy", names: serviceNames, recursive: true }) + const taskDeps = await this.getDependenciesForMany({ nodeType: "run", names: taskNames, recursive: true }) const modules = [ - ...(await this.getModules(moduleNames)), + ...(await this.getModules({ names: moduleNames, includeDisabled: true })), ...(await this.modulesForRelations(await this.mergeRelations(buildDeps, serviceDeps, taskDeps))), ] @@ -459,19 +530,24 @@ export class ConfigGraph { private async relationsFromNames(names: DependencyRelationNames): Promise { return Bluebird.props({ - build: this.getModules(names.build), - deploy: this.getServices(names.deploy), - run: this.getTasks(names.run), + build: this.getModules({ names: names.build, includeDisabled: true }), + deploy: this.getServices({ names: names.deploy, includeDisabled: true }), + run: this.getTasks({ names: names.run, includeDisabled: true }), test: Object.values(pick(this.testConfigs, names.test)).map((t) => t.config), }) } - private getDependencyNodes( - nodeType: DependencyGraphNodeType, - name: string, - recursive: boolean, + private getDependencyNodes({ + nodeType, + name, + recursive, + filterFn, + }: { + nodeType: DependencyGraphNodeType + name: string + recursive: boolean filterFn?: DependencyRelationFilterFn - ): DependencyGraphNode[] { + }): DependencyGraphNode[] { const node = this.dependencyGraph[nodeKey(nodeType, name)] if (node) { if (recursive) { @@ -484,12 +560,17 @@ export class ConfigGraph { } } - private getDependantNodes( - nodeType: DependencyGraphNodeType, - name: string, - recursive: boolean, + private getDependantNodes({ + nodeType, + name, + recursive, + filterFn, + }: { + nodeType: DependencyGraphNodeType + name: string + recursive: boolean filterFn?: DependencyRelationFilterFn - ): DependencyGraphNode[] { + }): DependencyGraphNode[] { const node = this.dependencyGraph[nodeKey(nodeType, name)] if (node) { if (recursive) { @@ -507,12 +588,17 @@ export class ConfigGraph { } // Idempotent. - private addRelation( - dependant: DependencyGraphNode, - dependencyType: DependencyGraphNodeType, - dependencyName: string, + private addRelation({ + dependant, + dependencyType, + dependencyName, + dependencyModuleName, + }: { + dependant: DependencyGraphNode + dependencyType: DependencyGraphNodeType + dependencyName: string dependencyModuleName: string - ) { + }) { const dependency = this.getNode(dependencyType, dependencyName, dependencyModuleName) dependant.addDependency(dependency) dependency.addDependant(dependant) diff --git a/garden-service/src/config/base.ts b/garden-service/src/config/base.ts index de78d1c0cd..ead9017ab6 100644 --- a/garden-service/src/config/base.ts +++ b/garden-service/src/config/base.ts @@ -140,6 +140,7 @@ export function prepareModuleResource( }, configPath, description: spec.description, + disabled: spec.disabled, include: spec.include, exclude: spec.exclude, name: spec.name, diff --git a/garden-service/src/config/module.ts b/garden-service/src/config/module.ts index 3c6f64f492..c1cb603449 100644 --- a/garden-service/src/config/module.ts +++ b/garden-service/src/config/module.ts @@ -7,7 +7,7 @@ */ import stableStringify = require("json-stable-stringify") -import { ServiceConfig, ServiceSpec, serviceConfigSchema } from "./service" +import { ServiceConfig, serviceConfigSchema } from "./service" import { joiArray, joiIdentifier, @@ -19,8 +19,8 @@ import { joi, includeGuideLink, } from "./common" -import { TestConfig, TestSpec, testConfigSchema } from "./test" -import { TaskConfig, TaskSpec, taskConfigSchema } from "./task" +import { TestConfig, testConfigSchema } from "./test" +import { TaskConfig, taskConfigSchema } from "./task" import { DEFAULT_API_VERSION } from "../constants" import { joiVariables } from "./common" import { dedent } from "../util/string" @@ -77,21 +77,22 @@ export interface ModuleSpec {} export interface AddModuleSpec { apiVersion?: string - name: string - path: string allowPublish?: boolean build?: BaseBuildSpec description?: string - include?: string[] exclude?: string[] - type: string + include?: string[] + name: string + path: string repositoryUrl?: string + type: string } export interface BaseModuleSpec extends AddModuleSpec { apiVersion: string allowPublish: boolean build: BaseBuildSpec + disabled: boolean } export const baseBuildSpecSchema = joi @@ -134,6 +135,27 @@ export const coreModuleSpecSchema = joi // These fields may be resolved later in the process, and allow for usage of template strings export const baseModuleSpecSchema = coreModuleSpecSchema.keys({ description: joi.string(), + disabled: joi + .boolean() + .default(false) + .description( + dedent` + Set this to \`true\` to disable the module. You can use this with conditional template strings to + disable modules based on, for example, the current environment or other variables (e.g. + \`disabled: \${environment.name == "prod"}\`). This can be handy when you only need certain modules for + specific environments, e.g. only for development. + + Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. + It also means that the module is not built _unless_ it is declared as a build dependency by another enabled + module (in which case building this module is necessary for the dependant to be built). + + If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden + will automatically ignore those dependency declarations. Note however that template strings referencing the + module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, + so you need to make sure to provide alternate values for those if you're using them, using conditional + expressions. + ` + ), include: joi .array() .items( @@ -190,12 +212,8 @@ export const baseModuleSpecSchema = coreModuleSpecSchema.keys({ build: baseBuildSpecSchema.unknown(true), }) -export interface ModuleConfig< - M extends ModuleSpec = any, - S extends ServiceSpec = any, - T extends TestSpec = any, - W extends TaskSpec = any -> extends BaseModuleSpec { +export interface ModuleConfig + extends BaseModuleSpec { outputs: PrimitiveMap path: string configPath?: string diff --git a/garden-service/src/config/service.ts b/garden-service/src/config/service.ts index 2383d3a484..4bbb899a56 100644 --- a/garden-service/src/config/service.ts +++ b/garden-service/src/config/service.ts @@ -6,18 +6,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import deline = require("deline") import { joiIdentifier, joiArray, joiUserIdentifier, joi, joiVariables } from "./common" - -export interface ServiceSpec {} +import { deline, dedent } from "../util/string" /** * This interface provides a common set of Service attributes, that are also required for the higher-level * ServiceConfig. It is exported as a convenience for plugins. */ -export interface CommonServiceSpec extends ServiceSpec { +export interface CommonServiceSpec { name: string dependencies: string[] + disabled: boolean } export const serviceOutputsSchema = joiVariables() @@ -32,12 +31,30 @@ export const baseServiceSpecSchema = joi .keys({ name: joiUserIdentifier().required(), dependencies: dependenciesSchema, + disabled: joi + .boolean() + .default(false) + .description( + dedent` + Set this to \`true\` to disable the service. You can use this with conditional template strings to + enable/disable services based on, for example, the current environment or other variables (e.g. + \`enabled: \${environment.name != "prod"}\`). This can be handy when you only need certain services for + specific environments, e.g. only for development. + + Disabling a service means that it will not be deployed, and will also be ignored if it is declared as a + runtime dependency for another service, test or task. + + Note however that template strings referencing the service's outputs (i.e. runtime outputs) will fail to + resolve when the service is disabled, so you need to make sure to provide alternate values for those if + you're using them, using conditional expressions. + ` + ), }) .unknown(true) .meta({ extendable: true }) .description("The required attributes of a service. This is generally further defined by plugins.") -export interface ServiceConfig extends CommonServiceSpec { +export interface ServiceConfig extends CommonServiceSpec { hotReloadable: boolean sourceModuleName?: string diff --git a/garden-service/src/config/task.ts b/garden-service/src/config/task.ts index c8e7cd7544..c326afc29f 100644 --- a/garden-service/src/config/task.ts +++ b/garden-service/src/config/task.ts @@ -6,8 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import deline = require("deline") import { joiArray, joiUserIdentifier, joi } from "./common" +import { deline, dedent } from "../util/string" export interface TaskSpec {} @@ -15,6 +15,7 @@ export interface BaseTaskSpec extends TaskSpec { name: string dependencies: string[] description?: string + disabled: boolean timeout: number | null } @@ -32,6 +33,24 @@ export const baseTaskSpecSchema = joi The names of any tasks that must be executed, and the names of any services that must be running, before this task is executed. `), + disabled: joi + .boolean() + .default(false) + .description( + dedent` + Set this to \`true\` to disable the task. You can use this with conditional template strings to + enable/disable tasks based on, for example, the current environment or other variables (e.g. + \`enabled: \${environment.name != "prod"}\`). This can be handy when you only want certain tasks to run in + specific environments, e.g. only for development. + + Disabling a task means that it will not be run, and will also be ignored if it is declared as a + runtime dependency for another service, test or task. + + Note however that template strings referencing the task's outputs (i.e. runtime outputs) will fail to + resolve when the task is disabled, so you need to make sure to provide alternate values for those if + you're using them, using conditional expressions. + ` + ), timeout: joi .number() .optional() @@ -64,6 +83,10 @@ export const taskSchema = joi .string() .optional() .description("A description of the task."), + disabled: joi + .boolean() + .default(false) + .description("Set to true if the task or its module is disabled."), module: joi.object().unknown(true), config: taskConfigSchema, spec: joi diff --git a/garden-service/src/config/test.ts b/garden-service/src/config/test.ts index 79bfb2e95a..551f1c34d7 100644 --- a/garden-service/src/config/test.ts +++ b/garden-service/src/config/test.ts @@ -6,14 +6,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import deline = require("deline") import { joiArray, joiUserIdentifier, joi } from "./common" +import { deline, dedent } from "../util/string" -export interface TestSpec {} - -export interface BaseTestSpec extends TestSpec { +export interface BaseTestSpec { name: string dependencies: string[] + disabled: boolean timeout: number | null } @@ -25,6 +24,17 @@ export const baseTestSpecSchema = joi.object().keys({ The names of any services that must be running, and the names of any tasks that must be executed, before the test is run. `), + disabled: joi + .boolean() + .default(false) + .description( + dedent` + Set this to \`true\` to disable the test. You can use this with conditional template strings to + enable/disable tests based on, for example, the current environment or other variables (e.g. + \`enabled: \${environment.name != "prod"}\`). This is handy when you only want certain tests to run in + specific environments, e.g. only during CI. + ` + ), timeout: joi .number() .allow(null) @@ -32,7 +42,7 @@ export const baseTestSpecSchema = joi.object().keys({ .description("Maximum duration (in seconds) of the test run."), }) -export interface TestConfig extends BaseTestSpec { +export interface TestConfig extends BaseTestSpec { // Plugins can add custom fields that are kept here spec: T } diff --git a/garden-service/src/docs/config.ts b/garden-service/src/docs/config.ts index f8adf590eb..8617de45fe 100644 --- a/garden-service/src/docs/config.ts +++ b/garden-service/src/docs/config.ts @@ -35,7 +35,7 @@ const populateProviderSchema = (schema: Joi.ObjectSchema) => providers: joiArray(schema), }) -const maxWidth = 100 +const maxWidth = 120 const moduleTypes = [ { name: "exec" }, { name: "container" }, diff --git a/garden-service/src/events.ts b/garden-service/src/events.ts index 9a1ee70eed..125eff072e 100644 --- a/garden-service/src/events.ts +++ b/garden-service/src/events.ts @@ -51,8 +51,9 @@ export class EventBus extends EventEmitter2 { */ export type Events = { // Internal test/control events - _restart: string - _test: string + _exit: {} + _restart: {} + _test: any // Watcher events configAdded: { @@ -103,6 +104,8 @@ export type Events = { taskGraphComplete: { completedAt: Date } + + watchingForChanges: {} } export type EventName = keyof Events diff --git a/garden-service/src/exceptions.ts b/garden-service/src/exceptions.ts index 0d5644f5f7..7f93368e1a 100644 --- a/garden-service/src/exceptions.ts +++ b/garden-service/src/exceptions.ts @@ -43,6 +43,10 @@ export class ConfigurationError extends GardenBaseError { type = "configuration" } +export class CommandError extends GardenBaseError { + type = "command" +} + export class LocalConfigError extends GardenBaseError { type = "local-config" } diff --git a/garden-service/src/garden.ts b/garden-service/src/garden.ts index 07f720a611..a7294f9834 100644 --- a/garden-service/src/garden.ts +++ b/garden-service/src/garden.ts @@ -117,10 +117,10 @@ export interface GardenParams { export class Garden { public readonly log: LogEntry private loadedPlugins: GardenPlugin[] - private moduleConfigs: ModuleConfigMap + protected moduleConfigs: ModuleConfigMap private pluginModuleConfigs: ModuleConfig[] private resolvedProviders: Provider[] - private modulesScanned: boolean + protected modulesScanned: boolean private readonly registeredPlugins: { [key: string]: GardenPlugin } private readonly taskGraph: TaskGraph private watcher: Watcher diff --git a/garden-service/src/plugins/container/container.ts b/garden-service/src/plugins/container/container.ts index 3a95def615..0466f4c0b2 100644 --- a/garden-service/src/plugins/container/container.ts +++ b/garden-service/src/plugins/container/container.ts @@ -120,6 +120,7 @@ export async function configureContainerModule({ ctx, log, moduleConfig }: Confi return { name, dependencies: spec.dependencies, + disabled: spec.disabled, hotReloadable, spec, } @@ -128,6 +129,7 @@ export async function configureContainerModule({ ctx, log, moduleConfig }: Confi moduleConfig.testConfigs = moduleConfig.spec.tests.map((t) => ({ name: t.name, dependencies: t.dependencies, + disabled: t.disabled, spec: t, timeout: t.timeout, })) @@ -135,6 +137,7 @@ export async function configureContainerModule({ ctx, log, moduleConfig }: Confi moduleConfig.taskConfigs = moduleConfig.spec.tasks.map((t) => ({ name: t.name, dependencies: t.dependencies, + disabled: t.disabled, spec: t, timeout: t.timeout, })) diff --git a/garden-service/src/plugins/exec.ts b/garden-service/src/plugins/exec.ts index 053553cecc..ac9af20248 100644 --- a/garden-service/src/plugins/exec.ts +++ b/garden-service/src/plugins/exec.ts @@ -189,6 +189,7 @@ export async function configureExecModule({ moduleConfig.taskConfigs = moduleConfig.spec.tasks.map((t) => ({ name: t.name, dependencies: t.dependencies, + disabled: t.disabled, timeout: t.timeout, spec: t, })) @@ -196,6 +197,7 @@ export async function configureExecModule({ moduleConfig.testConfigs = moduleConfig.spec.tests.map((t) => ({ name: t.name, dependencies: t.dependencies, + disabled: t.disabled, spec: t, timeout: t.timeout, })) diff --git a/garden-service/src/plugins/google/google-cloud-functions.ts b/garden-service/src/plugins/google/google-cloud-functions.ts index e08b9ec7ce..5fde7ccaa1 100644 --- a/garden-service/src/plugins/google/google-cloud-functions.ts +++ b/garden-service/src/plugins/google/google-cloud-functions.ts @@ -68,6 +68,7 @@ export async function configureGcfModule({ { name, dependencies: spec.dependencies, + disabled: spec.disabled, hotReloadable: false, spec, }, @@ -76,6 +77,7 @@ export async function configureGcfModule({ moduleConfig.testConfigs = moduleConfig.spec.tests.map((t) => ({ name: t.name, dependencies: t.dependencies, + disabled: t.disabled, timeout: t.timeout, spec: t, })) diff --git a/garden-service/src/plugins/kubernetes/helm/config.ts b/garden-service/src/plugins/kubernetes/helm/config.ts index d2a52a58d0..a8e7a3f553 100644 --- a/garden-service/src/plugins/kubernetes/helm/config.ts +++ b/garden-service/src/plugins/kubernetes/helm/config.ts @@ -8,7 +8,6 @@ import { find } from "lodash" -import { ServiceSpec } from "../../../config/service" import { joiPrimitive, joiArray, @@ -153,7 +152,7 @@ export const testSchema = baseTestSpecSchema.keys({ ), }) -export interface HelmServiceSpec extends ServiceSpec { +export interface HelmServiceSpec { base?: string chart?: string chartPath: string @@ -288,6 +287,7 @@ export async function configureHelmModule({ { name: moduleConfig.name, dependencies, + disabled: moduleConfig.disabled, // Note: We can't tell here if the source module supports hot-reloading, // so we catch it in the handler if need be. hotReloadable: !!sourceModuleName, @@ -337,6 +337,7 @@ export async function configureHelmModule({ return { name: spec.name, dependencies: spec.dependencies, + disabled: moduleConfig.disabled, timeout: spec.timeout, spec, } @@ -350,6 +351,7 @@ export async function configureHelmModule({ return { name: spec.name, dependencies: spec.dependencies, + disabled: moduleConfig.disabled, timeout: spec.timeout, spec, } diff --git a/garden-service/src/plugins/kubernetes/kubernetes-module/config.ts b/garden-service/src/plugins/kubernetes/kubernetes-module/config.ts index a00fe31f8c..31ff97f0e2 100644 --- a/garden-service/src/plugins/kubernetes/kubernetes-module/config.ts +++ b/garden-service/src/plugins/kubernetes/kubernetes-module/config.ts @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { ServiceSpec, dependenciesSchema } from "../../../config/service" +import { dependenciesSchema } from "../../../config/service" import { joiArray, joi, joiModuleIncludeDirective } from "../../../config/common" import { Module } from "../../../types/module" import { ConfigureModuleParams, ConfigureModuleResult } from "../../../types/plugin/module/configure" @@ -22,7 +22,7 @@ export type KubernetesModuleSpec = KubernetesServiceSpec export interface KubernetesModule extends Module {} export type KubernetesModuleConfig = KubernetesModule["_ConfigType"] -export interface KubernetesServiceSpec extends ServiceSpec { +export interface KubernetesServiceSpec { dependencies: string[] files: string[] manifests: KubernetesResource[] @@ -78,6 +78,7 @@ export async function configureKubernetesModule({ { name: moduleConfig.name, dependencies: moduleConfig.spec.dependencies, + disabled: moduleConfig.disabled, hotReloadable: false, spec: moduleConfig.spec, }, diff --git a/garden-service/src/plugins/local/local-google-cloud-functions.ts b/garden-service/src/plugins/local/local-google-cloud-functions.ts index 2870dd0dbf..07f9e5a80a 100644 --- a/garden-service/src/plugins/local/local-google-cloud-functions.ts +++ b/garden-service/src/plugins/local/local-google-cloud-functions.ts @@ -35,6 +35,7 @@ export const gardenPlugin = createGardenPlugin({ dependencies: [], }, description: "Base container for running Google Cloud Functions emulator", + disabled: false, name: "local-gcf-container", path: emulatorBaseModulePath, outputs: {}, @@ -76,6 +77,7 @@ export const gardenPlugin = createGardenPlugin({ const spec = { name: s.name, dependencies: s.dependencies, + disabled: parsed.disabled, outputs: { ingress: `http://${s.name}:${emulatorPort}/local/local/${functionEntrypoint}`, }, @@ -109,6 +111,7 @@ export const gardenPlugin = createGardenPlugin({ return { name: spec.name, dependencies: spec.dependencies, + disabled: parsed.disabled, hotReloadable: false, outputs: spec.outputs, spec, @@ -133,6 +136,7 @@ export const gardenPlugin = createGardenPlugin({ }, ]), }, + disabled: parsed.disabled, name: parsed.name, outputs: {}, path: parsed.path, diff --git a/garden-service/src/plugins/openfaas/config.ts b/garden-service/src/plugins/openfaas/config.ts index 1d2db236eb..2fcc1be4d9 100644 --- a/garden-service/src/plugins/openfaas/config.ts +++ b/garden-service/src/plugins/openfaas/config.ts @@ -204,11 +204,13 @@ export async function configureModule({ moduleConfig.serviceConfigs = [ { dependencies, + disabled: moduleConfig.disabled, hotReloadable: false, name: moduleConfig.name, spec: { name: moduleConfig.name, dependencies, + disabled: moduleConfig.disabled, }, }, ] @@ -216,6 +218,7 @@ export async function configureModule({ moduleConfig.testConfigs = moduleConfig.spec.tests.map((t) => ({ name: t.name, dependencies: union(t.dependencies, dependencies), + disabled: t.disabled, spec: t, timeout: t.timeout, })) diff --git a/garden-service/src/plugins/openfaas/openfaas.ts b/garden-service/src/plugins/openfaas/openfaas.ts index 9765431a24..51f57ca89e 100644 --- a/garden-service/src/plugins/openfaas/openfaas.ts +++ b/garden-service/src/plugins/openfaas/openfaas.ts @@ -99,6 +99,7 @@ const templateModuleConfig: ExecModuleConfig = { dependencies: [], }, description: "OpenFaaS templates for building functions", + disabled: false, name: "templates", path: join(systemDir, "openfaas-templates"), repositoryUrl: "https://github.com/openfaas/templates.git#1.2", @@ -190,6 +191,7 @@ async function configureProvider({ dependencies: [], }, description: "OpenFaaS runtime", + disabled: false, name: "system", outputs: {}, path: join(systemDir, "openfaas-system"), diff --git a/garden-service/src/plugins/terraform/module.ts b/garden-service/src/plugins/terraform/module.ts index bf285267d5..5bd4166273 100644 --- a/garden-service/src/plugins/terraform/module.ts +++ b/garden-service/src/plugins/terraform/module.ts @@ -87,6 +87,7 @@ export async function configureTerraformModule({ ctx, moduleConfig }: ConfigureM { name: moduleConfig.name, dependencies: moduleConfig.spec.dependencies, + disabled: false, hotReloadable: false, spec: moduleConfig.spec, }, diff --git a/garden-service/src/process.ts b/garden-service/src/process.ts index 612ec91d13..4cb69f671a 100644 --- a/garden-service/src/process.ts +++ b/garden-service/src/process.ts @@ -6,12 +6,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import Bluebird = require("bluebird") +import Bluebird from "bluebird" import chalk from "chalk" import { padEnd, keyBy, flatten } from "lodash" import { Module } from "./types/module" -import { Service } from "./types/service" import { BaseTask } from "./tasks/base" import { TaskResults } from "./task-graph" import { isModuleLinked } from "./util/ext-source-util" @@ -31,56 +30,28 @@ interface ProcessParams { log: LogEntry footerLog?: LogEntry watch: boolean - handler: ProcessHandler + initialTasks: BaseTask[] // use this if the behavior should be different on watcher changes than on initial processing - changeHandler?: ProcessHandler + changeHandler: ProcessHandler } export interface ProcessModulesParams extends ProcessParams { modules: Module[] } -export interface ProcessServicesParams extends ProcessParams { - services: Service[] -} - export interface ProcessResults { taskResults: TaskResults restartRequired?: boolean } -export async function processServices({ - garden, - graph, - log, - footerLog, - services, - watch, - handler, - changeHandler, -}: ProcessServicesParams): Promise { - const modules = Array.from(new Set(services.map((s) => s.module))) - - return processModules({ - modules, - garden, - graph, - log, - footerLog, - watch, - handler, - changeHandler, - }) -} - export async function processModules({ garden, graph, log, footerLog, modules, + initialTasks, watch, - handler, changeHandler, }: ProcessModulesParams): Promise { log.silly("Starting processModules") @@ -98,8 +69,6 @@ export async function processModules({ log.info(divider) } - const tasks: BaseTask[] = flatten(await Bluebird.map(modules, (module) => handler(graph, module))) - if (watch && !!footerLog) { garden.events.on("taskGraphProcessing", () => { const emoji = printEmoji("hourglass_flowing_sand", footerLog) @@ -107,7 +76,7 @@ export async function processModules({ }) } - const results = await garden.processTasks(tasks) + const results = await garden.processTasks(initialTasks) if (!watch) { return { @@ -116,33 +85,30 @@ export async function processModules({ } } - if (!changeHandler) { - changeHandler = handler - } - - const buildDependecies = ( - await graph.getDependenciesForMany( - "build", - modules.map((m) => m.name), - true - ) - ).build - const modulesToWatch = uniqByName(buildDependecies.concat(modules)) + const deps = await graph.getDependenciesForMany({ + nodeType: "build", + names: modules.map((m) => m.name), + recursive: true, + }) + const modulesToWatch = uniqByName(deps.build.concat(modules)) const modulesByName = keyBy(modulesToWatch, "name") await garden.startWatcher(graph) - const footerWaiting = () => { + const waiting = () => { if (!!footerLog) { const emoji = printEmoji("clock2", footerLog) footerLog.setState(`\n${emoji} ${chalk.gray("Waiting for code changes...")}`) } + + garden.events.emit("watchingForChanges", {}) } - footerWaiting() - const restartPromise = new Promise((resolve) => { + let restartRequired = true + + await new Promise((resolve) => { garden.events.on("taskGraphComplete", () => { - footerWaiting() + waiting() }) garden.events.on("_restart", () => { @@ -150,6 +116,12 @@ export async function processModules({ resolve() }) + garden.events.on("_exit", () => { + log.debug({ symbol: "info", msg: `Manual exit triggered` }) + restartRequired = false + resolve() + }) + garden.events.on("projectConfigChanged", async () => { if (await validateConfigChange(garden, log, garden.projectRoot, "changed")) { log.info({ @@ -202,7 +174,7 @@ export async function processModules({ } // Make sure the modules' versions are up to date. - const changedModules = await graph.getModules(changedModuleNames) + const changedModules = await graph.getModules({ names: changedModuleNames }) const moduleTasks = flatten( await Bluebird.map(changedModules, async (m) => { @@ -212,13 +184,13 @@ export async function processModules({ ) await garden.processTasks(moduleTasks) }) - }) - await restartPromise + waiting() + }) return { taskResults: {}, // TODO: Return latest results for each task key processed between restarts? - restartRequired: true, + restartRequired, } } diff --git a/garden-service/src/tasks/build.ts b/garden-service/src/tasks/build.ts index d01bd97e19..196b3bbab9 100644 --- a/garden-service/src/tasks/build.ts +++ b/garden-service/src/tasks/build.ts @@ -69,10 +69,10 @@ export class BuildTask extends BaseTask { async getDependencies() { const dg = await this.garden.getConfigGraph(this.log) - const deps = (await dg.getDependencies("build", this.getName(), false)).build + const deps = await dg.getDependencies({ nodeType: "build", name: this.getName(), recursive: false }) const buildTasks = flatten( - await Bluebird.map(deps, async (m: Module) => { + await Bluebird.map(deps.build, async (m: Module) => { return BuildTask.factory({ garden: this.garden, log: this.log, diff --git a/garden-service/src/tasks/delete-service.ts b/garden-service/src/tasks/delete-service.ts index 05ebb9f4e3..cbcef7cefb 100644 --- a/garden-service/src/tasks/delete-service.ts +++ b/garden-service/src/tasks/delete-service.ts @@ -49,7 +49,7 @@ export class DeleteServiceTask extends BaseTask { } // Note: We delete in _reverse_ dependency order, so we query for dependants - const deps = await this.graph.getDependants("deploy", this.getName(), false) + const deps = await this.graph.getDependants({ nodeType: "deploy", name: this.getName(), recursive: false }) const dependants = deps.deploy.map((service) => { return new DeleteServiceTask({ diff --git a/garden-service/src/tasks/deploy.ts b/garden-service/src/tasks/deploy.ts index e6970b2738..ca5e5324b8 100644 --- a/garden-service/src/tasks/deploy.ts +++ b/garden-service/src/tasks/deploy.ts @@ -64,12 +64,12 @@ export class DeployTask extends BaseTask { const dg = this.graph // We filter out service dependencies on services configured for hot reloading (if any) - const deps = await dg.getDependencies( - "deploy", - this.getName(), - false, - (depNode) => !(depNode.type === "deploy" && includes(this.hotReloadServiceNames, depNode.name)) - ) + const deps = await dg.getDependencies({ + nodeType: "deploy", + name: this.getName(), + recursive: false, + filterFn: (depNode) => !(depNode.type === "deploy" && includes(this.hotReloadServiceNames, depNode.name)), + }) const statusTask = new GetServiceStatusTask({ garden: this.garden, @@ -152,7 +152,11 @@ export class DeployTask extends BaseTask { let version = this.version const hotReload = includes(this.hotReloadServiceNames, this.service.name) - const dependencies = await this.graph.getDependencies("deploy", this.getName(), false) + const dependencies = await this.graph.getDependencies({ + nodeType: "deploy", + name: this.getName(), + recursive: false, + }) const serviceStatuses = getServiceStatuses(dependencyResults) const taskResults = getRunTaskResults(dependencyResults) diff --git a/garden-service/src/tasks/get-service-status.ts b/garden-service/src/tasks/get-service-status.ts index 9d97e30b0f..4f7997efc7 100644 --- a/garden-service/src/tasks/get-service-status.ts +++ b/garden-service/src/tasks/get-service-status.ts @@ -42,7 +42,7 @@ export class GetServiceStatusTask extends BaseTask { } async getDependencies() { - const deps = await this.graph.getDependencies("deploy", this.getName(), false) + const deps = await this.graph.getDependencies({ nodeType: "deploy", name: this.getName(), recursive: false }) const stageBuildTask = new StageBuildTask({ garden: this.garden, @@ -90,7 +90,11 @@ export class GetServiceStatusTask extends BaseTask { const hotReload = includes(this.hotReloadServiceNames, this.service.name) - const dependencies = await this.graph.getDependencies("deploy", this.getName(), false) + const dependencies = await this.graph.getDependencies({ + nodeType: "deploy", + name: this.getName(), + recursive: false, + }) const serviceStatuses = getServiceStatuses(dependencyResults) const taskResults = getRunTaskResults(dependencyResults) diff --git a/garden-service/src/tasks/helpers.ts b/garden-service/src/tasks/helpers.ts index 6f005e1910..334d17210f 100644 --- a/garden-service/src/tasks/helpers.ts +++ b/garden-service/src/tasks/helpers.ts @@ -7,114 +7,89 @@ */ import Bluebird from "bluebird" -import { intersection, uniq, flatten } from "lodash" +import { intersection, flatten, uniqBy } from "lodash" import { DeployTask } from "./deploy" import { Garden } from "../garden" import { Module } from "../types/module" -import { Service } from "../types/service" -import { DependencyGraphNode, ConfigGraph } from "../config-graph" +import { ConfigGraph } from "../config-graph" import { LogEntry } from "../logger/log-entry" import { BaseTask } from "./base" import { BuildTask } from "./build" +import { HotReloadTask } from "./hot-reload" -export async function getDependantTasksForModule({ +/** + * Helper used by the `garden dev` and `garden deploy --watch` commands, to get all the tasks that should be + * executed for those when a particular module changes. + */ +export async function getModuleWatchTasks({ garden, log, graph, module, hotReloadServiceNames, - force = false, - forceBuild = false, - fromWatch = false, - includeDependants = false, }: { garden: Garden log: LogEntry graph: ConfigGraph module: Module hotReloadServiceNames: string[] - force?: boolean - forceBuild?: boolean - fromWatch?: boolean - includeDependants?: boolean }): Promise { let buildTasks: BaseTask[] = [] - let dependantBuildModules: Module[] = [] - let services: Service[] = [] - - if (!includeDependants) { - buildTasks.push( - ...(await BuildTask.factory({ - garden, - log, - module, - force: forceBuild, - })) - ) - services = await graph.getServices(module.serviceNames) - } else { - const hotReloadModuleNames = await getModuleNames(graph, hotReloadServiceNames) - const dependantFilterFn = (dependantNode: DependencyGraphNode) => - !hotReloadModuleNames.includes(dependantNode.moduleName) + const dependants = await graph.getDependantsForModule(module, true) - if (intersection(module.serviceNames, hotReloadServiceNames).length) { - // Hot reloading is enabled for one or more of module's services. - const serviceDeps = await graph.getDependantsForMany("deploy", module.serviceNames, true, dependantFilterFn) + if (intersection(module.serviceNames, hotReloadServiceNames).length === 0) { + buildTasks = await BuildTask.factory({ + garden, + log, + module, + force: true, + }) + } - dependantBuildModules = serviceDeps.build - services = serviceDeps.deploy - } else { - const dependants = await graph.getDependantsForModule(module, dependantFilterFn) + const dependantBuildTasks = flatten( + await Bluebird.map( + dependants.build.filter((m) => !m.disabled), + (m) => + BuildTask.factory({ + garden, + log, + module: m, + force: false, + }) + ) + ) - buildTasks.push( - ...(await BuildTask.factory({ + const deployTasks = dependants.deploy + .filter((s) => !s.disabled && !hotReloadServiceNames.includes(s.name)) + .map( + (service) => + new DeployTask({ garden, log, - module, + graph, + service, force: true, - })) - ) - dependantBuildModules = dependants.build - services = (await graph.getServices(module.serviceNames)).concat(dependants.deploy) - } - } + forceBuild: false, + fromWatch: true, + hotReloadServiceNames, + }) + ) - const dependantBuildTasks = flatten( - await Bluebird.map(dependantBuildModules, (m) => - BuildTask.factory({ - garden, - log, - module: m, - force: forceBuild, - }) + const hotReloadServices = await graph.getServices({ names: hotReloadServiceNames, includeDisabled: true }) + const hotReloadTasks = hotReloadServices + .filter( + (service) => + !service.disabled && (service.module.name === module.name || service.sourceModule.name === module.name) ) - ) + .map((service) => new HotReloadTask({ garden, graph, log, service, force: true })) - const deployTasks = services.map( - (service) => - new DeployTask({ - garden, - log, - graph, - service, - force, - forceBuild, - fromWatch, - hotReloadServiceNames, - }) - ) + const outputTasks = [...buildTasks, ...dependantBuildTasks, ...deployTasks, ...hotReloadTasks] - const outputTasks = [...buildTasks, ...dependantBuildTasks, ...deployTasks] - log.silly(`getDependantTasksForModule called for module ${module.name}, returning the following tasks:`) + log.silly(`getModuleWatchTasks called for module ${module.name}, returning the following tasks:`) log.silly(` ${outputTasks.map((t) => t.getKey()).join(", ")}`) - return outputTasks -} - -async function getModuleNames(dg: ConfigGraph, hotReloadServiceNames: string[]) { - const services = await dg.getServices(hotReloadServiceNames) - return uniq(services.map((s) => s.module.name)) + return uniqBy(outputTasks, (t) => t.getKey()) } export function makeTestTaskName(moduleName: string, testConfigName: string) { diff --git a/garden-service/src/tasks/stage-build.ts b/garden-service/src/tasks/stage-build.ts index 356ac436a5..93ede66954 100644 --- a/garden-service/src/tasks/stage-build.ts +++ b/garden-service/src/tasks/stage-build.ts @@ -37,7 +37,7 @@ export class StageBuildTask extends BaseTask { async getDependencies() { const dg = await this.garden.getConfigGraph(this.log) - const deps = (await dg.getDependencies("build", this.getName(), false)).build + const deps = (await dg.getDependencies({ nodeType: "build", name: this.getName(), recursive: false })).build const stageDeps = await Bluebird.map(deps, async (m: Module) => { return new StageBuildTask({ diff --git a/garden-service/src/tasks/task.ts b/garden-service/src/tasks/task.ts index 3e63748e16..7682cdf456 100644 --- a/garden-service/src/tasks/task.ts +++ b/garden-service/src/tasks/task.ts @@ -61,7 +61,7 @@ export class TaskTask extends BaseTask { }) const dg = await this.garden.getConfigGraph(this.log) - const deps = await dg.getDependencies("run", this.getName(), false) + const deps = await dg.getDependencies({ nodeType: "run", name: this.getName(), recursive: false }) const deployTasks = deps.deploy.map((service) => { return new DeployTask({ @@ -129,7 +129,7 @@ export class TaskTask extends BaseTask { status: "active", }) - const dependencies = await this.graph.getDependencies("run", this.getName(), false) + const dependencies = await this.graph.getDependencies({ nodeType: "run", name: this.getName(), recursive: false }) const serviceStatuses = getServiceStatuses(dependencyResults) const taskResults = getRunTaskResults(dependencyResults) diff --git a/garden-service/src/tasks/test.ts b/garden-service/src/tasks/test.ts index 7790c73884..2d1d5ed95c 100644 --- a/garden-service/src/tasks/test.ts +++ b/garden-service/src/tasks/test.ts @@ -50,7 +50,17 @@ export class TestTask extends BaseTask { private testConfig: TestConfig private forceBuild: boolean - constructor({ garden, graph, log, module, testConfig, force, forceBuild, version }: TestTaskParams & TaskParams) { + constructor({ + garden, + graph, + log, + module, + testConfig, + force, + forceBuild, + version, + }: TestTaskParams & TaskParams & { _guard: true }) { + // Note: The _guard attribute is to prevent accidentally bypassing the factory method super({ garden, log, force, version }) this.module = module this.graph = graph @@ -62,7 +72,7 @@ export class TestTask extends BaseTask { static async factory(initArgs: TestTaskParams): Promise { const { garden, graph, module, testConfig } = initArgs const version = await getTestVersion(garden, graph, module, testConfig) - return new TestTask({ ...initArgs, version }) + return new TestTask({ ...initArgs, version, _guard: true }) } async getDependencies() { @@ -73,7 +83,7 @@ export class TestTask extends BaseTask { } const dg = this.graph - const deps = await dg.getDependencies("test", this.getName(), false) + const deps = await dg.getDependencies({ nodeType: "test", name: this.getName(), recursive: false }) const buildTasks = await BuildTask.factory({ garden: this.garden, @@ -138,7 +148,11 @@ export class TestTask extends BaseTask { status: "active", }) - const dependencies = await this.graph.getDependencies("test", this.testConfig.name, false) + const dependencies = await this.graph.getDependencies({ + nodeType: "test", + name: this.testConfig.name, + recursive: false, + }) const serviceStatuses = getServiceStatuses(dependencyResults) const taskResults = getRunTaskResults(dependencyResults) @@ -220,7 +234,9 @@ export async function getTestTasks({ // If there are no filters we return the test otherwise // we check if the test name matches against the filterNames array const configs = module.testConfigs.filter( - (test) => !filterNames || filterNames.length === 0 || find(filterNames, (n: string) => minimatch(test.name, n)) + (test) => + !test.disabled && + (!filterNames || filterNames.length === 0 || find(filterNames, (n: string) => minimatch(test.name, n))) ) return Bluebird.map(configs, (test) => diff --git a/garden-service/src/types/module.ts b/garden-service/src/types/module.ts index 9339c1ae7a..c67f170fcd 100644 --- a/garden-service/src/types/module.ts +++ b/garden-service/src/types/module.ts @@ -8,10 +8,7 @@ import { flatten, uniq, cloneDeep, keyBy, some } from "lodash" import { getNames } from "../util/util" -import { TestSpec } from "../config/test" -import { ModuleSpec, ModuleConfig, moduleConfigSchema } from "../config/module" -import { ServiceSpec } from "../config/service" -import { TaskSpec } from "../config/task" +import { ModuleConfig, moduleConfigSchema } from "../config/module" import { ModuleVersion, moduleVersionSchema } from "../vcs/vcs" import { pathToCacheContext } from "../cache" import { Garden } from "../garden" @@ -30,12 +27,8 @@ export interface FileCopySpec { /** * The Module interface adds several internally managed keys to the ModuleConfig type. */ -export interface Module< - M extends ModuleSpec = any, - S extends ServiceSpec = any, - T extends TestSpec = any, - W extends TaskSpec = any -> extends ModuleConfig { +export interface Module + extends ModuleConfig { buildPath: string buildMetadataPath: string configPath: string @@ -136,7 +129,7 @@ export async function moduleFromConfig(garden: Garden, graph: ConfigGraph, confi } const buildDependencyModules = await Bluebird.map(module.build.dependencies, (d) => - graph.getModule(getModuleKey(d.name, d.plugin)) + graph.getModule(getModuleKey(d.name, d.plugin), true) ) module.buildDependencies = keyBy(buildDependencyModules, "name") diff --git a/garden-service/src/types/service.ts b/garden-service/src/types/service.ts index adeb0ec444..eeac203c38 100644 --- a/garden-service/src/types/service.ts +++ b/garden-service/src/types/service.ts @@ -19,6 +19,7 @@ export interface Service { name: string module: M config: M["serviceConfigs"][0] + disabled: boolean sourceModule: S spec: M["serviceConfigs"][0]["spec"] } @@ -30,6 +31,10 @@ export const serviceSchema = joi name: joiUserIdentifier().description("The name of the service."), module: joi.object().unknown(true), // This causes a stack overflow: joi.lazy(() => moduleSchema), sourceModule: joi.object().unknown(true), // This causes a stack overflow: joi.lazy(() => moduleSchema), + disabled: joi + .boolean() + .default(false) + .description("Set to true if the service or its module is disabled."), config: serviceConfigSchema, spec: joi.object().description("The raw configuration of the service (specific to each plugin)."), }) @@ -45,6 +50,7 @@ export async function serviceFromConfig( name: config.name, module, config, + disabled: module.disabled || config.disabled, sourceModule, spec: config.spec, } diff --git a/garden-service/src/types/task.ts b/garden-service/src/types/task.ts index 5193d9e27d..8be2771409 100644 --- a/garden-service/src/types/task.ts +++ b/garden-service/src/types/task.ts @@ -13,6 +13,7 @@ export interface Task { name: string description?: string module: M + disabled: boolean config: M["taskConfigs"][0] spec: M["taskConfigs"][0]["spec"] } @@ -21,6 +22,7 @@ export function taskFromConfig(module: M, config: Tas return { name: config.name, module, + disabled: module.disabled || config.disabled, config, spec: config.spec, } diff --git a/garden-service/src/types/test.ts b/garden-service/src/types/test.ts new file mode 100644 index 0000000000..7fe12e5d88 --- /dev/null +++ b/garden-service/src/types/test.ts @@ -0,0 +1,28 @@ +/* + * 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 { Module } from "./module" +import { TestConfig } from "../config/test" + +export interface Test { + name: string + module: M + disabled: boolean + config: M["testConfigs"][0] + spec: M["testConfigs"][0]["spec"] +} + +export function testFromConfig(module: M, config: TestConfig): Test { + return { + name: config.name, + module, + disabled: module.disabled || config.disabled, + config, + spec: config.spec, + } +} diff --git a/garden-service/test/data/test-project-a/module-c/garden.yml b/garden-service/test/data/test-project-a/module-c/garden.yml index 0ff5a5ca02..e59860d372 100644 --- a/garden-service/test/data/test-project-a/module-c/garden.yml +++ b/garden-service/test/data/test-project-a/module-c/garden.yml @@ -3,6 +3,7 @@ name: module-c type: test services: - name: service-c + dependencies: [task-c] build: dependencies: - module-b diff --git a/garden-service/test/helpers.ts b/garden-service/test/helpers.ts index 04c765996a..2c8c9eacea 100644 --- a/garden-service/test/helpers.ts +++ b/garden-service/test/helpers.ts @@ -9,7 +9,7 @@ import td from "testdouble" import Bluebird = require("bluebird") import { resolve, join } from "path" -import { extend } from "lodash" +import { extend, keyBy } from "lodash" import { remove, readdirSync, existsSync, copy, mkdirp, pathExists, truncate } from "fs-extra" import execa = require("execa") @@ -106,6 +106,7 @@ export async function configureTestModule({ moduleConfig }: ConfigureModuleParam moduleConfig.serviceConfigs = moduleConfig.spec.services.map((spec) => ({ name: spec.name, dependencies: spec.dependencies, + disabled: spec.disabled, sourceModuleName: spec.sourceModuleName, spec, })) @@ -113,6 +114,7 @@ export async function configureTestModule({ moduleConfig }: ConfigureModuleParam moduleConfig.taskConfigs = moduleConfig.spec.tasks.map((t) => ({ name: t.name, dependencies: t.dependencies, + disabled: t.disabled, spec: t, timeout: t.timeout, })) @@ -120,6 +122,7 @@ export async function configureTestModule({ moduleConfig }: ConfigureModuleParam moduleConfig.testConfigs = moduleConfig.spec.tests.map((t) => ({ name: t.name, dependencies: t.dependencies, + disabled: t.disabled, spec: t, timeout: t.timeout, })) @@ -267,6 +270,7 @@ const defaultModuleConfig: ModuleConfig = { path: "bla", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, spec: { services: [ @@ -280,6 +284,7 @@ const defaultModuleConfig: ModuleConfig = { { name: "test-service", dependencies: [], + disabled: false, hotReloadable: false, spec: {}, }, @@ -327,6 +332,11 @@ export class TestGarden extends Garden { super(params) this.events = new TestEventBus(this.log) } + + setModuleConfigs(moduleConfigs: ModuleConfig[]) { + this.modulesScanned = true + this.moduleConfigs = keyBy(moduleConfigs, "name") + } } export const makeTestGarden = async (projectRoot: string, opts: GardenOpts = {}): Promise => { diff --git a/garden-service/test/integ/src/plugins/conftest/conftest-container.ts b/garden-service/test/integ/src/plugins/conftest/conftest-container.ts index 97e573d9f9..d15a955377 100644 --- a/garden-service/test/integ/src/plugins/conftest/conftest-container.ts +++ b/garden-service/test/integ/src/plugins/conftest/conftest-container.ts @@ -70,6 +70,7 @@ describe("conftest-container provider", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: containerModule.path, serviceConfigs: [], diff --git a/garden-service/test/integ/src/plugins/conftest/conftest-kubernetes.ts b/garden-service/test/integ/src/plugins/conftest/conftest-kubernetes.ts index 8a75e56ff6..b87dde16ff 100644 --- a/garden-service/test/integ/src/plugins/conftest/conftest-kubernetes.ts +++ b/garden-service/test/integ/src/plugins/conftest/conftest-kubernetes.ts @@ -58,6 +58,7 @@ describe("conftest-kubernetes provider", () => { force: true, forceBuild: true, version: module.version, + _guard: true, }) const key = testTask.getKey() diff --git a/garden-service/test/integ/src/plugins/conftest/conftest.ts b/garden-service/test/integ/src/plugins/conftest/conftest.ts index 5717da40d5..52afb20a8a 100644 --- a/garden-service/test/integ/src/plugins/conftest/conftest.ts +++ b/garden-service/test/integ/src/plugins/conftest/conftest.ts @@ -40,6 +40,7 @@ describe("conftest provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() @@ -76,6 +77,7 @@ describe("conftest provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() @@ -103,6 +105,7 @@ describe("conftest provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() @@ -133,6 +136,7 @@ describe("conftest provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() diff --git a/garden-service/test/integ/src/plugins/hadolint/hadolint.ts b/garden-service/test/integ/src/plugins/hadolint/hadolint.ts index cd4223af59..7a46699819 100644 --- a/garden-service/test/integ/src/plugins/hadolint/hadolint.ts +++ b/garden-service/test/integ/src/plugins/hadolint/hadolint.ts @@ -63,6 +63,7 @@ describe("hadolint provider", () => { type: "container", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: tmpPath, serviceConfigs: [], @@ -77,6 +78,7 @@ describe("hadolint provider", () => { type: "container", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: tmpPath, serviceConfigs: [], @@ -125,6 +127,7 @@ describe("hadolint provider", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: tmpPath, serviceConfigs: [], @@ -160,11 +163,12 @@ describe("hadolint provider", () => { type: "hadolint", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path, serviceConfigs: [], taskConfigs: [], - testConfigs: [{ name: "foo", dependencies: [], spec: {}, timeout: 10 }], + testConfigs: [{ name: "foo", dependencies: [], disabled: false, spec: {}, timeout: 10 }], spec: { dockerfilePath: "errAndWarn.Dockerfile" }, }, } @@ -181,6 +185,7 @@ describe("hadolint provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() @@ -224,11 +229,12 @@ describe("hadolint provider", () => { type: "hadolint", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: modulePath, serviceConfigs: [], taskConfigs: [], - testConfigs: [{ name: "foo", dependencies: [], spec: {}, timeout: 10 }], + testConfigs: [{ name: "foo", dependencies: [], disabled: false, spec: {}, timeout: 10 }], spec: { dockerfilePath: "errAndWarn.Dockerfile" }, }, } @@ -245,6 +251,7 @@ describe("hadolint provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() @@ -283,11 +290,12 @@ describe("hadolint provider", () => { type: "hadolint", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path, serviceConfigs: [], taskConfigs: [], - testConfigs: [{ name: "foo", dependencies: [], spec: {}, timeout: 10 }], + testConfigs: [{ name: "foo", dependencies: [], disabled: false, spec: {}, timeout: 10 }], spec: { dockerfilePath: "errAndWarn.Dockerfile" }, }, } @@ -304,6 +312,7 @@ describe("hadolint provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() @@ -336,11 +345,12 @@ describe("hadolint provider", () => { type: "hadolint", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path, serviceConfigs: [], taskConfigs: [], - testConfigs: [{ name: "foo", dependencies: [], spec: {}, timeout: 10 }], + testConfigs: [{ name: "foo", dependencies: [], disabled: false, spec: {}, timeout: 10 }], spec: { dockerfilePath: "warn.Dockerfile" }, }, } @@ -357,6 +367,7 @@ describe("hadolint provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() @@ -379,11 +390,12 @@ describe("hadolint provider", () => { type: "hadolint", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path, serviceConfigs: [], taskConfigs: [], - testConfigs: [{ name: "foo", dependencies: [], spec: {}, timeout: 10 }], + testConfigs: [{ name: "foo", dependencies: [], disabled: false, spec: {}, timeout: 10 }], spec: { dockerfilePath: "warn.Dockerfile" }, }, } @@ -400,6 +412,7 @@ describe("hadolint provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() @@ -425,11 +438,12 @@ describe("hadolint provider", () => { type: "hadolint", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path, serviceConfigs: [], taskConfigs: [], - testConfigs: [{ name: "foo", dependencies: [], spec: {}, timeout: 10 }], + testConfigs: [{ name: "foo", dependencies: [], disabled: false, spec: {}, timeout: 10 }], spec: { dockerfilePath: "errAndWarn.Dockerfile" }, }, } @@ -446,6 +460,7 @@ describe("hadolint provider", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() diff --git a/garden-service/test/integ/src/plugins/kubernetes/container/container.ts b/garden-service/test/integ/src/plugins/kubernetes/container/container.ts index 3ddc1d477e..88d28a1a9d 100644 --- a/garden-service/test/integ/src/plugins/kubernetes/container/container.ts +++ b/garden-service/test/integ/src/plugins/kubernetes/container/container.ts @@ -440,6 +440,7 @@ describe("kubernetes container module handlers", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) result = await garden.processTasks([testTask], { throwOnError: true }) @@ -462,6 +463,7 @@ describe("kubernetes container module handlers", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) await emptyDir(garden.artifactsPath) @@ -484,6 +486,7 @@ describe("kubernetes container module handlers", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) await emptyDir(garden.artifactsPath) @@ -506,6 +509,7 @@ describe("kubernetes container module handlers", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) result = await garden.processTasks([testTask]) @@ -532,6 +536,7 @@ describe("kubernetes container module handlers", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) result = await garden.processTasks([testTask]) diff --git a/garden-service/test/integ/src/plugins/kubernetes/helm/config.ts b/garden-service/test/integ/src/plugins/kubernetes/helm/config.ts index e989bd6ded..d260b60655 100644 --- a/garden-service/test/integ/src/plugins/kubernetes/helm/config.ts +++ b/garden-service/test/integ/src/plugins/kubernetes/helm/config.ts @@ -46,6 +46,7 @@ describe("validateHelmModule", () => { }, configPath: resolve(ctx.projectRoot, "api", "garden.yml"), description: "The API backend for the voting UI", + disabled: false, include: ["*", "charts/**/*", "templates/**/*"], exclude: undefined, name: "api", @@ -58,6 +59,7 @@ describe("validateHelmModule", () => { { name: "api", dependencies: [], + disabled: false, hotReloadable: true, sourceModuleName: "api-image", spec: { diff --git a/garden-service/test/integ/src/plugins/kubernetes/helm/test.ts b/garden-service/test/integ/src/plugins/kubernetes/helm/test.ts index b11497fb1c..7bf0eefb8f 100644 --- a/garden-service/test/integ/src/plugins/kubernetes/helm/test.ts +++ b/garden-service/test/integ/src/plugins/kubernetes/helm/test.ts @@ -32,6 +32,7 @@ describe("testHelmModule", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) const key = testTask.getKey() @@ -55,6 +56,7 @@ describe("testHelmModule", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) await emptyDir(garden.artifactsPath) @@ -77,6 +79,7 @@ describe("testHelmModule", () => { force: true, forceBuild: false, version: module.version, + _guard: true, }) await emptyDir(garden.artifactsPath) diff --git a/garden-service/test/integ/src/plugins/kubernetes/kubernetes-module/config.ts b/garden-service/test/integ/src/plugins/kubernetes/kubernetes-module/config.ts index 85cd5923f9..122da16a6d 100644 --- a/garden-service/test/integ/src/plugins/kubernetes/kubernetes-module/config.ts +++ b/garden-service/test/integ/src/plugins/kubernetes/kubernetes-module/config.ts @@ -40,6 +40,7 @@ describe("validateKubernetesModule", () => { }, configPath: resolve(ctx.projectRoot, "module-simple", "garden.yml"), description: "Simple Kubernetes module with minimum config", + disabled: false, exclude: undefined, include: [], kind: "Module", @@ -50,6 +51,7 @@ describe("validateKubernetesModule", () => { serviceConfigs: [ { dependencies: [], + disabled: false, hotReloadable: false, name: "module-simple", spec: { diff --git a/garden-service/test/unit/src/actions.ts b/garden-service/test/unit/src/actions.ts index f2df028ccf..17fb52346d 100644 --- a/garden-service/test/unit/src/actions.ts +++ b/garden-service/test/unit/src/actions.ts @@ -131,6 +131,7 @@ describe("ActionRouter", () => { services: [{ name }], allowPublish: true, build: { dependencies: [] }, + disabled: false, }, ], }) @@ -265,6 +266,7 @@ describe("ActionRouter", () => { testConfig: { name: "test", dependencies: [], + disabled: false, timeout: 1234, spec: {}, }, @@ -291,6 +293,7 @@ describe("ActionRouter", () => { const testConfig = { name: "test", dependencies: [], + disabled: false, timeout: 1234, spec: { artifacts: [ @@ -1743,6 +1746,7 @@ const testPlugin = createGardenPlugin({ const serviceConfigs = params.moduleConfig.spec.services.map((spec) => ({ name: spec.name, dependencies: spec.dependencies || [], + disabled: false, hotReloadable: false, spec, })) @@ -1750,6 +1754,7 @@ const testPlugin = createGardenPlugin({ const taskConfigs = (params.moduleConfig.spec.tasks || []).map((spec) => ({ name: spec.name, dependencies: spec.dependencies || [], + disabled: false, spec, })) diff --git a/garden-service/test/unit/src/commands/build.ts b/garden-service/test/unit/src/commands/build.ts index f2347aeed3..ef124361ae 100644 --- a/garden-service/test/unit/src/commands/build.ts +++ b/garden-service/test/unit/src/commands/build.ts @@ -3,7 +3,7 @@ import { expect } from "chai" import { makeTestGardenA, withDefaultGlobalOpts } from "../../../helpers" import { taskResultOutputs } from "../../../helpers" -describe("commands.build", () => { +describe("BuildCommand", () => { it("should build all modules in a project", async () => { const garden = await makeTestGardenA() const log = garden.log @@ -56,4 +56,59 @@ describe("commands.build", () => { const command = new BuildCommand() expect(command.protected).to.be.true }) + + it("should skip disabled modules", async () => { + const garden = await makeTestGardenA() + const log = garden.log + const footerLog = garden.log + const command = new BuildCommand() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].disabled = true + + const { result } = await command.action({ + garden, + log, + headerLog: log, + footerLog, + args: { modules: undefined }, + opts: withDefaultGlobalOpts({ watch: false, force: true }), + }) + + expect(taskResultOutputs(result!)).to.eql({ + "build.module-a": { fresh: true, buildLog: "A" }, + "build.module-b": { fresh: true, buildLog: "B" }, + "stage-build.module-a": {}, + "stage-build.module-b": {}, + }) + }) + + it("should build disabled modules if they are dependencies of enabled modules", async () => { + const garden = await makeTestGardenA() + const log = garden.log + const footerLog = garden.log + const command = new BuildCommand() + + await garden.scanModules() + // module-b is a build dependency of module-c + garden["moduleConfigs"]["module-b"].disabled = true + + const { result } = await command.action({ + garden, + log, + headerLog: log, + footerLog, + args: { modules: undefined }, + opts: withDefaultGlobalOpts({ watch: false, force: true }), + }) + + expect(taskResultOutputs(result!)).to.eql({ + "build.module-a": { fresh: true, buildLog: "A" }, + "build.module-b": { fresh: true, buildLog: "B" }, + "build.module-c": {}, + "stage-build.module-a": {}, + "stage-build.module-b": {}, + "stage-build.module-c": {}, + }) + }) }) diff --git a/garden-service/test/unit/src/commands/deploy.ts b/garden-service/test/unit/src/commands/deploy.ts index a7aedde175..b1ad4fbced 100644 --- a/garden-service/test/unit/src/commands/deploy.ts +++ b/garden-service/test/unit/src/commands/deploy.ts @@ -32,9 +32,6 @@ const placeholderTaskResult = (moduleName: string, taskName: string, command: st }, }) -const taskResultA = placeholderTaskResult("module-a", "task-a", ["echo", "A"]) -const taskResultC = placeholderTaskResult("module-c", "task-c", ["echo", "C"]) - const testProvider = () => createGardenPlugin(() => { const testStatuses: { [key: string]: ServiceStatus } = { @@ -96,13 +93,13 @@ const testProvider = () => }) describe("DeployCommand", () => { + const plugins = [testProvider()] const projectRootB = join(dataDir, "test-project-b") // TODO: Verify that services don't get redeployed when same version is already deployed. // TODO: Test with --watch flag it("should build and deploy all modules in a project", async () => { - const plugins = [testProvider()] const garden = await Garden.factory(projectRootB, { plugins }) const log = garden.log const command = new DeployCommand() @@ -127,54 +124,29 @@ describe("DeployCommand", () => { throw errors[0] } - expect(taskResultOutputs(result!)).to.eql({ - "stage-build.module-a": {}, - "stage-build.module-b": {}, - "stage-build.module-c": {}, - "build.module-a": { fresh: true, buildLog: "A" }, - "build.module-b": { fresh: true, buildLog: "B" }, - "build.module-c": {}, - "get-task-result.task-a": null, - "get-task-result.task-c": null, - "task.task-a": taskResultA, - "task.task-c": taskResultC, - "get-service-status.service-a": { - forwardablePorts: [], - ingresses: [ - { - hostname: "service-a.test-project-b.local.app.garden", - path: "/path-a", - port: 80, - protocol: "http", - }, - ], - state: "ready", - detail: {}, - }, - "get-service-status.service-b": { - forwardablePorts: [], - state: "unknown", - detail: {}, - }, - "get-service-status.service-c": { - forwardablePorts: [], - state: "ready", - detail: {}, - }, - "get-service-status.service-d": { - forwardablePorts: [], - state: "unknown", - detail: {}, - }, - "deploy.service-a": { forwardablePorts: [], version: "1", state: "ready", detail: {} }, - "deploy.service-b": { forwardablePorts: [], version: "1", state: "ready", detail: {} }, - "deploy.service-c": { forwardablePorts: [], version: "1", state: "ready", detail: {} }, - "deploy.service-d": { forwardablePorts: [], version: "1", state: "ready", detail: {} }, - }) + expect(Object.keys(taskResultOutputs(result!)).sort()).to.eql([ + "build.module-a", + "build.module-b", + "build.module-c", + "deploy.service-a", + "deploy.service-b", + "deploy.service-c", + "deploy.service-d", + "get-service-status.service-a", + "get-service-status.service-b", + "get-service-status.service-c", + "get-service-status.service-d", + "get-task-result.task-a", + "get-task-result.task-c", + "stage-build.module-a", + "stage-build.module-b", + "stage-build.module-c", + "task.task-a", + "task.task-c", + ]) }) it("should optionally build and deploy single service and its dependencies", async () => { - const plugins = [testProvider()] const garden = await Garden.factory(projectRootB, { plugins }) const log = garden.log const command = new DeployCommand() @@ -199,42 +171,116 @@ describe("DeployCommand", () => { throw errors[0] } - expect(taskResultOutputs(result!)).to.eql({ - "stage-build.module-a": {}, - "stage-build.module-b": {}, - "stage-build.module-c": {}, - "build.module-a": { fresh: true, buildLog: "A" }, - "build.module-b": { fresh: true, buildLog: "B" }, - "build.module-c": {}, - "get-task-result.task-a": null, - "get-task-result.task-c": null, - "task.task-a": taskResultA, - "task.task-c": taskResultC, - "get-service-status.service-a": { - forwardablePorts: [], - ingresses: [ - { - hostname: "service-a.test-project-b.local.app.garden", - path: "/path-a", - port: 80, - protocol: "http", - }, - ], - state: "ready", - detail: {}, - }, - "get-service-status.service-b": { - forwardablePorts: [], - state: "unknown", - detail: {}, - }, - "deploy.service-a": { forwardablePorts: [], version: "1", state: "ready", detail: {} }, - "deploy.service-b": { forwardablePorts: [], version: "1", state: "ready", detail: {} }, - }) + expect(Object.keys(taskResultOutputs(result!)).sort()).to.eql([ + "build.module-a", + "build.module-b", + "build.module-c", + "deploy.service-a", + "deploy.service-b", + "get-service-status.service-a", + "get-service-status.service-b", + "get-task-result.task-a", + "get-task-result.task-c", + "stage-build.module-a", + "stage-build.module-b", + "stage-build.module-c", + "task.task-a", + "task.task-c", + ]) }) it("should be protected", async () => { const command = new DeployCommand() expect(command.protected).to.be.true }) + + it("should skip disabled services", async () => { + const garden = await Garden.factory(projectRootB, { plugins }) + const log = garden.log + const command = new DeployCommand() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].spec.services[0].disabled = true + + const { result, errors } = await command.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { + services: undefined, + }, + opts: withDefaultGlobalOpts({ + "hot-reload": undefined, + "watch": false, + "force": false, + "force-build": true, + }), + }) + + if (errors) { + throw errors[0] + } + + expect(Object.keys(taskResultOutputs(result!)).sort()).to.eql([ + "build.module-a", + "build.module-b", + "build.module-c", + "deploy.service-a", + "deploy.service-b", + "deploy.service-d", + "get-service-status.service-a", + "get-service-status.service-b", + "get-service-status.service-d", + "get-task-result.task-a", + "get-task-result.task-c", + "stage-build.module-a", + "stage-build.module-b", + "stage-build.module-c", + "task.task-a", + "task.task-c", + ]) + }) + + it("should skip services from disabled modules", async () => { + const garden = await Garden.factory(projectRootB, { plugins }) + const log = garden.log + const command = new DeployCommand() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].disabled = true + + const { result, errors } = await command.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { + services: undefined, + }, + opts: withDefaultGlobalOpts({ + "hot-reload": undefined, + "watch": false, + "force": false, + "force-build": true, + }), + }) + + if (errors) { + throw errors[0] + } + + expect(Object.keys(taskResultOutputs(result!)).sort()).to.eql([ + "build.module-a", + "build.module-b", + "deploy.service-a", + "deploy.service-b", + "get-service-status.service-a", + "get-service-status.service-b", + "get-task-result.task-a", + "stage-build.module-a", + "stage-build.module-b", + "task.task-a", + ]) + }) }) diff --git a/garden-service/test/unit/src/commands/dev.ts b/garden-service/test/unit/src/commands/dev.ts index d7e3e17adc..8d3062944d 100644 --- a/garden-service/test/unit/src/commands/dev.ts +++ b/garden-service/test/unit/src/commands/dev.ts @@ -1,9 +1,206 @@ +import pEvent from "p-event" import { expect } from "chai" -import { DevCommand } from "../../../../src/commands/dev" +import { DevCommand, DevCommandArgs, DevCommandOpts } from "../../../../src/commands/dev" +import { makeTestGardenA, withDefaultGlobalOpts, TestGarden } from "../../../helpers" +import { ParameterValues } from "../../../../src/commands/base" +import { GlobalOptions } from "../../../../src/cli/cli" describe("DevCommand", () => { + const command = new DevCommand() + + async function waitForEvent(garden: TestGarden, name: string) { + return pEvent(garden.events, name, { timeout: 5000 }) + } + + async function completeFirstTasks( + garden: TestGarden, + args: ParameterValues, + opts: ParameterValues + ) { + const log = garden.log + + await command.prepare({ log, footerLog: log, headerLog: log, args, opts }) + + const promise = command + .action({ + garden, + log, + headerLog: log, + footerLog: log, + args, + opts, + }) + .then(({ errors }) => { + if (errors) { + throw errors[0] + } + }) + .catch((err) => { + // tslint:disable-next-line: no-console + console.error(err) + }) + + await waitForEvent(garden, "watchingForChanges") + + garden.events.emit("_exit", {}) + + const completedTasks = garden.events.eventLog + .filter((e) => e.name === "taskComplete") + .map((e) => e.payload["key"]) + .sort() + + return { promise, completedTasks } + } + it("should be protected", async () => { - const command = new DevCommand() expect(command.protected).to.be.true }) + + it("should deploy, run and test everything in a project", async () => { + const garden = await makeTestGardenA() + + const args = {} + const opts = withDefaultGlobalOpts({ + "force-build": false, + }) + + const { promise, completedTasks } = await completeFirstTasks(garden, args, opts) + + expect(completedTasks).to.eql([ + "build.module-a", + "build.module-b", + "build.module-c", + "deploy.service-a", + "deploy.service-b", + "deploy.service-c", + "get-service-status.service-a", + "get-service-status.service-b", + "get-service-status.service-c", + "get-task-result.task-c", + "resolve-provider.container", + "resolve-provider.exec", + "resolve-provider.test-plugin", + "resolve-provider.test-plugin-b", + "stage-build.module-a", + "stage-build.module-b", + "stage-build.module-c", + "task.task-c", + "test.module-a.integration", + "test.module-a.unit", + "test.module-b.unit", + "test.module-c.integ", + "test.module-c.unit", + ]) + + return promise + }) + + it("should skip disabled services", async () => { + const garden = await makeTestGardenA() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].spec.services[0].disabled = true + + const args = {} + const opts = withDefaultGlobalOpts({ + "force-build": false, + }) + + const { promise, completedTasks } = await completeFirstTasks(garden, args, opts) + + expect(completedTasks).to.not.include("deploy.service-c") + + return promise + }) + + it("should skip disabled tasks", async () => { + const garden = await makeTestGardenA() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].spec.tasks[0].disabled = true + + const args = {} + const opts = withDefaultGlobalOpts({ + "force-build": false, + }) + + const { promise, completedTasks } = await completeFirstTasks(garden, args, opts) + + expect(completedTasks).to.not.include("task.task-c") + + return promise + }) + + it("should skip disabled tests", async () => { + const garden = await makeTestGardenA() + + await garden.scanModules() + garden["moduleConfigs"]["module-b"].spec.tests[0].disabled = true + + const args = {} + const opts = withDefaultGlobalOpts({ + "force-build": false, + }) + + const { promise, completedTasks } = await completeFirstTasks(garden, args, opts) + + expect(completedTasks).to.not.include("test.module-b.unit") + + return promise + }) + + it("should skip services from disabled modules", async () => { + const garden = await makeTestGardenA() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].disabled = true + + const args = {} + const opts = withDefaultGlobalOpts({ + "force-build": false, + }) + + const { promise, completedTasks } = await completeFirstTasks(garden, args, opts) + + expect(completedTasks).to.not.include("deploy.service-c") + + return promise + }) + + it("should skip tasks from disabled modules", async () => { + const garden = await makeTestGardenA() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].disabled = true + + const args = {} + const opts = withDefaultGlobalOpts({ + "force-build": false, + }) + + const { promise, completedTasks } = await completeFirstTasks(garden, args, opts) + + expect(completedTasks).to.not.include("task.task-c") + + return promise + }) + + it("should skip tests from disabled modules", async () => { + const garden = await makeTestGardenA() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].disabled = true + + const args = {} + const opts = withDefaultGlobalOpts({ + "force-build": false, + }) + + const { promise, completedTasks } = await completeFirstTasks(garden, args, opts) + + expect(completedTasks).to.not.include("test.module-c.unit") + expect(completedTasks).to.not.include("test.module-c.integ") + + return promise + }) }) diff --git a/garden-service/test/unit/src/commands/run/service.ts b/garden-service/test/unit/src/commands/run/service.ts index 9697cf9255..40e8619d46 100644 --- a/garden-service/test/unit/src/commands/run/service.ts +++ b/garden-service/test/unit/src/commands/run/service.ts @@ -1,15 +1,17 @@ import { RunServiceCommand } from "../../../../../src/commands/run/service" import { RunResult } from "../../../../../src/types/plugin/base" -import { makeTestGardenA, testModuleVersion, testNow, withDefaultGlobalOpts } from "../../../../helpers" +import { makeTestGardenA, testModuleVersion, testNow, withDefaultGlobalOpts, expectError } from "../../../../helpers" import { expect } from "chai" import { Garden } from "../../../../../src/garden" import td from "testdouble" import { LogEntry } from "../../../../../src/logger/log-entry" +import stripAnsi from "strip-ansi" describe("RunServiceCommand", () => { // TODO: test optional flags - let garden + let garden: Garden let log: LogEntry + const cmd = new RunServiceCommand() beforeEach(async () => { td.replace(Garden.prototype, "resolveVersion", async () => testModuleVersion) @@ -18,14 +20,13 @@ describe("RunServiceCommand", () => { }) it("should run a service", async () => { - const cmd = new RunServiceCommand() const { result } = await cmd.action({ garden, log, headerLog: log, footerLog: log, args: { service: "service-a" }, - opts: withDefaultGlobalOpts({ "force-build": false }), + opts: withDefaultGlobalOpts({ "force": false, "force-build": false }), }) const expected: RunResult = { @@ -40,4 +41,42 @@ describe("RunServiceCommand", () => { expect(result).to.eql(expected) }) + + it("should throw if the service is disabled", async () => { + await garden.getRawModuleConfigs() + garden["moduleConfigs"]["module-a"].disabled = true + + await expectError( + () => + cmd.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { service: "service-a" }, + opts: withDefaultGlobalOpts({ "force": false, "force-build": false }), + }), + (err) => + expect(stripAnsi(err.message)).to.equal( + "Service service-a is disabled for the local environment. If you're sure you want to run it anyway, " + + "please run the command again with the --force flag." + ) + ) + }) + + it("should allow running a disabled service with --force flag", async () => { + await garden.scanModules() + garden["moduleConfigs"]["module-a"].disabled = true + + const { errors } = await cmd.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { service: "service-a" }, + opts: withDefaultGlobalOpts({ "force": true, "force-build": false }), + }) + + expect(errors).to.not.exist + }) }) diff --git a/garden-service/test/unit/src/commands/run/task.ts b/garden-service/test/unit/src/commands/run/task.ts index 5927d93c26..eb45ffb6d7 100644 --- a/garden-service/test/unit/src/commands/run/task.ts +++ b/garden-service/test/unit/src/commands/run/task.ts @@ -1,13 +1,15 @@ +import stripAnsi from "strip-ansi" import { expect } from "chai" import { omit } from "lodash" import { RunTaskCommand } from "../../../../../src/commands/run/task" -import { makeTestGardenA, withDefaultGlobalOpts } from "../../../../helpers" +import { makeTestGardenA, withDefaultGlobalOpts, expectError } from "../../../../helpers" describe("RunTaskCommand", () => { + const cmd = new RunTaskCommand() + it("should run a task", async () => { const garden = await makeTestGardenA() const log = garden.log - const cmd = new RunTaskCommand() const { result } = await cmd.action({ garden, @@ -33,4 +35,48 @@ describe("RunTaskCommand", () => { expect(omit(result!.output, omittedKeys)).to.eql(expected) }) + + it("should throw if the task is disabled", async () => { + const garden = await makeTestGardenA() + const log = garden.log + + await garden.getRawModuleConfigs() + garden["moduleConfigs"]["module-a"].disabled = true + + await expectError( + () => + cmd.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { task: "task-a" }, + opts: withDefaultGlobalOpts({ "force": false, "force-build": false }), + }), + (err) => + expect(stripAnsi(err.message)).to.equal( + "Task task-a is disabled for the local environment. If you're sure you want to run it anyway, " + + "please run the command again with the --force flag." + ) + ) + }) + + it("should allow running a disabled task with --force flag", async () => { + const garden = await makeTestGardenA() + const log = garden.log + + await garden.scanModules() + garden["moduleConfigs"]["module-a"].disabled = true + + const { errors } = await cmd.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { task: "task-a" }, + opts: withDefaultGlobalOpts({ "force": true, "force-build": false }), + }) + + expect(errors).to.not.exist + }) }) diff --git a/garden-service/test/unit/src/commands/run/test.ts b/garden-service/test/unit/src/commands/run/test.ts new file mode 100644 index 0000000000..bdb3bb5c26 --- /dev/null +++ b/garden-service/test/unit/src/commands/run/test.ts @@ -0,0 +1,77 @@ +import stripAnsi from "strip-ansi" +import { expect } from "chai" +import { omit } from "lodash" +import { makeTestGardenA, withDefaultGlobalOpts, expectError } from "../../../../helpers" +import { RunTestCommand } from "../../../../../src/commands/run/test" + +describe("RunTestCommand", () => { + const cmd = new RunTestCommand() + + it("should run a test", async () => { + const garden = await makeTestGardenA() + const log = garden.log + + const { result } = await cmd.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { test: "unit", module: "module-a" }, + opts: withDefaultGlobalOpts({ "force-build": false }), + }) + + const expected = { + command: ["echo", "OK"], + moduleName: "module-a", + log: "OK", + success: true, + testName: "unit", + } + + expect(omit(result, ["completedAt", "startedAt", "version"])).to.eql(expected) + }) + + it("should throw if the test is disabled", async () => { + const garden = await makeTestGardenA() + const log = garden.log + + await garden.getRawModuleConfigs() + garden["moduleConfigs"]["module-a"].disabled = true + + await expectError( + () => + cmd.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { module: "module-a", test: "unit" }, + opts: withDefaultGlobalOpts({ "force": false, "force-build": false }), + }), + (err) => + expect(stripAnsi(err.message)).to.equal( + "Test module-a.unit is disabled for the local environment. If you're sure you want to run it anyway, " + + "please run the command again with the --force flag." + ) + ) + }) + + it("should allow running a disabled test with the --force flag", async () => { + const garden = await makeTestGardenA() + const log = garden.log + + await garden.scanModules() + garden["moduleConfigs"]["module-a"].disabled = true + + const { errors } = await cmd.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { module: "module-a", test: "unit" }, + opts: withDefaultGlobalOpts({ "force": true, "force-build": false }), + }) + + expect(errors).to.not.exist + }) +}) diff --git a/garden-service/test/unit/src/commands/test.ts b/garden-service/test/unit/src/commands/test.ts index c9547b3ad5..117463e58d 100644 --- a/garden-service/test/unit/src/commands/test.ts +++ b/garden-service/test/unit/src/commands/test.ts @@ -3,11 +3,12 @@ import { TestCommand } from "../../../../src/commands/test" import isSubset = require("is-subset") import { makeTestGardenA, taskResultOutputs, withDefaultGlobalOpts } from "../../../helpers" -describe("commands.test", () => { +describe("TestCommand", () => { + const command = new TestCommand() + it("should run all tests in a simple project", async () => { const garden = await makeTestGardenA() const log = garden.log - const command = new TestCommand() const { result } = await command.action({ garden, @@ -53,7 +54,6 @@ describe("commands.test", () => { it("should optionally test single module", async () => { const garden = await makeTestGardenA() const log = garden.log - const command = new TestCommand() const { result } = await command.action({ garden, @@ -86,7 +86,6 @@ describe("commands.test", () => { it("should only run integration tests if the option 'name' is specified with a glob", async () => { const garden = await makeTestGardenA() const log = garden.log - const command = new TestCommand() const { result } = await command.action({ garden, @@ -134,7 +133,76 @@ describe("commands.test", () => { }) it("should be protected", async () => { - const command = new TestCommand() expect(command.protected).to.be.true }) + + it("should skip disabled tests", async () => { + const garden = await makeTestGardenA() + const log = garden.log + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].spec.tests[0].disabled = true + + const { result, errors } = await command.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { modules: ["module-c"] }, + opts: withDefaultGlobalOpts({ + "force": true, + "force-build": false, + "watch": false, + }), + }) + + if (errors) { + throw errors[0] + } + + expect(Object.keys(taskResultOutputs(result!)).sort()).to.eql([ + "build.module-a", + "build.module-b", + "build.module-c", + "stage-build.module-a", + "stage-build.module-b", + "stage-build.module-c", + "test.module-c.integ", + ]) + }) + + it("should skip tests from disabled modules", async () => { + const garden = await makeTestGardenA() + const log = garden.log + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].disabled = true + + const { result, errors } = await command.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { modules: undefined }, + opts: withDefaultGlobalOpts({ + "force": true, + "force-build": false, + "watch": false, + }), + }) + + if (errors) { + throw errors[0] + } + + expect(Object.keys(taskResultOutputs(result!)).sort()).to.eql([ + "build.module-a", + "build.module-b", + "stage-build.module-a", + "stage-build.module-b", + "test.module-a.integration", + "test.module-a.unit", + "test.module-b.unit", + ]) + }) }) diff --git a/garden-service/test/unit/src/config-graph.ts b/garden-service/test/unit/src/config-graph.ts index 28b7a49c9e..302f8b1d7f 100644 --- a/garden-service/test/unit/src/config-graph.ts +++ b/garden-service/test/unit/src/config-graph.ts @@ -1,17 +1,22 @@ -import { resolve } from "path" +import { resolve, join } from "path" import { expect } from "chai" +import { ensureDir } from "fs-extra" import { makeTestGardenA, makeTestGarden, dataDir, expectError } from "../../helpers" import { getNames } from "../../../src/util/util" import { ConfigGraph, DependencyGraphNode } from "../../../src/config-graph" import { Garden } from "../../../src/garden" +import { DEFAULT_API_VERSION, GARDEN_SERVICE_ROOT } from "../../../src/constants" describe("ConfigGraph", () => { let gardenA: Garden let graphA: ConfigGraph + let tmpPath: string before(async () => { gardenA = await makeTestGardenA() graphA = await gardenA.getConfigGraph(gardenA.log) + tmpPath = join(GARDEN_SERVICE_ROOT, "tmp") + await ensureDir(tmpPath) }) it("should throw when two services have the same name", async () => { @@ -67,13 +72,51 @@ describe("ConfigGraph", () => { }) it("should optionally return specified modules in the context", async () => { - const modules = await graphA.getModules(["module-b", "module-c"]) + const modules = await graphA.getModules({ names: ["module-b", "module-c"] }) expect(getNames(modules).sort()).to.eql(["module-b", "module-c"]) }) + it("should omit disabled modules", async () => { + const garden = await makeTestGardenA() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].disabled = true + + const graph = await garden.getConfigGraph(garden.log) + const modules = await graph.getModules() + + expect(modules.map((m) => m.name).sort()).to.eql(["module-a", "module-b"]) + }) + + it("should optionally include disabled modules", async () => { + const garden = await makeTestGardenA() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].disabled = true + + const graph = await garden.getConfigGraph(garden.log) + const modules = await graph.getModules({ includeDisabled: true }) + + expect(modules.map((m) => m.name).sort()).to.eql(["module-a", "module-b", "module-c"]) + }) + + it("should throw if specifically requesting a disabled module", async () => { + const garden = await makeTestGardenA() + + await garden.scanModules() + garden["moduleConfigs"]["module-c"].disabled = true + + const graph = await garden.getConfigGraph(garden.log) + + await expectError( + () => graph.getModules({ names: ["module-c"] }), + (err) => expect(err.message).to.equal("Could not find module(s): module-c") + ) + }) + it("should throw if named module is missing", async () => { try { - await graphA.getModules(["bla"]) + await graphA.getModules({ names: ["bla"] }) } catch (err) { expect(err.type).to.equal("parameter") return @@ -91,14 +134,124 @@ describe("ConfigGraph", () => { }) it("should optionally return specified services in the context", async () => { - const services = await graphA.getServices(["service-b", "service-c"]) + const services = await graphA.getServices({ names: ["service-b", "service-c"] }) expect(getNames(services).sort()).to.eql(["service-b", "service-c"]) }) + it("should omit disabled services", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "disabled-service", + dependencies: [], + disabled: true, + hotReloadable: false, + spec: {}, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const deps = await graph.getServices() + + expect(deps).to.eql([]) + }) + + it("should optionally include disabled services", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "disabled-service", + dependencies: [], + disabled: true, + hotReloadable: false, + spec: {}, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const deps = await graph.getServices({ includeDisabled: true }) + + expect(deps.map((s) => s.name)).to.eql(["disabled-service"]) + }) + + it("should throw if specifically requesting a disabled service", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-a", + dependencies: [], + disabled: true, + hotReloadable: false, + spec: {}, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + + await expectError( + () => graph.getServices({ names: ["service-a"] }), + (err) => expect(err.message).to.equal("Could not find service(s): service-a") + ) + }) + it("should throw if named service is missing", async () => { try { - await graphA.getServices(["bla"]) + await graphA.getServices({ names: ["bla"] }) } catch (err) { expect(err.type).to.equal("parameter") return @@ -134,13 +287,117 @@ describe("ConfigGraph", () => { }) it("should optionally return specified tasks in the context", async () => { - const tasks = await graphA.getTasks(["task-b", "task-c"]) + const tasks = await graphA.getTasks({ names: ["task-b", "task-c"] }) expect(getNames(tasks).sort()).to.eql(["task-b", "task-c"]) }) + it("should omit disabled tasks", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + tasks: [ + { + name: "disabled-task", + dependencies: [], + disabled: true, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const deps = await graph.getTasks() + + expect(deps).to.eql([]) + }) + + it("should optionally include disabled tasks", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + tasks: [ + { + name: "disabled-task", + dependencies: [], + disabled: true, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const deps = await graph.getTasks({ includeDisabled: true }) + + expect(deps.map((t) => t.name)).to.eql(["disabled-task"]) + }) + + it("should throw if specifically requesting a disabled task", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + tasks: [ + { + name: "disabled-task", + dependencies: [], + disabled: true, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + + await expectError( + () => graph.getTasks({ names: ["disabled-task"] }), + (err) => expect(err.message).to.equal("Could not find task(s): disabled-task") + ) + }) + it("should throw if named task is missing", async () => { try { - await graphA.getTasks(["bla"]) + await graphA.getTasks({ names: ["bla"] }) } catch (err) { expect(err.type).to.equal("parameter") return @@ -169,6 +426,465 @@ describe("ConfigGraph", () => { }) }) + describe("getDependencies", () => { + it("should include disabled modules in build dependencies", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: true, + name: "module-a", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: {}, + testConfigs: [], + type: "test", + }, + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [{ name: "module-a", copy: [] }] }, + disabled: false, + name: "module-b", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: {}, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + + const deps = await graph.getDependencies({ + nodeType: "build", + name: "module-b", + recursive: false, + }) + + expect(deps.build.map((m) => m.name)).to.eql(["module-a"]) + }) + + it("should ignore dependencies by services on disabled services", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "disabled-service", + dependencies: [], + disabled: true, + }, + { + name: "enabled-service", + dependencies: ["disabled-service"], + disabled: true, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + + const deps = await graph.getDependencies({ + nodeType: "deploy", + name: "enabled-service", + recursive: false, + }) + + expect(deps.deploy).to.eql([]) + }) + + it("should ignore dependencies by services on disabled tasks", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "enabled-service", + dependencies: ["disabled-task"], + disabled: false, + }, + ], + tasks: [ + { + name: "disabled-task", + dependencies: [], + disabled: true, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + + const deps = await graph.getDependencies({ + nodeType: "deploy", + name: "enabled-service", + recursive: false, + }) + + expect(deps.run).to.eql([]) + }) + + it("should ignore dependencies by services on services in disabled modules", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-a", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "disabled-service", + dependencies: [], + disabled: true, + }, + ], + }, + testConfigs: [], + type: "test", + }, + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-b", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "enabled-service", + dependencies: ["disabled-service"], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + + const deps = await graph.getDependencies({ + nodeType: "deploy", + name: "enabled-service", + recursive: false, + }) + + expect(deps.deploy).to.eql([]) + }) + + it("should ignore dependencies by tasks on disabled services", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "disabled-service", + dependencies: [], + disabled: true, + }, + ], + tasks: [ + { + name: "enabled-task", + dependencies: ["disabled-service"], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + + const deps = await graph.getDependencies({ + nodeType: "deploy", + name: "enabled-task", + recursive: false, + }) + + expect(deps.deploy).to.eql([]) + }) + + it("should ignore dependencies by tests on disabled services", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "foo", + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "disabled-service", + dependencies: [], + disabled: true, + }, + ], + tests: [ + { + name: "enabled-test", + dependencies: ["disabled-service"], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + + const deps = await graph.getDependencies({ + nodeType: "deploy", + name: "enabled-test", + recursive: false, + }) + + expect(deps.deploy).to.eql([]) + }) + }) + + describe("resolveDependencyModules", () => { + it("should include disabled modules in build dependencies", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: true, + name: "module-a", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: {}, + testConfigs: [], + type: "test", + }, + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-b", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: {}, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const deps = await graph.resolveDependencyModules([{ name: "module-a", copy: [] }], []) + + expect(deps.map((m) => m.name)).to.eql(["module-a"]) + }) + }) + + describe("getDependants", () => { + it("should not traverse past disabled services", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-a", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-a", + dependencies: [], + disabled: true, + }, + ], + }, + testConfigs: [], + type: "test", + }, + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-b", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-b", + dependencies: ["service-a"], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const deps = await graph.getDependants({ nodeType: "build", name: "module-a", recursive: true }) + + expect(deps.deploy.map((m) => m.name)).to.eql(["service-a"]) + }) + }) + + describe("getDependantsForModule", () => { + it("should return services and tasks for a build dependant of the given module", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-a", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: {}, + testConfigs: [], + type: "test", + }, + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [{ name: "module-a", copy: [] }] }, + disabled: false, + name: "module-b", + include: [], + outputs: {}, + path: tmpPath, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-b", + dependencies: [], + disabled: false, + }, + ], + tasks: [ + { + name: "task-b", + dependencies: [], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const moduleA = await graph.getModule("module-a") + const deps = await graph.getDependantsForModule(moduleA, true) + + expect(deps.deploy.map((m) => m.name)).to.eql(["service-b"]) + expect(deps.run.map((m) => m.name)).to.eql(["task-b"]) + }) + }) + describe("resolveDependencyModules", () => { it("should resolve build dependencies", async () => { const modules = await graphA.resolveDependencyModules([{ name: "module-c", copy: [] }], []) diff --git a/garden-service/test/unit/src/config/base.ts b/garden-service/test/unit/src/config/base.ts index e18095fe28..32ca392d18 100644 --- a/garden-service/test/unit/src/config/base.ts +++ b/garden-service/test/unit/src/config/base.ts @@ -115,6 +115,7 @@ describe("loadConfig", () => { type: "test", configPath, description: undefined, + disabled: undefined, include: undefined, exclude: undefined, repositoryUrl: undefined, @@ -186,6 +187,7 @@ describe("loadConfig", () => { type: "test", configPath, description: undefined, + disabled: undefined, include: ["*"], exclude: undefined, repositoryUrl: undefined, @@ -219,6 +221,7 @@ describe("loadConfig", () => { configPath, allowPublish: undefined, description: undefined, + disabled: undefined, include: ["*"], exclude: undefined, repositoryUrl: undefined, @@ -248,6 +251,7 @@ describe("loadConfig", () => { configPath, allowPublish: undefined, description: undefined, + disabled: undefined, include: ["*"], exclude: undefined, repositoryUrl: undefined, diff --git a/garden-service/test/unit/src/garden.ts b/garden-service/test/unit/src/garden.ts index 15d0ea4044..d2b9fb91c3 100644 --- a/garden-service/test/unit/src/garden.ts +++ b/garden-service/test/unit/src/garden.ts @@ -1402,6 +1402,7 @@ describe("Garden", () => { apiVersion: DEFAULT_API_VERSION, allowPublish: false, build: { dependencies: [] }, + disabled: false, name: "foo", outputs: {}, path: "/tmp", @@ -2155,6 +2156,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: pathFoo, serviceConfigs: [], @@ -2205,6 +2207,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: pathFoo, serviceConfigs: [], @@ -2276,6 +2279,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: pathFoo, serviceConfigs: [], @@ -2342,6 +2346,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: pathFoo, serviceConfigs: [], @@ -2426,6 +2431,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: pathFoo, serviceConfigs: [], @@ -2504,6 +2510,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: pathFoo, serviceConfigs: [], @@ -2569,6 +2576,7 @@ describe("Garden", () => { kind: "Module", allowPublish: true, build: { dependencies: [] }, + disabled: false, name: "foo", outputs: {}, configPath: "/tmp", @@ -2617,6 +2625,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: "/tmp", include: [], @@ -2631,6 +2640,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: "/tmp", include: [], @@ -2649,6 +2659,7 @@ describe("Garden", () => { kind: "Module", allowPublish: false, build: { dependencies: [{ name: "bar", copy: [] }] }, + disabled: false, name: "foo", outputs: {}, path: "/tmp", @@ -2722,6 +2733,7 @@ describe("Garden", () => { kind: "Module", allowPublish: true, build: { dependencies: [{ name: "bar", copy: [] }] }, + disabled: false, name: "foo", outputs: {}, configPath: "/tmp", @@ -2731,6 +2743,7 @@ describe("Garden", () => { { name: "foo", dependencies: ["bar"], + disabled: false, hotReloadable: false, }, ], @@ -2826,6 +2839,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: "/tmp", serviceConfigs: [], @@ -2839,6 +2853,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: "/tmp", serviceConfigs: [], @@ -2856,6 +2871,7 @@ describe("Garden", () => { kind: "Module", allowPublish: false, build: { dependencies: [] }, + disabled: false, name: "foo", outputs: {}, path: "/tmp", @@ -2864,6 +2880,7 @@ describe("Garden", () => { { name: "foo", dependencies: ["bar"], + disabled: false, hotReloadable: false, }, ], @@ -2915,6 +2932,7 @@ describe("Garden", () => { type: "foo", allowPublish: false, build: { dependencies: [] }, + disabled: false, outputs: {}, path: "/tmp", serviceConfigs: [], @@ -3000,6 +3018,7 @@ describe("Garden", () => { kind: "Module", allowPublish: true, build: { dependencies: [{ name: "bar", copy: [] }] }, + disabled: false, name: "foo", outputs: {}, configPath: "/tmp", @@ -3086,9 +3105,9 @@ describe("Garden", () => { }) context("test against fixed version hashes", async () => { - const moduleAVersionString = "v-0cf3cb04c0" - const moduleBVersionString = "v-db85090197" - const moduleCVersionString = "v-18ffe09ae4" + const moduleAVersionString = "v-58abca9921" + const moduleBVersionString = "v-f25bb29260" + const moduleCVersionString = "v-b4f769e846" it("should return the same module versions between runtimes", async () => { const projectRoot = getDataDir("test-projects", "fixed-version-hashes-1") diff --git a/garden-service/test/unit/src/plugins/container/container.ts b/garden-service/test/unit/src/plugins/container/container.ts index b67ec10f13..41f3698bed 100644 --- a/garden-service/test/unit/src/plugins/container/container.ts +++ b/garden-service/test/unit/src/plugins/container/container.ts @@ -38,6 +38,7 @@ describe("plugins.container", () => { build: { dependencies: [], }, + disabled: false, apiVersion: "garden.io/v0", name: "test", outputs: {}, @@ -93,6 +94,7 @@ describe("plugins.container", () => { build: { dependencies: [], }, + disabled: false, apiVersion: "garden.io/v0", name: "module-a", outputs: {}, @@ -113,6 +115,7 @@ describe("plugins.container", () => { args: ["echo"], dependencies: [], daemon: false, + disabled: false, ingresses: [ { annotations: {}, @@ -151,6 +154,7 @@ describe("plugins.container", () => { args: ["echo", "OK"], artifacts: [], dependencies: [], + disabled: false, env: { TASK_ENV_VAR: "value", }, @@ -163,6 +167,7 @@ describe("plugins.container", () => { args: ["echo", "OK"], artifacts: [], dependencies: [], + disabled: false, env: { TEST_ENV_VAR: "value", }, @@ -182,6 +187,7 @@ describe("plugins.container", () => { moduleConfig: { allowPublish: false, build: { dependencies: [] }, + disabled: false, apiVersion: "garden.io/v0", name: "module-a", include: ["Dockerfile"], @@ -204,6 +210,7 @@ describe("plugins.container", () => { annotations: {}, args: ["echo"], dependencies: [], + disabled: false, daemon: false, ingresses: [ { @@ -231,6 +238,7 @@ describe("plugins.container", () => { args: ["echo", "OK"], artifacts: [], dependencies: [], + disabled: false, env: { TASK_ENV_VAR: "value", }, @@ -243,6 +251,7 @@ describe("plugins.container", () => { args: ["echo", "OK"], artifacts: [], dependencies: [], + disabled: false, env: { TEST_ENV_VAR: "value", }, @@ -254,12 +263,14 @@ describe("plugins.container", () => { { name: "service-a", dependencies: [], + disabled: false, hotReloadable: false, spec: { name: "service-a", annotations: {}, args: ["echo"], dependencies: [], + disabled: false, daemon: false, ingresses: [ { @@ -285,11 +296,13 @@ describe("plugins.container", () => { taskConfigs: [ { dependencies: [], + disabled: false, name: "task-a", spec: { args: ["echo", "OK"], artifacts: [], dependencies: [], + disabled: false, env: { TASK_ENV_VAR: "value", }, @@ -303,11 +316,13 @@ describe("plugins.container", () => { { name: "unit", dependencies: [], + disabled: false, spec: { name: "unit", args: ["echo", "OK"], artifacts: [], dependencies: [], + disabled: false, env: { TEST_ENV_VAR: "value", }, @@ -326,6 +341,7 @@ describe("plugins.container", () => { build: { dependencies: [], }, + disabled: false, apiVersion: "garden.io/v0", name: "module-a", outputs: {}, @@ -346,6 +362,7 @@ describe("plugins.container", () => { args: ["echo"], dependencies: [], daemon: false, + disabled: false, ingresses: [ { annotations: {}, @@ -366,6 +383,7 @@ describe("plugins.container", () => { args: ["echo"], artifacts: [], dependencies: [], + disabled: false, env: {}, timeout: null, }, @@ -376,6 +394,7 @@ describe("plugins.container", () => { args: ["echo", "OK"], artifacts: [], dependencies: [], + disabled: false, env: {}, timeout: null, }, @@ -396,6 +415,7 @@ describe("plugins.container", () => { build: { dependencies: [], }, + disabled: false, apiVersion: "garden.io/v0", name: "module-a", outputs: {}, @@ -416,6 +436,7 @@ describe("plugins.container", () => { args: ["echo"], dependencies: [], daemon: false, + disabled: false, ingresses: [], env: {}, healthCheck: { @@ -436,6 +457,7 @@ describe("plugins.container", () => { args: ["echo"], artifacts: [], dependencies: [], + disabled: false, env: {}, timeout: null, }, @@ -457,6 +479,7 @@ describe("plugins.container", () => { build: { dependencies: [], }, + disabled: false, apiVersion: "garden.io/v0", name: "module-a", outputs: {}, @@ -477,6 +500,7 @@ describe("plugins.container", () => { args: ["echo"], dependencies: [], daemon: false, + disabled: false, ingresses: [], env: {}, healthCheck: { @@ -494,6 +518,7 @@ describe("plugins.container", () => { args: ["echo"], artifacts: [], dependencies: [], + disabled: false, env: {}, timeout: null, }, diff --git a/garden-service/test/unit/src/plugins/container/helpers.ts b/garden-service/test/unit/src/plugins/container/helpers.ts index 1b553746e9..343df40e35 100644 --- a/garden-service/test/unit/src/plugins/container/helpers.ts +++ b/garden-service/test/unit/src/plugins/container/helpers.ts @@ -27,10 +27,11 @@ describe("containerHelpers", () => { const baseConfig: ModuleConfig = { allowPublish: false, + apiVersion: "garden.io/v0", build: { dependencies: [], }, - apiVersion: "garden.io/v0", + disabled: false, name: "test", outputs: {}, path: modulePath, @@ -222,12 +223,13 @@ describe("containerHelpers", () => { it("should use image name if specified with commit hash if no version is set", async () => { const module = await getTestModule({ + apiVersion: "garden.io/v0", allowPublish: false, build: { dependencies: [], }, + disabled: false, name: "test", - apiVersion: "garden.io/v0", outputs: {}, path: modulePath, type: "container", @@ -425,6 +427,7 @@ describe("containerHelpers", () => { type: "container", allowPublish: false, build: { dependencies: [] }, + disabled: false, name: "test", path: tmpDir.path, outputs: {}, diff --git a/garden-service/test/unit/src/plugins/exec.ts b/garden-service/test/unit/src/plugins/exec.ts index 56e9d5fdf0..f880a39774 100644 --- a/garden-service/test/unit/src/plugins/exec.ts +++ b/garden-service/test/unit/src/plugins/exec.ts @@ -46,24 +46,28 @@ describe("exec plugin", () => { { name: "banana", dependencies: ["orange"], + disabled: false, timeout: null, spec: { name: "banana", command: ["echo", "BANANA"], env: {}, dependencies: ["orange"], + disabled: false, timeout: null, }, }, { name: "orange", dependencies: [], + disabled: false, timeout: 999, spec: { name: "orange", command: ["echo", "ORANGE"], env: {}, dependencies: [], + disabled: false, timeout: 999, }, }, @@ -72,10 +76,12 @@ describe("exec plugin", () => { { name: "unit", dependencies: [], + disabled: false, timeout: null, spec: { name: "unit", dependencies: [], + disabled: false, command: ["echo", "OK"], env: { FOO: "boo", @@ -98,10 +104,12 @@ describe("exec plugin", () => { { name: "unit", dependencies: [], + disabled: false, timeout: null, spec: { name: "unit", dependencies: [], + disabled: false, command: ["echo", "OK"], env: {}, timeout: null, @@ -122,10 +130,12 @@ describe("exec plugin", () => { { name: "unit", dependencies: [], + disabled: false, timeout: null, spec: { name: "unit", dependencies: [], + disabled: false, command: ["echo", "OK"], env: {}, timeout: null, @@ -146,12 +156,14 @@ describe("exec plugin", () => { { name: "pwd", dependencies: [], + disabled: false, timeout: null, spec: { name: "pwd", env: {}, command: ["pwd"], dependencies: [], + disabled: false, timeout: null, }, }, @@ -216,6 +228,7 @@ describe("exec plugin", () => { force: false, forceBuild: false, version: module.version, + _guard: true, }) await emptyDir(_garden.artifactsPath) @@ -289,6 +302,7 @@ describe("exec plugin", () => { testConfig: { name: "test", dependencies: [], + disabled: false, timeout: 1234, spec: { command: ["pwd"], diff --git a/garden-service/test/unit/src/plugins/kubernetes/container/ingress.ts b/garden-service/test/unit/src/plugins/kubernetes/container/ingress.ts index b46d817133..13b3ad583f 100644 --- a/garden-service/test/unit/src/plugins/kubernetes/container/ingress.ts +++ b/garden-service/test/unit/src/plugins/kubernetes/container/ingress.ts @@ -351,6 +351,7 @@ describe("createIngressResources", () => { args: [], daemon: false, dependencies: [], + disabled: false, env: {}, ingresses, limits: defaultContainerLimits, @@ -360,6 +361,7 @@ describe("createIngressResources", () => { } const moduleConfig = { allowPublish: false, + disabled: false, build: { command: [], dependencies: [], @@ -394,9 +396,11 @@ describe("createIngressResources", () => { config: { name: spec.name, dependencies: [], + disabled: false, hotReloadable: false, spec, }, + disabled: false, module, sourceModule: module, spec, diff --git a/garden-service/test/unit/src/tasks/deploy.ts b/garden-service/test/unit/src/tasks/deploy.ts index 55a1e71f2e..35b5bdabb0 100644 --- a/garden-service/test/unit/src/tasks/deploy.ts +++ b/garden-service/test/unit/src/tasks/deploy.ts @@ -91,6 +91,7 @@ describe("DeployTask", () => { name: "test", type: "test", allowPublish: false, + disabled: false, build: { dependencies: [] }, outputs: {}, path: tmpDir.path, @@ -98,6 +99,7 @@ describe("DeployTask", () => { { name: "test-service", dependencies: ["test-task"], + disabled: false, hotReloadable: false, spec: { log: "${runtime.tasks.test-task.outputs.log}", @@ -108,6 +110,7 @@ describe("DeployTask", () => { { name: "test-task", dependencies: [], + disabled: false, spec: { log: "test output", }, diff --git a/garden-service/test/unit/src/tasks/get-service-status.ts b/garden-service/test/unit/src/tasks/get-service-status.ts index 5084969d0c..5ff73e7f06 100644 --- a/garden-service/test/unit/src/tasks/get-service-status.ts +++ b/garden-service/test/unit/src/tasks/get-service-status.ts @@ -84,6 +84,7 @@ describe("GetServiceStatusTask", () => { name: "test", type: "test", allowPublish: false, + disabled: false, build: { dependencies: [] }, outputs: {}, path: tmpDir.path, @@ -91,6 +92,7 @@ describe("GetServiceStatusTask", () => { { name: "test-service", dependencies: ["test-task"], + disabled: false, hotReloadable: false, spec: { log: "${runtime.tasks.test-task.outputs.log}", @@ -101,6 +103,7 @@ describe("GetServiceStatusTask", () => { { name: "test-task", dependencies: [], + disabled: false, spec: { log: "test output", }, diff --git a/garden-service/test/unit/src/tasks/helpers.ts b/garden-service/test/unit/src/tasks/helpers.ts index 4cda93bbf4..d87cb8312a 100644 --- a/garden-service/test/unit/src/tasks/helpers.ts +++ b/garden-service/test/unit/src/tasks/helpers.ts @@ -1,75 +1,168 @@ -import Bluebird from "bluebird" import { expect } from "chai" -import { flatten, uniq } from "lodash" +import { uniq } from "lodash" import { resolve } from "path" import { Garden } from "../../../../src/garden" -import { makeTestGarden, dataDir } from "../../../helpers" -import { getDependantTasksForModule } from "../../../../src/tasks/helpers" +import { makeTestGarden, dataDir, makeTestGardenA } from "../../../helpers" +import { getModuleWatchTasks } from "../../../../src/tasks/helpers" import { BaseTask } from "../../../../src/tasks/base" import { LogEntry } from "../../../../src/logger/log-entry" -import { ConfigGraph } from "../../../../src/config-graph" - -async function dependencyBaseKeys(tasks: BaseTask[]): Promise { - const dependencies = await Bluebird.map(tasks, async (t) => t.getDependencies(), { concurrency: 1 }) - const tasksdependencyTasks = flatten(dependencies) - return sortedBaseKeys(tasksdependencyTasks) -} +import { DEFAULT_API_VERSION } from "../../../../src/constants" function sortedBaseKeys(tasks: BaseTask[]): string[] { return uniq(tasks.map((t) => t.getKey())).sort() } describe("TaskHelpers", () => { - let garden: Garden - let graph: ConfigGraph + let depGarden: Garden let log: LogEntry before(async () => { - garden = await makeTestGarden(resolve(dataDir, "test-project-dependants")) - graph = await garden.getConfigGraph(garden.log) - log = garden.log + depGarden = await makeTestGarden(resolve(dataDir, "test-project-dependants")) + log = depGarden.log }) /** * Note: Since we also test with dependencies included in the task lists generated , these tests also check the * getDependencies methods of the task classes in question. */ - describe("getDependantTasksForModule", () => { - it("returns the correct set of tasks for the changed module", async () => { - const module = await graph.getModule("good-morning") - await garden.getConfigGraph(garden.log) + describe("getModuleWatchTasks", () => { + it("should return no deploy tasks for a disabled module, but include its dependant tasks", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: true, // <--------------- + name: "module-a", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-a", + dependencies: [], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [{ name: "module-a", copy: [] }] }, + disabled: false, + name: "module-b", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-b", + dependencies: [], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a", true) + + const tasks = await getModuleWatchTasks({ + garden, + graph, + log, + module, + hotReloadServiceNames: [], + }) + + expect(sortedBaseKeys(tasks)).to.eql(["build.module-a", "build.module-b", "deploy.service-b"]) + }) + + it("should omit tasks for disabled dependant modules", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-a", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-a", + dependencies: [], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [{ name: "module-a", copy: [] }] }, + disabled: true, // <--------------- + name: "module-b", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-b", + dependencies: [], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a", true) - const tasks = await getDependantTasksForModule({ + const tasks = await getModuleWatchTasks({ garden, graph, log, module, hotReloadServiceNames: [], - force: true, - forceBuild: true, - fromWatch: false, - includeDependants: false, }) - expect(sortedBaseKeys(tasks)).to.eql(["build.good-morning", "deploy.good-morning"]) - - expect(await dependencyBaseKeys(tasks)).to.eql( - [ - "build.build-dependency", - "build.good-morning", - "get-service-status.good-morning", - "stage-build.good-morning", - "task.good-morning-task", - ].sort() - ) + expect(sortedBaseKeys(tasks)).to.eql(["build.module-a", "deploy.service-a"]) }) context("without hot reloading enabled", () => { const expectedBaseKeysByChangedModule = [ { moduleName: "build-dependency", - taskKeys: ["build.build-dependency", "deploy.build-dependency"], - withDependants: [ + expectedTasks: [ "build.build-dependant", "build.build-dependency", "build.good-morning", @@ -82,8 +175,7 @@ describe("TaskHelpers", () => { }, { moduleName: "good-morning", - taskKeys: ["build.good-morning", "deploy.good-morning"], - withDependants: [ + expectedTasks: [ "build.build-dependant", "build.good-morning", "deploy.build-dependant", @@ -94,118 +186,306 @@ describe("TaskHelpers", () => { }, { moduleName: "good-evening", - taskKeys: ["build.good-evening", "deploy.good-evening"], - withDependants: ["build.good-evening", "deploy.good-evening"], + expectedTasks: ["build.good-evening", "deploy.good-evening"], }, { moduleName: "build-dependant", - taskKeys: ["build.build-dependant", "deploy.build-dependant"], - withDependants: ["build.build-dependant", "deploy.build-dependant"], + expectedTasks: ["build.build-dependant", "deploy.build-dependant"], }, { moduleName: "service-dependant", - taskKeys: ["build.service-dependant", "deploy.service-dependant"], - withDependants: ["build.service-dependant", "deploy.service-dependant"], + expectedTasks: ["build.service-dependant", "deploy.service-dependant"], }, ] - for (const { moduleName, taskKeys, withDependants } of expectedBaseKeysByChangedModule) { - it(`returns the correct set of tasks for ${moduleName}`, async () => { - const module = await graph.getModule(moduleName) - const tasks = await getDependantTasksForModule({ - garden, - graph, - log, - module, - hotReloadServiceNames: [], - force: true, - forceBuild: true, - fromWatch: true, - includeDependants: false, - }) - expect(sortedBaseKeys(tasks)).to.eql(taskKeys.sort()) - }) - + for (const { moduleName, expectedTasks } of expectedBaseKeysByChangedModule) { it(`returns the correct set of tasks for ${moduleName} with dependants`, async () => { + const graph = await depGarden.getConfigGraph(depGarden.log) const module = await graph.getModule(moduleName) - const tasks = await getDependantTasksForModule({ - garden, + + const tasks = await getModuleWatchTasks({ + garden: depGarden, graph, log, module, hotReloadServiceNames: [], - force: true, - forceBuild: true, - fromWatch: true, - includeDependants: true, }) - expect(sortedBaseKeys(tasks)).to.eql(withDependants.sort()) + expect(sortedBaseKeys(tasks)).to.eql(expectedTasks.sort()) }) } + + it("should omit deploy tasks for disabled services in the module", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-a", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-a", + dependencies: [], + disabled: true, // <--------------- + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a", true) + + const tasks = await getModuleWatchTasks({ + garden, + graph, + log, + module, + hotReloadServiceNames: [], + }) + + expect(sortedBaseKeys(tasks)).to.eql(["build.module-a"]) + }) + + it("should omit deploy tasks for disabled dependant services", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-a", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-a", + dependencies: [], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [{ name: "module-a", copy: [] }] }, + disabled: false, + name: "module-b", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-b", + dependencies: [], + disabled: true, // <--------------- + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a", true) + + const tasks = await getModuleWatchTasks({ + garden, + graph, + log, + module, + hotReloadServiceNames: [], + }) + + expect(sortedBaseKeys(tasks)).to.eql(["build.module-a", "build.module-b", "deploy.service-a"]) + }) }) context("with hot reloading enabled", () => { const expectedBaseKeysByChangedModule = [ { moduleName: "build-dependency", - taskKeys: ["build.build-dependency", "deploy.build-dependency"], - withDependants: ["build.build-dependency", "deploy.build-dependency"], + expectedTasks: [ + "build.build-dependant", + "build.build-dependency", + "build.good-morning", + "deploy.build-dependant", + "deploy.build-dependency", + "deploy.service-dependant", + "deploy.service-dependant2", + ], }, { moduleName: "good-morning", - taskKeys: ["build.good-morning", "deploy.good-morning"], - withDependants: ["deploy.service-dependant", "deploy.service-dependant2"], + expectedTasks: [ + "build.build-dependant", + "deploy.build-dependant", + "deploy.service-dependant", + "deploy.service-dependant2", + "hot-reload.good-morning", + ], }, { moduleName: "good-evening", - taskKeys: ["build.good-evening", "deploy.good-evening"], - withDependants: ["build.good-evening", "deploy.good-evening"], + expectedTasks: ["build.good-evening", "deploy.good-evening"], }, { moduleName: "build-dependant", - taskKeys: ["build.build-dependant", "deploy.build-dependant"], - withDependants: ["build.build-dependant", "deploy.build-dependant"], + expectedTasks: ["build.build-dependant", "deploy.build-dependant"], }, { moduleName: "service-dependant", - taskKeys: ["build.service-dependant", "deploy.service-dependant"], - withDependants: ["build.service-dependant", "deploy.service-dependant"], + expectedTasks: ["build.service-dependant", "deploy.service-dependant"], }, ] - for (const { moduleName, taskKeys, withDependants } of expectedBaseKeysByChangedModule) { - it(`returns the correct set of tasks for ${moduleName}`, async () => { - const module = await graph.getModule(moduleName) - const tasks = await getDependantTasksForModule({ - garden, - graph, - log, - module, - hotReloadServiceNames: ["good-morning"], - force: true, - forceBuild: true, - fromWatch: true, - includeDependants: false, - }) - expect(sortedBaseKeys(tasks)).to.eql(taskKeys.sort()) - }) - + for (const { moduleName, expectedTasks } of expectedBaseKeysByChangedModule) { it(`returns the correct set of tasks for ${moduleName} with dependants`, async () => { + const graph = await depGarden.getConfigGraph(depGarden.log) const module = await graph.getModule(moduleName) - const tasks = await getDependantTasksForModule({ - garden, + + const tasks = await getModuleWatchTasks({ + garden: depGarden, graph, log, module, hotReloadServiceNames: ["good-morning"], - force: true, - forceBuild: true, - fromWatch: true, - includeDependants: true, }) - expect(sortedBaseKeys(tasks)).to.eql(withDependants.sort()) + expect(sortedBaseKeys(tasks)).to.eql(expectedTasks.sort()) }) } + + it("should omit hot reload tasks for disabled services in the module", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-a", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-a", + dependencies: [], + disabled: true, // <--------------- + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a", true) + + const tasks = await getModuleWatchTasks({ + garden, + graph, + log, + module, + hotReloadServiceNames: ["service-a"], + }) + + expect(sortedBaseKeys(tasks)).to.eql([]) + }) + + it("should omit hot reload tasks for disabled dependant services", async () => { + const garden = await makeTestGardenA() + + garden.setModuleConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + name: "module-a", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-a", + dependencies: [], + disabled: false, + }, + ], + }, + testConfigs: [], + type: "test", + }, + { + apiVersion: DEFAULT_API_VERSION, + allowPublish: false, + build: { dependencies: [{ name: "module-a", copy: [] }] }, + disabled: false, + name: "module-b", + include: [], + outputs: {}, + path: garden.projectRoot, + serviceConfigs: [], + taskConfigs: [], + spec: { + services: [ + { + name: "service-b", + dependencies: [], + disabled: true, // <--------------- + }, + ], + }, + testConfigs: [], + type: "test", + }, + ]) + + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a", true) + + const tasks = await getModuleWatchTasks({ + garden, + graph, + log, + module, + hotReloadServiceNames: ["service-a", "service-b"], + }) + + expect(sortedBaseKeys(tasks)).to.eql(["build.module-b", "hot-reload.service-a"]) + }) }) }) }) diff --git a/garden-service/test/unit/src/types/service.ts b/garden-service/test/unit/src/types/service.ts index 070049cf83..6450133cbc 100644 --- a/garden-service/test/unit/src/types/service.ts +++ b/garden-service/test/unit/src/types/service.ts @@ -1,5 +1,7 @@ import { expect } from "chai" -import { combineStates, serviceStates } from "../../../../src/types/service" +import { combineStates, serviceStates, serviceFromConfig } from "../../../../src/types/service" +import { ServiceConfig } from "../../../../src/config/service" +import { makeTestGardenA } from "../../../helpers" describe("combineStates", () => { it("should return ready if all states are ready", () => { @@ -29,3 +31,40 @@ describe("combineStates", () => { expect(result).to.equal("outdated") }) }) + +describe("serviceFromConfig", () => { + it("should propagate the disabled flag from the config", async () => { + const config: ServiceConfig = { + name: "test", + dependencies: [], + disabled: true, + hotReloadable: false, + spec: {}, + } + + const garden = await makeTestGardenA() + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a") + const service = await serviceFromConfig(graph, module, config) + + expect(service.disabled).to.be.true + }) + + it("should set disabled=true if the module is disabled", async () => { + const config: ServiceConfig = { + name: "test", + dependencies: [], + disabled: false, + hotReloadable: false, + spec: {}, + } + + const garden = await makeTestGardenA() + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a") + module.disabled = true + const service = await serviceFromConfig(graph, module, config) + + expect(service.disabled).to.be.true + }) +}) diff --git a/garden-service/test/unit/src/types/task.ts b/garden-service/test/unit/src/types/task.ts new file mode 100644 index 0000000000..239b4f3740 --- /dev/null +++ b/garden-service/test/unit/src/types/task.ts @@ -0,0 +1,41 @@ +import { expect } from "chai" +import { makeTestGardenA } from "../../../helpers" +import { TaskConfig } from "../../../../src/config/task" +import { taskFromConfig } from "../../../../src/types/task" + +describe("taskFromConfig", () => { + it("should propagate the disabled flag from the config", async () => { + const config: TaskConfig = { + name: "test", + dependencies: [], + disabled: true, + spec: {}, + timeout: null, + } + + const garden = await makeTestGardenA() + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a") + const task = taskFromConfig(module, config) + + expect(task.disabled).to.be.true + }) + + it("should set disabled=true if the module is disabled", async () => { + const config: TaskConfig = { + name: "test", + dependencies: [], + disabled: false, + spec: {}, + timeout: null, + } + + const garden = await makeTestGardenA() + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a") + module.disabled = true + const task = taskFromConfig(module, config) + + expect(task.disabled).to.be.true + }) +}) diff --git a/garden-service/test/unit/src/types/test.ts b/garden-service/test/unit/src/types/test.ts new file mode 100644 index 0000000000..0c2c5326be --- /dev/null +++ b/garden-service/test/unit/src/types/test.ts @@ -0,0 +1,41 @@ +import { expect } from "chai" +import { makeTestGardenA } from "../../../helpers" +import { TestConfig } from "../../../../src/config/test" +import { testFromConfig } from "../../../../src/types/test" + +describe("testFromConfig", () => { + it("should propagate the disabled flag from the config", async () => { + const config: TestConfig = { + name: "test", + dependencies: [], + disabled: true, + spec: {}, + timeout: null, + } + + const garden = await makeTestGardenA() + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a") + const test = testFromConfig(module, config) + + expect(test.disabled).to.be.true + }) + + it("should set disabled=true if the module is disabled", async () => { + const config: TestConfig = { + name: "test", + dependencies: [], + disabled: false, + spec: {}, + timeout: null, + } + + const garden = await makeTestGardenA() + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("module-a") + module.disabled = true + const test = testFromConfig(module, config) + + expect(test.disabled).to.be.true + }) +}) diff --git a/garden-service/test/unit/src/vcs/vcs.ts b/garden-service/test/unit/src/vcs/vcs.ts index fb2e8ba2bf..ea1e0bae78 100644 --- a/garden-service/test/unit/src/vcs/vcs.ts +++ b/garden-service/test/unit/src/vcs/vcs.ts @@ -277,7 +277,7 @@ describe("VcsHandler", () => { const garden = await makeTestGarden(projectRoot) const config = await garden.resolveModuleConfig(garden.log, "module-a") - const fixedVersionString = "v-72ab6d8477" + const fixedVersionString = "v-748612a7c4" expect(getVersionString(config, [namedVersionA, namedVersionB, namedVersionC])).to.eql(fixedVersionString) delete process.env.TEST_ENV_VAR