Skip to content
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

AWS estimation #9

Merged
merged 10 commits into from
Dec 10, 2020
Merged

AWS estimation #9

merged 10 commits into from
Dec 10, 2020

Conversation

patrislav
Copy link
Contributor

This PR adds the functionality to estimate Terraform plan costs using the pricing data stored in the DB.

It adds the following packages:

  • terraform - contains the logic of reading a Terraform plan and generating a list of query.Components found in there. This is done by passing Terraform resource values to registered providers (e.g. aws/terraform package) that fetch the pricing using a passed Backend
  • aws/terraform - contains rules for transforming terraform.Resources into query.Components (only aws_instance and aws_ebs_volume in this PR)
  • cost - calculate costs of a State (collection of resources) and a Plan (combination of a "prior" and "planned" State)

Usage to calculate prior and planned costs of every resource in a plan:

db, err := sql.Open("mysql", "...")
backend := mysql.NewBackend(db)
file, err := os.Open("plan.json")
plan, err := costestimation.EstimateTerraformPlan(ctx, backend, file)

for _, res := range plan.ResourceDifferences() {
	fmt.Println("Resource `%s`: prior ( %s ), planned ( %s )", res.Address, res.PriorCost(), res.PlannedCost())
}

@patrislav patrislav self-assigned this Nov 9, 2020
@patrislav patrislav mentioned this pull request Nov 9, 2020
Copy link
Member

@xescugc xescugc left a comment

Choose a reason for hiding this comment

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

So a few things:

  • Does TF have a way to read Plans? I say this just for the sake of maintainability, they change the API quite often and if you have to check it one by one it'll be a nightmare, so if they have already an implementation maybe would be good to use it (IDK if they do though, or if it's usable).
  • I see that on the Provider.New* you make a manual assignation, tenancyVal, _ := values["tenancy"].(string) and then assign and then validate, could be better to just json.Unmarshal it directly, so you don't have to care about empty/casting errors.
  • Also on the same line of TF, I would try to get a more up to date State as we are already on TF 0.13.5 and things may have changed on the way there.

Comment on lines 19 to 30
// NewPlan returns an empty Plan.
func NewPlan(opts ...Option) *Plan {
plan := &Plan{providerInitializers: make(map[string]ProviderInitializer)}
for _, opt := range opts {
opt(plan)
}
return plan
}
Copy link
Member

Choose a reason for hiding this comment

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

A Plan without providerInitializers is useless no? I'm saying this because you should validate it, and also if the only configuration is the providerInitializers then maybe it's better to have it as a (pi ...ProviderInitializer) and have the ProviderInitializer have a pi.Key/Name/Type which is basically the name.

I would not let the user do a WithProvider("aw", aws.PI) as the aws.PI will ALWAYS need to have the "aws" no?

Copy link
Contributor Author

@patrislav patrislav Nov 10, 2020

Choose a reason for hiding this comment

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

The reason was that there might be multiple names for the same provider implementation, e.g. google and google-beta and also that the ProviderInitializer was just a type alias for a func, so it cannot provide its name. However, I see now that it would be indeed better to have it as (pi ...ProviderInitializer) with ProviderInitializer being a struct with a slice of provider names that will match it.

Comment on lines +29 to +38
func (p *Plan) Read(r io.Reader) error {
if err := json.NewDecoder(r).Decode(p); err != nil {
return err
}
return nil
}
Copy link
Member

Choose a reason for hiding this comment

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

Does TF have a way to load Plans?

I'm saying this because if they change formats on the future you'll not know and will have to manually maintain it, but if they have it they maintain it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned in the other comment, they have but it's unexported 😅

return p.extractQueries(p.PriorState.Values, providers)
}

func (p *Plan) extractProviders() map[string]Provider {
Copy link
Member

Choose a reason for hiding this comment

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

Docs

return providers
}

