8000 feat(aws): Initial sketch of cloudformation support by tim775 · Pull Request #803 · infracost/infracost · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat(aws): Initial sketch of cloudformation support #803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 29, 2021
Merged

feat(aws): Initial sketch of cloudformation support #803

merged 4 commits into from
Jun 29, 2021

Conversation

tim775
Copy link
Member
@tim775 tim775 commented Jun 10, 2021

No description provided.

@tim775
Copy link
Member Author
tim775 commented Jun 10, 2021

I was looking into how to support cloudformation. The basic idea is to split resource creation into extract resource info from provider config and create Resource/CostComponents from resource info. Extracting the info would be provider specific and remain in the /provider/<cloud>, but it would call a shared resource helper to generate the the actual Resource/CostComponents. I refactored dynamodb in this resource as an example.

I think the biggest thing to figure out for multi-provider support is the usage .yml file. The current support is essentially terraform specific because the usage keys map directly to a terraform resource block.

One option for multi-provider support would be to keep the current format but create a mapping from the current usage keys to CF resources. This may not be straightforward because there is not a 1-1 correlation between CF and TF resources. E.g. CF has separate AWS::DynamoDB::Table and AWS::DynamoDB::GlobalTable r 8000 esources, while TF handles both regular and global tables with the aws_dynamodb_table resource.

