Skip to main content
Version: Enterprise Edition v1.3

Infracost

The Infracost CLI tool parses Terraform files and estimates costs for your infrastructure.

Overview

This guide will show you how to:

  • Run the Infracost CLI in Semaphore
  • Comment Git commits with cost deltas
  • Comment pull requests with cost deltas
  • Fail the CI pipeline if costs exceed a custom-defined policy

Prerequisites

For this guide, you will need the following:

  • A working Semaphore project with a CI pipeline
  • At least one Terraform file in your project
  • An Infracost API key. You must sign up with a free account to obtain it
  • A GitHub, Bitbucket, or GitLab API key with permission to write comments in the repository

Adding a baseline to the repository

In order to estimate deviations from the expected cost, you must store a baseline file in your repository. The following command will generate baseline.json based on all the Terraform files found in your project folder:

infracost breakdown --path . --format json --out-file baseline.json

Now you can push baseline.json into your repository.

Storing the API keys in secrets

Follow the Infracost getting started guide to install the CLI tool on your machine and obtain an API key. Create a secret in Semaphore to store it:

Infracost API key secret

Create a token for your Git provider:

Store the access token in Semaphore:

GitHub Token Secret

Adding cost estimates to commits

When Infracost runs in your CI/CD workflow, it can post comments in commits and pull requests with the estimated cost difference from the baseline or between branches.

Estimates on GitHub

Before you can calculate cost differences in commits or peer reviews, you need to establish a baseline. If you have any usage-based resources such as serverless functions, you need to first create an usage file.

infracost breakdown --sync-usage-file --usage-file usage.yml --path .

Now, edit usage.yml to add your usage estimates for the moth.

Next, you're ready to create a baseline file. Skip --usage-file if you're not using any usage-based cloud resources:

infracost breakdown --path . --format json --usage-file usage.yml --out-file baseline.json

After checking in all the new files into the repository, edit the pipeline to run the cost analysis. Use the following commands in your CI job to post a comment on GitHub with the cost delta between the current commit and the baseline:

curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
checkout
infracost diff --path . --format json --compare-to baseline.json --out-file /tmp/infracost-diff-commit.json
infracost comment github --path=/tmp/infracost-diff-commit.json --repo=$SEMAPHORE_GIT_REPO_SLUG --commit=$SEMAPHORE_GIT_SHA --github-token=$GITHUB_TOKEN --behavior=update

Commenting on BitBucket

Use the following command in a job to comment on BitBucket the difference in cost between the current commit and the baseline:

curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
checkout
infracost diff --path . --format json --compare-to baseline.json --out-file /tmp/infracost-diff-commit.json
infracost comment bitbucket --path=/tmp/infracost-diff-commit.json --repo=$SEMAPHORE_GIT_REPO_SLUG --commit=$SEMAPHORE_GIT_SHA --bitbucket-token=$BITBUCKET_TOKEN --behavior=update

Commenting on GitLab

Use the following command in a job to comment on GitLab the difference in cost between the current commit and the baseline:

curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
checkout
infracost diff --path . --format json --compare-to baseline.json --out-file /tmp/infracost-diff-commit.json
infracost comment gitlab --path=/tmp/infracost-diff-commit.json --repo=$SEMAPHORE_GIT_REPO_SLUG --commit=$SEMAPHORE_GIT_SHA --gitlab-token=$GITLAB_TOKEN --behavior=update

Adding cost estimate to pull requests

A separate job can also be created to post comment on pull requests. This allows the reviewer to quickly assess the cost changes between branches.

Estimates on GitHub

The following example calculates the cost change between the master and the branch that triggered the workflow in GitHub:

curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
checkout
git checkout master
infracost breakdown --path . --format json --out-file /tmp/infracost-master.json
git checkout FETCH_HEAD
infracost diff --path . --format json --compare-to /tmp/infracost-master.json --out-file /tmp/infracost-diff-master.json
infracost comment github --path=/tmp/infracost-diff-master.json --repo=$SEMAPHORE_GIT_REPO_SLUG --pull-request=$SEMAPHORE_GIT_PR_NUMBER --github-token=$GITHUB_TOKEN --behavior=update

Estimates on BitBucket

The following example calculates the cost change between the master and the branch that triggered the workflow in BitBucket:

curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
checkout
git checkout master
infracost breakdown --path . --format json --out-file /tmp/infracost-master.json
git checkout FETCH_HEAD
infracost diff --path . --format json --compare-to /tmp/infracost-master.json --out-file /tmp/infracost-diff-master.json
infracost comment bitbucket --path=/tmp/infracost-diff-master.json --repo=$SEMAPHORE_GIT_REPO_SLUG --pull-request=$SEMAPHORE_GIT_PR_NUMBER --bitbucket-token=$BITBUCKET_TOKEN --behavior=update

Estimates on GitLab

The following example calculates the cost change between the master and the branch that triggered the workflow in GitLab:

curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
checkout
git checkout master
infracost breakdown --path . --format json --out-file /tmp/infracost-master.json
git checkout FETCH_HEAD
infracost diff --path . --format json --compare-to /tmp/infracost-master.json --out-file /tmp/infracost-diff-master.json
infracost comment gitlab --path=/tmp/infracost-diff-master.json --repo=$SEMAPHORE_GIT_REPO_SLUG --pull-request=$SEMAPHORE_GIT_PR_NUMBER --gitlab-token=$GITLAB_TOKEN --behavior=update

Enforcing policies

Infracost can be used to enforce cost policies with continuous integration. When a policy is used, the Infracost CLI will return a non-zero exit status, stopping the pipeline and preventing a deployment that would run over the budget.

First, we must create a policy file and push it into the repository. To learn about the policy syntax, read the cost policies docs on Infracost.

The following example sets a maximum budget of USD 1000 per month:

# policy.rego

package infracost

deny[out] {

# define a variable
maxMonthlyCost = 1000.0

msg := sprintf(
"Total monthly cost must be less than $%.2f (actual cost is $%.2f)",
[maxMonthlyCost, to_number(input.totalMonthlyCost)],
)

out := {
"msg": msg,
"failed": to_number(input.totalMonthlyCost) >= maxMonthlyCost
}
}

To evaluate the policy file, you must add the --policy-path POLICY_FILENAME option to any of the comment commands. For example:

# calculate difference between commit and baseline
infracost diff --path . --format json --compare-to baseline.json --out-file /tmp/infracost-diff-commit.json

# enforce policy
infracost comment github --path=/tmp/infracost-diff-commit.json --repo=$SEMAPHORE_GIT_REPO_SLUG --commit=$SEMAPHORE_GIT_SHA --github-token=$GITHUB_API_KEY --behavior=update

Tips for using Infracost

  • You can use monorepo conditions like change_in('/**/*.tf') or change_in('/**/*.tfvars') to run Infracost only when Terraform files change
  • You can create a config file manage monorepo workflows provide utilization forecast for per-usage services such as AWS lambda
  • You can add a badge to your repository with the estimated monthly cost