Skip to content
This repository has been archived by the owner on Apr 26, 2022. It is now read-only.

Email alert notification receipts #46

Merged
merged 7 commits into from
Jun 2, 2016

Conversation

gpeng
Copy link
Contributor

@gpeng gpeng commented Apr 22, 2016

This work is part of the ongoing efforts to improve travel advice alert email monitoring and will allow us to reconcile a 'received' email with a published edition in Travel Advice Publisher (where we'll add checks that can be run by Jenkins -> Icinga)

This project sets up an S3 bucket and a lambda function that parses files and renames them with a prefix (currently only travel-advice-alerts) and the govuk_request_id parsed from the email body.

An SES domain and RuleSet and an S3 event source are also required but can't currently be configured via Terraform. (details in the README)

I've not been able to test this via the rake tasks from the repo root as I've not got access to the state files, environments etc so I'm not sure that the paths work from there.

The lambda code is deployed from a zip file which I've checked in and included in this PR. I'm not sure this is the best approach as it's 'unreviewable'. I'd welcome any better ideas. Zipping the file as part of the rake task to apply maybe?

Apologies for the one big commit. Code was moved from a local repo. Happy to break up if it will help with reviews.

UPDATE: Added an aws_s3_bucket_notification to trigger the lambda. This is only supported in Terraform 0.6.15

@gpeng gpeng force-pushed the email_alert_notification_receipts branch from 06593b1 to f1a823d Compare April 22, 2016 13:25
@@ -0,0 +1,55 @@
provider "aws" {}
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't required because it is supplied as part of common variables

@surminus
Copy link
Contributor

I think currently the way the Rakefile deploys stuff is to copy everything from resource directories with the *.tf filename extension into a temporary directory and then deploy, so this may miss out the zip/py files, so we'll need to tweak that. I think it's also worth discussing whether we want "extra" files within the resource/ dir.

Currently if an environment isn't defined within the resources/ dir then it'll assume that you want it deployed to all environments.

@deanwilson
Copy link
Contributor

The file paths that use path.module will all be incorrect I think, as this isn't a module. You can use relative ones although I suspect something like path.cwd might also work.

@deanwilson
Copy link
Contributor

This depends on #47 being merged.

@@ -0,0 +1,32 @@
{
"Version": "2012-10-17",
"Id": "Policy1460992104238",
Copy link
Contributor

Choose a reason for hiding this comment

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

Could give this a proper ID 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.

Fixed in ca7c3c1. Seems that using a uuid is the way to go although I haven't used an ID at all in the other policies. I'm not sure if there's any benefit to using them. As it happens this policy confuses terraform apply into thinking it has changed every time it is run which I hoped the change you suggested might fix but didn't unfortunately. I think it's line 27 not being specific enough but haven't been able to work out a more 'fully qualified' version that works.

@deanwilson
Copy link
Contributor

In terms of the lambda build I've been looking at something like this rather than building the zip manually.

resource "null_resource" "build_poller_zip" {
    provisioner "local-exec" {
        command = "zip -j py-poller.zip files/url-poller.py"
    }
}

resource "aws_lambda_function"
    depends_on = [ "null_resource.build_poller_zip"]

Would you be willing to give this a go on this PR?

@surminus
Copy link
Contributor

We're going to add some code that works with the additional files, but the structure needs to be rejigged:

  • *.tf files should live in <project>/resources
  • templates can go in <project>/templates
  • any other files should go in <project>/files

The README can stay in the project root. If you remove the references to the module.path then our rake tasks should hopefully do the right thing.

role = "${aws_iam_role.lambda_execute_and_write_to_email_alert_bucket.arn}"
handler = "rename_email_files_with_request_id.lambda_handler"
runtime = "python2.7"
source_code_hash = "${base64sha256(file("files/rename_email_files_with_request_id.py"))}"

Choose a reason for hiding this comment

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

FYI: source_code_hash expects a hash of the ZIP file, so you'll keep getting unnecessary diffs in plan with the current approach as the hash will never match with what the API returns.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for your comment. I was using the zip file path originally but in an effort to try and avoid having zipped files in the repo I've moved its creation into a null_resource. As a result the base64sha256 function raises a no such file error if I use the zip file. Is there a better way that I can trigger this resource to be updated when the .py source changes?

Copy link

@radeksimko radeksimko Apr 23, 2016

Choose a reason for hiding this comment

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

Is there a better way that I can trigger this resource to be updated when the .py source changes?

Before Terraform gets to plan/apply stage, it (by default) performs refresh (effectively Read from CRUD for each resource in all *.tf files) so it can figure out what may have changed and how does it affect plan/apply. After refresh is done for all resources, it performs plan/apply.

As a result the base64sha256 function raises a no such file error if I use the zip file.

The base64sha256() function is executed at the time of refresh since it has static value (static path to zip file) hence Terraform won't mark it as "computed". All provisioners are always executed at the time of apply, not during refresh. That's why you're hitting this.

To mark it as "computed" you would probably need to have a 1st class support for "zipping" in terraform which would be returning the shasum that you would reference in aws_lambda_function. source_code_hash. There are some (good) reasons why Terraform doesn't support this (yet):
hashicorp/terraform#3858

The only way to get around this would be possibly executing terraform twice (+ removing any unnecessary triggers & depends_on):

terraform apply -target=null_resource.build_rename_email_files_with_request_id
terraform plan/apply

that way terraform won't perform any validation/checks on any resources except the targeted one.
Obviously the null_resource's provisioner would be executed twice (in the second run too), but Terraform would at least get a chance to prepare the right plan and apply it correctly.

Also keep in mind that any local-exec provisioner makes your Terraform files platform-specific - i.e. the requirement suddenly isn't just terraform binary, but also platform-specific zip utility having the same interface (e.g. supporting -j flag). This is also why I think that building ZIP files via CI is a better solution.

@gpeng
Copy link
Contributor Author

gpeng commented Apr 22, 2016

@surminus I've reorganised the files as you suggested. I think it will need a slight change to #47 as per my comments there

@deanwilson I've used that null_resource approach you suggested with a couple of tweaks to get it running. Good shout 👍

@radeksimko
Copy link

I'm not entirely sure how you do deal with other artefacts, like ruby gems in GDS, but my friendly recommendation would be to treat the ZIP files for Lambda functions the same way as other artefacts - i.e. have their own repo with CI (Jenkins/Travis/Circle) that is running tests (if you have any) and most importantly generating ZIP file and uploading it into a versioned S3 bucket. That build in your CI can spit out an S3 VersionId of the file it has just uploaded.

Then you can just bump the s3_object_version in *.tf and actually release the ZIP file. It may look like too many steps, but it's codebase like any other and IMO should be treated the same way.

There is also a separate thread about proper versioning of Lambda Functions, which isn't well supported in Terraform yet.

* An S3 event configured to execute the lambda (`rename_email_files_with_request_id`)
on the `ObjectCreated` `Put` event for the bucket

Terraform v0.6.14 doesn't support S3 event sources but support is in

Choose a reason for hiding this comment

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

0.6.15 was released just a few hours ago 😉
https://www.terraform.io/downloads.html

@gpeng
Copy link
Contributor Author

gpeng commented Apr 22, 2016

Thanks for all your comments @radeksimko . Really helpful.

@radeksimko
Copy link

There is an ongoing effort to release Terraform 0.7 soon which will contain something that might help you to make the workflow cleaner/more automated - Data Sources.

Effectively you'd be able to do something like this:

data "aws_s3_object" "lambda_zip_file" {
  bucket = "my-lambda-functions"
  path = "lambda.zip"
}
...
resource "aws_lambda_function" "rename_email_files_with_request_id" {
  s3_object_version = "${data.aws_s3_object.lambda_zip_file.latest_version}"

That way you wouldn't need that extra step of bumping Version ID manually, but you'd just let Terraform to pull down the latest from S3 at the time of refresh.

The syntax and actual implementation is WIP though - this is just to give you an idea.

@gpeng gpeng force-pushed the email_alert_notification_receipts branch from c0d54f2 to 6e33128 Compare April 25, 2016 20:44
@gpeng gpeng force-pushed the email_alert_notification_receipts branch 2 times, most recently from 56d65e1 to 85f3756 Compare May 3, 2016 13:04
@gpeng
Copy link
Contributor Author

gpeng commented May 3, 2016

I've updated the directory structure and reverted the null_resource commit. Once we've settled on where the zipped lambdas are going to live I'll add support for that.

@surminus
Copy link
Contributor

@gpeng as suggested in #47 (comment) I think what I stated above was incorrect.

@gpeng
Copy link
Contributor Author

gpeng commented May 12, 2016

Thanks... I've laid it out as per #47 so I think it should be good to go now. Just need to update to pull the lambda zip from s3 once that is setup.

@surminus
Copy link
Contributor

We have the config in for buckets to store the lambda zips from (#50) although not sure if they're deployed yet (easy to do). I guess the next thing to do is https://trello.com/c/eukUJfOU/84-write-jenkins-deployment-job-for-lambda-apps

@surminus
Copy link
Contributor

@gpeng, we've just had a chat about this. To deploy this the process would be:

  1. Dev team add a jenkins.sh script which zips up the code for the lambda function
  2. Jenkins uploads the zip to an S3 bucket
  3. Jenkins kicks off this Terraform job to deploy the function

We are prioritising steps 2 & 3 along with setting up the email endpoints.

@gpeng
Copy link
Contributor Author

gpeng commented May 16, 2016

Ok... Sounds good. So would we have a convention for where zipped lambdas live within a typical app and Jenkins would upload any that it finds in the 'standard' location /lambdas/<whatevs.zip>?

@surminus
Copy link
Contributor

That sounds like a good plan.

@gpeng
Copy link
Contributor Author

gpeng commented May 16, 2016

Would that mean that we'd have to have a separate update/install lambda Terraform job?

@surminus
Copy link
Contributor

I think this is fine as it is for the time-being and don't think we should have a separate job. This is dependent on #47 which I think @deanwilson has thoughts on, though I am happy with it in this way and don't really mind either way. I think this terraform project looks fine to run on each deployment (though if you want to do the work to split it up feel free).

For this part, will the source change as the lambda.zip will be stored in another S3 bucket that were created here?

@gpeng
Copy link
Contributor Author

gpeng commented May 17, 2016

Yes... the lambda source needs to change there. I think we'll have to keep the lambda source within this repo too now as I think about it as we need to be able to get the hash of it here unless we upload the source and the zip as part of the Jenkins job that pulls the lambdas in. That might be a better idea.

I think we also need to add support for the 3 environments too don't we for both the lambda source and the bucket where the emails land? Might need some pointers on that...

gpeng added 3 commits May 19, 2016 13:00
This configures PUT events to the s3 bucket to invoke the lambda. NB
this is only supported in terraform v0.6.15
This commit moves the lambda source code out of this repo and now pulls
it from S3 (`govuk-lambda-applications-<environment>`)

The lambda zip file version needs to be passed in as an environment
variable `TF_VAR_rename_email_files_with_request_id_version`
@gpeng gpeng force-pushed the email_alert_notification_receipts branch from 85f3756 to 2d35845 Compare May 19, 2016 16:59
@gpeng
Copy link
Contributor Author

gpeng commented May 19, 2016

@surminus I've updated this to get the lambda from S3 now as discussed. We need to pass in the S3 object version as per the last commit message. I think this should be pretty much good to go now. Just need to work out which repo to put the lambda source in.

@surminus
Copy link
Contributor

I'm going to write the deployment job today. The last thing to decide as you suggest is where the source goes, I'd suggest either a new repo (govuk-lambda-app-deployment?) or the govuk-app-deployment repo? We could put it in this repo but ideally I think this should be separate from Terraform code. Either way it should be easy enough to update the deployment job.

default = "govuk-email-alert-notifications"
}

variable "environment"{
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't required because it's set by default.

@@ -1,6 +1,7 @@
require 'fileutils'
require 'rspec/core/rake_task'
require 'tmpdir'
require 'aws-sdk-resources'
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to go in the Gemfile?

Copy link
Contributor

Choose a reason for hiding this comment

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

The dependency is pulled in by awspec so I don't think it also needs to be included in the Gemfile.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep sorry, you're right!

There was a requirement to be able to pull in the latest VersionID of a
specific object in a bucket, with a prerequisite that it was triggered
by a Jenkins deployment job. This changes seeks to add that
functionality by allowing the addition of an environmental variable in
the deploy job named "TF_VAR_LAMBDA_FILENAME".

I updated this in the project it's used so it could be used more
generically in the future. There might be a bit of extra work if we wish
to override the VersionID for whatever reason.
Terraform only works with stuff when it is quoted from the relative
directory. Our rake task pushes everything into a temporary directory
and then feeds that in during the plan/apply system call. In the
Terraform code we specified a relative path, so to fix this I have
created a new templates directory with the project name underneath it.

Another solution would be to pass in the "tmpdir" variable in the Rake
task as env var, but I thought this was not as tidy as just having a
separate directory especially for templates.
@surminus surminus force-pushed the email_alert_notification_receipts branch from a143e95 to 48ad577 Compare May 27, 2016 13:30
@alexmuller
Copy link
Contributor

alexmuller commented Jun 1, 2016

I'm not sure I understand the template change in 48ad577. I think it's duplicating the templates but I'm not sure why we can't use a relative path of projects/email_alert_notifications/resources/templates/foo.json. Going to have a play with this now.

@surminus
Copy link
Contributor

surminus commented Jun 1, 2016

@alexmuller you're right, we could just have a full relative path in the Terraform code. I thought it was easier to read in it's own directory, but I don't really mind either way!

And update paths. This keeps everything together in the same directory.
@alexmuller
Copy link
Contributor

Maybe something like 1cfb375, is that ok? I think that's what @deanwilson had in mind with the directory structure but I might be wrong.

@surminus
Copy link
Contributor

surminus commented Jun 1, 2016

Fine with me!

@alexmuller
Copy link
Contributor

I know hardly anything about how this SES/Lambda interaction works but the code seems like it probably makes sense. Let's run it in integration and see what happens.

@alexmuller alexmuller merged commit 1d8559c into master Jun 2, 2016
@alexmuller alexmuller deleted the email_alert_notification_receipts branch June 2, 2016 10:51
@boffbowsh
Copy link
Contributor

@alexmuller 👋 I know these things. Shout if I can help.

@alexmuller
Copy link
Contributor

There's a bit of a loop here where this Terraform code creates an S3 bucket but the Rakefile relies on that bucket existing.

@alexmuller
Copy link
Contributor

@boffbowsh: Thanks!

@surminus
Copy link
Contributor

surminus commented Jun 2, 2016

I didn't think the Rakefile should rely on the bucket existing that the Terraform code creates? It relies upon a different bucket where we upload the Lambda app, then creates a new bucket for a different function (I think).

@alexmuller
Copy link
Contributor

@surminus: Ah yep sorry, you're right. I have no idea how any of this works.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants