infracost/actions

Infracost estimate: monthly cost will increase by $0.00 - When base branch is empty

kaykhan opened this issue · 5 comments

We have started a new infrastructure project where we are deploying aws-msk. We have two branches.

In our main branch it is empty.
In dev/modules it contains our new terraform config.

We have created a PR from dev/modules to main. During the CI run we get the following message when we are expecting more.

Infracost estimate: monthly cost will increase by $0.00

image

If we run infracost breakdown --path . --show-skipped locally in our dev/modules branch we get what we are expecting to see in our CI.

Project: <redacted>/infra-kafka/applications/prod/msk

 Name                                                                           Monthly Qty  Unit                    Monthly Cost

 aws_s3_bucket.custom_plugins
 └─ Standard
    ├─ Storage                                                            Monthly cost depends on usage: $0.023 per GB
    ├─ PUT, COPY, POST, LIST requests                                     Monthly cost depends on usage: $0.005 per 1k requests
    ├─ GET, SELECT, and all other requests                                Monthly cost depends on usage: $0.0004 per 1k requests
    ├─ Select data scanned                                                Monthly cost depends on usage: $0.002 per GB
    └─ Select data returned                                               Monthly cost depends on usage: $0.0007 per GB

 module.prod_msk_default.aws_cloudwatch_log_group.this
 ├─ Data ingested                                                         Monthly cost depends on usage: $0.50 per GB
 ├─ Archival Storage                                                      Monthly cost depends on usage: $0.03 per GB
 └─ Insights queries data scanned                                         Monthly cost depends on usage: $0.005 per GB

 module.prod_msk_default.aws_msk_cluster.this
 ├─ Instance (kafka.m5.large)                                                         2,190  hours                        $459.90
 └─ Storage (autoscaling)                                                                 3  GB                             $0.30

 module.prod_msk_mongodb_default_connector.aws_cloudwatch_log_group.this
 ├─ Data ingested                                                         Monthly cost depends on usage: $0.50 per GB
 ├─ Archival Storage                                                      Monthly cost depends on usage: $0.03 per GB
 └─ Insights queries data scanned                                         Monthly cost depends on usage: $0.005 per GB

 module.prod_msk_mysql_default_connector.aws_cloudwatch_log_group.this
 ├─ Data ingested                                                         Monthly cost depends on usage: $0.50 per GB
 ├─ Archival Storage                                                      Monthly cost depends on usage: $0.03 per GB
 └─ Insights queries data scanned                                         Monthly cost depends on usage: $0.005 per GB

 OVERALL TOTAL                                                                                                            $460.20
──────────────────────────────────
30 cloud resources were detected:
∙ 6 were estimated, 4 of which include usage-based costs, see https://infracost.io/usage-file
∙ 14 were free:
  ∙ 4 x aws_iam_role
  ∙ 4 x aws_iam_role_policy_attachment
  ∙ 2 x aws_iam_policy
  ∙ 1 x aws_appautoscaling_policy
  ∙ 1 x aws_iam_instance_profile
  ∙ 1 x aws_msk_configuration
  ∙ 1 x aws_security_group
∙ 10 are not supported yet, see https://infracost.io/requested-resources:
  ∙ 3 x aws_mskconnect_custom_plugin
  ∙ 3 x aws_s3_object
  ∙ 2 x aws_mskconnect_connector
  ∙ 2 x aws_mskconnect_worker_configuration

terraform-ci.yaml

name: terraform-ci

on:
  workflow_call:
    inputs:
      TF_ROOT:
        required: true
        type: string
      TF_APP_NAME:
        required: true
        type: string

jobs:
  cost:
    needs: plan
    name: cost
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout/@v3
    - name: Setup Infracost
      uses: infracost/actions/setup@v2
      with:
        api-key: ${{ secrets.INFRACOST_API_KEY }}
    - name: Checkout base branch
      uses: actions/checkout@v3
      with:
        ref: '${{ github.event.pull_request.base.ref }}'
    - name: Generate Infracost cost estimate baseline
      run: |
        infracost breakdown --path=${{ inputs.TF_ROOT }} \
                            --format=json \
                            --out-file=/tmp/infracost-base.json
        cat /tmp/infracost-base.json

    - name: Checkout PR branch
      uses: actions/checkout@v3
    - name: Generate Infracost diff
      run: |
        infracost diff --path=${{ inputs.TF_ROOT }} \
                        --format=json \
                        --compare-to=/tmp/infracost-base.json \
                        --out-file=/tmp/infracost.json
        cat /tmp/infracost-base.json
        echo "here"
        cat /tmp/infracost.json
    - name: Post Infracost comment
      run: |
          infracost comment github --path=/tmp/infracost.json \
                                   --repo=$GITHUB_REPOSITORY \
                                   --github-token=${{github.token}} \
                                   --pull-request=${{github.event.pull_request.number}} \
                                   --behavior=new

