Resourcely-cli

resourcely-cli is a command line utility that helps Resourcely evaluate Guardrails against Terraform plans. It runs in on your infrastructure, typically a CI/CD runner, and only sends lightweight metadata about Guardrail violations back to Resourcely.

This page describes general resourcely-cli concepts that apply to any runner.

Running the CLI

resourcely-cli is available as a Docker container or as a self-contained binary. Individual users never need to run resourcely-cli manually. See CI/CD & Terraform Runners for how to configure various CI systems to run resourcely-cli automatically.

JSON output

resourcely-cli can produce a JSON file with finding details. You can consume them in code that gathers statistics, sends slack messages, etc. Pass --output_file <filename> to enable this feature.

Here is an example file with comments describing each field:

{
  // Contains a list of all findings, including ones from evaluate-only guardrails
  "findings": [
    {
      "actions": {
        // PR approvals triggered by this finding
        "approval": {
          // The name of the Notification Group in resourcely
          "resourcely_team": "default",
          
          // The rest of the fields are properties of the Notification Group
          "github_reviewers": [
            "secops"
          ],
          "gitlab_reviewers": [],
        }
      },
      
      // The ID of the guardrail
      "guardrail_id": "787de9a7-8cd7-4dd7-8851-f88653ed0900",
      
      // A link to the guardrail's detail page
      "guardrail_link": "https://portal.resourcely.io/guardrails/5e89f370-f0f9-4e5f-95c0-4b0759c7b0a8",
      
      // The state of the guardrail at the time resourcely-cli ran. Values:
      // GUARDRAIL_STATE_ACTIVE
      // GUARDRAIL_STATE_EVALUATE_ONLY
      "guardrail_state": "GUARDRAIL_STATE_ACTIVE",
      
      // The part of the guardrail that was violated
      "requirement": "REQUIRE egress.from_port != 10",
      
      // The resource that violated the guardrail
      "resource": {
        "address": "aws_security_group.blah",
        "tf_config_root_path": "us-east-1",
        "environment": "dev",
        "actions": [
          "create"
        ]
      }
    }
  ]
}

Advanced usage

If you use .resourcely.yaml to define multiple config roots (and optionally, environments), you probably need to use resourcely-cli to evaluate multiple plans, one per config root (and environment).

Below, we'll use the following resourcely.yaml in examples:

terraform_config_roots:
  - name: "Global resources"
    path: global
    # no environments for the sake of example
  - name: "Regional resources"
    path: regional
    environments:
      - name: dev
        tfvars_file: regional/dev.tfvars
      - name: prod
        tfvars_file: regional/prod.tfvars

Running resourcely-cli with many plans

You can run resourcely-cli with several plans at once. For each plan, you must specify the config root (and environment, if applicable). Resourcely uses these values to merge findings from each plan and determine when it can approve the pull request.

Note: The values of --config_root_path and --environment must match the corresponding path and name fields in resourcely.yaml.

Examples:

$ resourcely-cli <other args> \
  --config_root_path global --plan global.tfplan.json

$ resourcely-cli <other args> \
  --config_root_path regional --environment dev --plan regional-dev.tfplan.json \
  --config_root_path regional --environment prod --plan regional-prod.tfplan.json

You can pass --error_on_violations parameter to Resourcely-cli if you want it to exist with non-zero when it finds guardrail violations

Multiple runs of resourcely-cli

Some CI/CD setups can't access every plan in one place, so they must run resourcely-cli multiple times. After each run of resourcely-cli, Resourcely will merge findings from that run over any prior findings, using the config root and (optionally) environment as the key.

An example for the sake of demonstrating the merge logic:

# Assume we ran the commands from the previous section. There were 9 findings:
# 1 in global, 3 in regional-dev, and 5 in regional-prod.

# The developer fixes 3/3 regional-dev findings, and 2/5 regional-prod findings.
# Your CI job then runs resourcely-cli for dev and prod separately.

$ resourcely-cli <other args> \
  --config_root_path regional --environment dev --plan regional-dev.tfplan.json

# This run replaced the 3 regional-dev findings with zero findings. There are now
# 6 findings total - 1 in global and 5 in regional-prod.

$ resourcely-cli <other args> \
  --config_root_path regional --environment prod --plan regional-prod.tfplan.json

# This run replaced the 5 regional-prod findings with the 3 un-fixed findings. There
# are now 4 findings total - 1 in global and 3 in regional-prod.

You can pass --output_file to write findings to a file (as json)

Pull request approval

When a pull request is created, Resourcely computes the set of plans that it expects to receive findings from. Resourcely will only approve the pull request once it has received findings from every plan for the PR's latest commit.

Resourcely computes the set of expected plans based on which files the pull request has changed:

  • If the PR changes a .tf file in a config root...

    • ...if that config root does not have environments, Resourcely expects one plan from it.

    • ...otherwise, Resourcely expects one plan from each of its environments.

  • Additionally, if the PR changes a .tfvars file listed in resourcely.yaml, Resourcely expects one plan from that config root and environment.

Examples:

Files changed: global/main.tf, regional/main.tf
3 expected plans: global (no env), regional dev, regional prod

# Note the change to main.tf. Resourcely will expect a plan from both envs, even
# though only one env's tfvars changed.
Files changed: regional/main.tf, regional/dev.tfvars
2 expected plans: regional dev, regional prod

Files changed: regional/prod.tfvars
1 expected plans: regional prod

Last updated