Profiles: Patches
Patches are a way to perform minor overrides to the configuration without having to create a separate config file. Patch functionality follows JSON Patch(RFC) semantics, as well as enhanced path selectors, as implemented by the yaml-jsonpath library.
Example
Patches are ideal for reflecting changes between different environments, e.g. dev, staging and production.
images:
backend:
image: john/devbackend
backend-debugger:
image: john/debugger
deployments:
backend:
helm:
values:
containers:
- image: john/devbackend
- image: john/debugger
profiles:
- name: production
patches:
- op: replace
path: images.backend.image
value: john/prodbackend
- op: replace
path: deployments.backend.helm.values.containers[0].image
value: john/prodbackend
- op: replace
path: deployments.backend.helm.values.containers[1].image
value: john/cache
Explanation:
- The above example defines 1 profile:
production - When using the profile
production, the config would be patched with 3 patches. - The resulting config used in-memory when the profile
productionis used would look like this:
# In-Memory Config After Applying Patches For Profile `production`
images:
backend:
image: john/prodbackend
backend-debugger:
image: john/debugger
deployments:
backend:
helm:
values:
containers:
- image: john/prodbackend
- image: john/cache
Configuration
name
The name option is mandatory and expects a string defining the name of the profile.
patches
The patches option expects an array of patch objects which consists of the following properties:
opstating the patch operation (possible values:replace,add,remove)pathstating a jsonpath or a xpath within the config (e.g.images.backend.image,deployments.backend.helm.values.containers.name=backend)valuestating an arbitrary value used by the operation (e.g. a string, an integer, a boolean, a yaml object)
op: add only for arraysUsing op: add only works as expected when path points to an array value. Using op: add to add properties to an object (e.g. deployments.*.helm.values) will not work and instead replace all existing properties.
When you want to define a path that contains an array (e.g. hooks), you have several options:
- Use the index of the array item you want to patch, e.g.
hooks[0],hooks/0 - Use a property selector matching the array item(s) you want to patch, e.g.
hooks.name=backend - Use a wildcard selector to match all array item(s), e.g.
hooks.*,hooks[*]orhooks/* - Use a comparison selector matching the array item(s) you want to patch, e.g.
hooks[?(@.events=='before:build')] - Use a regular expression selector matching the array item(s) you want to patch, e.g.
hooks[?(@.name=~/^production/)]
Using a selector rather than the array index is often better because it is more resilient and will not cause any issues even if the order of an array's items is changed later on. A selector is also able to select multiple array items if all of them have the same value for this property.
If you use the replace or add operation, value is a mandatory property.
If value is defined, the new value must provide the correct type to be used when adding or replacing the existing value found under path using the newly provided value, e.g. an array must be replaced with an array.
The patches of a profile can modify all parts of the configuration except the sections profiles, commands and imports.
Example: Config Patches
images:
backend:
image: john/devbackend
backend-debugger:
image: john/debugger
deployments:
backend:
helm:
values:
containers:
- image: john/devbackend
- image: john/debugger
profiles:
- name: staging
patches:
- op: replace
path: images.backend.image
value: john/stagingbackend
- op: replace
path: deployments.backend.helm.values.containers.image=john/devbackend.image
value: john/stagingbackend
- op: remove
path: deployments.backend.helm.values.containers.image=john/debugger
- name: production
patches:
- op: replace
path: images.backend.image
value: john/prodbackend
- op: replace
path: deployments.backend.helm.values.containers.image=john/devbackend.image
value: john/prodbackend
- op: replace
path: deployments.backend.helm.values.containers.image=john/debugger.image
value: john/cache
Explanation:
- The above example defines 2 profiles:
staging,production - Users can use the flag
-p stagingto use thestagingprofile for a single command execution - Users can use the flag
-p productionto use theproductionprofile for a single command execution - Users can permanently switch to the
stagingprofile using:devspace use profile staging - Users can permanently switch to the
productionprofile using:devspace use profile production
Example: Config Patches using Recursive Descent Selectors
images:
backend:
image: john/devbackend
backend-debugger:
image: john/debugger
deployments:
backend:
helm:
values:
containers:
- image: john/devbackend
- image: john/debugger
profiles:
- name: staging
patches:
- op: replace
path: ..[?(@.image=='john/devbackend')].image
value: john/stagingbackend
- op: remove
path: deployments..[?(@.image=='john/debugger')]
- name: production
patches:
- op: replace
path: ..[?(@.image=='john/devbackend')].image
value: john/prodbackend
- op: replace
path: deployments..[?(@.image=='john/debugger')].image
value: john/cache
Explanation:
- The above example has the same result as this previous example.
- By using a recursive descent selector, we can change many
imagevalues at once. - When the
stagingprofile is used, allimageproperties that have valuejohn/devbackendare changed tojohn/stagingbackend, and all deployment containers withimageproperty equal tojohn/debuggerare removed. - When the
productionprofile is used, allimageproperties that have valuejohn/devbackendare changed tojohn/prodbackend, and all deployment containers withimageproperty equal tojohn/debuggerare changed tojohn/cache.
Useful Commands
devspace print -p [profile]
The following command is useful to verify that the profile modifies the config as intended:
devspace print -p profile-1 -p profile-2
The above command would print the config after applying all profile patches and replace statements from profile-1 first and then profile-2.
