-
Notifications
You must be signed in to change notification settings - Fork 9.3k
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
Support VPC configuration of aws_elasticsearch_domain resources. #1958
Conversation
This turns out to need a little more work, so relabelled it as [WIP]; I hope to have it finished up during 2017-10-19 US business time. |
OK, bugs are fixed, acceptance tests written, this is ready to go! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @handlerbot
thanks for the PR.
I left you some comments in the code, let me know what you think.
Elem: &schema.Schema{Type: schema.TypeString}, | ||
Set: schema.HashString, | ||
}, | ||
"vpc_id": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any particular reason for keeping those two fields outside of vpc_options
and drift away from the API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ES-in-VPC API is kind of weird compared to the other AWS APIs, from what I've seen. You don't actually pass in the vpc_id
or availability_zones
as a parameter to the API, you pass in a list of subnets that you want the ES domain to have endpoints in, and AWS enforces that all of those subnets are in the same VPC, and calculates vpc_id
and availability_zones
from the subnets, and returns them to you in a slightly different datatype.
Structure VPCOptions
has SubnetIds
and SecurityGroupIds
, and you use that for create and update requests for the domain.
Structure VPCDerivedInfo
has VPCId
, SubnetIds
, AvailabilityZones
, and SecurityGroupIds
, and you get that back as part of a ElasticsearchDomainStatus
struct during create or delete requests.
And then there's a structure VPCDerivedInfoStatus
, which contains both a VPCDerivedInfo
and an OptionStatus
structure (unrelated to VPC), which you get back as part of ElasticsearchDomainConfig
if you describe the domain.
And then to make your life even more interesting, the API uses the name VPCOptions
to refer to all three of the structures, without even trying to do anything Go-idiomatic like defining an interface that just has the two common fields (which would work for VPCOptions
and VPCDerivedInfo
, but not for VPCDerivedInfoStatus
, because VPCDerivedInfo
is a named member of it).
$ grep 'VPCOptions.*structure' vendor/github.com/aws/aws-sdk-go/service/elasticsearchservice/api.go
VPCOptions *VPCOptions `type:"structure"`
VPCOptions *VPCDerivedInfoStatus `type:"structure"`
VPCOptions *VPCDerivedInfo `type:"structure"`
VPCOptions *VPCOptions `type:"structure"`
So, the whole thing is kind of a mess, but after puzzling all of that out, I figured the least complicated thing was, for the Terraform code, to keep vpc_options
equivalent to VPCOptions
.
But the end of it is: You can't specify the VPC id or the availability zones via the API, you have to let AWS compute them for you and return them, so they're never options that an operator will specify. And, once they are returned, I wanted to export them as top-level attributes of the resource, as I see other resources do, and I don't see a way to export schema sub-members as top-level attributes, other than defining them twice and exporting them twice?
Feedback encouraged if I am missing a more Terraform-idiomatic way to implement this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SubnetIds are globally unique. This behavior of submitting just subnets is seen elsewhere (ie ALB) and should be familiar to anyone used to using VPC.
Filtering by VPC is often just client-side to narrow down subnet list.
There are exceptions of course, for resources such as security groups that go into a VPC but not into a subnet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@handlerbot understood, but I don't think that gives us a reason to drift away from the API. They can both live nested under vpc_options
if I understand correctly?
Additionally we should remove Optional
from both mentioned fields as the user cannot specify them in the config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a way to export schema sub-members as top-level attributes, other than defining them twice and exporting them twice?
I'm not sure I follow - the user can access nested attributes of any TypeList
like this:
aws_elasticsearch_domain.vpc_options.0.vpc_id
the 0th
index may feel a bit weird, but that's how most other resources do it, so I'd just stick with it. It's something we plan to address in core/HCL eventually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only found one example of the docs specifying a usage like that, and only in passing (cache_nodes
in https://www.terraform.io/docs/providers/aws/r/elasticache_cluster.html). I am not personally convinced, but you folks are the bosses, so. :-)
options := d.Get("vpc_options").([]interface{}) | ||
|
||
if len(options) > 1 { | ||
return fmt.Errorf("Only a single vpc_options block is expected") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do the same validation just by adding MaxItems: 1
to the field in the schema which is a bit more idiomatic and more importantly the user will get the validation error during plan, rather than apply.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I was cribbing/copying/keeping style with the other optional config sections in this provider, I didn't know that MaxItems
was even an option! :-/
|
||
resource "aws_security_group" "first" { | ||
vpc_id = "${aws_default_vpc.default.id}" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid sharing any resources and allow running multiple tests in parallel do you mind building custom VPC & subnets here, instead of creating default ones?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done & re-running this test as I write this. It hasn't completed yet, but it brought up the VPC and subnets and SGs, and the ES domain is creating, so I have very high confidence the tear-down will work. :-)
Next feedback round in, acceptance tests passing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HI @handlerbot
Just added my 2cents there, small nitpicks from my side 😄
Looks very good, can't wait to see this added! 🚀
} | ||
|
||
resource "aws_elasticsearch_domain" "example" { | ||
domain_name = "tf-test-in-vpc" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to add randomization to this name? 'would help avoid conflicts when running tests :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ninir Sure, I actually had that (name randomization) earlier and then removed it because I dumbly assumed that you could have duplicate ES domain names within your account as long as they were unique within the VPC, but that turned out to be incorrect, so I've got the restoration of the randomness in my pending change.
Check: resource.ComposeTestCheckFunc( | ||
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain), | ||
), | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add another step, where we would update the VPC configuration, so that we ensure the update works as expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ninir Yeah, I can do that, once I sort the larger IAM problem for the test (update re: that coming next).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disregard "larger IAM problem", I figured it out. 🎉
Thanks, there is one last thing before we can merge this. The attached test is failing:
I assume the config in the test will need an IAM role with the right policy. |
- If creating an ES domain in VPC, create IAM service-linked role if not already existing. - Randomize domain name during basic create/destroy test for ES in VPC. - Add create/update test for ES in VPC.
Next update in: automatic IAM service-linked role creation + requested test improvements. This time for sure! 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just modified your code a bit, mainly to make it safe to create multiple VPC-enabled ES domains in parallel.
Also the usual approach we take on IAM eventual consistency is to retry on particular error codes instead of sleeping. It's a little bit more robust and efficient as it only takes as much time as necessary to create the resource successfully.
Thanks for being reactive here! I hope all the changes I made make sense, but feel free to ask if not.
@@ -155,6 +187,38 @@ func resourceAwsElasticSearchDomainImport( | |||
return []*schema.ResourceData{d}, nil | |||
} | |||
|
|||
func createAwsElasticsearchIAMServiceRoleIfMissing(meta interface{}) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I first thought this is a bad idea, until I took time and read http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html#es-enabling-slr 😢
I wish there was an ES service method to do this... but that's not the reality, so good call 👍
@radeksimko Thank you for the review and the merge. All of your final modifications make sense, thank you for doing them and for making the code more consistent and idiomatic. And yeah, the dissonance between needing to use the IAM API call to create the service-linked role, and the ES API call to delete it is definitely... weird. VPC support in the API is so new, maybe they'll refine things as they get real-world experience and feedback from it. shrug Anyway: on to the next thing! |
I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thanks! |
For #1949