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

interpolation variable #81

Merged
merged 6 commits into from Feb 20, 2020
Merged

interpolation variable #81

merged 6 commits into from Feb 20, 2020

Conversation

tormath1
Copy link
Contributor

@tormath1 tormath1 commented Jan 7, 2020

In this PR:

I added a function Interpolate with two args: the HCL writer (since there's no interpolation with state AFAIK) and an interpolation map which map a value with its interpolation name. (<resource_type>.<resource_name>.(id|self_link))

Feel free to test it.

example of output:

security_groups = ["sg-8a5124ec", "sg-01514a64", "${aws_security_group.Xiskc.id}", "${aws_security_group.inuev.id}"]

⚠️ This is TF0.11 format (#82) ⚠️

Close #37

@tormath1 tormath1 self-assigned this Jan 7, 2020
@@ -130,6 +134,16 @@ func Import(ctx context.Context, p Provider, hcl, tfstate writer.Writer, f *filt
return errors.Wrapf(err, "error while calculating the satate of resource %q", t)
}
}
// we construct a map[string]string to perform
// interpolation later. By default, the value to interpolate is the ID
// but in some case (ex: google), it can be different.
Copy link
Contributor

Choose a reason for hiding this comment

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

Different how?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

actually the comment is misleading. I modified it consequently.

it can be different

resource key could be self_link with Google.

@@ -35,6 +35,13 @@ type Resource interface {
// Type is the type of resource (ex: aws_instance)
Type() string

// InstanceState is the Terraform state of the resource
// it contains important elements likes `Attributes`
Copy link
Contributor

Choose a reason for hiding this comment

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

like*

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 it's ok, I've commented some changes and also some divagations over the implementation haha.

Comment on lines 190 to 192
// google special case
// sometimes, the resource is referred by its self_link (https://...)
// so we need to identify if we reference the ressource by its ID or its selfLink (the key)
Copy link
Member

Choose a reason for hiding this comment

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

Better add it to the actual comment of the function.

Comment on lines 156 to 169
if _, ok := hcl.(*h.Writer); ok {
err := util.Interpolate(hcl.(*h.Writer), interpolation)
if err != nil {
return errors.Wrapf(err, "error while Interpolate Config")
}
}
Copy link
Member

Choose a reason for hiding this comment

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

When I was reading the util.Interpolate I already thought that it should go to the hcl pkg, and now even more so the implementation is the one that tries to convert to the specific type even with that I don't like that as the Interface was ment to avoid knowing the implementation behind it, and this brakes it.

Ways to solve this:

  • Have the util be the one that converts so it expects a write.Writer not an hcl.Writer (still not good IMO)
  • Move it to hcl pkg
  • Add it to the write.Wiriter interface
  • Make the Sync() expect the map[string]string.

The last 2 are the ones I prefer as they delay the implementation to the actual one (State will do nothing as no Interpolation is needed and HCL will do something).

if _, ok := s.Attributes["self_link"]; ok {
return "self_link"
}
return "id"
Copy link
Member

Choose a reason for hiding this comment

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

I've commented it to @tormath1 on PM that this could be much more things than just the id, on an aws_instance the relations could be done with https://www.terraform.io/docs/providers/aws/r/instance.html#attributes-reference any of those. But maybe for now only the ID it's the important one.

Copy link
Contributor

Choose a reason for hiding this comment

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

Among the list we want for sure - and that we already widely use (some services I can think of):
id (general reference), arn (iam,alb,etc), name (iam,r53), fqdn (r53)

Copy link
Contributor Author

@tormath1 tormath1 Jan 17, 2020

Choose a reason for hiding this comment

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

Well, my current thought would be to have an AttributesReference associated to a Resource. This attribute would be simply feed by https://github.com/cycloidio/tfdocs.

Interpolation would now be made there:

for i, re := range resources {
since we have all the resources.

Then looping on the resources, if a value of resources.AttributesReferences[*] match with a value in the resources, we interpolate.
WDYT ?

Impletemented: 9460151

@@ -65,6 +67,8 @@ func Import(ctx context.Context, p Provider, hcl, tfstate writer.Writer, f *filt
fmt.Fprintf(out, "Importing with filters: %s", f)
logger.Log("filters", f.String())

interpolation := make(map[string]string)
Copy link
Member

Choose a reason for hiding this comment

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

Add documentations on what it holds and why is it used.

Comment on lines 136 to 156
state := r.InstanceState()
if state != nil {
key := getKey(state)
value := state.Attributes[key]
interpolatedValue := fmt.Sprintf("${%s.%s.%s}", r.Type(), r.Name(), key)
interpolation[value] = interpolatedValue
}
Copy link
Member

Choose a reason for hiding this comment

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

Also all this logic could be done on the r.HCL for example as it has all the logic and the actual scope for all this information (state and Name and all of them)

Maybe better have a method on the Resource that actually returns all the possible interpolated values (right now just ID but potentially more) as the Resource has access to the schema and could potentially know them.

But maybe for now it's ok this way.

@tormath1 tormath1 force-pushed the mt-add-interpolation branch 2 times, most recently from c7b6303 to 2f3e972 Compare January 13, 2020 10:15
@tormath1 tormath1 added Status: Need review and removed Status: In progress Status: Under discussion Must be discussed for determining what to do labels Jan 13, 2020
@tormath1
Copy link
Contributor Author

tormath1 commented Jan 13, 2020

@xescugc ready for review !

I'm not really convinced by this solution and it increases the running time. On master: 2m8s to fetch 10 instances and 21 SG, on this branch: 2m44s for the same thing.

@tormath1
Copy link
Contributor Author

tormath1 commented Feb 11, 2020

@xescugc ready for review.

In the latest commit, I totally modified the way I was updating the resources. At the beginning I was trying to simply bytes.ReplaceAll but it's clearly not enough, since we are going to replace also custom tags (e.g cycloid.io was always interpolated), it's also not enough to avoid interpolation with the resource itself (let's suppose you are inside aws_instance.toto you can't refer to aws_instance.toto.some_field from this resource) or cyclic interpolation.

To summarize:

  • we are now using tfdocs: e7234db in order to get all potentials key to be interpolated.

  • we are walking recursively into a resources interface in order to interpolate the value whom are able to be interpolated (no self reference and no cyclic reference.)

I tried out with a large infra (security groups and instances) and it works as expected.

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 it's a good implementation.

hcl/writer.go Outdated
Comment on lines 139 to 148
src := reflect.ValueOf(block)
// this will store the updated block
dest := reflect.New(src.Type()).Elem()
// walk through the resources to interpolate the good values
walk(dest, src, i, name, rt)
// remove reflect.Value wrapper from dest
resources.(map[string]map[string]interface{})[rt][name] = dest.Interface()
Copy link
Member

Choose a reason for hiding this comment

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

Could you space up this part a bit? Comment+Code+\n so it's easy to read if not it's too dense.

hcl/writer.go Outdated
Comment on lines 151 to 155
// walk through a resource block. it's easier since we do not how the block is made
func walk(dest, src reflect.Value, interpolate map[string]string, name string, resourceType string) {
Copy link
Member

Choose a reason for hiding this comment

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

Improve the docs, what does it actually do? It goes though all the src and put it on dest, so instead of "returning" the result is the "dest". (better wording ofc hehe).


// what we want to interpolate is a string
// we do not interpolate a custom tag (like cycloid.io) since it's key.
case reflect.String:
Copy link
Member

Choose a reason for hiding this comment

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

Commented on PM with you already but to keep the record, could it be anything else than String no? Int, bool etc.

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.

IDK why I forgot to review that code/commit.

res, err = awsdocs.GetResource(r.resourceType)
case "google":
res, err = googledocs.GetResource(r.resourceType)
}
Copy link
Member

Choose a reason for hiding this comment

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

Return the errors first, basically check them after the end of the switch

Comment on lines 135 to 143
case "aws":
res, err = awsdocs.GetResource(r.resourceType)
case "google":
res, err = googledocs.GetResource(r.resourceType)
}
Copy link
Member

Choose a reason for hiding this comment

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

This could also be done by having an static map where the key is the r.provider.String() and the value func (r string) (*tfdocresource.Resource, error), this way is an static map, and easy to add more instead of having to add a new case to the switch.

Copy link
Contributor Author

@tormath1 tormath1 Feb 14, 2020

Choose a reason for hiding this comment

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

@xescugc good catch ! it's way cleaner (c7daac0). Ready for final review 😉

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. Rebase ans squash the commits that you want.

@tormath1 tormath1 force-pushed the mt-add-interpolation branch 3 times, most recently from 7dc9363 to 6689ba3 Compare February 18, 2020 09:17
hcl/writer.go Outdated
resources.(map[string]map[string]interface{})[rt][name] = dest.Interface()
}
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Why return an error, if it never will?

Copy link
Member

Choose a reason for hiding this comment

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

Yeh it could be removed now as it's using a different logic.

hcl/writer.go Outdated
return nil
}

// walk through a resource block. it's easier since we do not how the block is made
Copy link
Contributor

Choose a reason for hiding this comment

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

do not know how* ?

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.

For the commits:

  • This 42dfe1d, a99de04 and c9751f1 change the same logic 3 times, a logic that was added on this PR clean it a bit :)
  • abcf2e2 try to split the go.mod|sum in a different commit just to not pollute commits, as it's hard to read then.