/tmp/infracost-base.json

{"version":"0.2","metadata":{"infracostCommand":"breakdown","vcsBranch":"main","vcsCommitSha":"c9137bb438353a9a2d6630c7491c16bc28bd584e","vcsCommitAuthorName":"Kay Khan","vcsCommitAuthorEmail":"redacted","vcsCommitTimestamp":"2023-08-16T12:43:47Z","vcsCommitMessage":"first commit","vcsRepositoryUrl":"https://github.com/redacted/infra-kafka","vcsProvider":"github","vcsBaseBranch":"main","vcsPullRequestTitle":"feat: kafka + kafkaui","vcsPullRequestUrl":"https://github.com/redacted/infra-kafka/pull/1","vcsPullRequestAuthor":"kaykhan","vcsPipelineRunId":"5901458303","vcsPullRequestId":"1"},"currency":"USD","projects":[{"name":"redacted/infra-kafka/applications/prod/msk","metadata":{"path":"applications/prod/msk","type":"error","vcsSubPath":"applications/prod/msk","errors":[{"code":0,"message":"No such file or directory applications/prod/msk\n\nTry adding a config-file to configure how Infracost should run. See https://infracost.io/config-file for details and examples.","data":null}]},"pastBreakdown":{"resources":[],"totalHourlyCost":"0","totalMonthlyCost":"0"},"breakdown":{"resources":[],"totalHourlyCost":"0","totalMonthlyCost":"0"},"diff":{"resources":[],"totalHourlyCost":"0","totalMonthlyCost":"0"},"summary":{"totalDetectedResources":0,"totalSupportedResources":0,"totalUnsupportedResources":0,"totalUsageBasedResources":0,"totalNoPriceResources":0,"unsupportedResourceCounts":{},"noPriceResourceCounts":{}}}],"totalHourlyCost":"0","totalMonthlyCost":"0","pastTotalHourlyCost":"0","pastTotalMonthlyCost":"0","diffTotalHourlyCost":"0","diffTotalMonthlyCost":"0","timeGenerated":"2023-08-18T10:15:30.213041174Z","summary":{"totalDetectedResources":0,"totalSupportedResources":0,"totalUnsupportedResources":0,"totalUsageBasedResources":0,"totalNoPriceResources":0,"unsupportedResourceCounts":{},"noPriceResourceCounts":{}}}

/tmp/infracost.json

{"version":"0.2","metadata":{"infracostCommand":"diff","vcsBranch":"dev/modules","vcsCommitSha":"40567b7ed14441c64890b09f0cca994f688a910e","vcsCommitAuthorName":"Kay Khan","vcsCommitAuthorEmail":"redacted","vcsCommitTimestamp":"2023-08-18T10:13:53Z","vcsCommitMessage":"update: debug","vcsRepositoryUrl":"https://github.com/redacted/infra-kafka","vcsProvider":"github","vcsBaseBranch":"main","vcsPullRequestTitle":"feat: kafka + kafkaui","vcsPullRequestUrl":"https://github.com/redacted/infra-kafka/pull/1","vcsPullRequestAuthor":"kaykhan","vcsPipelineRunId":"5901458303","vcsPullRequestId":"1"},"currency":"USD","projects":[{"name":"redacted/infra-kafka/applications/prod/msk","metadata":{"path":"applications/prod/msk","type":"terraform_dir","vcsSubPath":"applications/prod/msk","errors":[{"code":0,"message":"Diff baseline error: No such file or directory applications/prod/msk\n\nTry adding a config-file to configure how Infracost should run. See https://infracost.io/config-file for details and examples.","data":null}],"providers":[{"name":"aws","filename":"applications/prod/msk/versions.tf","startLine":13,"endLine":15}]},"pastBreakdown":null,"breakdown":{"resources":[],"totalHourlyCost":"0","totalMonthlyCost":"0"},"diff":null,"summary":{"totalDetectedResources":30,"totalSupportedResources":6,"totalUnsupportedResources":10,"totalUsageBasedResources":4,"totalNoPriceResources":14,"unsupportedResourceCounts":{"aws_mskconnect_connector":2,"aws_mskconnect_custom_plugin":3,"aws_mskconnect_worker_configuration":2,"aws_s3_object":3},"noPriceResourceCounts":{"aws_appautoscaling_policy":1,"aws_iam_instance_profile":1,"aws_iam_policy":2,"aws_iam_role":4,"aws_iam_role_policy_attachment":4,"aws_msk_configuration":1,"aws_security_group":1}}}],"totalHourlyCost":"0","totalMonthlyCost":"0","pastTotalHourlyCost":null,"pastTotalMonthlyCost":null,"diffTotalHourlyCost":null,"diffTotalMonthlyCost":null,"timeGenerated":"2023-08-18T10:15:33.247471322Z","summary":{"totalDetectedResources":30,"totalSupportedResources":6,"totalUnsupportedResources":10,"totalUsageBasedResources":4,"totalNoPriceResources":14,"unsupportedResourceCounts":{"aws_mskconnect_connector":2,"aws_mskconnect_custom_plugin":3,"aws_mskconnect_worker_configuration":2,"aws_s3_object":3},"noPriceResourceCounts":{"aws_appautoscaling_policy":1,"aws_iam_instance_profile":1,"aws_iam_policy":2,"aws_iam_role":4,"aws_iam_role_policy_attachment":4,"aws_msk_configuration":1,"aws_security_group":1}}}