func (p *Plan) extractQueries(modules map[string]Module, providers map[string]Provider) []query.Resource {
Copy link
Member

Choose a reason for hiding this comment

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

Docs

Comment on lines 13 to 15
// ProviderName returns the name of the Provider.
ProviderName() string

Copy link
Member

Choose a reason for hiding this comment

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

I think that Provierd.ProviderName is a bit redundant, should just be Name.

// NewInstance creates a new Instance from Terraform values.
func (p *Provider) NewInstance(values map[string]interface{}) *Instance {
instType, _ := values["instance_type"].(string)
tenancyVal, _ := values["tenancy"].(string)
Copy link
Member

Choose a reason for hiding this comment

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

This is "optional" on the tfstate, If it's not defined it may cause a panic which it's not dealt with.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAIK if the double-value-return form of the type assertion is used (foo, ok := bar.(string)) then it won't cause a panic but instead the ok will be false. In this case, we don't care about the ok, but rely on the variable to hold the zero-value in case of an assertion failure.

func (p *Provider) NewInstance(values map[string]interface{}) *Instance {
instType, _ := values["instance_type"].(string)
tenancyVal, _ := values["tenancy"].(string)
zone, _ := values["availability_zone"].(string)
Copy link
Member

Choose a reason for hiding this comment

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

This is "optional" on the tfstate, If it's not defined it may cause a panic which it's not dealt with.

Comment on lines 8 to 24
// Provider is an implementation of the terraform.Provider, used to extract component queries from
// terraform resources.
type Provider struct {
name string
region string
}

// NewProvider returns a new Provider.
func NewProvider(name string, region string) *Provider {
return &Provider{name: name, region: region}
}
Copy link
Member

Choose a reason for hiding this comment

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

What is the name? This is inside aws/ already, what do you expect as name?

I think those 2 have to be validated, as they are used afterwards as if they are always set, for example the region is always set based on this Provider.region.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, the name will not be needed anymore (as it will be provided by the ProviderInitializer) and I will validate the region to match the list of regions that we already have.

Comment on lines 23 to 25
volType, _ := values["type"].(string)
volSize, _ := values["size"].(float64)
volIops, _ := values["iops"].(float64)
Copy link
Member

Choose a reason for hiding this comment

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

All this 3 are optional.

Copy link
Contributor

Choose a reason for hiding this comment

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

The volume type too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it's "gp2" by default

Comment on lines 1 to 2
{
"format_version": "0.1",
Copy link
Member

Choose a reason for hiding this comment

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

Is this the same plan you added before? If so just use the same one :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to keep them separate as they belong to two different test suites (e2e/testdata and terraform/testdata). But I suppose this can be moved to a testdata dir at the root and be used by both? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

The question is more: are those two likely to evolve differently, if the answer is no then let's merge them, if so then let's keep them separated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think they'll evolve differently, so putting them in a single place might make more sense

@patrislav
Copy link
Contributor Author

  • Does TF have a way to read Plans? I say this just for the sake of maintainability, they change the API quite often and if you have to check it one by one it'll be a nightmare, so if they have already an implementation maybe would be good to use it (IDK if they do though, or if it's usable).

Unfortunately not. They define a plan JSON format here but the plan struct and all related functions are unexported (at least not internal 🙄 )

  • I see that on the Provider.New* you make a manual assignation, tenancyVal, _ := values["tenancy"].(string) and then assign and then validate, could be better to just json.Unmarshal it directly, so you don't have to care about empty/casting errors.

Do you mean having a struct into which the JSON would be unmarshalled? I don't think his would work as at the time of the unmarshal we don't know what the resource is or even to which provider it belongs. So we need to unmarshal each resource into a map[string]interface{} and retrieve the properties dynamically.

  • Also on the same line of TF, I would try to get a more up to date State as we are already on TF 0.13.5 and things may have changed on the way there.

It seems that the JSON representation of the plan is still on version 0.1 and haven't changed yet.

Copy link
Contributor

@xlr-8 xlr-8 left a comment

Choose a reason for hiding this comment

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

Various comments done, I think an important part will be the documentation associated with the implementation to make sure it's understandable and thorough regarding what it can/cannot do, whys, etc.

aws/region/code.go Outdated Show resolved Hide resolved

operatingSystem string
capacityStatus string
preInstalledSW string
Copy link
Contributor

Choose a reason for hiding this comment

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

While understanding the other, I'm having trouble knowing what this is for - perhaps some comments would help on those fields for people less familiar with AWS/Terraform?

}

// Valid returns true if the region exists and is supported, false otherwise.
func (c Code) Valid() bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

As Code can also be "" it might be worth checking that ahead of iterating?

}
if volParams == nil {
// Default EBS Volume values
volParams = map[string]interface{}{"availability_zone": zone}
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be the initialization then, removing the need to check for volParams == nil as it would be override afterwards if need be

aws/terraform/instance.go Show resolved Hide resolved
"github.com/cycloidio/cost-estimation/terraform"
)

var TerraformQueryKey = "aws"
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure about the compiler behaviour, but can be directly a const

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's var so that I can override it for the tests, it seemed to be the simplest way

Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this?

IDK why it should be overrided from the tests, this is aws/ pkg already this should not change no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but I override it in e2e so that it doesn't conflict with any entries that had been already ingested before. E.g. if real AWS pricing data was ingested, the test might fetch it, breaking the result.

Copy link
Member

Choose a reason for hiding this comment

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

I don't quite like this, doing something due to tests, maybe the code is not right then, should this be a paramter then?

And for what I see, the arguemtn is that the e2e test may break actual exported data? That is like saying that you have a constant on the code for the DB and you have to change it if not you remove production data, then that constant it's a variable I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If it's a parameter, it would never be used by real consumers, since they'd always use only the aws value so it'd clutter the public API, I believe. Yes, it could break real data since the test could possibly add an entry with the same properties as a real entry, as well as real data could possibly break a test (if only 1 entry is expected, but there is another, "real" one.) The estimation might give wrong results if two SKU's are found with the same properties.

So I'm not sure what'd be the best way to do it. I could add a QueryKey field to the ProviderInitializer, for example, and then have the TerraformProviderInitializer.QueryKey to be changed from the test (like the MatchNames field already is) but that just moves the problem into another global variable (the global singleton TerraformProviderInitializer struct.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't we make it another singleton or something? Initialise it with a certain value if provided else fallback to this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean a separate struct that holds the query key as a field? 🤔 I believe that would just move the problem into a struct, instead of dealing with it, usage would stay the same as with a global variable. Or did I misunderstand what you mean?

}

// State represents a collection of all the Resource costs (either prior or planned.)
type State struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume this is supposed to be a tfstate? No more tf way to get it / exported from TF?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not a TF state, but rather an intermediate representation of a state of resources. This struct could possibly be generated by using tools different than TF (if we get to that in the future.) A State here is like a snapshot (could this be a better name?) of resources at a given time, and two states (prior and planned) form a Plan that can be estimated.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I'm not sure whats the best wording for that. Perhaps improving slightly the comments would do?

cost/state.go Outdated
rate := prices[0].Value
if quantity.Equals(decimal.Zero) {
quantity = comp.HourlyQuantity
rate = rate.Mul(decimal.NewFromInt(730))
Copy link
Contributor

Choose a reason for hiding this comment

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

730?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Number of hours in a month, to convert an hourly rate to a monthly one, will add a doc

e2e/aws_estimation_test.go Show resolved Hide resolved
Comment on lines 1 to 2
{
"format_version": "0.1",
Copy link
Contributor

Choose a reason for hiding this comment

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

The question is more: are those two likely to evolve differently, if the answer is no then let's merge them, if so then let's keep them separated.

@patrislav
Copy link
Contributor Author

Pushed fixups for most of the comments (except the ones awaiting feedback)

@xescugc
Copy link
Member

xescugc commented Nov 16, 2020

Do you mean having a struct into which the JSON would be unmarshalled? I don't think his would work as at the time of the unmarshal we don't know what the resource is or even to which provider it belongs. So we need to unmarshal each resource into a map[string]interface{} and retrieve the properties dynamically.

Ok I meant this https://github.com/mitchellh/mapstructure

It seems that the JSON representation of the plan is still on version 0.1 and haven't changed yet.

I can tell you that they have changed it some things on the State, as I had to make changes for version

I'll take a look to the new commits.

Copy link
Member

@xescugc xescugc left a comment

Choose a reason for hiding this comment

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

Some general things but I would also try to use golint to solve some of the missing comments. Also there are some missing tests :)

aws/region/code.go Show resolved Hide resolved
"github.com/cycloidio/cost-estimation/terraform"
)

var TerraformQueryKey = "aws"
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this?

IDK why it should be overrided from the tests, this is aws/ pkg already this should not change no?

"io"

"github.com/cycloidio/cost-estimation/query"
)

