<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.0.0">Jekyll</generator><link href="https://www.davekonopka.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.davekonopka.com/" rel="alternate" type="text/html" /><updated>2022-04-19T14:04:21+00:00</updated><id>https://www.davekonopka.com/feed.xml</id><title type="html">Dave Konopka</title><subtitle>Hi, I'm Dave. I'm interested in engineering leadership, DevOps practices, and distributed teams.</subtitle><entry><title type="html">Encrypted Amazon EC2 boot volumes with Packer and Ansible</title><link href="https://www.davekonopka.com/2016/03/18/ec2-encrypted-boot-volumes.html" rel="alternate" type="text/html" title="Encrypted Amazon EC2 boot volumes with Packer and Ansible" /><published>2016-03-18T00:00:00+00:00</published><updated>2016-03-18T00:00:00+00:00</updated><id>https://www.davekonopka.com/2016/03/18/ec2-encrypted-boot-volumes</id><content type="html" xml:base="https://www.davekonopka.com/2016/03/18/ec2-encrypted-boot-volumes.html">&lt;p&gt;At the end of 2015 Amazon added support for &lt;a href=&quot;https://aws.amazon.com/blogs/aws/new-encrypted-ebs-boot-volumes/&quot;&gt;encrypted EBS boot volumes&lt;/a&gt;. EBS storage volumes had offered optional encryption for some time before that. Now it’s possible to encrypt an AMI and bring up EC2 instances with fully encrypted starting volumes.&lt;/p&gt;

&lt;p&gt;I set out recently to use encrypted EBS boot volumes for a HIPAA compliant project at &lt;a href=&quot;https://www.reactiveops.com&quot;&gt;ReactiveOps&lt;/a&gt;. It’s very easy to convert an existing AMI with an unencrypted boot volume to use encryption. I hit a few snags though building encryption into my automated AMI generation workflows using &lt;a href=&quot;https://www.packer.io/&quot;&gt;Packer&lt;/a&gt; and &lt;a href=&quot;https://www.ansible.com/&quot;&gt;Ansible&lt;/a&gt; tooling.&lt;/p&gt;

&lt;h3 id=&quot;encrypt-an-existing-ami-boot-volume&quot;&gt;Encrypt an existing AMI boot volume&lt;/h3&gt;

&lt;p&gt;Once you have an AMI with an encrypted boot volume any EC2 instance you launch using that AMI will have its boot volume encrypted. There’s nothing extra to do at instance launch time.&lt;/p&gt;

&lt;p&gt;If you already have an AMI with an unencrypted boot volume it’s easy to encrypt it. This feature piggybacks on the ability to encrypt EBS volume snapshots while &lt;a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-copy-snapshot.html&quot;&gt;copying them&lt;/a&gt;. You can enable encryption for an AMI by copying it using the CLI, API, or the Amazon web panel and enabling encryption.&lt;/p&gt;

&lt;p&gt;Amazon’s &lt;a href=&quot;https://aws.amazon.com/blogs/aws/new-encrypted-ebs-boot-volumes/&quot;&gt;announcement post&lt;/a&gt; contains detailed instructions for copying an AMI using either the CLI or the web panel. After you make the copy you will have a new and separate AMI. Any EC2 instance you start from this new AMI will have an encrypted boot volume. There’s nothing special to do at instance launch.&lt;/p&gt;

&lt;h3 id=&quot;marketplace-amis&quot;&gt;Marketplace AMI’s&lt;/h3&gt;

&lt;p&gt;The first hitch I ran into involved AMI’s from the &lt;a href=&quot;https://aws.amazon.com/marketplace&quot;&gt;AWS Marketplace&lt;/a&gt;. For most projects I start with a base operating system AMI maintained by the OS’s official backing organization. Canonical publishes &lt;a href=&quot;https://aws.amazon.com/marketplace/seller-profile/ref=dtl_pcp_sold_by?ie=UTF8&amp;amp;id=565feec9-3d43-413e-9760-c651546613f2&quot;&gt;Ubuntu images&lt;/a&gt;. CentOS.org publishes &lt;a href=&quot;https://aws.amazon.com/marketplace/seller-profile?id=16cb8b03-256e-4dde-8f34-1b0f377efe89&quot;&gt;CentOS images&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately Amazon doesn’t allow AMI’s with a product code to be copied. All marketplace images have a product code. This makes it impossible to encrypt a marketplace sourced AMI by copying it into your account. The alternative is to stand up an EC2 instance from the marketplace AMI and create an AMI from the instance. The resulting AMI copy can then itself be copied and encrypted. That’s a lot of copying.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side note: Ubuntu publishes a &lt;a href=&quot;https://cloud-images.ubuntu.com/locator/ec2/&quot;&gt;“Cloud Image” library&lt;/a&gt; of their official AMI’s. These AMI’s are separate from the AWS Marketplace. They can be copied with encryption freely unlike the marketplace versions.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;packer-amazon-ebs-encryption-is-not-possible&quot;&gt;Packer amazon-ebs encryption is not possible&lt;/h3&gt;

&lt;p&gt;I tend to use Packer’s &lt;a href=&quot;https://www.packer.io/docs/builders/amazon-ebs.html&quot;&gt;amazon-ebs&lt;/a&gt; to build AMI’s. I had hopes for a Packer native option to produce an encrypted boot volume from a marketplace AMI. Though you can set an encryption field on volumes using the &lt;code class=&quot;highlighter-rouge&quot;&gt;ami_block_device_mappings&lt;/code&gt; this doesn’t produce an encrypted EBS snapshot for the boot volume. It does not appear there is a way to do this natively with Packer. I had to resort to following up Packer AMI builds with a separate process to copy the resulting unencrypted AMI to an encrypted format.&lt;/p&gt;

&lt;h3 id=&quot;creating-encrypted-amis-with-ansible&quot;&gt;Creating encrypted AMI’s with Ansible&lt;/h3&gt;

&lt;p&gt;The following Ansible playbook copies an unencrypted AMI and encrypts it. It tags the resulting AMI with &lt;code class=&quot;highlighter-rouge&quot;&gt;Encrypted='true'&lt;/code&gt;. Ansible 2.0 or greater is required.&lt;/p&gt;

&lt;p&gt;Ansible has an &lt;code class=&quot;highlighter-rouge&quot;&gt;ec2_ami_copy&lt;/code&gt; module. Due to limitations in Boto though this module does not yet support enabling encryption on copy. There are &lt;a href=&quot;https://github.com/ansible/ansible-modules-extras/issues/1379&quot;&gt;pending PR’s&lt;/a&gt; for bringing this feature to the module. Until those are released, this code shells out to the AWS CLI to create the AMI copy. You’ll need to configure the AWS CLI with appropriate credentials on your machine to use this playbook.&lt;/p&gt;

&lt;pre class=&quot;yaml&quot;&gt;
&lt;code&gt;
---

- name: Create an encrypted copy of a given AMI
  hosts: localhost
  connection: local
  gather_facts: False
  vars:
    aws_region: &quot;us-east-1&quot;
    unencrypted_ami_id: &quot;ami-XXXXXXXX&quot;

  tasks:
    - name: Find the latest AMI by tags
      ec2_ami_find:
        owner: self
        ami_id: &quot;&quot;
        no_result_action: fail
        region: &quot;&quot;
        sort: name
        sort_order: descending
        sort_end: 1
      register: latest_ami

    - set_fact: unencrypted_ami_name=&quot;&quot;

    - name: Check if encrypted copy of AMI already exists
      ec2_ami_find:
        owner: self
        name: &quot;&quot;
        ami_tags:
          Encrypted: &quot;true&quot;
        no_result_action: success
        region: &quot;&quot;
      register: latest_ami_encrypted

    # Proceed with encrypting only if no encrypted copy already exists
    - block:
        - name: Encrypt AMI by copying it
          shell: &quot;aws ec2 copy-image --source-region '' --region '' --encrypted --source-image-id '' --name ''&quot;
          register: ami_copy

        - set_fact: ami_copy_json=&quot;&quot;
        - set_fact: encrypted_ami_id=&quot;&quot;

        # Retry until ami is available for tagging
        - name: Confirm encrypted AMI id.
          ec2_ami_find:
            owner: self
            ami_id: &quot;&quot;
            no_result_action: success
            region: &quot;&quot;
            state: pending
          register: confirm_ami_encrypted
          until: confirm_ami_encrypted.results|length &amp;gt; 0
          retries: 4
          delay: 15

        - name: Tag encrypted AMI
          ec2_tag:
            region: &quot;&quot;
            resource: &quot;&quot;
            state: present
            tags:
              Name: &quot;&quot;
              Encrypted: &quot;true&quot;
          with_items: confirm_ami_encrypted.results

      when: latest_ami.results|length == 1 and latest_ami_encrypted.results|length == 0