it appears like it is unable to estiamte the cost if there is no base to compare with?

I think it would be better if it produced the output of infracost breakdown --path . --show-skipped when there is no base to compare with? How might we do this, has this been considered?

@kaykhan thanks for reporting. In the pipeline what are you passing in as inputs.TF_ROOT?

@kaykhan thanks for reporting. In the pipeline what are you passing in as inputs.TF_ROOT?

To be clear terraform-ci.yaml is a reusable workflow (updated original post to show that). This reusable workflow is triggered via the below workflow.

TF_ROOT is applications/prod/msk

msk-prod-tf-ci.yaml

name: msk-prod-tf-ci

on:
  pull_request:
    paths: ['applications/prod/msk/**']
    
concurrency:
  group: terraform

jobs:
  ci:
    uses: ./.github/workflows/terraform-ci.yaml
    with: 
      TF_ROOT: applications/prod/msk
      TF_APP_NAME: msk-prod
    secrets: inherit

Thanks @kaykhan. My initial thoughts are since you are passing in applications/prod/msk as the path to Infracost, then when it runs against the base branch it is actually erroring, instead of producing an empty output since that dir doesn't exist in that branch. Since the base branch is erroring for that project then the diff command will fail as well for that project.

One way to resolve this would be to use an Infracost config file to dynamically find the project directories that exist on each given branch.

Something like this infracost.yml.tmpl might work for your case:

version: 0.1
projects:
{{- range $project := matchPaths "applications/:env/:name" }}
    - path: {{ $project._path }}
      name: {{ $project.env }}/{{ $project.name }}
{{- end }}

And then updating your pipeline to generate the config.

For the base run:

infracost generate config --repo-path=. \
    --template-path=infracost.yml.tmpl \
    --out-file=infracost.yml
infracost breakdown --config-file=infracost.yml \
    --format=json \
    --out-file=/tmp/infracost-base.json

For the diff run:

infracost generate config --repo-path=. \
    --template-path=infracost.yml.tmpl \
    --out-file=infracost.yml
infracost diff --config-file=infracost.yml \
    --format=json \
    --compare-to=/tmp/infracost-base.json \
    --out-file=/tmp/infracost.json

Another option that might be simpler to test initially is just to ensure the directory exists when you run the base branch, i.e.:

mkdir -p ${{ inputs.TF_ROOT }}
infracost breakdown --path=${{ inputs.TF_ROOT }} \
    --format=json \
    --out-file=/tmp/infracost-base.json

Alright i'll have to try the infracost config file another time.

Another option that might be simpler to test initially is just to ensure the directory exists when you run the base branch, i.e.:

Tested this just now and of course there is an additional error. No valid Terraform files found at path applications/prod/msk (main branch is emtpy)


What i am thinking of doing is checking for the presence of the folder inputs.TF_ROOT in an initial step.

  - name: Check baseline exists
    run: |

Based on the result of that step i can either continue to run the baseline + diff or just run the breakdown directly.

Maybe i was hoping for an extra flag that could be added to infracost diff (ignore any errors that are in infracost-base.json and run the breakdown) to do this but i dont know if this would introduce some unforeseen conflicts.

Thanks @kaykhan, yeah this seems like something that could be accounted for in the Infracost CLI itself. We don't have a solution for that just now, but it's something we should probably consider.