Reference / CLI reference

CLI reference

Every bidirekt command with an example, its arguments, and the mistakes worth knowing about.

bidirekt is the command-line client of the broker. One section per command, in the order a pipeline uses them; the help output, exit codes and output streams are in Overview at the end.

Every command that talks to the broker needs to know which broker: there is no default. Save it once with configure, or pass it with --broker-url or BIDIREKT_BROKER_URL; see Broker address for the order that wins.

configure

$ bidirekt configure
Broker URL: https://broker.example.com
  • โ”œโ”€โ”€
    --profile: the profile to save into. Without it, BIDIREKT_PROFILE, then default.
  • โ””โ”€โ”€
    --broker-url: saves this URL without asking, which is how a script or a pipeline configures a profile.

The URL must start with http:// or https:// followed by a host; http://localhost:8080 is valid. Anything else is asked again, or with --broker-url fails with invalid broker URL "broker.example.com" (from --broker-url) โ€” use http:// or https:// followed by a host, and nothing is saved.

A profile is one broker. A company with one broker only needs default. A group of companies, or a freelancer working for several clients, keeps one profile per broker, such as acme and globex. Environments such as production and staging live inside a broker, so they are never profiles.

Running it again for a profile that already has a URL shows it, and Enter keeps it:

$ bidirekt configure --profile acme
Broker URL [https://broker.acme.example]:

Without --broker-url and outside a terminal, it fails with no terminal to ask for the broker URL โ€” pass --broker-url. BIDIREKT_BROKER_URL is ignored here: configure only writes the config file.

create-participant

$ bidirekt create-participant petstore_api
petstore_api participant created
  • โ””โ”€โ”€
    name: the participant's name, in snake_case (lowercase letters, digits and single underscores). Anything else fails with participant name must be snake_case.

Running it again for an existing name prints petstore_api participant already exists and exits 0, so a pipeline can run it on every build.

create-environment

$ bidirekt create-environment production
production environment created
  • โ””โ”€โ”€
    name: the environment's name, such as production or staging.

Running it again prints production environment already exists and exits 0.

validate

$ bidirekt validate contracts/*.yaml --participant petstore_web --environment production
petstore_web local contract can be deployed to production
  • โ”œโ”€โ”€
    file...: one or more contract files, .yaml or .yml, the same files publish takes.
  • โ”œโ”€โ”€
    --participant: the participant the files belong to.
  • โ””โ”€โ”€
    --environment: the environment to check against.

It answers, before you commit, what can-i-deploy would answer after a publish: the files are checked as publish checks them, then compared with every counterpart deployed in the environment. Nothing is stored: no version, no check.

It exits 0 when the answer is yes and 1 when it is no; the report goes to stdout either way, with the same lines as can-i-deploy:

$ bidirekt validate contracts/*.yaml --participant petstore_web --environment production
petstore_web local contract cannot be deployed to production

petstore_api (1.4.0, deployed):
  GET /pets/*
    response 200:
      - petstore_web reads "$.status", but petstore_api doesn't provide it โ†’ stop reading it, or mark it optional
      - petstore_web reads "$.weight" as string, but petstore_api provides integer โ†’ read it as integer

petstore_reviews:
  GET /reviews/summary
    response 200:
      - petstore_web calls GET /reviews/summary, but petstore_reviews doesn't provide it โ†’ stop calling it, or wait until petstore_reviews publishes it

Errors that stop the check go to stderr: an unknown participant fails with participant not found, an environment that does not exist with environment not found, and a file that breaks the specification prints its violations as publish does.

publish

$ bidirekt publish contracts/*.yaml --participant petstore_api --version 1.4.0
petstore_api contract publish successful
  • โ”œโ”€โ”€
    file...: one or more contract files, .yaml or .yml. All of them publish together as one contract (Several files); the shell expands globs.
  • โ”œโ”€โ”€
    --participant: the participant the contract belongs to. It must exist, or the publish fails with contract participant not found.
  • โ””โ”€โ”€
    --version: any label, usually a commit hash or a release tag.

Publishing the same version again with different content fails with contract version already exists with different content; a version never changes once published.

A file that breaks the specification is rejected as a whole, with one line per violation:

$ bidirekt publish petstore_api.yaml --participant petstore_api --version 1.4.0
contract validation failed
  - petstore_api.yaml: invalid endpoint "/pets/{petId}" at provides rest, dynamic path segments must use *

can-i-deploy

$ bidirekt can-i-deploy petstore_web --version 2.3.0 --environment production
petstore_web 2.3.0 can be deployed to production
  • โ”œโ”€โ”€
    participant: the participant you are about to deploy.
  • โ”œโ”€โ”€
    --version: the published version you are about to deploy.
  • โ””โ”€โ”€
    --environment: where you are about to deploy it.

It exits 0 when the answer is yes and 1 when it is no; the report goes to stdout either way. Errors that stop the check go to stderr: a version that was never published fails with contract not found, an environment that does not exist with environment not found, and an unknown participant with participant not found.

$ bidirekt can-i-deploy petstore_web --version 2.3.0 --environment production
petstore_web 2.3.0 cannot be deployed to production

petstore_api (1.4.0, deployed):
  GET /pets/*
    response 200:
      - petstore_web reads "$.status", but petstore_api doesn't provide it โ†’ stop reading it, or mark it optional
      - petstore_web reads "$.weight" as string, but petstore_api provides integer โ†’ read it as integer

petstore_reviews:
  GET /reviews/summary
    response 200:
      - petstore_web calls GET /reviews/summary, but petstore_reviews doesn't provide it โ†’ stop calling it, or wait until petstore_reviews publishes it

One block per counterpart that is not compatible, with the version of it deployed in the environment; the version is left out when it is not deployed there. Every line a break can carry, written from the side of <participant>, the participant under check, against <counterpart>, the counterpart it breaks:

LineWhen
<participant> reads "<property>", but <counterpart> doesn't provide it โ†’ stop reading it, or mark it optionalresponse: the participant requires a property the provider does not declare
<participant> doesn't provide "<property>", but <counterpart> reads it โ†’ keep providing itresponse: the consumer requires a property the participant does not declare
<participant> requires "<property>", but <counterpart> only sometimes provides it โ†’ mark it optionalresponse: the participant requires a property the provider marks optional
<participant> provides "<property>" only sometimes, but <counterpart> requires it โ†’ keep it requiredresponse: the consumer requires a property the participant marks optional
<participant> doesn't send "<property>", but <counterpart> requires it โ†’ send itrequest: the provider requires a property the participant does not send
<participant> requires "<property>", but <counterpart> doesn't send it โ†’ make it optionalrequest: the participant requires a property the consumer does not send
<participant> sends "<property>" only sometimes, but <counterpart> requires it โ†’ always send itrequest: the provider requires a property the participant marks optional
<participant> requires "<property>", but <counterpart> sends it only sometimes โ†’ make it optionalrequest: the participant requires a property the consumer marks optional
<participant> reads "<property>" as <type>, but <counterpart> provides <counterpart type> โ†’ read it as <counterpart type>response: the participant reads the property with a different type than the provider declares
<participant> provides "<property>" as <type>, but <counterpart> reads <counterpart type> โ†’ provide <counterpart type>response: the participant declares the property with a different type than the consumer reads
<participant> sends "<property>" as <type>, but <counterpart> expects <counterpart type> โ†’ send <counterpart type>request: the participant sends the property with a different type than the provider declares
<participant> expects "<property>" as <type>, but <counterpart> sends <counterpart type> โ†’ accept <counterpart type>request: the participant declares the property with a different type than the consumer sends
<participant> calls <METHOD> <endpoint>, but <counterpart> doesn't provide it โ†’ stop calling it, or wait until <counterpart> publishes itthe participant names an endpoint, method or status that no published contract of the provider declares, or a provider that does not exist
<participant> calls <METHOD> <endpoint>, but <counterpart> is not deployed in <environment> (deployed in: <environments>) โ†’ deploy <counterpart> firstthe provider is not deployed in the target environment; the parenthesis is left out when it is deployed nowhere
<participant> removed <METHOD> <endpoint>, but <counterpart> still calls it โ†’ keep it until <counterpart> stops calling itthe participant dropped a resource that a consumer deployed in the environment still consumes

<property> is written from the root of the body: $.owner.name for a member, $[].photoUrl for a member of each array item. An array type prints its item type, such as array<object>. Which side's required properties count is in How the broker works.

record-deployment

$ bidirekt record-deployment petstore_web --version 2.3.0 --environment production
petstore_web deployment recorded to production
  • โ”œโ”€โ”€
    participant: the participant you deployed.
  • โ”œโ”€โ”€
    --version: the version you deployed. It must be published, or the command fails with version not found.
  • โ””โ”€โ”€
    --environment: where you deployed it. It must exist, or the command fails with environment not found.

Run it after the deployment succeeds. It never checks compatibility and never blocks; recording an earlier version again is how a rollback is recorded.

rename-participant

$ bidirekt rename-participant petstore_inventory petstore_stock
petstore_inventory participant renamed to petstore_stock
  • โ”œโ”€โ”€
    old: the current name.
  • โ””โ”€โ”€
    new: the new name, in snake_case. A name that is already taken fails with participant already exists and exits 1; unlike create-participant, renaming is not idempotent.

The participant keeps its versions and deployments, but a provider's published resources keep the old name. Renaming a provider is a migration:

  1. 1.
    Rename the participant.
  2. 2.
    Publish and deploy a new version of the provider, so its resources carry the new name.
  3. 3.
    Switch every consumer's consumes key to the new name.

Between steps 2 and 3 one side fails with a line such as petstore_web calls GET /stock/*, but petstore_inventory doesn't provide it โ†’ stop calling it, or wait until petstore_inventory publishes it: consumers still on the old name stop matching once the new version is deployed, and consumers already on the new name fail until it is. Renaming a consumer needs none of this.

list-participants

$ bidirekt list-participants
petstore_api
petstore_reviews
petstore_web

One name per line, in alphabetical order. Use it to check a participant's exact name before writing it under consumes or passing it to a command. With no participants it prints nothing and exits 0.

list-environments

$ bidirekt list-environments
production
staging

One name per line, in alphabetical order. Use it to check an environment's exact name before passing it to --environment. With no environments it prints nothing and exits 0.

version

$ bidirekt version
bidirekt version dev

bidirekt --version and bidirekt -v print the same. dev is what a binary built without a version prints; a release prints its own.

Overview

$ bidirekt --help
CLI for Bidirekt

Usage:
  bidirekt [command]

Available Commands:
  can-i-deploy       Check whether a participant version can be deployed to an environment
  completion         Generate the autocompletion script for the specified shell
  configure          Save the broker URL of a profile in the config file
  create-environment Create a new environment on the broker
  create-participant Create a new participant on the broker
  help               Help about any command
  list-environments  List the environments on the broker
  list-participants  List the participants on the broker
  publish            Publish one or more contract YAML files to the broker
  record-deployment  Record a deployment of a participant version to an environment
  rename-participant Rename an existing participant on the broker
  validate           Validate contract YAML files against an environment without publishing them
  version            Print the bidirekt version

Flags:
      --broker-url string   Broker base URL
  -h, --help                help for bidirekt
      --profile string      Profile in the config file (falls back to BIDIREKT_PROFILE, then "default")
  -v, --version             version for bidirekt

Use "bidirekt [command] --help" for more information about a command.

Broker address. The first of these wins: --broker-url, then the BIDIREKT_BROKER_URL environment variable, then the URL saved in the active profile. The active profile is --profile, then BIDIREKT_PROFILE, then default. The flags go before or after the command, and the variables suit a pipeline:

$ bidirekt --broker-url https://broker.example.com publish contracts/*.yaml --participant petstore_api --version 1.4.0
$ BIDIREKT_BROKER_URL=https://broker.example.com bidirekt can-i-deploy petstore_api --version 1.4.0 --environment production
$ bidirekt record-deployment petstore_api --version 1.4.0 --environment production --profile acme

Before calling the broker, each command prints which broker it uses and where that came from, on stderr; a password in the URL shows as xxxxx:

Broker: https://broker.example.com (profile: default)
Broker: https://broker.example.com (from BIDIREKT_BROKER_URL)
Broker: https://broker.example.com (from --broker-url)

When none of them has a URL, the command fails without calling anything, in a terminal or in a pipeline alike; it never asks. Run configure once, then run the command again:

no broker configured โ€” pass --broker-url, set BIDIREKT_BROKER_URL, or run "bidirekt configure"

A profile named with --profile or BIDIREKT_PROFILE that is not in the config file fails the same way with profile "acme" not found in <path>. An invalid URL fails wherever it comes from, naming the source: invalid broker URL "broker.example.com" (from BIDIREKT_BROKER_URL) โ€” use http:// or https:// followed by a host.

Config file. Profiles live in ~/.config/bidirekt/config.json ($XDG_CONFIG_HOME/bidirekt/config.json when XDG_CONFIG_HOME is set), or in the file BIDIREKT_CONFIG_FILE names. configure creates it readable only by you:

{
  "profiles": {
    "default": { "brokerUrl": "https://broker.example.com" },
    "acme": { "brokerUrl": "https://broker.acme.example" },
    "globex": { "brokerUrl": "https://broker.globex.example" }
  }
}

A .env file in the current directory is not read. A request that gets no answer is cancelled after 30 seconds.

Exit codes. 0 on success, help and version; 1 for everything else, including a refused publish and a can-i-deploy answer of no.

Output. Success lines go to stdout and failures to stderr, except the can-i-deploy and validate reports, which always go to stdout. In a terminal, success is green and failures are red; NO_COLOR turns color off and CLICOLOR_FORCE=1 keeps it on when the output is piped.