// Plan is a representation of a Terraform plan file.
type Plan struct {
providerInitializers map[string]ProviderInitializer
providerInitializers []ProviderInitializer
Copy link
Member

Choose a reason for hiding this comment

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

Nah, IMO the ProviderInitializer should have a whay for you to know the Provider.

So then you can have a map[string]ProviderInitializer where the key is the Provider.Type/Name

It makes things much more easy on the rest of the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason was that a single provider might match multiple names (such as google and google-beta, for example), so every ProviderInitializer has a MatchNames slice instead. Could it be better to have the same initializer under multiple keys in the map[string]ProviderInitializer?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, so then it's an easy acces and you can also validate that 2 names are not colliding.

terraform/provider.go Outdated Show resolved Hide resolved
terraform/provider.go Outdated Show resolved Hide resolved
@patrislav
Copy link
Contributor Author

Pushed fixups addressing all the comments.

Ok I meant this https://github.com/mitchellh/mapstructure

Updated to use this library, it's much better now, thanks.

I can tell you that they have changed it some things on the State, as I had to make changes for version

Do you mean the JSON output format? 🤔 I will check if there are any differences.

Some general things but I would also try to use golint to solve some of the missing comments. Also there are some missing tests :)

I've run golint after the fixups and all of the files added by this PR pass now. There are still issues with files that already existed but these can be dealt with in a dedicated PR.

