Skip to content

Environments

environments lists the environments a service runs in and the configuration values each one uses. pacto validate checks those values against the schema of every configuration they name, on every change. Pacto applies nothing and deploys nothing. The other sections are in Contract sections and Configuration and policy.

environments

Optional. Needs pactoVersion: "2.3": an older contract with it fails validation with NEEDS_PACTO_2_3. 2.3 needs Pacto core 4.4.0 or later and operator 6.4.0 or later; see Versions.

pactoVersion: "2.3"
service:
  name: web
  version: 1.4.0
configurations:
  - name: app
    schema: configuration/schema.json
    required: true
    values:
      LOG_LEVEL: info
  - name: chart
    schema: chart/values.schema.json
    required: true
environments:
  - name: staging
    file: environments/staging.yaml
  - name: production
    file: environments/production.yaml
Field Type Required Constraints
name string Yes Lowercase alphanumeric and hyphens, at most 63 characters. Unique in the contract (DUPLICATE_ENVIRONMENT_NAME otherwise)
file string Yes Path to a YAML or JSON file, from the bundle root, with no leading / or ./ and no .. (FILE_NOT_FOUND otherwise, or when the file is missing)

The file maps each configuration name to the values that environment uses for it:

# environments/production.yaml
app:
  LOG_LEVEL: warn
chart:
  replicaCount: 5
  ingress:
    host: shop.example.com

The -f/--values and --set flags change the contract itself; an environment's values live in its file.

The file is part of the bundle, so pacto pack and pacto push ship it with the contract. A .pactoignore entry that hides it makes validation report FILE_NOT_FOUND.

How values are checked

For each environment, and for each configuration its file names, Pacto merges the environment's values over the configuration's default values and checks the result against the configuration's schema. Objects merge key by key, and the environment's value wins. A null removes its key from the merged values, at any depth, so an environment can drop a default:

# environments/staging.yaml
chart:
  ingress:
    className: null  # staging uses the cluster's default ingress class

A configuration named with no values, such as app:, keeps its defaults. A required: true configuration that the file leaves out must pass its schema with its defaults alone. A configuration that is optional and left out is not checked.

Condition Code
The file names a configuration the contract does not declare ENVIRONMENT_UNKNOWN_CONFIGURATION
The merged values fail the configuration's schema, or the file does not parse, is not a mapping of configuration names or has a key that is not a string ENVIRONMENT_VALUES_INVALID
A required configuration has no values in the file and its defaults fail its schema ENVIRONMENT_VALUES_MISSING
The configuration names its schema by ref, so the bundle holds no schema to check against, or there are no bundle files to read ENVIRONMENT_VALUES_UNVERIFIED (warning)

Each error names the environment, the configuration and the JSON Pointer of the value, and points at its line in the environment's file. A key that holds a dot or a slash, such as prometheus.io/scrape, is one step of the pointer, with / written as ~1 and ~ as ~0. The checks run in the environments stage of pacto validate.

Values hold references to secrets, such as a Secret or ExternalSecret name. Keep secret material out of the environment files, as you keep it out of the contract; see Secret references.

Catch a chart change that breaks an environment

A Helm chart's values.schema.json is a JSON Schema, so you can reference it as a configuration and keep one file per environment. Version 1.4.0 of the web service ships this chart schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "replicaCount": {"type": "integer", "minimum": 1},
    "ingress": {
      "type": "object",
      "properties": {"host": {"type": "string"}}
    }
  }
}

The contract references it and lists two environments:

pactoVersion: "2.3"
service:
  name: web
  version: 1.4.0
  owner: {team: storefront}
configurations:
  - name: chart
    schema: chart/values.schema.json
    required: true
environments:
  - name: staging
    file: environments/staging.yaml
  - name: production
    file: environments/production.yaml

Staging sets an ingress host. Production sets only a class name:

# environments/staging.yaml
chart:
  replicaCount: 1
  ingress:
    host: staging.example.com
# environments/production.yaml
chart:
  replicaCount: 5
  ingress:
    className: nginx

Version 1.5.0 updates the chart, and its schema makes ingress.host required. pacto validate reports the environment it breaks before you deploy it:

$ pacto validate web-1.5.0
web-1.5.0 is invalid
PASS load
SKIP lock         no pacto.lock
PASS structural
PASS cross-field
PASS policy
FAIL environments
  web-1.5.0/environments/production.yaml:3:3: ERROR [ENVIRONMENT_VALUES_INVALID] environments[1].file: environment production: configuration chart at /chart/ingress: missing property 'host'
SKIP guarantees   no dependency declares requires
SKIP readiness    not requested (--readiness)
SKIP closure      not requested (--closure)
Next: fix environments[1].file at web-1.5.0/environments/production.yaml:3:3 [ENVIRONMENT_VALUES_INVALID], then run pacto validate web-1.5.0
validation failed with 1 error(s)

The command exits 1. Add the host to the production file and run pacto validate again. pacto diff then lists the schema change and the new value:

$ pacto diff web-1.4.0 web-1.5.0
Classification: BREAKING
Required bump: major
Changes (4):
  [NON_BREAKING] service.version (modified): service.version modified [1.4.0 -> 1.5.0]
  [POTENTIAL_BREAKING] schema.properties.ingress.properties.className (added): schema.properties.ingress.properties.className added [+ map[type:string]]
  [BREAKING] schema.properties.ingress.required (added): schema.properties.ingress.required added [+ [host]]
  [NON_BREAKING] environments[production].chart.ingress.host (added): environments[production].chart.ingress.host added [+ shop.example.com]
breaking changes detected

What a change needs

pacto diff compares environments by name:

Change Classification
An environment added Non-breaking: it only adds values to check
An environment removed Potentially breaking: the environment may still run the service
A value in an environment's file added, removed or changed, at environments[<name>].<configuration>.<path> Non-breaking: the values are this service's own inputs, and only the service reads them

Environments and Stages

When delivery promotes a version, it matches the Stage's spec.environment against the environment names the version's contract declares. The pacto.environment check runs the same environments stage as pacto validate, on the version's own bundle:

The version's contract The promote
Declares no environments Goes on. The check says it does not apply
Declares environments, but none named like the Stage's spec.environment Waits for the service's team to approve it
Declares that environment, and its values fail with an error from the table above Waits for the service's team to approve it, with each error located in the file
Declares that environment, and its values are ENVIRONMENT_VALUES_UNVERIFIED Waits for a person to accept the unknown
Declares that environment, and its values pass Goes on

Name each environment after the spec.environment of the Stages that deliver the service there, such as staging and production.