Another option that might feel more natural for CF users would be to let them specify usage in a similar structure as the CF template. So given a template resource:

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Resources": {
        "AlertsTable": {
            "Type": "AWS::DynamoDB::Table",
            "Properties": {
            ...

The usage parameters could be specified as

{
    "InfracostUsageVersion": "0.1",
    "Resources": {
        "AlertsTable": {
            "Type": "AWS::DynamoDB::Table",
            "Usage": {
                 "monthly_write_request_units": 3000000 
                 "monthly_read_request_units": 8000000 
                ...

If we want to go this route, it probably means adding more structure to the usage parameters so the files can be parsed and generated easily in whatever format is needed.

@tim775
Copy link
Member Author
tim775 commented Jun 10, 2021

There are additional cloudformation specific issues that need to be figured out:

  • How should users specify region? This information isn't in the template file, users specify which region to run in when executing a template.
  • Adding support for stacks. The current implementation simply parses an existing template file. The next step would be to support downloading and processing the template for an existing CF stack using the aws cli.
  • Adding support for changesets/diffs. Templates don't contain any information about the current state and therefore a single template can't be used to generate a diff. One option for generating diffs would be to use the AWS CLI to download a template for the existing stack state and compare it to a modified local template. Another option is to use the AWS CLI to create a new ChangeSet based on the modified template, then download the resulting "plan" information and generate the diff from that.
  • The goformation library I'm using only has minimal support for the template lanugae "Ref" function, so we may need to build a more complement implementation.
  • We should refactor the tftest goldenfile testing stuff so it can be used with CF (and other future providers).
  • How should we extract Tags? I couldn't figure out a nice generic way to extract tags (if they exist) from any CF resource using the goformation template parsing library, so I set them with the resource-specific registry function. It seems like it would be easy to mistakenly leave this out when creating resources, so it would be good to look at a better way of doing this.

@aliscott
Copy link
Member
aliscott commented Jun 10, 2021

Thanks @tim775! Still going through this, but will leave initial thoughts/comments.

but it would call a shared resource helper to generate the the actual Resource/CostComponents.

This is cool. I can see how in the future DynamoDbTableArguments could be sent to an external service/API along with the usage data, which could then return the resource and cost components. This would mean other clients, not just Infracost, could use that logic as well.

I think the biggest thing to figure out for multi-provider support is the usage .yml file.

One thing we had in the past was a Terraform provider so users could specify the usage data in HCL code, so it was similar to the TF they were writing. We found that nobody used it, but not sure if that was due to it being HCL or the fact it had to be bundled in their existing TF project. Totally making an assumption here, but I'm wondering if more people write their CF in YAML if they're handwriting it, and maybe JSON is used more if they're generated it.

Would this be how you'd see option 1 looking?

version: 0.1
resource_usage:

  # TF resource
  aws_dynamodb_table.my_table:
    monthly_write_request_units: 3000000
    monthly_read_request_units: 8000000
  
  # CF resource (AWS::DynamoDB::Table)
  AlertsTable:
    monthly_write_request_units: 3000000
    monthly_read_request_units: 8000000
    
  # CF resource (AWS::DynamoDB::GlobalTable)
  GlobalAlertsTable:
    monthly_write_request_units: 3000000
    monthly_read_request_units: 8000000

@tim775
Copy link
Member Author
tim775 commented Jun 10, 2021

I can see how in the future DynamoDbTableArguments could be sent to an external service/API
Right the /resources logic could ultimately be an external service that generates and prices the cost components. I was wondering if there is a use case for an API that processes TF/CF config files (or snippets).

Would this be how you'd see option 1 looking?

So NO that wasn't what I was thinking, but I like that better 🙂. I guess that really simplifies things because we're still just using the natural 'address' which for TF is . but for CF is just . That gets my vote.

@aliscott
Copy link
Member

How should users specify region? This information isn't in the template file, users specify which region to run in when executing a template.

We might need to do this for TF as well, since sometimes it's impossible to detect from the plan file. We could have a usage file key for it, or maybe an environment variable like the AWS cli has.

How should we extract Tags?

I guess we could use reflect (although not sure if we want to)? Maybe something like this:

func mapTags(d interface{}) map[string]string {
	r := reflect.ValueOf(d)
	f := reflect.Indirect(r).FieldByName("Tags")
    
	mapped := make(map[string]string)

	if f.IsValid() {
		tags, ok := f.Interface().([]Tag)
		if ok {
			for _, tag := range tags {
				mapped[tag.Key] = tag.Value
			}
		}
	}

	return mapped
}

@PatMyron
Copy link
Contributor
PatMyron commented Jun 10, 2021

#190

Not sure if infracost is handling any Terraform equivalents already, but this should be probably ignore some unresolved CloudFormation template syntax where values aren't resolved until runtime like dynamic references and intrinsic functions

return false
}

return jsonFormat.AWSTemplateFormatVersion != "" && jsonFormat.Resources != nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReplicaRegions []string `json:"replicaRegions,omitempty"`
}

func NewDynamoDBTable(args *DynamoDbTableArguments, u *schema.UsageData) *schema.Resource {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tim775 Just leaving my comment here too. I believe the Resource Abstraction should not care about what fields come from what file. So all it takes is a bunch of mandatory and optional fields. The caller of this function then can decide how to construct it.

If I want to make an example is let's say we have the resource X with fields of A, B and C. In terraform, fields A and B are present in the state file and field C comes from usage data. But, there's no guarantee that all providers are like terraform. Another provider like Pulumi might have support for field C in its own config and state and here's where things get super complicated if we take two different parameters.

@tim775 tim775 marked this pull request as ready for review June 16, 2021 15:14
Comment on lines 17 to 23
MonthlyWriteRequestUnits *int64 `json:"monthlyWriteRequestUnits,omitempty"`
MonthlyReadRequestUnits *int64 `json:"monthlyReadRequestUnits,omitempty"`
StorageGB *int64 `json:"storageGB,omitempty"`
PitrBackupStorageGB *int64 `json:"pitrBackupStorageGB,omitempty"`
OnDemandBackupStorageGB *int64 `json:"onDemandBackupStorageGB,omitempty"`
MonthlyDataRestoredGB *int64 `json:"monthlyDataRestoredGB,omitempty"`
MonthlyStreamsReadRequestUnits *int64 `json:"monthlyStreamsReadRequestUnits,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add the fields that @sinabakh mentioned in his usage-file schema comment here so we have 1 source of truth for them? That source could then be used in the future by --sync-usage-file and docs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the usage schema for the resource is defined separately from the args a few lines below:
https://github.com/infracost/infracost/pull/803/files/ded0f66110486cb9aeffb5992fe7223aec699ba5#diff-71ed323ee96294a7b798471f490d0ecde86fa1b63763f1e023d4bf93e40dc21eR38-R46
and it's added to the resource created by NewDynamoDBTable. (This is a little bit different than adding it to the RegistryItem which is what @sinabakh had proposed).

I made changes to sync-usage-file so it will use this resource.UsageSchema if present, or fall back to the "old way" of using the usage-example.

Copy link
Member
@aliscott aliscott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. I like how smoothly it works with the usage data.

I wonder if we should split out the CF-specific stuff for just now into a separate integration branch until we have a bit more to keep things cleaner and the bin size leaner?

@@ -30,6 +50,15 @@ func (u *UsageData) Get(key string) gjson.Result {
return u.Attributes[key]
}

func (u *UsageData) GetInt(key string) *int64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this, a lot cleaner than checking for gjson.Null everywhere.

@tim775 tim775 marked this pull request as draft June 18, 2021 14:44
@tim775
Copy link
Member Author
tim775 commented Jun 18, 2021

I extracted the non-cloudformation stuff to #824

@aliscott
Copy link
Member

Going with the philosophy of putting stuff out there to get feedback, I think we should merge this since it doesn't affect anything in the core Terraform functionality. @tim775 can you rebase this to pick up the Float64 changes and I will merge?

@tim775 tim775 marked this pull request as ready for review June 29, 2021 11:33
@tim775
Copy link
Member Author
tim775 commented Jun 29, 2021

@aliscott I rebased and this is ready. I added the goformation package. Is it typical to see all the go.sum changes?

@aliscott
Copy link
Member

Is it typical to see all the go.sum changes?

@tim775 not usually this many. Not sure why it's added these new lines but when I run go mod tidy on this branch it removes a lot of them.

@tim775 tim775 merged commit 5e177bb into infracost:master Jun 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
0