@xescugc
Copy link
Member

xescugc commented Nov 19, 2020

Do you mean the JSON output format? thinking I will check if there are any differences.

they changed some things on the State, I found it on my tests for TC update to TF 0.13.5. IDK if the plan has changed though. But not much we can do if they do not export it.

Commented so some of the things but now looks better

@patrislav
Copy link
Contributor Author

Is this OK to merge now or does it require some more work?

they changed some things on the State, I found it on my tests for TC update to TF 0.13.5. IDK if the plan has changed though. But not much we can do if they do not export it.

I checked and apparently the JSON structure hasn't changed for 2 years, so it should be safe 😄

@xlr-8
Copy link
Contributor

xlr-8 commented Nov 27, 2020

Taking a look now

Copy link
Contributor

@xlr-8 xlr-8 left a comment

Choose a reason for hiding this comment

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

Only minor things remaining, but the documentation to come will have to be really thorough to document everything commented in here, the current scope and limitation of our tool (services, type within the service, etc).

aws/terraform/instance.go Show resolved Hide resolved
"github.com/cycloidio/cost-estimation/terraform"
)

var TerraformQueryKey = "aws"
Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't we make it another singleton or something? Initialise it with a certain value if provided else fallback to this one?

@xescugc
Copy link
Member

xescugc commented Dec 3, 2020

LGTM RS

@patrislav
Copy link
Contributor Author

Rebased

Copy link
Member

@xescugc xescugc left a comment

Choose a reason for hiding this comment

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

Overall I think it's all fine, just this small ones.

I'll check after this is merge more in the global structure/architecture, but I think it's fine.

Comment on lines +21 to +30
func NewPlan(providerInitializers ...ProviderInitializer) *Plan {
piMap := make(map[string]ProviderInitializer)
for _, pi := range providerInitializers {
for _, name := range pi.MatchNames {
piMap[name] = pi
}
}
plan := &Plan{providerInitializers: piMap}
return plan
}
Copy link
Member

Choose a reason for hiding this comment

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

Would make sense to validat len(providerInitializers)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not exactly sure - I wouldn't say that it's an "error" to have no providers configured. The Plan would always return a zero estimate, but that's expected (no input equals no output.) Also, this is a lower-level struct and func that is not supposed to be used by library consumers - the EstimateTerraformPlan func is and it validates providerInitializers. Does it make sense?

Comment on lines 20 to 27
func (c Code) Valid() bool {
if c == "" {
return false
}
for _, code := range regionMap {
if c == code {
return true
}
}
return false
}

Copy link
Member

Choose a reason for hiding this comment

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

I think that to make things "easier" I would call on func init(){} and generate a reverse map of the regionMap so we can search for both ways easy.

// capacityStatus describes the status of capacity reservations.
// Valid values include: Used, UnusedCapacityReservation, AllocatedCapacityReservation.
// Note: only "Used" is supported at the moment.
capacityStatus string // valid values: Used, UnusedCapacityReservation, AllocatedCapacityReservation
Copy link
Member

Choose a reason for hiding this comment

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

I think we can remove this comment as it's already on the docs.


// TerraformProviderInitializer is a terraform.ProviderInitializer that initializes the default AWS provider.
var TerraformProviderInitializer = terraform.ProviderInitializer{
MatchNames: []string{"aws"},
Copy link
Member

Choose a reason for hiding this comment

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

Why not TerraformQueryKey?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason was that the names from MatchNames and from TerraformQueryKey are actually two different things technically: TerraformQueryKey is the name under which the pricing data was saved in the DB, while MatchNames includes a list of names that Terraform recognizes. These may be different (e.g. for GCP, there are two providers: google and google-beta, while there'd be only one query key).

However, in the case of AWS they're all the same, including the fact that TerraformQueryKey is a duplicate of the existing ProviderName 😅 - so I will use that in all cases.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, yes Azure also have a few providers.

return rds
}

func mergeResourceDiffsFromState(rdmap map[string]ResourceDiff, state *State, planned bool) {
Copy link
Member

Choose a reason for hiding this comment

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

docs

estimation.go Outdated
Comment on lines 12 to 15
// EstimateTerraformPlan is a helper function that reads a Terraform plan using the provided io.Reader,
// generates the prior and planned cost.State, and then creates a cost.Plan from them that is returned.
// It uses the Backend to retrieve the pricing data.
func EstimateTerraformPlan(ctx context.Context, backend Backend, r io.Reader, providerInitializers ...terraform.ProviderInitializer) (*cost.Plan, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe for clarity I would s/r io.Reader/ plan io.Reader/ having it just be r it's not clear, it requires you to actually read the docs or it's not possible, just changing that I can "easily" know what it needs.

Copy link
Contributor

@xlr-8 xlr-8 left a comment

Choose a reason for hiding this comment

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

Mostly minor changes on test cases. The rest looks good to me!

queries, err := plan.ExtractPriorQueries()
require.NoError(t, err)
require.Len(t, queries, 1)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd add cases for failures too, just to ensure an error stops the execution / is returned

return total
}

func (s *State) addComponent(resAddress, compLabel string, component Component) {
Copy link
Contributor

Choose a reason for hiding this comment

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

docs


state, err := cost.NewState(ctx, backend, queries)
require.NoError(t, err)
assert.Equal(t, expected, state)
Copy link
Contributor

Choose a reason for hiding this comment

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

Add cases of failures too

rootVol := diffs[0].ComponentDiffs["Root volume: Storage"]
require.NotNil(t, rootVol)
assertDecimalEqual(t, decimal.NewFromFloat(3.6), rootVol.PriorCost())
assertDecimalEqual(t, decimal.NewFromFloat(3.6), rootVol.PlannedCost())
Copy link
Contributor

Choose a reason for hiding this comment

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

Cases of failures too, doesn't have to make the other calls fail voluntarily but just ensuring all errors are handled

@patrislav
Copy link
Contributor Author

Pushed fixups that should resolve all the comments

@xescugc
Copy link
Member

xescugc commented Dec 8, 2020

LGTM RS when @xlr-8 says

@xlr-8
Copy link
Contributor

xlr-8 commented Dec 9, 2020

Seems like the tests of failure cases were added, were they?

Patryk Kalinowski added 8 commits December 9, 2020 14:30
The purpose of the terraform package is to read a Terraform plan file
and extract query.Resource records. These will be subsequently used
to estimate the plan cost.
Define a Provider that implements the terraform.Provider interface and
provide a NewTerraformProvider func that matches the signature of the
terraform.ProviderInitializer type. This allows it to be used as one of
the providers during plan reading process.

The Provider returns a query.Component slice for each terraform.Resource
that uses the Provider.
The purpose of the cost package is to retrieve pricing data for a
particular State (list of resources) and to merge two States to achieve
a Plan - containing cost differences for all resources included in both
or either of the states.
@patrislav
Copy link
Contributor Author

Rebased

Copy link
Member

@xescugc xescugc left a comment

Choose a reason for hiding this comment

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

LGTM :)

@patrislav patrislav merged commit d50eb11 into master Dec 10, 2020
@patrislav patrislav deleted the pk-aws-estimation branch December 10, 2020 07:59
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.

3 participants