&lt;/code&gt;
&lt;/pre&gt;</content><author><name></name></author><summary type="html">At the end of 2015 Amazon added support for encrypted EBS boot volumes. EBS storage volumes had offered optional encryption for some time before that. Now it’s possible to encrypt an AMI and bring up EC2 instances with fully encrypted starting volumes.</summary></entry><entry><title type="html">Terraform conditionals. Sort of.</title><link href="https://www.davekonopka.com/2016/03/07/terraform-conditionals.html" rel="alternate" type="text/html" title="Terraform conditionals. Sort of." /><published>2016-03-07T00:00:00+00:00</published><updated>2016-03-07T00:00:00+00:00</updated><id>https://www.davekonopka.com/2016/03/07/terraform-conditionals</id><content type="html" xml:base="https://www.davekonopka.com/2016/03/07/terraform-conditionals.html">&lt;p&gt;&lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt; doesn’t have support for conditionals on resources. There’s nothing like Ansible’s &lt;a href=&quot;http://docs.ansible.com/ansible/playbooks_conditionals.html#the-when-statement&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;when&lt;/code&gt;&lt;/a&gt; statement to conditionally create Terraform resources based on a boolean variable value. At least &lt;a href=&quot;https://github.com/hashicorp/terraform/issues/1604&quot;&gt;not yet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I value the declarative simplicity that Terraform configuration enforces by design. However, the lack of conditionals makes it difficult to create reusable, DRY Terraform &lt;a href=&quot;https://www.terraform.io/docs/modules/index.html&quot;&gt;modules&lt;/a&gt;. Luckily, there is a workaround. Terraform resources offer a &lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt; parameter that can be used as an indirect conditional. Before I give a usage example, let me explain my use case.&lt;/p&gt;

&lt;h3 id=&quot;why-do-i-want-conditionals&quot;&gt;Why do I want conditionals?&lt;/h3&gt;

&lt;p&gt;At &lt;a href=&quot;https://www.reactiveops.com/&quot;&gt;ReactiveOps&lt;/a&gt; where I work we created a Terraform module to manage client Amazon VPCs. We have a standard pattern of VPC architecture that includes public and private subnets. Our TF VPC module sets up all the subnets, route tables, and NAT EC2 appliance instances needed for that pattern. This helps us setup clients rapidly on AWS with a best-practices VPC design.&lt;/p&gt;

&lt;p&gt;Amazon recently released &lt;a href=&quot;http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/vpc-nat-gateway.html&quot;&gt;NAT Gateways&lt;/a&gt; as an alternative to NAT EC2 instances. NAT Gateways offer availability and network throughput benefits over EC2 instances. I want to add an option to our TF module to launch a VPC with either NAT Gateways or NAT EC2 instances. We have existing customers using NAT EC2 instances, so we need to support both paths.&lt;/p&gt;

&lt;p&gt;Our VPC TF module creates a few resources specific to NAT EC2 instances:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;An EC2 instance per availability zone.&lt;/li&gt;
  &lt;li&gt;A security group with rules for the EC2 instances.&lt;/li&gt;
  &lt;li&gt;Route table entries pointing egress traffic at the EC2 instances.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using NAT Gateways we won’t need any of the above listed resources. We will still need the VPC, subnets, route tables, new route table entries, and the NAT Gateways.&lt;/p&gt;

&lt;p&gt;Without conditionals one option would be to isolate the NAT portions of the VPC module to two separate modules. Then we could include the VPC module and the appropriate NAT module for a given project. In this case though the VPC module is not very usable on its own without one of the two NAT modules.&lt;/p&gt;

&lt;p&gt;Another option would be to create two separate VPC modules duplicating all the shared resources with separate resources alongside two different NAT approaches. This duplication of VPC resources would be unwieldy to support.&lt;/p&gt;

&lt;h3 id=&quot;using-count-as-a-conditional&quot;&gt;Using count as a conditional&lt;/h3&gt;

&lt;p&gt;Enter the &lt;a href=&quot;https://github.com/hashicorp/terraform/tree/master/examples/aws-count&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt;&lt;/a&gt; parameter. This parameter is typically used to create more than one instance of a resource. If the value is set to 0 though none will be created.&lt;/p&gt;

&lt;p&gt;With this in mind, we can add two variables to our module’s &lt;code class=&quot;highlighter-rouge&quot;&gt;variables.tf&lt;/code&gt; file:&lt;/p&gt;

&lt;pre class=&quot;json&quot;&gt;
&lt;code&gt;
variable &quot;nat_instance_enabled&quot; {
    description = &quot;set to 1 to create nat ec2 instances for private subnets&quot;
    default = 0
}

variable &quot;nat_gateway_enabled&quot; {
    description = &quot;set to 1 to create nat gateway instances for private subnets&quot;
    default = 0
}
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Resources that are specific to one approach can then set a &lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt; value using one of the flag variables. When either of these variables is set to &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt; the related resources will not be created. When either is set to &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt; the related resources will be created.&lt;/p&gt;

&lt;p&gt;Below is a security group only needed by the EC2 instance approach:&lt;/p&gt;

&lt;pre class=&quot;json&quot;&gt;
&lt;code&gt;
resource &quot;aws_security_group&quot; &quot;nat&quot; {
 count = &quot;${var.nat_instance_enabled}&quot;
 ...
}
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Some resources in our VPC module already set a &lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt; to create multiple instances. For these, we can use the flag variables as multipliers. Anything multiplied by zero is zero which means the resources will or will not be created depending on the multiplier.&lt;/p&gt;

&lt;p&gt;Below are the actual instances required by the EC2 instance approach:&lt;/p&gt;

&lt;pre class=&quot;json&quot;&gt;
&lt;code&gt;
resource &quot;aws_instance&quot; &quot;nat&quot; {
  count = &quot;${var.az_count * var.nat_instance_enabled}&quot;
  ...
}
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;When the module is used we can flip either flag to 1 or 0 to pick between the two NAT approaches using a single Terraform module.&lt;/p&gt;

&lt;pre class=&quot;json&quot;&gt;
&lt;code&gt;
# variables.tf
...
variable &quot;nat_instance_enabled&quot; {
  default = 0
}

variable &quot;nat_gateway_enabled&quot; {
  default = 0
}
...

# terraform.tfvars
...
nat_instance_enabled = 0
nat_gateway_enabled = 1
...

# main.tf
module &quot;vpc&quot; {
  ...
  nat_instance_enabled = &quot;${var.nat_instance_enabled}&quot;
  nat_gateway_enabled = &quot;${var.nat_gateway_enabled}&quot;
  ...
}
&lt;/code&gt;
&lt;/pre&gt;

&lt;h3 id=&quot;caveats&quot;&gt;Caveats&lt;/h3&gt;

&lt;p&gt;One caveat here is Terraform doesn’t offer variable validation. So you have to be careful not to set &lt;code class=&quot;highlighter-rouge&quot;&gt;nat_instance_enabled&lt;/code&gt; to 100 or you would create 100’s of resources. A &lt;code class=&quot;highlighter-rouge&quot;&gt;terraform plan&lt;/code&gt; run &lt;strong&gt;should&lt;/strong&gt; make this kind of mistake  obvious.&lt;/p&gt;

&lt;p&gt;This is admittedly a simplistic example of a conditional. It doesn’t cover conditionally setting parameters or other more complex scenarios based on variable value ranges. The &lt;a href=&quot;https://github.com/hashicorp/terraform/issues/1604&quot;&gt;Support use cases with conditional logic&lt;/a&gt; thread up on the Terraform repo covers a few of the more complicated use cases. I’m hopeful that thread ends up forming the basis of a kick-ass Terraform conditional feature.&lt;/p&gt;</content><author><name></name></author><summary type="html">Terraform doesn’t have support for conditionals on resources. There’s nothing like Ansible’s when statement to conditionally create Terraform resources based on a boolean variable value. At least not yet.</summary></entry><entry><title type="html">Forgot about Amazon API Gateway models</title><link href="https://www.davekonopka.com/2016/03/04/api-gateway-models.html" rel="alternate" type="text/html" title="Forgot about Amazon API Gateway models" /><published>2016-03-04T00:00:00+00:00</published><updated>2016-03-04T00:00:00+00:00</updated><id>https://www.davekonopka.com/2016/03/04/api-gateway-models</id><content type="html" xml:base="https://www.davekonopka.com/2016/03/04/api-gateway-models.html">&lt;p&gt;In my post on &lt;a href=&quot;http://www.davekonopka.com/2016/serverless-aws-lambda-api-gateway.html&quot;&gt;building a serverless URL shortener&lt;/a&gt; I neglected to mention API Gateway &lt;a href=&quot;http://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html&quot;&gt;models&lt;/a&gt; at all.&lt;/p&gt;

&lt;p&gt;A model defines a JSON schema for data passed in or out of API methods. Models are assigned to method requests/responses. They play a role in data transformations that happen within &lt;a href=&quot;http://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html#models-mappings-mappings&quot;&gt;mapping templates&lt;/a&gt;. Models are not used for validating input data. That seems like a logical use for them so perhaps they will serve this role in the future.&lt;/p&gt;

&lt;p&gt;It’s easy to forget about API Gateway models. You can stage a functioning API without creating any models contrary to misleading web panel error messages (more on this below). Also, Swagger API exports don’t seem to include any sign of models you do create. That’s why if you import the &lt;a href=&quot;https://github.com/davekonopka/serverless-url-shortener/blob/master/redir-v1-swagger.yml&quot;&gt;Swagger definition&lt;/a&gt; included with the &lt;a href=&quot;https://github.com/davekonopka/serverless-url-shortener&quot;&gt;serverless URL shortener repo&lt;/a&gt; you’ll get a  misleading popup error message in the AWS web panel:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;blockquote&gt;
    &lt;blockquote&gt;
      &lt;p&gt;&lt;em&gt;You need to define a Model to select before adding a status code output. You can define models on the Models page.&lt;/em&gt;&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve been able to stage API’s without a model despite this error message. You can get rid of the error though by creating a model. Within the API, click Resources -&amp;gt; Model. Add a model named “Token”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.davekonopka.com/w/uAIuiVw9Ur.png&quot; alt=&quot;API Gateway model panel&quot; style=&quot;width:800px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Add the following schema which defines an object with &lt;code class=&quot;highlighter-rouge&quot;&gt;url&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;token&lt;/code&gt; properties.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  &quot;$schema&quot;: &quot;http://json-schema.org/draft-04/schema#&quot;,
  &quot;id&quot;: &quot;http://jsonschema.net&quot;,
  &quot;type&quot;: &quot;object&quot;,
  &quot;properties&quot;: {
    &quot;url&quot;: {
      &quot;id&quot;: &quot;http://jsonschema.net/url&quot;,
      &quot;type&quot;: &quot;string&quot;
    },
    &quot;token&quot;: {
      &quot;id&quot;: &quot;http://jsonschema.net/token&quot;,
      &quot;type&quot;: &quot;string&quot;
    }
  },
  &quot;required&quot;: [
    &quot;url&quot;,
    &quot;token&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name></name></author><summary type="html">In my post on building a serverless URL shortener I neglected to mention API Gateway models at all.</summary></entry><entry><title type="html">Using custom domains with Amazon’s API Gateway</title><link href="https://www.davekonopka.com/2016/02/26/api-gateway-domain.html" rel="alternate" type="text/html" title="Using custom domains with Amazon's API Gateway" /><published>2016-02-26T00:00:00+00:00</published><updated>2016-02-26T00:00:00+00:00</updated><id>https://www.davekonopka.com/2016/02/26/api-gateway-domain</id><content type="html" xml:base="https://www.davekonopka.com/2016/02/26/api-gateway-domain.html">&lt;p&gt;In my last post &lt;a href=&quot;http://www.davekonopka.com/2016/serverless-aws-lambda-api-gateway.html&quot;&gt;Build a serverless URL shortener with AWS Lambda and API Gateway services&lt;/a&gt; I walked through creating a URL shortener service using Amazon’s API Gateway and Lambda services. One of my goals for that project was to use a custom domain  instead of the randomized URL provided by API Gateway. This turned out to be trickier than I expected.&lt;/p&gt;

&lt;p&gt;API Gateway supports custom domains but it requires an SSL certificate. And there are no integrations with Route 53 or the new AWS Certificate Manager. You’ll need to do a few things by hand to get started. Amazon’s &lt;a href=&quot;http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html&quot;&gt;docs on the subject&lt;/a&gt; are thorough. The following is what I did to get &lt;a href=&quot;https://coolstory.me/yo&quot;&gt;coolstory.me&lt;/a&gt; working with my URL shortener service.&lt;/p&gt;

&lt;h3 id=&quot;ssl-certificate-required&quot;&gt;SSL Certificate Required&lt;/h3&gt;

&lt;p&gt;You can’t get started adding a custom domain to an API Gateway service without  a valid SSL certificate for the domain you want to use. If you’re experimenting with API Gateway and hesitant to commit to purchasing a certificate consider using a free provider such as &lt;a href=&quot;https://letsencrypt.org/getting-started/&quot;&gt;Let’s Encrypt&lt;/a&gt; or &lt;a href=&quot;https://www.startssl.com/&quot;&gt;StartSSL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I created a free certificate with StartSSL. If you’re familiar with generating SSL certificates you can skip this next part. If not you’ll need to do a few things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Have a custom domain registered and resolvable.&lt;/li&gt;
  &lt;li&gt;Validate ownership of your domain with your provider of choice. StartSSL provides a wizard to do this.&lt;/li&gt;
  &lt;li&gt;Generate a private key and certificate signing request using &lt;a href=&quot;https://www.openssl.org/&quot;&gt;openssl&lt;/a&gt; on your computer:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; mkdir coolstory-ssl
&amp;gt; cd coolstory-ssl
&amp;gt; openssl genrsa -out coolstory.me.key 2048
&amp;gt; openssl req -new -sha256 -key coolstory.me.key -out coolstory.me.csr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;openssl req&lt;/code&gt; will ask a series of questions. The most important one is the domain you want to use. Make sure to use exactly the domain you will use.&lt;/li&gt;
  &lt;li&gt;Submit a request for a new certificate with your provider of choice. StartSSL provides a web wizard for this step as well. You will need the CSR file you generated to do this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have the certificate you can add the custom domain under the API Gateway web panel. You’ll need the private key you generated and used to create the CSR. Create a new custom domain under the API Gateway tabs: &lt;code class=&quot;highlighter-rouge&quot;&gt;APIs&lt;/code&gt; » &lt;code class=&quot;highlighter-rouge&quot;&gt;Custom Domain Names&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.davekonopka.com/w/aIz0FRsvnW.png&quot; alt=&quot;API Gateway custom domain creation form&quot; style=&quot;width:800px&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;pointing-your-domain-at-an-api-gateway-service&quot;&gt;Pointing Your Domain at an API Gateway Service&lt;/h3&gt;

&lt;p&gt;Once you create the custom domain name entry with API Gateway you will need to map it to an API stage. The example below shows how &lt;code class=&quot;highlighter-rouge&quot;&gt;coolstory.me&lt;/code&gt; maps to the &lt;code class=&quot;highlighter-rouge&quot;&gt;/v1&lt;/code&gt; endpoint of my &lt;code class=&quot;highlighter-rouge&quot;&gt;redir&lt;/code&gt; service.&lt;/p&gt;

&lt;p&gt;The example also shows a CloudFront distribution domain name. This is domain is different than the invocation URL API Gateway gives you when you deploy a service stage. You can map a CNAME from you domain to this CloudFront domain.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.davekonopka.com/w/BoQxUXguCS.png&quot; alt=&quot;API Gateway custom domain update form&quot; style=&quot;width:800px&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;using-an-apex-domain&quot;&gt;Using an Apex Domain&lt;/h3&gt;

&lt;p&gt;Since this project is a URL shortener I wanted to use an apex domain &lt;code class=&quot;highlighter-rouge&quot;&gt;coolstory.me&lt;/code&gt; not a longer subdomain like &lt;code class=&quot;highlighter-rouge&quot;&gt;links.coolstory.me&lt;/code&gt;. Route 53 supports pointing apex domain records at internal service aliases. This works with API Gateway custom domain CloudFront distribution domains.&lt;/p&gt;

&lt;p&gt;Since I have my domain DNS zone hosted with Route 53 I was able to create a link to the CloudFront distribution domain for the A record.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.davekonopka.com/w/xndIKUxxIt.png&quot; alt=&quot;Route 53 coolstory.me A record&quot; style=&quot;width:400px&quot; /&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">In my last post Build a serverless URL shortener with AWS Lambda and API Gateway services I walked through creating a URL shortener service using Amazon’s API Gateway and Lambda services. One of my goals for that project was to use a custom domain instead of the randomized URL provided by API Gateway. This turned out to be trickier than I expected.</summary></entry><entry><title type="html">Build a serverless URL shortener with AWS Lambda and API Gateway services</title><link href="https://www.davekonopka.com/2016/02/24/serverless-aws-lambda-api-gateway.html" rel="alternate" type="text/html" title="Build a serverless URL shortener with AWS Lambda and API Gateway services" /><published>2016-02-24T00:00:00+00:00</published><updated>2016-02-24T00:00:00+00:00</updated><id>https://www.davekonopka.com/2016/02/24/serverless-aws-lambda-api-gateway</id><content type="html" xml:base="https://www.davekonopka.com/2016/02/24/serverless-aws-lambda-api-gateway.html">&lt;p&gt;I’ve had my eye on Amazon’s Lambda and API Gateway services for a few months. Running a web application without the costs or headaches of maintaining servers is attractive. Of course, no platform is without tradeoffs. So to get myself familiar with the finer points of serverless apps I decided to launch a simple project: a URL shortener service.&lt;/p&gt;

&lt;p&gt;With a few hours of work and a few dollars per month in cost I got a decent prototype working capable of handling millions of requests per month. This post walks through the setup along with the hitches I hit. There’s also an &lt;a href=&quot;https://github.com/davekonopka/serverless-url-shortener&quot;&gt;accompanying code repo&lt;/a&gt; that you can use to try this out yourself.&lt;/p&gt;

&lt;h3 id=&quot;project-overview&quot;&gt;Project Overview&lt;/h3&gt;

&lt;p&gt;Here were the basic requirements for the project:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;One HTTP endpoint to accept a JSON &lt;code class=&quot;highlighter-rouge&quot;&gt;POST&lt;/code&gt; containing a short token and destination URL. These values would need to be stored somewhere.&lt;/li&gt;
  &lt;li&gt;A second HTTP endpoint to take a short token via &lt;code class=&quot;highlighter-rouge&quot;&gt;GET&lt;/code&gt;, lookup a corresponding destination URL, and return a 301 redirect.&lt;/li&gt;
  &lt;li&gt;The redirection endpoint should be accessible without authentication while the &lt;code class=&quot;highlighter-rouge&quot;&gt;POST&lt;/code&gt; should require authentication.&lt;/li&gt;
  &lt;li&gt;Define everything via code as much as possible to make it reproducible.&lt;/li&gt;
  &lt;li&gt;Put the service on a custom domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To accomplish this I needed a few components:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A front-end to take in HTTP requests.&lt;/li&gt;
  &lt;li&gt;A back-end to do something with the requests and generate responses.&lt;/li&gt;
  &lt;li&gt;A datastore to keep all the associated short tokens and destination URL’s.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s take a closer look at each of these components. At the end of the post I’ll layout some gotchas and how much this all costs.&lt;/p&gt;

&lt;h3 id=&quot;front-end-amazon-api-gateway&quot;&gt;Front-end: Amazon API Gateway&lt;/h3&gt;

&lt;p&gt;At the outset I wasn’t sure Amazon API Gateway would handle everything needed for the front-end. It would need to support both &lt;code class=&quot;highlighter-rouge&quot;&gt;application/json&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;text/html&lt;/code&gt; content as well as setting custom response codes and headers. Different content types are supported. And support for mapping Lambda function results to response headers was added at the end of 2015. API Gateway looked like it would fit the bill.&lt;/p&gt;

&lt;h4 id=&quot;creating-an-api&quot;&gt;Creating an API&lt;/h4&gt;

&lt;p&gt;An API Gateway service is composed of resources and methods. Resources are essentially URL endpoints. Methods correspond to HTTP methods like &lt;code class=&quot;highlighter-rouge&quot;&gt;GET&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;POST&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;PUT&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;The URL shortener’s endpoints are limited. There’s one &lt;code class=&quot;highlighter-rouge&quot;&gt;GET&lt;/code&gt; to turn a provided short URL into a redirect. And a &lt;code class=&quot;highlighter-rouge&quot;&gt;POST&lt;/code&gt; to take in short token and destination URL associations for storage.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.davekonopka.com/w/qtL6myQs6g.png&quot; alt=&quot;URL shortener endpoints&quot; style=&quot;width:400px&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;method-components&quot;&gt;Method Components&lt;/h4&gt;

&lt;p&gt;Each API Gateway method has four components. This is an example for the &lt;code class=&quot;highlighter-rouge&quot;&gt;/{token}&lt;/code&gt; endpoint &lt;code class=&quot;highlighter-rouge&quot;&gt;GET&lt;/code&gt; method that accepts a token and returns a redirect:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.davekonopka.com/w/M9ppchGfAk.png&quot; alt=&quot;/{token} endpoint components&quot; style=&quot;width:800px&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Method Request&lt;/strong&gt; defines the incoming request including any parameters that get pulled from the request path, query string, and headers. It also supports enabling authorization for the particular method.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Integration Request&lt;/strong&gt; maps the request to a backend. In this case I’m using Lambda Function but it also supports proxying to another HTTP service or mocking responses for development purposes. Here you pick which Lambda function will handle a single request and map request parameters to a backend data payload.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Method Response&lt;/strong&gt; defines a collection of response status codes and headers that your service supports.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Integration Response&lt;/strong&gt; maps return data from Lambda function execution to appropriate response codes. This can be done using regex matchers. More detail to follow on that. It also allows you to set custom response headers and setup templates to transform Lambda results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;mapping-lambda-results-to-response-headers&quot;&gt;Mapping Lambda Results to Response Headers&lt;/h4&gt;

&lt;p&gt;Part of the magic for the URL shortener is mapping Lambda function results to a response header. I needed to set the &lt;code class=&quot;highlighter-rouge&quot;&gt;Location&lt;/code&gt; header to a destination URL provided by a Lambda function based on a short token lookup.&lt;/p&gt;

&lt;p&gt;Below is the &lt;strong&gt;Integration Response&lt;/strong&gt; settings for the &lt;code class=&quot;highlighter-rouge&quot;&gt;/{token}&lt;/code&gt; endpoint &lt;code class=&quot;highlighter-rouge&quot;&gt;GET&lt;/code&gt; method. Note the &lt;code class=&quot;highlighter-rouge&quot;&gt;Location&lt;/code&gt; header mapping to a value in the Lambda function’s return JSON.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.davekonopka.com/w/luancvOPyB.png&quot; alt=&quot;/{token} endpoint GET method Integration Response settings&quot; style=&quot;width:800px&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;requiring-authentication&quot;&gt;Requiring Authentication&lt;/h4&gt;

&lt;p&gt;The url shortener’s &lt;code class=&quot;highlighter-rouge&quot;&gt;POST&lt;/code&gt; method requires authentication to control who can post entries. One of the authentication options API Gateway offers is an API key. API keys can be generated and associated with applications. When enabled on a method, all requests to that method must include a valid API key header to execute:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;x-api-key: bkayZOMvuy8aZOhIgxq94K9Oe7Y70Hw55&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.davekonopka.com/w/hwwx3bfd9w.png&quot; alt=&quot;URL shortener endpoints&quot; style=&quot;width:800px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This approach allowed me to require authentication on the URL &lt;code class=&quot;highlighter-rouge&quot;&gt;POST&lt;/code&gt; and leave the &lt;code class=&quot;highlighter-rouge&quot;&gt;GET&lt;/code&gt; endpoint open to the world.&lt;/p&gt;

&lt;h4 id=&quot;managing-api-as-code&quot;&gt;Managing API as Code&lt;/h4&gt;

&lt;p&gt;One of my goals was to manage this project in code instead of clicking through the Amazon control panel. API Gateway offers the ability to export and import application configuration in Swagger format.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md&quot;&gt;Swagger&lt;/a&gt; is a specification aimed at describing and documenting RESTful API’s in JSON or YAML format. A variety of services can consume Swagger files to visualize, test, or implement an API service.&lt;/p&gt;

&lt;p&gt;I have to concede that getting started with Amazon’s API Gateway service was much easier using the web based control panel. I ultimately was able to export the service definition into a &lt;a href=&quot;https://github.com/davekonopka/serverless-url-shortener/blob/master/redir-v1-swagger.yml&quot;&gt;YAML file&lt;/a&gt;. I was also able to apply changes and clone the API using the &lt;a href=&quot;https://github.com/awslabs/aws-apigateway-importer&quot;&gt;aws-apigateway-importer&lt;/a&gt; tool provided by Amazon.&lt;/p&gt;

&lt;p&gt;All that said, I created the service first by clicking through the web panel. It greatly helped with my understanding of how API Gateway applications are structured. I recommend using the web panel as a starting point.&lt;/p&gt;

&lt;h3 id=&quot;back-end-amazon-lambda&quot;&gt;Back-end: Amazon Lambda&lt;/h3&gt;

&lt;p&gt;Lambda supports Java, JavaScript, and Python languages. Lambda also supports uploading and executing binaries so other languages are possible. I’m most familiar with JavaScript so I started there.&lt;/p&gt;

&lt;h4 id=&quot;function-inputs-and-outputs&quot;&gt;Function Inputs and Outputs&lt;/h4&gt;

&lt;p&gt;Lambda functions are executed on demand. Unlike EC2, AWS ensures the compute environment whenever the function is executed. There’s no server to maintain and capacity scales as needed.&lt;/p&gt;

&lt;p&gt;Each function can take data in the form of parameters, execute functions, and return data. Functions can import external libraries for things like connecting to other AWS services.&lt;/p&gt;

&lt;p&gt;Lambda &lt;a href=&quot;http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html&quot;&gt;handler functions&lt;/a&gt; receive two parameters when invoked: &lt;code class=&quot;highlighter-rouge&quot;&gt;event&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;context&lt;/code&gt;. When executed by an API Gateway &lt;code class=&quot;highlighter-rouge&quot;&gt;event&lt;/code&gt; includes parameters translated from the incoming request. In the case of a token lookup, the token value from the URL is mapped to a JSON property named &lt;code class=&quot;highlighter-rouge&quot;&gt;token&lt;/code&gt;. The &lt;code class=&quot;highlighter-rouge&quot;&gt;context&lt;/code&gt; argument provides runtime information about the function’s execution environment.&lt;/p&gt;

&lt;h4 id=&quot;url-shortener-functions&quot;&gt;URL Shortener Functions&lt;/h4&gt;

&lt;p&gt;The URL shortener has two simple functions:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;redir_lookup_token&lt;/code&gt; Takes in a token value and returns a corresponding destination URL.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/davekonopka/13b42c432971d6a4c941.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;redir_post_token&lt;/code&gt; Takes in a short URL and destination URL association and stores it.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/davekonopka/775c927aa29406e36ad0.js&quot;&gt;&lt;/script&gt;

&lt;h4 id=&quot;handling-errors-with-api-gateway&quot;&gt;Handling Errors with API Gateway&lt;/h4&gt;

&lt;p&gt;The Lambda function’s &lt;code class=&quot;highlighter-rouge&quot;&gt;context&lt;/code&gt; argument also includes methods for generating a response. It offers &lt;code class=&quot;highlighter-rouge&quot;&gt;succeed&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;fail&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;done&lt;/code&gt; methods. The last method is a helper wrapper for the first two. It takes two parameters and if the first is anything other than &lt;code class=&quot;highlighter-rouge&quot;&gt;null&lt;/code&gt; it triggers a &lt;code class=&quot;highlighter-rouge&quot;&gt;fail&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;API Gateway offers regex pattern matching for mapping &lt;code class=&quot;highlighter-rouge&quot;&gt;fail&lt;/code&gt; error messages to  appropriate response status codes. This allows for translating backend failures to meaningful client responses. Various response status codes can be defined and associated with a regex pattern. If the Lambda function returns an error message matching one of the patterns, the client response will return the matching status code.&lt;/p&gt;

&lt;p&gt;Each response code has its own template mapping. This allows response content to be tailored individually to each response condition. For the purposes of the short URL lookup, a 301 is the default response code for a successful request. If someone attempts a lookup for a token that doesn’t exist then a 404 should be returned.&lt;/p&gt;

&lt;p&gt;The following shows an error condition in the token lookup Lambda function (line 7) and the corresponding 404 message response from the API Gateway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Gateway response statuses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.davekonopka.com/w/5mv39PNual.png&quot; alt=&quot;/{token} response error handling screen, 404 error details&quot; style=&quot;width:800px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda function error result handling&lt;/strong&gt;&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/davekonopka/0caa39da1ad5cebc2c88.js&quot;&gt;&lt;/script&gt;

&lt;h4 id=&quot;managing-lambda-functions-with-apex&quot;&gt;Managing Lambda Functions with Apex&lt;/h4&gt;

&lt;p&gt;Lambda functions can be created directly in the AWS web panel or uploaded via zip file package. There are frameworks that help manage the coding and deployment of Lambda applications from a local development setup.&lt;/p&gt;

&lt;p&gt;I considered &lt;a href=&quot;https://github.com/serverless/serverless&quot;&gt;Serverless&lt;/a&gt;, an extensive framework for building applications with Lambda and API Gateway. I also looked at &lt;a href=&quot;https://github.com/apex/apex&quot;&gt;Apex&lt;/a&gt;, a minimal Lambda function manager.&lt;/p&gt;

&lt;p&gt;Apex provides a lightweight structure for organizing function code and metadata. It offers a CLI command for deploying and executing Lambda functions. I decided on the simpler approach of Apex for this project. With Apex each function is represented by a directory with a JSON metadata file and a JavaScript file containing the function code.&lt;/p&gt;

&lt;p&gt;As you write code you can deploy changes using the &lt;code class=&quot;highlighter-rouge&quot;&gt;apex deploy&lt;/code&gt; command.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; apex deploy
   • deploying                 function=post_token
   • deploying                 function=lookup_token
   • created build (1.1 kB)    function=lookup_token
   • created build (1.2 kB)    function=post_token
   • config unchanged          function=post_token
   • code unchanged            function=post_token
   • config unchanged          function=lookup_token
   • code unchanged            function=lookup_token
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once uploaded, you can execute functions from your laptop with sample input using the &lt;code class=&quot;highlighter-rouge&quot;&gt;apex invoke&lt;/code&gt; command.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/davekonopka/51965c484321e80277b7.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;For small projects Apex’s structure is easy to get working quickly and it doesn’t get in the way. The project mentions plans to include API Gateway management in the future.&lt;/p&gt;

&lt;h3 id=&quot;datastore-dynamodb&quot;&gt;Datastore: DynamoDB&lt;/h3&gt;

&lt;p&gt;In keeping with the serverless theme, DynamoDB was a natural fit for storing the short and destination URLs. With DynamoDB instead of standing up a server you specify allotments of &lt;a href=&quot;http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ProvisionedThroughputIntro.html&quot;&gt;read and write capacity&lt;/a&gt;. Amazon ensures the allotted read and write capacities are available. You can scale up or down capacity units as needed.&lt;/p&gt;

&lt;p&gt;DynamoDB tables are schemaless. Primary indexes can be a single value or a composite of two fields. An important limitation of composite keys is that they are hierarchical. This means that the second field is selectable in the context of the primary field. Selecting it on its own results in expensive table scans. Since the URL shortener primary index is a single short token string this was not a concern.&lt;/p&gt;

&lt;h3 id=&quot;how-much-does-this-cost&quot;&gt;How much does this cost?&lt;/h3&gt;

&lt;p&gt;So how much does this all cost? The following examples assume a working example of roughly 1 million hits over a month. Prices do not include data transfer costs which will vary.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Service&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Explanation&lt;/th&gt;
      &lt;th&gt;Cost&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://aws.amazon.com/api-gateway/pricing/&quot;&gt;API Gateway&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;per 1 million requests per month&lt;/td&gt;
      &lt;td&gt;$3.50&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://aws.amazon.com/lambda/pricing/&quot;&gt;Lambda&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;(hits * typical execution seconds) * (memory/1024) * $.00001667&lt;/td&gt;
      &lt;td&gt;$1.04&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://aws.amazon.com/dynamodb/pricing/&quot;&gt;DynamoDB&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1 read, 1 write per second&lt;/td&gt;
      &lt;td&gt;$0.58&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;Grand Total&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;$5.12/month&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;em&gt;Under the &lt;a href=&quot;http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html&quot;&gt;AWS Free Tier&lt;/a&gt; this would be free.&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;what-are-the-gotchas&quot;&gt;What are the gotchas?&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;API Gateway response times can vary greatly. There is support for response caching though which greatly improves latency.&lt;/li&gt;
  &lt;li&gt;Importing API Gateway from Swagger doesn’t create required Lambda permissions. I had to manually reselect each Lambda function through the web panel to apply the appropriate permissions for a service newly cloned from Swagger.&lt;/li&gt;
  &lt;li&gt;API Gateway Swagger exports don’t seem to include any model schemas you create. The web panel seems to show an error message if you have no models.
    &lt;ul&gt;
      &lt;li&gt;Quick followup post on creating a model: &lt;a href=&quot;/2016/api-gateway-models.html&quot;&gt;Forgot about Amazon API Gateway models&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;API Gateway custom domains require SSL. There’s no connection with AWS Certificate Manager of Route 53 at this point. Certificates must be manually posted.&lt;/li&gt;
  &lt;li&gt;Custom domains are pointed at API Gateway using a CNAME record. This means using a subdomain which isn’t ideal for short URLs.
    &lt;ul&gt;
      &lt;li&gt;I wrote a followup post explaining how to do this: &lt;a href=&quot;/2016/api-gateway-domain.html&quot;&gt;Using custom domains with Amazon’s API Gateway&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><summary type="html">I’ve had my eye on Amazon’s Lambda and API Gateway services for a few months. Running a web application without the costs or headaches of maintaining servers is attractive. Of course, no platform is without tradeoffs. So to get myself familiar with the finer points of serverless apps I decided to launch a simple project: a URL shortener service.</summary></entry><entry><title type="html">On-call rotations are a team sport</title><link href="https://www.davekonopka.com/2015/06/08/on-call-rotations-are-a-team-sport.html" rel="alternate" type="text/html" title="On-call rotations are a team sport" /><published>2015-06-08T00:00:00+00:00</published><updated>2015-06-08T00:00:00+00:00</updated><id>https://www.davekonopka.com/2015/06/08/on-call-rotations-are-a-team-sport</id><content type="html" xml:base="https://www.davekonopka.com/2015/06/08/on-call-rotations-are-a-team-sport.html">&lt;p&gt;No one ever says: “I want to ack &lt;a href=&quot;https://www.pagerduty.com/&quot;&gt;PagerDuty&lt;/a&gt; alerts when I grow up”. Lost sleep. Diverted focus. Scrambling under pressure. There’s no shortage of downsides to being on the frontline for a business’s technology.&lt;/p&gt;

&lt;p&gt;Still, a well run on-call rotation is key to the proper care and feeding of any company’s technology stack. A healthy on-call rotation shines a bright light on weaknesses and implements improvements with urgency. It requires a team with sharpened communication practices to do it well.&lt;/p&gt;

&lt;p&gt;Over time, how your team handles on-call can make all the difference between a business taking flight or drowning in technical debt.&lt;/p&gt;

&lt;h3 id=&quot;you-built-it-you-bought-it&quot;&gt;You built it, you bought it&lt;/h3&gt;

&lt;p&gt;Limiting on-call rotations to a single person or a relatively small handful of people in an organization is an anti-pattern. This practice tends to shift problems away from the people who can act on them. When the people fielding alerts aren’t empowered to make improvements they will ultimately optimize for silencing alerts rather than addressing underlying issues. Those people will also end up burning out in short order.&lt;/p&gt;

&lt;p&gt;Teams owning alerts for the systems that they create and/or maintain is a much healthier pattern. Teams will be much more likely to spend cycles on performance improvements and bug fixes when they’re experiencing the live fire of their work. They will roll lessons learned into improving their development process for new features. Over time this practice will also enhance a team’s ability to collect metrics and monitoring points relevant to the business.&lt;/p&gt;

&lt;h3 id=&quot;turn-on-the-flood-lights&quot;&gt;Turn on the flood lights&lt;/h3&gt;

&lt;p&gt;Hidden problems tend to fester until they turn into emergencies. Creating a culture that prefers exposing vulnerabilities over hiding them will keep the problem scopes smaller. Pushing the issues out in the open for all to see will lessen the chances they end up swept under a rug.&lt;/p&gt;

&lt;p&gt;Metrics are like a light switch. Tracking the occurrences and nature of on-call alerts will show which areas of a system need attention. Metrics will also reveal patterns that may not be apparent to the people fielding alerts.&lt;/p&gt;

&lt;p&gt;A shared on-call log is one way to collect and expose these metrics. Many monitoring tools along with alerting services like PagerDuty expose some sort of API. These API’s can be mined for alert history data. Regular review of this type of alerting data in a team setting will help prioritize attention.&lt;/p&gt;

&lt;p&gt;A chat room is another great place to shine a light on issues. Most monitoring systems can send alerts to a variety of chat services. The squeaky wheel gets the grease. Putting a stream of alerts front and center for the entire company to see will make it much harder for everyone to ignore squeaky wheels.&lt;/p&gt;

&lt;h3 id=&quot;build-a-culture-of-improvement&quot;&gt;Build a culture of improvement&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://s3.amazonaws.com/f.cl.ly/items/1q0Z3O3f3o0Y2L0L1F3a/knowing-is-half-the-battle.jpg&quot;&gt;Knowing is half the battle&lt;/a&gt;. The other half is actually fixing stuff.&lt;/p&gt;

&lt;p&gt;A sustainable on-call process includes time allotted for making improvements. In some situations this work may be doable during a person’s on-call rotation. In other cases it may require time after the rotation to follow-up on issues. Consider what works within your teams’ responsibilities and plan accordingly.&lt;/p&gt;

&lt;p&gt;Solidify your team’s process for ticketing issues as they arise. Problem tickets must contain enough information to be actionable. Groom the backlog of issue tickets regularly. Work improvement tickets into the team’s pipeline regularly. Anything less will end up becoming a vanity exercise producing little more than an ever increasing ticket backlog.&lt;/p&gt;

&lt;p&gt;Pay particular attention to systemic issues. Problems in core infrastructure tend to replicate out, compounding the number of alerts. Be on the look out for false positive and unactionable alerts. Prioritize fixing these type of issues quickly before recurring alerts result in pager fatigue across a team.&lt;/p&gt;

&lt;p&gt;Use metrics to publicize the improvements your team makes. Wins are contagious. Every time you speed up a page load by a high percentage or eliminate a flaw that has been waking up an engineer for days, shout it from the roof tops. Sharing these kind of measured wins will reinforce a culture of improvement throughout your organization.&lt;/p&gt;

&lt;h3 id=&quot;fewer-beeps-more-sleeps-and-much-happier-customers&quot;&gt;Fewer beeps. More sleeps. And much happier customers.&lt;/h3&gt;

&lt;p&gt;Few people get excited about taking on an on-call rotation. Handled properly though, teams can use on-call rotations to build a culture of improvement. The visibility that comes from metrics combined with clear pathways of action can be a powerful force for positive change in any organization.&lt;/p&gt;</content><author><name></name></author><summary type="html">No one ever says: “I want to ack PagerDuty alerts when I grow up”. Lost sleep. Diverted focus. Scrambling under pressure. There’s no shortage of downsides to being on the frontline for a business’s technology.</summary></entry><entry><title type="html">The secret to organizing a tech meetup</title><link href="https://www.davekonopka.com/2015/06/05/the-secret-to-organizing-a-tech-meetup.html" rel="alternate" type="text/html" title="The secret to organizing a tech meetup" /><published>2015-06-05T00:00:00+00:00</published><updated>2015-06-05T00:00:00+00:00</updated><id>https://www.davekonopka.com/2015/06/05/the-secret-to-organizing-a-tech-meetup</id><content type="html" xml:base="https://www.davekonopka.com/2015/06/05/the-secret-to-organizing-a-tech-meetup.html">&lt;p&gt;A little over three years ago &lt;a href=&quot;https://castro.io/&quot;&gt;Hector Castro&lt;/a&gt; brought up an idea he had for starting a DevOps meetup in Philadelphia. We both agreed that existing user groups weren’t covering many topics bridging the gap between developing and operating software. So we organized the first &lt;a href=&quot;http://www.meetup.com/PhillyDevOps/&quot;&gt;Philly DevOps&lt;/a&gt; meetup in July of 2012 and we’ve hosted regular monthly meetups since then.&lt;/p&gt;

&lt;p&gt;Every so often somebody asks: I want to start a meetup. What do I do?&lt;/p&gt;

&lt;p&gt;There’s plenty to talk about in response: Meeting space. Food. Speakers. Sponsorships. Scheduling. Web sites. Attracting and engaging community members.&lt;/p&gt;

&lt;p&gt;But there’s really only one answer that matters…&lt;/p&gt;

&lt;h3 id=&quot;do-it&quot;&gt;Do It.&lt;/h3&gt;

&lt;p&gt;Seriously. &lt;a href=&quot;https://speakerdeck.com/alexknowshtml/10-steps-to-jfdi&quot;&gt;JFDI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Meetups are a time and a place for a community to assemble. There’s very little you can do in isolation to orchestrate a successful meetup. Logistics are important but they only take you so far. The real development of a meetup happens when people start coming together and talking to each other.&lt;/p&gt;

&lt;h3 id=&quot;set-a-topic-book-a-room-announce-a-date&quot;&gt;Set a topic. Book a room. Announce a date.&lt;/h3&gt;

&lt;p&gt;Your first meetup talk doesn’t need to be perfect. You can deliver a brief talk on something you’ve worked with. You could even pose an open topic for group discussion. Pick something and move forward with it.&lt;/p&gt;

&lt;p&gt;Finding meeting space can be painful. Not many organizations are willing to offer space to the public in the evenings. Even fewer are willing to do so for free.&lt;/p&gt;

&lt;p&gt;We held our first meeting in a local &lt;a href=&quot;http://venturef0rth.com/&quot;&gt;coworking space&lt;/a&gt; that we reached out to via their website. Soon after we moved to a room in a bar/restaurant. We had to guarantee a minimum bar tab in exchange for using the space. There were some nervous moments the first few months but we generally covered the tab minimum.&lt;/p&gt;

&lt;p&gt;Ask around with your contacts for space connections. Maybe your employer would be willing to donate a conference room for an evening. Keep in mind, the space you start with doesn’t have to last forever. All you need is someplace adequate to get started.&lt;/p&gt;

&lt;p&gt;Once you lock in a topic, space, and date you can begin gathering your community. Announce the first meetup to your contacts. Share it with your coworkers. Post it to your social media accounts. Ask other similar meetup organizers if they would share it within their communities. Be sure to keep your communication genuine and avoid spamming people at all costs.&lt;/p&gt;

&lt;h3 id=&quot;get-to-know-the-community&quot;&gt;Get to know the community&lt;/h3&gt;

&lt;p&gt;Meetups thrive on interaction. High quality talks are a valuable resource. Well known speakers draw attendees. Still, the interactions people have with each other are what bring them back month after month. Without interaction people will drop off quickly after attending one or two events.&lt;/p&gt;

&lt;p&gt;Ask people what they’re interested in from the meetup. Ask them what they’re working on. Listen to what they say. Encourage them to consider giving a talk to the group. Over time you’ll build connections that will sustain the meetup.&lt;/p&gt;

&lt;p&gt;Build in time for socializing. Find a bar/restaurant nearby. Announce that everyone will head there to continue conversations after the meetup. Encourage people to follow over. Clarity and repetition help people feel more comfortable engaging with each other.&lt;/p&gt;

&lt;h3 id=&quot;be-inclusive&quot;&gt;Be inclusive&lt;/h3&gt;

&lt;p&gt;The best tech meetup communities are a continuum of experience and skill levels. There’s always something to learn from each other. It’s helpful to organize a range of content that speaks to different skill levels.&lt;/p&gt;

&lt;p&gt;Echo chambers are pointless. A diversity of backgrounds and experiences helps everyone advance. To this end, we enforce a &lt;a href=&quot;http://www.meetup.com/PhillyDevOps/pages/Philly_DevOps_Code_of_Conduct/&quot;&gt;code of conduct&lt;/a&gt; for Philly DevOps events and communication channels. We do not tolerate harassment in any form. We encourage you to be kind to others. No one can insult or put down other attendees. We expect professional behavior. Anyone interested in DevOps should feel safe and comfortable attending a meetup.&lt;/p&gt;

&lt;h3 id=&quot;the-secret-is-there-is-no-secret&quot;&gt;The secret is: There is no secret&lt;/h3&gt;

&lt;p&gt;There really is no secret to organizing a tech meetup. If you’ve decided there is a gap you want to fill in your community, start doing it. Get people together as quickly as possible. Listen to what they have to say. Encourage people to connect with each other. You will work out all of the logistics that matter along the way.&lt;/p&gt;</content><author><name></name></author><summary type="html">A little over three years ago Hector Castro brought up an idea he had for starting a DevOps meetup in Philadelphia. We both agreed that existing user groups weren’t covering many topics bridging the gap between developing and operating software. So we organized the first Philly DevOps meetup in July of 2012 and we’ve hosted regular monthly meetups since then.</summary></entry><entry><title type="html">Going remote, even when you’re on-site</title><link href="https://www.davekonopka.com/2015/06/01/remote-lessons.html" rel="alternate" type="text/html" title="Going remote, even when you're on-site" /><published>2015-06-01T00:00:00+00:00</published><updated>2015-06-01T00:00:00+00:00</updated><id>https://www.davekonopka.com/2015/06/01/remote-lessons</id><content type="html" xml:base="https://www.davekonopka.com/2015/06/01/remote-lessons.html">&lt;p&gt;Over the past few years I’ve worked on opposite extremes of the remote/on-site workplace spectrum. My current employer maintains &lt;a href=&quot;http://technical.ly/philly/2013/03/27/aweber-new-office-photos/&quot;&gt;top notch office facilities&lt;/a&gt; complete with chef prepared lunches, bio-walls, lego murals, and slides. Previously, I worked with a fully distributed company of 75+ people that had a strong culture and no physical office space at all.&lt;/p&gt;

&lt;p&gt;There are definite trade-off’s to both types of working environments. Whether your team is on-site or on-the-beach, I’ve learned that teams can find serious benefits in adopting remote-first communication patterns.&lt;/p&gt;

&lt;p&gt;Remote-first means interacting with your team members as if you all aren’t in the same location at the same time. Even if you happen to be in the same place at the same time, consider these scenarios.&lt;/p&gt;

&lt;p&gt;Instead of hitting up coworkers in their cubicles, try having a conversation in a community chat room. People can come along later, read the chat logs, and learn from a conversation that otherwise would have been lost between two people.&lt;/p&gt;

&lt;p&gt;Stop handing over half the week to all-hands, hour-long blocks of in-person meetings in conference rooms. Discard conventions like blocking off 30 - 60 minutes and filling the time. Establish a common way to advertise availability amongst your team. Agree to use Google Hangouts for impromptu group video chat as needed. Doing this you can be on the other side of the building or the country and collaborate effectively with your team.&lt;/p&gt;

&lt;p&gt;Ditch the obligatory after-the-fact employee to manager status updates. Expose activity streams from actual sources of work: Github pull requests, continuous integration/deployment job results, ticket/card updates, and service status alerts. These activity streams are transparent to everyone on the team. They’re also automatic and give a much more accurate picture than any one person’s recollection of what happened over the course of a week, month, or year.&lt;/p&gt;

&lt;p&gt;Remote-first is a transition to an asynchronous culture. It certainly doesn’t eliminate the need for personal communication. It actually elevates the value of communication by destroying artificial roadblocks associated with engaging your fellow team members.&lt;/p&gt;

&lt;p&gt;Back to my current on-site work life today. I’ll share one way my team has chosen to experiment with remote-first communication.&lt;/p&gt;

&lt;p&gt;I work a team of six people managing technical operations for a variety of web applications and core services. The entire company already communicates and shares activity streams heavily over &lt;a href=&quot;https://www.hipchat.com/&quot;&gt;HipChat&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Still, my team leans on in-person meetings for weekly planning and daily status updates. This has served us well but as the team grows these meetings take considerably longer. It also leaves us feeling disconnected when one of us works remotely for a day.&lt;/p&gt;

&lt;p&gt;We’ve decided to replace our daily in-person stand-up status meeting with two things. First, we each commit to updating a list of what we completed yesterday and what we plan to complete today by the start of each day. We’re using a service called &lt;a href=&quot;https://idonethis.com/&quot;&gt;iDoneThis&lt;/a&gt; to share everyone’s lists.&lt;/p&gt;

&lt;p&gt;Second, we’ll each commit to being available during a half hour period in the morning. During this half hour any of us can call for a stand up, on HipChat, Google Hangouts, or in person, if there’s something that requires discussion. If there’s nothing specific to discuss, there will be no meeting.&lt;/p&gt;

&lt;p&gt;This new approach aims to cut down time spent reading our status updates aloud. It also aims to keep us informed regardless of where our workspace is.&lt;/p&gt;

&lt;p&gt;Time will tell how this change works for the team. Willingness to experiment and make iterative improvements is key. Even though we regularly share the same space I expect the extra flexibility will help us better deliver on both our individual and team commitments.&lt;/p&gt;</content><author><name></name></author><summary type="html">Over the past few years I’ve worked on opposite extremes of the remote/on-site workplace spectrum. My current employer maintains top notch office facilities complete with chef prepared lunches, bio-walls, lego murals, and slides. Previously, I worked with a fully distributed company of 75+ people that had a strong culture and no physical office space at all.</summary></entry><entry><title type="html">Repeatable, randomly ordered RSpec test runs on TeamCity</title><link href="https://www.davekonopka.com/2013/03/22/teamcity-rspec-order.html" rel="alternate" type="text/html" title="Repeatable, randomly ordered RSpec test runs on TeamCity" /><published>2013-03-22T00:00:00+00:00</published><updated>2013-03-22T00:00:00+00:00</updated><id>https://www.davekonopka.com/2013/03/22/teamcity-rspec-order</id><content type="html" xml:base="https://www.davekonopka.com/2013/03/22/teamcity-rspec-order.html">&lt;p&gt;Running a suite of tests in the same order always can mask unintentional dependencies between tests. You can end up with a test that succeeds in a full run of the suite but fails by itself.&lt;/p&gt;

&lt;p&gt;One way to shake out these dependencies is to run tests in a random order. RSpec has an &lt;a href=&quot;https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/command-line/order-new-in-rspec-core-2-8&quot;&gt;–order&lt;/a&gt; option that you can use to randomize test order for each run.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;rspec --order rand&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Recent versions of RSpec &lt;a href=&quot;http://blog.davidchelimsky.net/2012/07/07/rspec-211-is-released/&quot;&gt;enable random ordering by default&lt;/a&gt; in generated spec_helper.rb files.&lt;/p&gt;

&lt;p&gt;It’s important though to be able to reproduce a specific run order to track down issues when they pop up. RSpec accepts a seed value to initiate a specific order.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;rspec --order rand:1234&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At work we wanted to randomize test order on our CI server. We run our projects on &lt;a href=&quot;http://www.jetbrains.com/teamcity/&quot;&gt;TeamCity&lt;/a&gt; which does a nice job of parsing and reporting test run results. It does this through a custom Rake wrapper that does not output the order seed into the build logs.&lt;/p&gt;

&lt;p&gt;A coworker &lt;a href=&quot;http://blog.markstarkman.com/&quot;&gt;Mark Starkman&lt;/a&gt; pointed us to this &lt;a href=&quot;http://jakegoulding.com/blog/2012/10/18/run-your-tests-in-a-deterministic-random-order/&quot;&gt;blog post&lt;/a&gt; with a handy shell one-liner for converting a Git commit hash into a seed numeric value.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;$(git rev-parse HEAD | tr -d 'a-z' | cut -b 1-5)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Armed with this, I created a Command Line build step that populates an environment variable at runtime with a commit hash generated number. TeamCity recognizes a &lt;a href=&quot;http://confluence.jetbrains.com/display/TCD7/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-AddingorChangingaBuildParameter&quot;&gt;special format&lt;/a&gt; to populate parameters like environment variables at runtime.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/davekonopka/5226204.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;Next up I updated the Rake step that handles RSpec runs to pass the environment variable in as the seed value.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;blog/2013-03-22-tc-rake.png&quot; alt=&quot;TeamCity Rake step&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now each build log on our CI server includes the command needed to reproduce the test run order on any developer system.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/davekonopka/5226209.js&quot;&gt;&lt;/script&gt;</content><author><name></name></author><summary type="html">Running a suite of tests in the same order always can mask unintentional dependencies between tests. You can end up with a test that succeeds in a full run of the suite but fails by itself.</summary></entry><entry><title type="html">RSpec run fails, returns exit code 0</title><link href="https://www.davekonopka.com/2013/03/09/rspec-exit-code.html.html" rel="alternate" type="text/html" title="RSpec run fails, returns exit code 0" /><published>2013-03-09T00:00:00+00:00</published><updated>2013-03-09T00:00:00+00:00</updated><id>https://www.davekonopka.com/2013/03/09/rspec-exit-code.html</id><content type="html" xml:base="https://www.davekonopka.com/2013/03/09/rspec-exit-code.html.html">&lt;p&gt;My team recently setup CI for a few Rails projects using &lt;a href=&quot;http://www.jetbrains.com/teamcity/&quot;&gt;TeamCity&lt;/a&gt; at &lt;a href=&quot;http://www.articulate.com/careers/&quot;&gt;work&lt;/a&gt;. When new commits hit a project’s repo if an RSpec test run passes the CI setup deploys the code to a Heroku site used by our QA team. One way or another some specs started failing on a project but the code still deployed to Heroku. That’s not how it’s supposed to work.&lt;/p&gt;

&lt;p&gt;TeamCity looks at each command’s exit code to determine if a CI step fails or succeeds. If the exit code is 0 everything is copacetic and it continues on to the next step. Anything other than 0 means shit’s broke so it stops the process and logs failure.&lt;/p&gt;

&lt;p&gt;It turns out the command &lt;code class=&quot;highlighter-rouge&quot;&gt;rspec spec&lt;/code&gt; was returning exit code 0 on the CI server even when specs were failing.&lt;/p&gt;

&lt;p&gt;After some research I found this Ruby bug which seemed to explain the behavior: &lt;a href=&quot;http://bugs.ruby-lang.org/issues/5218&quot;&gt;at_exit bug with exception handling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The suggested fix solved our problem. I put the monkey patch into place in the project’s spec_helper.rb file and &lt;code class=&quot;highlighter-rouge&quot;&gt;rspec spec&lt;/code&gt; exit codes are coming back “1” with failing specs and “0” with no fails on the server. TeamCity now stops short of sending broken code off to Heroku.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/davekonopka/5123917.js&quot;&gt;&lt;/script&gt;</content><author><name></name></author><summary type="html">My team recently setup CI for a few Rails projects using TeamCity at work. When new commits hit a project’s repo if an RSpec test run passes the CI setup deploys the code to a Heroku site used by our QA team. One way or another some specs started failing on a project but the code still deployed to Heroku. That’s not how it’s supposed to work.</summary></entry></feed>