Comment on lines 118 to 121
providerResources = map[string]func(string) (*tfdocs.Resource, error){
"aws": func(rt string) (*tfdocs.Resource, error) {
return awsdocs.GetResource(rt)
},
"google": func(rt string) (*tfdocs.Resource, error) {
return googledocs.GetResource(rt)
},
}
Copy link
Member

Choose a reason for hiding this comment

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

Ok a better way:

  • Define a function type func(rt string) (*tfdocs.Resource, error)
  • Then the map just have to have "google" googledocs.GetResource

Which make is much more simpler (that was my first intention but IDK why I read it wrong on the implementation).

Or actually the way you have it now you can still directly use the format without defining the function actually 😄

hcl/writer.go Outdated
resources.(map[string]map[string]interface{})[rt][name] = dest.Interface()
}
}
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.

Yeh it could be removed now as it's using a different logic.

we need to be able to get this name in order to construct
the complete path of the resource
<type>.<name>.<key>
@tormath1
Copy link
Contributor Author

@xescugc final rebase has been made :)

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.

Just one comment :) Rebase it directly.

@@ -119,6 +132,21 @@ func NewResource(id, rt string, p Provider) Resource {
}
}

func (r *resource) AttributesReference() ([]string, error) {
resourceFunc := providerResources[r.provider.String()]
Copy link
Member

Choose a reason for hiding this comment

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

Check the ok also, as it could not exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xescugc done in 6fbac85 :)

@tormath1 tormath1 force-pushed the mt-add-interpolation branch 3 times, most recently from 9ac8fef to 92fe28e Compare February 20, 2020 13:36
this function will be used to identify which fields have the ability
to be interpolated.
For each resource, the attributes will be provided by tfdocs
we first iterate on block resources then for each block
we walk through it and for each isolated string, we try to find
its interpolation value from the `interpolate` array.
@tormath1 tormath1 merged commit dfc5a99 into master Feb 20, 2020
@tormath1 tormath1 deleted the mt-add-interpolation branch February 20, 2020 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Enhancement Provide a new feature or improve an existing one
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add connections between resources
3 participants