<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>intricatecloud</title>
  <subtitle></subtitle>
  <link href="https://intricatecloud.io/feed.xml" rel="self"/>
  <link href="https://intricatecloud.io/"/>
  
    <updated>2023-06-22T05:17:10Z</updated>
  
  <id>https://intricatecloud.io</id>
  <author>
    <name>Danny Aslam-Perez</name>
    <email>danny@intricatecloud.io</email>
  </author>
  
    
    <entry>
      <title>Creating a serverless static website, part 1</title>
      <link href="https://intricatecloud.io/2018/04/creating-a-serverless-static-website/"/>
      <updated>2018-04-14T06:10:42Z</updated>
      <id>https://intricatecloud.io/2018/04/creating-a-serverless-static-website/</id>
      <content type="html">
        <![CDATA[
      <h1>Creating a serverless static website</h1>
<h2>What's the problem?</h2>
<p>I want to deploy a plain HTML/CSS/JS website with the minimal amount of fuss. I'm going to be using it for a simple web-app so I want to make sure its always up and always working as well.</p>
<p>There's many different ways of deploying websites with mostly static content.</p>
<ul>
<li>You could run a server using nginx/apache to serve files out of a directory, for example. This type of setup works fine for small setups, but starts to grow out of hand.
<ul>
<li>you'll need to keep your versions of your web server up to date to pull in any security updates for nginx/apache</li>
<li>you'll need to keep your OS patched with the latest security updates</li>
<li>you have servers to manage that need to be highly available</li>
<li>your servers are susceptible to attacks and you'll need to be defensive with your security</li>
</ul>
</li>
<li>You can always use hosted services to achieve some of this, but what's the fun in that? You're here to learn how to build solutions on AWS.</li>
</ul>
<h2>What's the solution?</h2>
<p>Here's the features we want for our website</p>
<ul>
<li>a main website that will be accessible at https://www.mysite.com</li>
<li>a redirect from https://mysite.com -&gt; https://www.mysite.com</li>
<li>an HTML/CSS/JS project</li>
</ul>
<p>AWS provides a number of services to make this easy. We'll be using:</p>
<ul>
<li>S3 to host our static content</li>
<li>CloudFront to act as a Content Delivery Network to provide faster speeds to end-users</li>
<li>Route53 to purchase and manage your domain</li>
<li>Cloudwatch to monitor traffic flowing to your website</li>
<li>Amazon Certificate Manager for free SSL certificates</li>
</ul>
<p>A lot of guides online will tell you, &quot;You want to learn AWS? Get a free account and use Cloudformation to deploy everything!&quot;. That's fine advice if you're already familiar with building things on AWS. But if you're interacting with a new AWS service, it's helpful to set it up manually the first time so you can see which services are dependent on what information. (Check out <a href="https://intricatecloud.wpengine.com/2018/04/creating-your-serverless-website-in-terraform-part-2/">Part 2 here</a>)</p>
<p>For the purposes of this tutorial, we'll be setting it all up manually to familiarize ourselves with the above AWS services.</p>
<h3>Why not just use a plain S3 website?</h3>
<p>Unfortunately, S3 configured as a website doesn't support <code>https</code> by itself, so you need to add Cloudfront to the mix in order to get support for <code>https</code>.</p>
<h2>Get started</h2>
<h3>Create the S3 bucket</h3>
<ol>
<li>
<p>Create 2 S3 buckets – one for your access logs first, and then one for your app. In this case, I’m making dperez-test-bucket-logs and dperez-test-bucket. The logs bucket can have all the default AWS settings, so you can click next through the wizard. <img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-7.37.15-PM.png" alt="" width="691" height="505" class="alignnone size-full wp-image-1182" />
Then create the bucket for your application.</p>
</li>
<li>
<p>Configure the bucket to send access logs to another bucket for monitoring
<img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-7.41.26-PM.png" alt="" width="690" height="531" class="alignnone size-full wp-image-1184" /></p>
</li>
</ol>
<h3>Configure your bucket</h3>
<ol>
<li>Configure the bucket as a website and set the default index document. Notice that the URL is a really long URL hosted under Amazon's domain. You'll likely want to have your users see your domain instead of Amazon's in the browser bar. More on that later.
<img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-7.44.43-PM.png" alt="" width="554" height="612" class="alignnone size-full wp-image-1186" /></li>
<li>Use the aws-cli to deploy your project to S3</li>
</ol>
<pre><code>☁  s3-project [master] ⚡ ll
total 4.0K
drwxr-xr-x 3 dperez 102 Apr 22 19:52 ./
drwxr-xr-x 9 dperez 306 Apr 22 19:52 ../
-rw-r--r-- 1 dperez  52 Apr 22 19:52 index.html
☁  s3-project [master] ⚡ aws s3 ls | grep dperez
2018-04-22 19:42:23 dperez-test-bucket
2018-04-22 19:39:53 dperez-test-bucket-logs
☁  s3-project [master] ⚡ aws s3 sync . s3://dperez-test-bucket/
upload: ./index.html to s3://dperez-test-bucket/index.html
☁  s3-project [master] ⚡
</code></pre>
<p>After finishing clicking the buttons for these steps, you should have a URL that you can access to see your site! Find the URL provided by S3 and visit your new website.</p>
<h3>More configuration</h3>
<ul>
<li>The first error you'll see is a 403 Forbidden page. This is because your bucket is not public yet. The bucket itself can have a policy attached to it, when we're creating websites, we want the bucket to be public on purpose. That does mean that you need to make sure to not keep any credentials in a public S3 bucket. Go to your bucket &gt; Permissions &gt; Bucket Policy and add the following IAM policy (make sure to swap out <code>dperez-test-bucket</code> for your buckets name.
<img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-7.58.43-PM.png" alt="" width="690" height="283" class="alignnone size-full wp-image-1187" /></li>
</ul>
<pre><code>{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;PublicRead&quot;,
      &quot;Principal&quot;: &quot;*&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;s3:GetObject&quot;
      ],
      &quot;Resource&quot;: &quot;arn:aws:s3:::dperez-test-bucket/*&quot;
    }
  ]
}
</code></pre>
<p>The bucket is public. This is expected and intentional when deploying this website, but worth calling out after the long string of &quot;hacks&quot; where data was leaked out of misconfigured buckets. However, as long as the bucket is public, the general internet will be crawling your website both under your pretty domain, as well as via the bucket directy. We'll fix this to make sure your viewers are only seeing your branded domain and website.</p>
<h3>Looking at logs</h3>
<ul>
<li>Take a look at the files that were dropped off in your access logs bucket. You'll see that it contains a bunch of log lines telling you who accessed your website, what they requested, how long it took, etc.</li>
</ul>
<h3>Create certificates to support HTTPS URLs</h3>
<p>We can get free certificates from Amazon Certificate Manager. They offer a few different ways of verifying your identity to prove that you own the domain you want a certificate for. Create a certificate for the domain you want your users to view. We'll need this later.</p>
<ul>
<li>Confirm an email that gets sent to the contact info for your domain - billing@mysite.com for example.</li>
<li>If you have control over the DNS, you can insert a CNAME record containing an ID that Route53 provides. As soon as the record exists, the certificate gets created.
<img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-8.22.37-PM.png" alt="" width="741" height="379" class="alignnone size-full wp-image-1190" /></li>
</ul>
<h3>Set up CloudFront as a CDN</h3>
<p>Now we can create the Cloudfront distribution and finish connecting the pieces together.</p>
<ol>
<li>Create an Origin Access Identity. We'll be using this later to further lock down access to our bucket.
<img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-8.25.00-PM.png" alt="" width="875" height="461" class="alignnone size-full wp-image-1191" /></li>
<li>Create the distribution.</li>
</ol>
<ul>
<li>For origin: the origin of the distribution will be set to your S3 bucket, so any request hitting your domain will first be received by CloudFront, and if it doesn't exist in the cache, it will grab the object from S3. Specify the Origin Access Identity that you created earlier. Also make sure to set &quot;Grant Read Permissions on Bucket&quot; to &quot;Yes, Update Bucket Policy&quot;. This will update the permissions on our S3 bucket to allow Cloudfront to connect to it.
<img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-8.25.56-PM.png" alt="" width="987" height="518" class="alignnone size-full wp-image-1192" /></li>
<li>Cache Behavior: CloudFront acts as a cache, so for the purposes of this example, we can leave all the defaults here except for &quot;Viewer Protocol Policy&quot;. You'll want to set that to Redirect HTTP to HTTPS.
<img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-8.28.20-PM.png" alt="" width="658" height="156" class="alignnone size-full wp-image-1193" />
<ul>
<li>Domain &amp; Certificate: For Alternate Domain Names (CNAMEs), use the URL that you want your viewers to use. Choose the certificate for your site that you provisioned via ACM.
<img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-8.29.15-PM.png" alt="" width="1076" height="520" class="alignnone size-full wp-image-1194" /></li>
</ul>
</li>
</ul>
<h3>Check out what Cloudfront did to your bucket policy</h3>
<p>Because we're going to be using Cloudfront for this application, we can keep the bucket private for an additional layer of security and only accessible by Cloudfront by using a feature called an Origin Access Identity. Cloudfront will create a randomized string that it will use to make authenticated requests to the S3 bucket. When your S3 bucket receives the request, it will check to make sure that it is the same string and if so, will allow Cloudfront to read your files.</p>
<p>Go to Permissions &gt; Bucket Policy to see the updated IAM policy.</p>
<h3>Update your Route53 Domain to map to your CloudFront distribution</h3>
<ul>
<li>When creating your record, make sure to use an Alias record. The advantage of using an Alias record over a CNAME, is that DNS queries to R53 for Alias records are free and it's one less DNS query than using a CNAME. Here's what it looks like for one of my domains.
<img src="https://www.intricatecloud.io/wp-content/uploads/2018/04/Screen-Shot-2018-04-22-at-8.35.02-PM.png" alt="" width="418" height="463" class="alignnone size-full wp-image-1195" /></li>
<li>Once your DNS has been updated, wait for DNS to propagate, and test out your new site to verify your requirements</li>
</ul>
<h3>Be aware of the constraints of this setup</h3>
<ul>
<li>CloudFront acts as a cache in front of S3. If you deploy new files to S3 and you refresh your website, you may or may not see the new content depending on the TTL of your cache. There's a balance to strike between setting a low TTL like 0 and a high TTL like 1 hour (a TTL of 0 is a feature itself that we'll dive into another time).</li>
<li>Amazon Certificates can only be used on AWS. You cannot export your certificate's private key out of AWS. It's generally fine if you're using AWS anyway, but if you need to be vendor independent, use Let's Encrypt's free certificates. There's no reason anymore to be paying for certificates.</li>
<li>CloudFront has some limitations around what headers you're able to use
<ul>
<li>Setting cache-control headers has to be added as metadata on your S3 object</li>
<li>Adding custom security headers like <code>X-Frame-Origin</code>, <code>Strict-Transport-Security</code> is not supported directly via CloudFront but can be added via Lambda@Edge</li>
</ul>
</li>
</ul>
<h3><a href="https://intricatecloud.wpengine.com/2018/04/creating-your-serverless-website-in-terraform-part-2/">Go to Part 2</a></h3>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Creating your serverless static website in terraform, part 2</title>
      <link href="https://intricatecloud.io/2018/04/creating-your-serverless-website-in-terraform-part-2/"/>
      <updated>2018-04-14T06:11:14Z</updated>
      <id>https://intricatecloud.io/2018/04/creating-your-serverless-website-in-terraform-part-2/</id>
      <content type="html">
        <![CDATA[
      <h3>Let's review the architecture we put together in Part 1.</h3>
<p><a href="https://intricatecloud.wpengine.com/2018/04/creating-a-serverless-static-website/">Check out Part 1</a></p>
<ul>
<li>a main website that is hosted at a <code>www.</code> subdomain</li>
<li>an html/js/css app that is hosted in an S3 bucket</li>
<li>a cloudfront distribution to give us an <code>https</code> URL</li>
<li>a certificate that will be used with the cloudfront distribution for that domain</li>
<li>a series of vanity URLs that will redirect to the main website</li>
</ul>
<h2>Let's start building some terraform</h2>
<h3>create your project directory</h3>
<ul>
<li>you need a main.tf to hold all your resources</li>
<li>you need a variables.tf to hold your inputs to your infrastructure</li>
<li>you need a var-file to hold the values of your inputs to your infrastructure</li>
</ul>
<h3>create your initial tf files</h3>
<p>The main website we want to build is www.example.com.</p>
<pre><code>resource &quot;aws_s3_bucket&quot; &quot;logs&quot; {
  bucket = &quot;${var.site_name}-site-logs&quot;
  acl = &quot;log-delivery-write&quot;
}

resource &quot;aws_s3_bucket&quot; &quot;www_site&quot; {
  bucket = &quot;www.${var.site_name}&quot;

  logging {
    target_bucket = &quot;${aws_s3_bucket.logs.bucket}&quot;
    target_prefix = &quot;www.${var.site_name}/&quot;
  }

  website {
    index_document = &quot;index.html&quot;
  }
}
</code></pre>
<p>variables.tf</p>
<pre><code>variable &quot;site_name&quot; {
  description = &quot;My site&quot;
}
</code></pre>
<p>var-files/my-site.tf</p>
<pre><code>site_name = &quot;mysite.com&quot;
</code></pre>
<p>I believe we learn best when we see how things break! And there's an opportunity to break things here.</p>
<h3>Why put access logs in a separate bucket?</h3>
<p>Let's make things &quot;easier&quot; by putting access logs in the same bucket as the website. Why should I need 2 buckets?</p>
<pre><code>resource &quot;aws_s3_bucket&quot; &quot;www_site&quot; {
  bucket = &quot;www.${var.site_name}&quot;

  logging {
    target_bucket = &quot;www.${var.site_name}&quot;
  }

  website {
    index_document = &quot;index.html&quot;
  }
}
</code></pre>
<p>Plan and apply your terraform, then visit your website directly via the S3 website URL. Refresh the page a few times and then look at the contents of your S3 bucket.</p>
<ul>
<li>Your access logs are now also public because they can be accessed via your website URL.</li>
<li>Your access logs have lengthy and obscure file names and they are clobbering your directory. If you deploy using <code>aws s3 sync</code> to your bucket, you'll wind up deleting your access logs every time you deploy.</li>
<li>Wait 10 minutes and review the activity in your access logs. You'll notice that the act of creating a file containing your access logs will create an access log of the act of creating a file containing your access logs, and etc, etc, etc.</li>
</ul>
<p>I've done it before. I was so confused. Use a separate bucket.</p>
<h3>Create your certificate</h3>
<p>We need a certificate which we can get for free via ACM, but there's a catch</p>
<pre><code>resource &quot;aws_acm_certificate&quot; &quot;cert&quot; {
  domain = &quot;www.example.com&quot;
}
</code></pre>
<p>Applying this terraform resource will begin the act of requesting a certificate, but the certificate needs to be approved and created out-of-band. There's 2 ways of handling this scenario</p>
<ul>
<li>Don't manage the certificate with terraform. Create it manually and drop the ARN of the certificate wherever you need to use it.</li>
<li>Manage the certificate with terraform, but create it manually and then use <code>terraform import</code> to import the resource AFTER its been approved and created.</li>
</ul>
<p>Now we can create the Cloudfront distribution and finish connecting the pieces together:</p>
<pre><code>resource &quot;aws_cloudfront_origin_access_identity&quot; &quot;origin_access_identity&quot; {
  comment = &quot;cloudfront origin access identity&quot;
}

resource &quot;aws_cloudfront_distribution&quot; &quot;website_cdn&quot; {
  enabled      = true
  price_class  = &quot;PriceClass_200&quot;
  http_version = &quot;http1.1&quot;
  aliases = [&quot;www.${var.site_name}&quot;]

  origin {
    origin_id   = &quot;origin-bucket-${aws_s3_bucket.www_site.id}&quot;
    domain_name = &quot;www.${var.site_name}.s3.us-east-2.amazonaws.com&quot;

    s3_origin_config {
      origin_access_identity = &quot;${aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path}&quot;
    }
  }

  default_root_object = &quot;index.html&quot;

  default_cache_behavior {
    allowed_methods = [&quot;GET&quot;, &quot;HEAD&quot;]
    cached_methods  = [&quot;GET&quot;, &quot;HEAD&quot;]
    target_origin_id = &quot;origin-bucket-${aws_s3_bucket.www_site.id}&quot;

    min_ttl          = &quot;0&quot;
    default_ttl      = &quot;300&quot;                                              //3600
    max_ttl          = &quot;1200&quot;                                             //86400

    // This redirects any HTTP request to HTTPS. Security first!
    viewer_protocol_policy = &quot;redirect-to-https&quot;
    compress               = true

    forwarded_values {
      query_string = false

      cookies {
        forward = &quot;none&quot;
      }
    }
  }

  restrictions {
    geo_restriction {
      restriction_type = &quot;none&quot;
    }
  }

  viewer_certificate {
    acm_certificate_arn      = &quot;${aws_acm_certificate.cert.arn}&quot;
    ssl_support_method       = &quot;sni-only&quot;
  }

}
</code></pre>
<p>A few important items here</p>
<ul>
<li><code>aws_cloudfront_origin_access_identity</code> will create and manage that for you in terraform. You can either create and share an origin access identity across multiple distributions, or you can use one origin access identity per distribution.</li>
<li>A viewer protocol policy <code>redirect-http-to-https</code> to enforce <code>https</code> to site visitors</li>
</ul>
<h3>Lock down the S3 bucket</h3>
<p>The bucket policy lets us define the security on the bucket. In this scenario, we can keep the bucket private and only accessible by Cloudfront by using an Origin Access Identity. We can express this in the bucket policy:</p>
<p>main.tf</p>
<pre><code>data &quot;template_file&quot; &quot;bucket_policy&quot; {
  template = &quot;${file(&quot;bucket_policy.json&quot;)}
  vars {
  	origin_access_identity_arn = &quot;${aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path}&quot;
  	bucket = &quot;${aws_s3_bucket.www_site.arn}&quot;
  }
}

resource &quot;aws_s3_bucket&quot; &quot;www_site&quot; {
  bucket = &quot;www.${var.site_name}&quot;
  policy = &quot;${data.template_file.bucket_policy.rendered}&quot;
  ...
}
</code></pre>
<p>bucket_policy.json</p>
<pre><code>{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;OnlyCloudfrontReadAccess&quot;,
      &quot;Principal&quot;: {
        &quot;AWS&quot;: &quot;${origin_access_identity_arn}&quot;
      },
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;s3:GetObject&quot;
      ],
      &quot;Resource&quot;: &quot;arn:aws:s3:::${bucket}/*&quot;
    }
  ]
}
</code></pre>
<h3>Create the DNS record</h3>
<p>main.tf</p>
<pre><code>resource &quot;aws_route53_record&quot; &quot;www_site&quot; {
  zone_id = &quot;${data.aws_route53_zone.site.zone_id}&quot;
  name = &quot;www.${var.site_name}&quot;
  type = &quot;A&quot;
  alias {
    name = &quot;${aws_cloudfront_distribution.website_cdn.domain_name}&quot;
    zone_id  = &quot;${aws_cloudfront_distribution.website_cdn.hosted_zone_id}&quot;
    evaluate_target_health = false
  }
}
</code></pre>
<p>Apply your terraform and test it out.</p>
<h2>Writing automated tests for your infrastructure</h2>
<p>Like any programming project, you want to make sure you have tests for your code. But we're writing terraform and there's no test runner for terraform, or is there?</p>
<p>All we need to be able to test our terraform is just any language that lets us write web requests. We'll create 2 sets of tests to make sure everything is working for mysite.com</p>
<h3>Create the tests to verify your bucket configuration</h3>
<p>s3_bucket_spec.rb</p>
<pre><code>require 'serverspec'

context 's3 bucket' do

  describe command('aws s3 ls s3://dperez-test-bucket') do
    its(:stdout) { should_match /Access Denied/ }
  end

  describe command('curl -i https://s3.amazonaws.com/dperez-test-bucket') do
    its(:stdout) { should_match /Access Denied/ }
  end
end

context 'cloudfront' do
  describe command('curl -i https://www.mysite.com') do
    its(:stdout) { should_match /200 OK/ }
  end

  describe command('curl -i http://www.mysite.com') do
    its(:stdout) { should_match /301 Redirect/ }
  end
end
</code></pre>
<p>Run the tests with rspec to validate the infrastructure you just created.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Gotcha&#39;s with Cloudfront + S3 Architecture</title>
      <link href="https://intricatecloud.io/2018/04/gotchas-with-cloudfront-s3-architecture/"/>
      <updated>2018-04-14T06:11:42Z</updated>
      <id>https://intricatecloud.io/2018/04/gotchas-with-cloudfront-s3-architecture/</id>
      <content type="html">
        <![CDATA[
      <p>Right now, we have a website hosted in an S3 bucket using CloudFront defined in terraform running with automated tests. There's a few catches with using this setup so we'll go into what these issues are and how to work around them.</p>
<h3>the lifecycle of the cache</h3>
<p>There's a few different ways to configure TTLs on CloudFront</p>
<ul>
<li>Use a TTL of 0</li>
</ul>
<p>Control the cache at your Origin. <a href="https://stackoverflow.com/questions/10621099/what-is-a-ttl-0-in-cloudfront-useful-for">See this Stack Overflow Question - What is a TTL 0 in Cloudfront Useful for</a>. S3 has that behavior where if an object has not been modified and it has been requested recently, S3 will return a 304 Not Modified response and Cloudfront will continue to use the cached version until S3 says it should update.</p>
<ul>
<li>Use a non-zero TTL</li>
</ul>
<p>Let Cloudfront be in charge of the cache. Things will stay cached in Cloudfront for the duration of your TTL, but <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/HTTPStatusCodes.html">see here</a> for what the different TTLs you have available are.</p>
<p>Use Cache Invalidation to bust the cache when you need it. You can invalidate the whole distribution or just a subpath, e.g. /blog. This takes time to propagate as the cache needs to be invalidated at multiple regional locations.</p>
<h3>S3 has outages too</h3>
<p>S3 has had at least one outage a year, and while they are actively monitoring their services and work hard to bring it back as soon as possible, there's always the chance that your website becomes inaccessible due to issues with S3. If your website brings in sales, then any outage on S3 costs you money. So how do you handle S3 outages?</p>
<ul>
<li>Figure out what type of SLA you need to have for your website. You can rely on Cloudfront's cache to give you a window of time before you need to act. For example, if S3 goes down, but your content is cached on Cloudfront, then you can configure it to continue serving content if the origin dies. <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/HTTPStatusCodes.html">See this AWS doc</a> for a more in-depth description to make sure you're still up when S3 is down.</li>
<li>Leverage S3 Bucket Replication. This is a feature of S3 that will propagate changes to a bucket in another region. This alone is not enough to be resilient as there's a whole slew of limitations of S3 & Cloudfront that get in the way 
<ul>
<li>Buckets must have the same name as the domain you want to use to access it. If your website is imcool.com, your bucket's name must be imcool.com. This means that you can't have imcool.com in us-east-1 AND us-east-2. </li>
<li>If you name your buckets imcool-us-east-1.com and imcool-us-east-2.com, then you won't be able to access them with your 1 domain name imcool.com.</li>
<li>A custom domain can only be associated with one Cloudfront distribution at a time. You cannot have Cloudfront-us-east-1 & Cloudfront-us-east-2 both with imcool.com as an alternate CNAME.</li>
<li>You can define multiple origins for your distribution but you can only control where certain paths like <code>/assets</code> or <code>/blog</code> get directed to which origins. So <code>/assets</code> should request from my assets origin, while <code>/blog</code> should request from the site origin. But you cannot define something like <code>/*</code> should request from s3-origin-us-east-1 AND (if that is down) <code>/*</code> should request from s3-origin-us-east-2.</li>
<li>Other solutions around the internet</li>
<li>There is this solution posted by <a href="https://read.iopipe.com/multi-region-s3-failover-w-route53-64ff2357aa30">IOpipe involving Cloudfront + R53 Health Checks + S3</a>. The downside here is that if you rely on S3 as a website, it can't reply to HTTPS requests so failover would downgrade you from HTTPS to HTTP.</li>
<li>Multi-region load balancers in front of multi-region s3 buckets with cloudfront suggest in a <a href="https://stackoverflow.com/questions/42545776/cloudfront-cdn-for-s3-bucket-which-is-cross-region-replicated">stack overflow answer</a></li>
<li>A pretty good <a href="https://www.reddit.com/r/aws/comments/5wqajg/cloudfront_with_redundant_s3_origins/">discussion on /r/aws</a> on possibilities and pros/cons of each.</li>
</ul></li>
</ul>
<h3>Setting custom headers</h3>
<p>S3 lets you configure <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html#RESTObjectPUT-requests">various headers</a> like cache-control, content-md5 depending on what you want to do with your content. However you are limited to what headers you can set so it doesn't support adding things like X-Frame-Options, or Content-Security-Policy with just S3.</p>
<h3>Configure Lambda@Edge to add security headers</h3>
<p>Use Lambda@Edge to manipulate the response coming out of Cloudfront. Cloudfront is now offering different hooks where you can attach a Lambda like when receiving a request, or sending a response - <a href="https://aws.amazon.com/blogs/networking-and-content-delivery/adding-http-security-headers-using-lambdaedge-and-amazon-cloudfront/">see here for their walkthrough for setting this up</a>. You can create a simple Lambda that looks at the response payload sent from the origin back to cloudfront and add whatever headers you'd like to include in the response. You can either set them globally, or apply them conditionally based on context using something like this</p>
<pre><code>'use strict';
exports.handler = (event, context, callback) => {

    //Get contents of response
    const response = event.Records[0].cf.response;
    response.headers['strict-transport-security'] = [{key: 'Strict-Transport-Security', value: 'max-age= 63072000; includeSubdomains; preload'}]; 
    callback(null, response);
}
</code></pre>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Getting started with chef cookbooks as an app dev with test-kitchen + docker, part 1</title>
      <link href="https://intricatecloud.io/2018/04/cookbook-testing-with-test-kitchen-chefspec-serverspec-part-1/"/>
      <updated>2018-04-26T10:00:29Z</updated>
      <id>https://intricatecloud.io/2018/04/cookbook-testing-with-test-kitchen-chefspec-serverspec-part-1/</id>
      <content type="html">
        <![CDATA[
      <h2>How do you get started with chef cookbooks as an app dev?</h2>
<p>My first time approaching Chef was after doing Android for about 2 years. Whether you're working on the front-end or backend or even on Mobile, the overall workflow of your application is the same.</p>
<ul>
<li>Make changes to your application locally</li>
<li>Write unit and/or integration tests to verify your changes</li>
<li>Deploy &amp; verify your changes to a test environment</li>
<li>Deploy &amp; verify your changes to a prod environment</li>
</ul>
<p>The setup that I first worked with wasn't too well aligned with that. Feedback loops involved deploying cookbooks to chef-server and re-converging instances in a live environment. Process prevented untested code from making it to production, but it did happen.</p>
<p>The following is my attempt at creating a quick feedback loop when working with cookbooks that makes it easy to approach as an app developer.</p>
<h2>Create the source files for your cookbook</h2>
<p>You want to create a hello world cookbook to configure a server and then verify that it works.</p>
<p>metadata.rb</p>
<pre><code>name &quot;helloworld&quot;
maintainer       &quot;me&quot;
maintainer_email &quot;me@me&quot;
license          &quot;Apache 2.0&quot;
description      &quot;helloworld&quot;
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version          &quot;0.0.1&quot;
</code></pre>
<p>README.md</p>
<pre><code>hello world cookbook
</code></pre>
<p>recipes/default.rb</p>
<pre><code>:;Chef::Log.error 'Hello World'
</code></pre>
<h2>&quot;Build&quot; a cookbook</h2>
<p>Enter test-kitchen. It helps you create different types of machines, and execute scripts and tests to verify your configuration.</p>
<p>We can start by creating a .kitchen.yml file containing the configuration we need to get going.</p>
<p>.kitchen.yml</p>
<pre><code>---
driver:
  name: docker

provisioner:
  name: chef_zero
  require_chef_omnibus: latest

platforms:
- name: trusty
  driver_config:
    image: ubuntu:14.04
    platform: ubuntu

suites:
  - name: default
    run_list:
      - &quot;recipe[helloworld::default]&quot;
</code></pre>
<p>In this file, we have</p>
<ul>
<li>configured the Docker driver. test-kitchen will create a docker container, SSH into it, and execute your test suite. The driver configuration here + the docker image ubuntu-upstart is necessary to work around known issues with docker and upstart/systemd.</li>
<li>defined your chef context - this could be chef_zero or chef-solo, they're essentially the same except chef_zero pretends to be a chef-server, whereas chef-solo knows he's not.</li>
<li>defined the platform we want to run on. for the sake of example, we'll run ubuntu 14.04 from the docker registry</li>
<li>defined the test suite</li>
</ul>
<p>We're ready to run this through kitchen now. Add a Gemfile.</p>
<p>Gemfile</p>
<pre><code>source 'https://rubygems.org'

gem 'test-kitchen'
gem 'kitchen-docker'
gem 'serverspec'
</code></pre>
<p>Run <code>bundle install</code> and then <code>bundle exec kitchen converge</code>. Test kitchen will now start the process of creating a container from your chosen image, it will SSH in, and install the chosen version of chef, and then finally run the recipe you defined in the test suite.</p>
<h2>Test your cookbook</h2>
<p>You can consider converging to be the equivalent to &quot;running&quot; or &quot;building&quot; a cookbook. If you have a successful converge and you see your Log message &quot;helloworld&quot;, then congrats - you've got a hello world cookbook that you can test like any other application. You've created a project, you ran/built it, and you verified your changes.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Testing your cookbooks with test-kitchen + chefspec + serverspec, part 2</title>
      <link href="https://intricatecloud.io/2018/05/cookbook-testing-with-test-kitchen-chefspec-serverspec-part-2/"/>
      <updated>2018-05-17T12:00:58Z</updated>
      <id>https://intricatecloud.io/2018/05/cookbook-testing-with-test-kitchen-chefspec-serverspec-part-2/</id>
      <content type="html">
        <![CDATA[
      <p>Cookbooks can also get the same red, green, refactor loop going the same way you would with a simple application. It's not all roses though because the loop itself takes a few minutes at a time. Even with a simple cookbook, you're looking at 3-4 minutes each run if you're constantly testing using converges.</p>
<p>In terms of the fastest feedback loops, you should:</p>
<ul>
<li>Use Chefspec to test that you're telling chef the correct things to do. These will run in a few seconds.</li>
<li>Use Serverspec that chef did what you told it to. This will take 3-4 minutes using a container.</li>
</ul>
<h2>Adding Chefspec</h2>
<p>spec/spec_helper.rb</p>
<pre><code>require 'chefspec'

RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.syntax = :expect
  end

  config.platform = 'ubuntu'
  config.version = '14.04'
end
</code></pre>
<p>spec/recipes/default_spec.rb</p>
<pre><code>require 'spec_helper'

describe 'hello-world-cookbook::default' do
  let(:chef_run) {
    ChefSpec::SoloRunner.new.converge(described_recipe)
  }

  it 'installs a file' do
    expect(chef_run).to create_file('/tmp/foobar')
  end
end

</code></pre>
<p>Run <code>bundle exec rspec spec</code> to start executing Chefspec tests. You'll see the failing test. Fix it by updating <code>recipes/default.rb</code> to create a file with content and watch it go green.</p>
<h3>How not to use ChefSpec</h3>
<pre><code>it 'creates file' do
  expect(File.new('/tmp/foobar').exists?).to be_truthy
end
</code></pre>
<p>This won't work because the chef run only happens in memory as part of chefspec. No changes are actually made to the system. If you're running rspec from your terminal, then the chef run happens in memory on your system on your current OS. When you want to do this type of testing, it's easier to test this stuff in Serverspec.</p>
<h2>Adding Serverspec</h2>
<p>Add the following files</p>
<p>test/integration/default/serverspec/spec_helper.rb</p>
<pre><code>require 'serverspec'

set :backend, :exec
</code></pre>
<p>test/integration/default/serverspec/default_spec.rb</p>
<pre><code>require 'spec_helper'

describe file('/tmp/foobar') do
  it { should be_readable.by_user('root') }
  it { should contain('foo')}
end
</code></pre>
<p>Now run <code>bundle exec kitchen verify</code>. test-kitchen will update the container with the new cookbook, execute the cookbook inside the container, upload the test runner inside the container, and then execute the tests. This is a great way to test modifications to the system like if files exist with the correct permissions.</p>
<h2>Lint your cookbook</h2>
<p>Add rubocop &amp; foodcritic to your project by adding it to your Gemfile,</p>
<pre><code>gem 'rubocop'
gem 'foodcritic'
</code></pre>
<p>Rubocop will sniff out smelly parts of your ruby. It occasionally gives you good advice and can be helpful to keep consistent styling. It does need to be tuned lest it constantly complain about long lines.</p>
<p>Run 'bundle exec rubocop' to see what it complains about out of the box. Things like <code>Style/FrozenStringLiteralComment</code> are just not useful.</p>
<p>Foodcritic will look for Chef specific errors in your cookbook. Like if you meant to render a template but forgot to include the template file.</p>
<p>Some of the stuff it complains about I don't care for, so you can always ignore some by adding <code>--tags ~RULE#</code> to your command. You can see what it complains about out of the box by running <code>bundle exec foodcritic ./</code>.</p>
<p>Most of its initial complaints are about fields you should have in your metadata to be a good Chef citizen publishing open-source cookbooks. If you're using them in a private setting, then its just boilerplate.</p>
<p>You've now got an entire development flow for your configuration.</p>
<ul>
<li>Build the cookbook by running <code>kitchen converge</code></li>
<li>Unit test your Chef logic by running <code>rspec spec</code> with Chefspec</li>
<li>Run an Integration Test by actually running Chef on the target container</li>
<li>Lint your cookbook to keep it close to ruby best practices and to catch obvious Chef errors.</li>
</ul>
<p>Stay tuned for Part 3 where we tie this into a CI workflow.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Troubleshooting SSL issues</title>
      <link href="https://intricatecloud.io/2018/05/troubleshooting-ssl-issues/"/>
      <updated>2018-05-22T11:00:30Z</updated>
      <id>https://intricatecloud.io/2018/05/troubleshooting-ssl-issues/</id>
      <content type="html">
        <![CDATA[
      <p>This is a pretty typical nightmare scenario. An engineer suddenly quit and I was part of a new team that was maintaining a brittle system with little documentation. Someone pings me and says, hey this website isn't working anymore - it says the cert's invalid! halp!</p>
<p>Debugging SSL issues are tough, especially when I hadn't dealt with them before and no one on the team was too familiar with our setup. I understood that using SSL certificates gives you the green Secure lock in Chrome, but that's about it. All the existing tooling assumes some level of knowledge about what certificates are and how they work. I guess that's fair - but why can't it be noob friendly.</p>
<p>Anyway, here's what I've wound up learning to trace down certificate errors. If nothing else, this is the one website that has everything you need - https://www.sslshopper.com/article-most-common-openssl-commands.html. Judging by the page ranking, I'm sure that website has helped a fair number of people get started with SSL issues.</p>
<h3>Finding information about your current certificate</h3>
<p>Going to Chrome helps you see certificate information if you're dealing with a web app, but where Chrome fails, you need to view things via the CLI. In my case, I was in the middle of setting up a proof-of-concept setup of the Logstash in the Elasticsearch/Logstash/Kibana centralized logging setup. If you want encrypted communication between your servers and logstash, you need to configure it to use a certificate on a specific port.</p>
<p>To get your website's certificate: <code>openssl s_client -connect my.website.com:443</code> or in logstash's case <code>openssl s_client -connect logstash.example.com:5044</code>
At the bottom of the output, you'll see whether the certificate checks out and if not, for what reason. Common reasons to see a certificate error</p>
<ul>
<li>hostname mismatch. The hostname on the certificate is for foo.bar.com, but you are bar.bar.com.</li>
<li>expired</li>
<li>self-signed certificate for a public-facing website</li>
</ul>
<h3>Getting new certificates</h3>
<p>This used to be bad news. Before Let's Encrypt came along, this happened to me within my first month or two in a new company and this meant that we had a multi-day turnaround &amp; a $1-2k sticker price for a signed certificate from a public certificate authority. I had to take this path for one of our certs, but fortunately, it was a pre-prod environment so customers didn't incur any downtime.</p>
<p>We had a folder containing a bunch of private keys and public keys, but we couldn't tell which private keys were used to sign which certificates. Using the commands below, you can use the md5's of all pieces of the certificate to verify that they all belong to the same certificate.</p>
<pre><code>openssl x509 -noout -modulus -in certificate.crt | openssl md5
openssl rsa -noout -modulus -in i-think-its-this.key | openssl md5
</code></pre>
<p>If you're on AWS and using some of their services anyway, use the Amazon Certificate Manager. It gives you the same workflow as Let's Encrypt, except you can use the certificates on AWS services. You get free certificates that auto-renew.</p>
<h2>Find out who's presenting the certificate</h2>
<p>Different places you could find a certificate being presented on AWS:</p>
<ul>
<li>A Cloudfront distribution may be presenting a *.cloudfront.net certificate, or if using custom domains, a certificate that you've uploaded to AWS</li>
<li>Elastic Load Balancers can present a certificate via HTTPS ports and either
<ul>
<li>terminate SSL at the load balancer and send plain text to the backend instance: <code>openssl s_client -connect myservice-xxxx.ec2.amazonaws.com</code></li>
<li>terminate SSL and then validate a different certificate on the backend: <code>openssl s_client -connect 10.0.0.10:443</code></li>
<li>pass the connection via TCP straight to the backend instance and let the instance present the certificate: <code>openssl s_client -connect 10.0.0.10:5000</code></li>
</ul>
</li>
</ul>
<p>You can upload your own certificates to IAM. There's no UI for this feature of IAM certificates. You can only upload and browse them via the CLI.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Building a blog with Gatsby</title>
      <link href="https://intricatecloud.io/2018/06/part-1-build-test-deploy-and-monitor-a-static-website-building-a-blog-with-gatsby/"/>
      <updated>2018-06-10T19:38:44Z</updated>
      <id>https://intricatecloud.io/2018/06/part-1-build-test-deploy-and-monitor-a-static-website-building-a-blog-with-gatsby/</id>
      <content type="html">
        <![CDATA[
      <h2>Create a blog using HTML and JS</h2>
<p>In this section, we're going to set up our new Gatsby project as a blog, add a few posts, and get a blog working locally.</p>
<p>Pre-requisites:</p>
<ul>
<li>node v8</li>
<li>npm</li>
<li>git</li>
</ul>
<p>You should be familiar with html, javascript, npm, and the command line. I won't go too far into details about using gatsby - here, I just want to get something half-decent looking on a live environment first. Knowing react may help you extend Gatsby, but you could also get by if you poke around. For the purpose of this tutorial, I don't want to include anything else other than JS.</p>
<h4>Get started</h4>
<p>Start by installing the gatsby cli and creating a new gatsby project</p>
<pre><code>npm install --global gatsby-cli
gatsby new dannyperez-blog https://github.com/gatsbyjs/gatsby-starter-blog
cd dannyperez-blog
</code></pre>
<h4>Gatsby Starter packs</h4>
<p><a href="https://www.gatsbyjs.org/docs/gatsby-starters/">Check out the full listing of starter packs here</a></p>
<p>Gatsby gives you a way of setting up your website using open-source templates. And they have a bunch of different options on that page for different use cases. These are the ones focused on blogs</p>
<ul>
<li><a href="https://github.com/gatsbyjs/gatsby-starter-blog">gatsby-starter-blog</a>
<a href="http://gatsbyjs.github.io/gatsby-starter-blog/">(demo)</a></li>
<li><a href="https://github.com/noahg/gatsby-starter-blog-no-styles">gatsby-starter-blog-no-styles</a>
<a href="http://capricious-spring.surge.sh/">(demo)</a></li>
<li><a href="https://github.com/dschau/gatsby-blog-starter-kit">gatsby-blog-starter-kit</a>
<a href="https://dschau.github.io/gatsby-blog-starter-kit/">(demo)</a></li>
<li><a href="https://github.com/100ideas/glitch-gatsby-starter-blog/">glitch-gatsby-starter-blog</a> <a href="https://gatsby-starter-blog.glitch.me">(demo)</a></li>
<li><a href="https://github.com/greglobinski/gatsby-starter-personal-blog">gatsby-starter-personal-blog</a>
<a href="https://gatsby-starter-personal-blog.greglobinski.com/">(demo)</a></li>
<li><a href="https://github.com/davad/gatsby-hampton-theme">gatsby-hampton-theme</a>
<a href="http://dmwl.net/gatsby-hampton-theme">(demo)</a></li>
<li><a href="https://github.com/LeKoArts/gatsby-starter-minimal-blog">gatsby-starter-minimal-blog</a>
<a href="https://minimal-blog.netlify.com/">(demo)</a></li>
<li><a href="https://github.com/wonism/gatsby-advanced-blog">gatsby-advanced-blog</a>
<a href="https://wonism.github.io/">(demo)</a></li>
<li><a href="https://github.com/greglobinski/gatsby-starter-hero-blog">gatsby-starter-hero-blog</a>
<a href="https://gatsby-starter-hero-blog.greglobinski.com/">(demo)</a></li>
</ul>
<p>To get started, we only need the gatsby-cli in this project, and only in development. In this example, I'm going to use <code>gatsby-starter-blog</code></p>
<h4>Make a checkpoint with git</h4>
<pre><code>npm install --save-dev gatsby-cli

git init
git add .
git commit -m&quot;Initial commit&quot;

gatsby develop
</code></pre>
<p>You should now be able to view your website on localhost:8000</p>
<h4>Make some changes</h4>
<p>Start personalizing it</p>
<ul>
<li>Update <code>components/header.js</code> to update the name of the site as it appears in the header</li>
<li>Change to YOUR bio</li>
<li>Add your first blog post by creating a new file with a title
<ul>
<li>The folder name under pages will be the URL that users see in the browser</li>
<li>The <code>title:</code> element in <code>index.md</code> is the title that appears in the list of posts, and the header of your blog post page</li>
</ul>
</li>
</ul>
<p>When you're finally done:</p>
<pre><code>git add .
git commit -m &quot;Personalized the site&quot;
</code></pre>
<p>Remember:</p>
<ul>
<li><code>gatsby develop</code> to see the site live on localhost:8000</li>
<li><code>gatsby build</code> when you want to prepare your site for publishing, and store it in <code>public/</code></li>
<li><code>gatsby serve</code> when you want to see the prepared site in <code>public/</code> on localhost:9000</li>
</ul>
<h4>Build the project</h4>
<pre><code>gatsby build
</code></pre>
<p>This will compile the project since Gatsby uses React. All the code will be generated and dropped into <code>public/</code> for you. You'll see that <code>public</code> is in your <code>.gitignore</code> since gatsby will be generating all those files for you automatically.</p>
<p>If you run <code>gatsby serve</code>, you can see your built project - which should look identical to your project served by doing <code>gatsby develop</code></p>
<h3>Review</h3>
<p>At this point, you have a node project on your computer. You can develop locally and make changes to your website. And you have a way of creating a &quot;built&quot; version of your website that you can then put on whatever host you want.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Is it worth the money for a beginner to play around with deploying apps on hosts like AWS, DigitalOcean?</title>
      <link href="https://intricatecloud.io/2018/09/is-it-worth-the-money-for-a-beginner-to-play-around-with-deploying-apps/"/>
      <updated>2018-09-17T03:47:07Z</updated>
      <id>https://intricatecloud.io/2018/09/is-it-worth-the-money-for-a-beginner-to-play-around-with-deploying-apps/</id>
      <content type="html">
        <![CDATA[
      <p>Yes! Absolutely. It'll be the only way for you to really know whats going on behind the scenes. When I got started, my first few expenses of my tech career were a domain, a private non-gmail email inbox, and a wordpress website via Bluehost, ...and some business cards. It must have cost me $100 for the first year which was mainly the domain + bluehost costs.</p>
<p>I was running a wordpress blog with Bluehost where I shared things that I learned during my college CS classes. As I started learning how to write and serve java + ruby projects, I wanted to make them public, but couldn't run them off my laptop which was my one and only machine. I was also getting limited by what I could do via Bluehost - either the plan I was on didn't give me direct SSH access, or I didn't know what SSH was at the time and I was used to admin'ing with cPanel and couldn't think of how you install ruby via cPanel.</p>
<p>I deployed my next few apps via Heroku, CenturyLink (fka AppFog), and AWS. They get more expensive as the tech gets more and more sophisticated, but the apps I was writing also needed databases and both Heroku + CenturyLink made it easy to link a database to your application. It seemed like a worthwhile expense and I focused on learning those in order to get my applications deployed.</p>
<p>If you want to keep costs low, DigitalOcean and other VPS hosts like Bluehost can give you a server with a public IP address for ~5/mo. Server costs would run you ~60/yr, not a bad investment for learning. You can always run a server out of your home (like a Mac Mini running in your living room). Although it can be a very good lesson in how to set up your network so that requests to your IP can arrive at your home server, you also expose your personal home network to potential hackers - FYI. Once you want to have multiple applications running at once, then it'll be worth it to learn about other hosting providers like AWS and GCP, along with a bit of a bigger sticker price for more reliable infrastructure.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>4 things I&#39;m looking for when hiring a junior DevOps engineer</title>
      <link href="https://intricatecloud.io/2018/09/4-things-im-looking-for-when-hiring-a-junior-devops-engineer/"/>
      <updated>2018-09-24T05:41:30Z</updated>
      <id>https://intricatecloud.io/2018/09/4-things-im-looking-for-when-hiring-a-junior-devops-engineer/</id>
      <content type="html">
        <![CDATA[
      <p>You've been hearing about DevOps as a career path, and you're looking to to break into the field as a Jr DevOps engineer, but you don't quite know if you're qualified for it yet. You might be a recent grad, or maybe you've been in the industry a few years and you want to improve your skills. How do you know whether you need another 3 months to get some better experience under your belt, or whether you should have applied for that one DevOps job last month but didn't because you thought you still had more to learn.</p>
<p>I currently work as an engineering manager at an ed-tech company focused on helping our teams ship their applications (APIs, websites) quickly and safely. Our DevOps engineers are responsible for building the tools that teams use to deploy and operate their apps in production hosted on AWS. Our team recently hired 2 junior devops engineers and a few interns so I wanted to share a few things that made some candidates stand out over others.</p>
<p>*Your mileage may vary, and different people and companies may have different expectations around a junior devops engineer based on how they've implemented their role. Here's one more data point.</p>
<h2>1. You've deployed a website or one of your own projects, and can talk about it</h2>
<p>Examples: You've built an Android app, a website using ruby on rails,  express for node, flask for python, react for the front-end, an npm package. It's incredibly helpful for you to have experienced more than one.</p>
<p>You've got some projects under your belt whether they're tinkering on your own, part of your grad course, or at your current job. Building and hosting a blog counts, but I'd expect to see one or two other projects that aren't a blog. I also expect you to be to talk at length about it - please tell me about that HTTP 500 error you spent 4 hours chasing down. I honestly love hearing a good memory leak story.</p>
<p>The main thing I'm looking for in this respect is that you've gone through the full lifecycle of building a package, testing to make sure it works, releasing it, and then monitoring it to see if did what you expected. Always be shipping!</p>
<h2>2. You know what its like to work as part of a team and what its like to interface between different people/teams to get something done.</h2>
<p>This one isn't quite so specific to DevOps but I think becomes a bit more important. You've worked in teams and people like to work with you and you like to work with people. There's no room for egos or jerks, and it might be hard to judge in an interview (there's even a Quora thread specifically around <a href="https://www.quora.com/How-do-you-find-out-if-someone-is-a-jerk-before-you-hire-them-I-want-someone-who-isn%E2%80%99t-too-abrasive-competitive-without-being-cutthroat-and-who-is-able-to-represent-the-company-well">weeding out jerks</a>). I mainly want to see that you've been able to deal with differing personalities on projects and how it may or may not have affected your success.</p>
<p>One helpful characteristic that I've seen from non-jerks is the willingness to teach and help someone else through their problem. For example, one engineer worked as a CS tutor and then put together a Slack channel where students could help each other on some school projects. Another engineer started working with another employee in the company guiding them through building features to automate some painful aspects of their job.</p>
<h2>3. You've worked on multiple different projects and not the same project multiple times</h2>
<p>Over the span of a few resumes, there were a handful of candidates that stuck out - for the wrong reasons.</p>
<ul>
<li>I had asked one engineer why he chose Jenkins for a particular solution and they responded by saying it was the only system he knew. I expect you to know of a few alternatives to your favorite tools and when you would recommend one over the other. If you're a fan of Jenkins, I'd expect you to know when to recommend Jenkins over Travis/CircleCI/Insert-CI-Tool-Here.</li>
<li>Another trend was that of an engineer (not a contractor) working on a team that worked on deployment pipelines using the same tools in 6-month to 1-year stints at multiple companies. If all you have is a hammer, everything looks like a nail. After the 3rd instance of it on a resume, I wonder whether they're really good at using the hammer, or if everything just looks like a nail.</li>
</ul>
<p>I'd expect a good candidate to have a curious mind and work on different types of projects over time. Maybe you like doing some Unity game development in your spare time, or you've been dabbling in data analytics or machine learning projects. Some candidates even have a handful of robotics projects. Not all our projects see life in production, but we still learned a good deal while figuring that out. In a DevOps role, you'll see applications of different shapes, sizes, and color so its helpful to have seen some of that variety on your own before you see it at a new job.</p>
<h2>4. You've used AWS (or other cloud provider at least) in some capacity for your projects</h2>
<p>You've played around with some of what AWS has had to offer. You may have set up a website using S3, you've managed to get an application running on Elastic Beanstalk, or a nodejs backend hosted on EC2. For me, I had started learning with EC2 and Chef (if I would do it over again, I would start with S3). If you haven't and all you've used is a hosted VPS, try migrating it to AWS - its a great place to start. A few candidates had built pet projects using S3 + RDS - that's great to see.</p>
<p>If you've got some of these characteristics, you're in great shape - stop thinking twice about applying for that job and take it. If you feel you're a little short, you've got some direction with where to focus whether its <a href="https://intricatecloud.wpengine.com/2018/04/creating-a-serverless-static-website/">building an app and deploying it to AWS S3</a>, working on a <a href="https://intricatecloud.wpengine.com/2018/04/cookbook-testing-with-test-kitchen-chefspec-serverspec-part-1/">different type of project like chef cookbooks</a>, or working with other people on your projects. Agree or disagree? How do you know if you've got enough experience to get into a DevOps gig? Let me know in the comments.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Simple automated testing for a static website with Nightwatch.js</title>
      <link href="https://intricatecloud.io/2018/09/simple-automated-testing-for-a-static-website-with-nightwatch-js/"/>
      <updated>2018-09-29T04:13:46Z</updated>
      <id>https://intricatecloud.io/2018/09/simple-automated-testing-for-a-static-website-with-nightwatch-js/</id>
      <content type="html">
        <![CDATA[
      <p>If you're following along, in my previous post, i was <a href="/2018/06/part-1-build-test-deploy-and-monitor-a-static-website-building-a-blog-with-gatsby/">building a blog with gatsby</a>. Now that I've got some changes made to my blog, I want to make sure that the blog is loading at least and click around a few pages to make sure everything loads correctly.</p>
<h2>Setting up browser tests</h2>
<p>We can use nightwatch js to set up some happy path tests for your blog. I've used this <a href="https://github.com/dwyl/learn-nightwatch">getting started guide</a> to get up and running quickly with nightwatch.</p>
<pre><code>npm install --save-dev nightwatch selenium-server chromedriver
</code></pre>
<p>Create a nightwatch.conf.js and save it in the project folder</p>
<pre><code>const seleniumServer = require(&quot;selenium-server&quot;);
const chromedriver = require(&quot;chromedriver&quot;);
const SCREENSHOT_PATH = &quot;./screenshots/&quot;;

// we use a nightwatch.conf.js file so we can include comments and helper functions
module.exports = {
&quot;src_folders&quot;: [
&quot;test/&quot;// Where you are storing your Nightwatch e2e tests
],
&quot;output_folder&quot;: &quot;./reports&quot;, // reports (test outcome) output by nightwatch
&quot;selenium&quot;: {
&quot;start_process&quot;: true, // tells nightwatch to start/stop the selenium process
&quot;server_path&quot;: seleniumServer.path,
&quot;host&quot;: &quot;127.0.0.1&quot;,
&quot;port&quot;: 4444, // standard selenium port
&quot;cli_args&quot;: {
&quot;webdriver.chrome.driver&quot; : chromedriver.path
}
},
&quot;test_settings&quot;: {
&quot;default&quot;: {
&quot;screenshots&quot;: {
&quot;enabled&quot;: true, // if you want to keep screenshots
&quot;path&quot;: SCREENSHOT_PATH // save screenshots here
},
&quot;globals&quot;: {
&quot;waitForConditionTimeout&quot;: 5000 // sometimes internet is slow so wait.
},
&quot;desiredCapabilities&quot;: { // use Chrome as the default browser for tests
&quot;browserName&quot;: &quot;chrome&quot;
}
},
&quot;chrome&quot;: {
&quot;desiredCapabilities&quot;: {
&quot;browserName&quot;: &quot;chrome&quot;,
&quot;javascriptEnabled&quot;: true // turn off to test progressive enhancement
}
}
}
}

function padLeft (count) { // theregister.co.uk/2016/03/23/npm_left_pad_chaos/
return count &lt; 10 ? '0' + count : count.toString();
}

var FILECOUNT = 0; // &quot;global&quot; screenshot file count
/**
* The default is to save screenshots to the root of your project even though
* there is a screenshots path in the config object above! ... so we need a
* function that returns the correct path for storing our screenshots.
* While we're at it, we are adding some meta-data to the filename, specifically
* the Platform/Browser where the test was run and the test (file) name.
*/
function imgpath (browser) {
var a = browser.options.desiredCapabilities;
var meta = [a.platform];
meta.push(a.browserName ? a.browserName : 'any');
meta.push(a.version ? a.version : 'any');
meta.push(a.name); // this is the test filename so always exists.
var metadata = meta.join('~').toLowerCase().replace(/ /g, '');
return SCREENSHOT_PATH + metadata + '_' + padLeft(FILECOUNT++) + '_';
}

module.exports.imgpath = imgpath;
module.exports.SCREENSHOT_PATH = SCREENSHOT_PATH;

</code></pre>
<p>Then run <code>node nightwatch.conf.js</code> to get selenium-server installed on your machine so that nightwatch can interact with the browser</p>
<h3>Add test/happy-path.js to write our test</h3>
<pre><code>var config = require('../nightwatch.conf.js');

module.exports = {
'Danny Perez Blog test': function(browser) {
browser
.url('http://localhost:9000/')
.waitForElementVisible('body')
.assert.containsText('body', 'Gatsby Default Starter')
.end();
}
};
</code></pre>
<p>Before we start running our tests, we must first get it running. We can do this by doing <code>gatsby build &amp;&amp; gatsby serve &amp;</code> to put the process in the background.</p>
<p>Once the site is up, we can run <code>nightwatch</code> which will run the tests you have in your <code>test/</code> folder.</p>
<h2>Set up automated scripts to repeat the whole process</h2>
<p>To recap what we did earlier,</p>
<ul>
<li>in order for us to be able to view our site, we first need to <code>gatsby build</code> the site, which will store everything in <code>public/.</code></li>
<li>running <code>gatsby serve</code> will serve the content you currently have in the <code>public/</code> folder</li>
<li>running <code>nightwatch</code> will then run the tests you've defined in <code>test/</code></li>
</ul>
<p>Best practice is to run these scripts with npm. Add these scripts to your <code>package.json</code></p>
<pre><code>...
&quot;scripts&quot;: {
&quot;build&quot;: &quot;gatsby build&quot;,
&quot;serve&quot;: &quot;gatsby serve&quot;,
&quot;develop&quot;: &quot;gatsby develop&quot;,
&quot;format&quot;: &quot;prettier --write 'src/**/*.js'&quot;,
&quot;test&quot;: &quot;nightwatch&quot;
},
</code></pre>
<p>so now, you can do <code>npm run-scripts build &amp;&amp; npm run-scripts serve &amp;</code> and then <code>npm run-scripts test</code> and you're good to go as far as developing and testing your new application.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>4 things I look for in a senior engineer</title>
      <link href="https://intricatecloud.io/2018/10/4-things-i-look-for-in-a-senior-engineer/"/>
      <updated>2018-10-08T01:31:23Z</updated>
      <id>https://intricatecloud.io/2018/10/4-things-i-look-for-in-a-senior-engineer/</id>
      <content type="html">
        <![CDATA[
      <p>As a follow up from my last post about <a href="/2018/09/4-things-im-looking-for-when-hiring-a-junior-devops-engineer/">things I look for when hiring a junior DevOps engineer</a> - these are the things I'm looking for out of a senior engineer. It all boils down to what I consider the core of DevOps - helping engineering teams ship their apps quickly and safely to production.</p>
<p>At a high level, this means being able to provide:</p>
<ul>
<li>a CI workflow</li>
<li>knowledge of release workflows</li>
<li>an automated delivery workflow of multiple services to multiple test environments</li>
<li>production monitoring &amp; alerting</li>
</ul>
<p>A senior engineer should know how to provide these basic resources to the engineering team in a way that allow for quick release cycles. They might have a specialty in one of these areas, but know enough to provide a decent foundation across all of these areas.  Let's see what this means in practice.</p>
<h2>Setup and support a CI system/workflow to support many services spanning multiple engineering teams.</h2>
<p>This is like basic need number one of any developer or dev team. These questions should have decent answers:</p>
<ul>
<li>How/Where should I run my browser tests?</li>
<li>How/Where do I test my configuration/infrastructure-as-code?</li>
<li>How do I get more nodes to run more tests?</li>
<li>Can your teams build out their pipelines as they want?</li>
<li>Are they able to deploy to production on their own?</li>
<li>How do I deploy?</li>
</ul>
<p>For example - a team of 20 engineers are building an android app - maybe you set up AWS Device Farm, Code Build, and CodePipeline and provide terraform/cloudformation modules and best practices to get your teams onboarded quickly. They should be able to view logs, retry builds, see screenshots of their tests to name a few. If you're a small team with just a few services, TravisCI/CircleCI might be good enough. If you're a bigger team, you might opt to use Gitlab. If you have very specific testing requirements, you might need to lean on Jenkins.</p>
<p>Some practices scale better than others, and a lot of it has to do with some of your organization culture. I've seen dozens of teams each managing their own Jenkins instance, as well as a dozen teams depending on a centrally managed Jenkins instance. In one context it works great, while at another, it might be a nightmare in practice.</p>
<h2>Have seen multiple release workflows</h2>
<p>As the number of sites/services that your team creates increases, you need to have a method to the madness to get things shipped out quickly to customers and also keep all your internal stakeholders updated as you ship out changes. I'd hope they've gotten to see a good many of them and most importantly, I'd love for someone to be able to provide great feedback about a poor release workflow they've seen.</p>
<p>Some teams create interesting workflows to support it. Things like <a href="https://www.plutora.com/blog/what-is-a-release-train">a release train</a> for organizing releases across teams, or a <a href="https://www.slideshare.net/ThoughtWorks/continuous-delivery-a-happier-safer-alternative-to-release-trains">continuous delivery environment</a> where everything gets shipped after it goes through the pipeline. A senior engineer would be able to recommend and implement a proof-of-concept with other engineering teams outlining the release process. Will you be <a href="https://www.atlassian.com/blog/devops/bridging-devops-itil">using an ITIL workflow</a></p>
<h2>Set up automated deployments to different runtimes and environments</h2>
<p>At the beginning, your teams might be focused on JS, but there will come a time where there's a solution that can only be built using a different language. Or a different framework. As your teams grow, your configuration management solution should scale to the needs of your teams. Are they able to deploy java apps as quickly/easily and nodejs apps? What if you need to introduce python/go into the mix, how much or how little of your pipeline needs to change?</p>
<p>You might set up a docker build/deploy workflow where teams can build/deploy whatever they want to their container to a mangaged k8 cluster, OR you might have a Chef/Ansible workflow where deployments are centrally executed from chef-server or Ansible tower. Maybe using <code>scp</code> to deploy to a remote server is really all you need.</p>
<h2>Configure and scale a distributed monitoring platform for applications and infrastructure</h2>
<p>When you deploy your application, you need to see whether or not it works as intended. Your teams should have all the tools available at their disposal - they should be able to view things like:</p>
<ul>
<li>CPU/memory metrics and plot it on a graph alongside other hosts</li>
<li>centralized logs across all hosts</li>
</ul>
<p>As you get more mature and your needs grow, you'll want to add more custom metrics to your application like application level details using tools like NewRelic APM and/or Datadog.</p>
<p>To start with, maybe using a SaaS tool like Papertrail or logz.io might be good enough. As your services grow and needs change, it might make sense to use a bigger provider like Splunk or Elastic. Or if your senior engineers have a specialty in this area, they can deploy an elasticsearch + kibana + logstash/fluentd architecture and link it automatically to all of your services.</p>
<h2>Conclusion</h2>
<p>There are other things that I'm paying attention to with senior engineers (especially with regards to things like security practices), but their experiences and solutions to these common basic problems is what I'm interested in the most. What I'm looking for is experience seeing different types of applications get deployed across a wide variety of teams with differing requirements and priorities. After you've seen a few, you're able to see processes that work and ones that don't.</p>
<p>How you release to production is a particular interest of mine, and there's always something to learn in what other companies do. What's the best/worst release process you've seen? Let me know in the comments.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Managing passwords and other secrets in a bigger team</title>
      <link href="https://intricatecloud.io/2018/10/managing-passwords-and-other-secrets-in-a-bigger-team/"/>
      <updated>2018-10-17T05:44:17Z</updated>
      <id>https://intricatecloud.io/2018/10/managing-passwords-and-other-secrets-in-a-bigger-team/</id>
      <content type="html">
        <![CDATA[
      <p>A few years ago, I was helping a company get its secrets in order. There were secrets stored in Google Docs, in Chef, in git, in an OSX image stored in git, in email, in a file manually placed in a Jenkins instance somewhere near you. It helps to split out your solutions by common use cases rather than use one tool for everything so here's 4 things that your org might need a safe and secure way to do (because odds are they aren't right now). Disclaimer: I have no affiliation with any of these companies mentioned below.</p>
<ol>
<li>
<p>Have a way of sharing credentials internally including non-technical users. Use something like <a href="https://www.lastpass.com/enterprise-password-management">LastPass Enterprise</a>, <a href="https://1password.com/teams/">1Password Teams</a> to share things like admin logins and billing credentials. These services also come with an extension for most browsers to auto-fill your passwords on all your sites and you can still share them with groups.
Things like database credentials aren't a great fit here, and belong closer to the machines that need to read them where your engineers can interact with them.</p>
</li>
<li>
<p>Have a way of securely storing &amp; sharing sensitive files including non-technical users. LastPass lets you save files, perfectly fine for things like zips containing SSL certificates and that kinda thing. It's not a good fit for sending things like sensitive PDFs.
Google Drive/Dropbox work up to a certain extent, but lack good auditing features to know which files were shared with what users. <a href="https://www.egnyte.com/">Egnyte</a> &amp; <a href="https://www.accellion.com/platform/simple/secure-file-sharing/">Accellion</a> offer paid services for this particular type of use case (and you'd probably have compliance/regulatory requirements) before you really need it.</p>
</li>
<li>
<p>Have a safe way of sending secrets to someone else (not to a group). Some of your users will likely be sending over slack and then deleting the message - this is a terrible solution - but I'm also guilty of it - its just too easy.  onetimesecret.com is a useful site that lets you send secrets using a one-time use link (and they can have a passphrase, and expire too). <a href="https://www.vaultproject.io/docs/concepts/response-wrapping.html">Vault can do &quot;response wrapping&quot;</a> which lets you share a secret without you ever seeing the secret - not very user-friendly, but more dev-friendly.</p>
</li>
<li>
<p>Have a way of storing machine-facing credentials somewhere thats not on your computer. This is where using tools like <a href="https://www.vaultproject.io/">Vault</a> or <a href="https://medium.com/@tdi/ssm-parameter-store-for-keeping-secrets-in-a-structured-way-53a25d48166a">AWS Parameter Store</a> make it super-easy if you're working in a cloud environment on a larger team. You can also go barebones and store them in git using <a href="https://www.agwa.name/projects/git-crypt/">git-crypt</a> or <a href="https://github.com/adieuadieu/aws-kms-thingy">using AWS KMS to encrypt secrets locally</a>. You can always use <a href="https://docs.chef.io/knife_data_bag.html">Encrytped Data Bags from Chef</a>, <a href="https://serversforhackers.com/c/how-ansible-vault-works">Ansible Vault</a>, or <a href="https://puppet.com/blog/using-node-side-secrets-puppet">Puppet hiera-eyaml</a> if you're using one of those config management systems.</p>
</li>
</ol>
<p>The last thing is figuring out a decent authorization scheme so that you know who has access to what. That largely depends on how well you can define &quot;who&quot;, the different types of &quot;access&quot;, and &quot;what&quot; are all the permutations of things people can have access to.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>A few lessons learned using AWS Parameter Store</title>
      <link href="https://intricatecloud.io/2018/10/a-few-lessons-learned-using-aws-parameter-store/"/>
      <updated>2018-10-20T07:47:35Z</updated>
      <id>https://intricatecloud.io/2018/10/a-few-lessons-learned-using-aws-parameter-store/</id>
      <content type="html">
        <![CDATA[
      <p>At my current gig, we've got 50+ services, ~6+ environments, and a rough count of 3.5k parameters across our environments. We used to use Chef::EncryptedDataBags when we used chef-server. A few years later, we migrated to using Vault. And then a year after that, we finally settled on using Parameter Store. Over time, we've grown used to the intricacies of managing secrets and access to secrets.</p>
<p>If you've tried using the Parameter Store console, you'll know that the experience isn't great. But the ease/security of using it outweighs a few of its nuisances. Here's how we've dealt with some of these annoying bits.</p>
<h3>the ui isn't that great to use.</h3>
<p>One really annoying bit is that I can't easily search for parameters across the whole Parameter Store. For example, I can't search for all parameters starting with &quot;db_&quot; i.e. <code>*db_*</code></p>
<p>a useful workaround  - do a quick search* of parameter store. this requires getting all the parameters - but lets you apply plain regex across available paths which you can't quite do in the UI. *It is a little bit slow but can get you a quick preview of the secrets you have available</p>
<pre><code>&gt; aws ssm describe-parameters \
  --output text \
  | egrep '^PARAMETERS' \
  | awk '{print $5}' \
  | egrep $REGEX
  
/dev/myApp/foo
/dev/myApp/bar
...
</code></pre>
<p>anyone know of a better way of doing this?</p>
<h3>keep your db connection parameters together as unique entities.</h3>
<p>use a convention like <code>/$env/databases/$appDb/{host,user,password,port,dbname}</code> which should include all the fields necessary to build out the ODBC connection string</p>
<p>This would let you control who has access to which sets of databases via IAM policies. You can also script it to automatically log you into a  database without you needing to see the password.</p>
<p>here's an example of how you might do it in bash</p>
<pre><code>dbInfo=$(aws ssm get-parameters \
  --names &quot;/dev/dbs/$dbName/database&quot; \
  &quot;/dev/dbs/$dbName/host&quot; \
  &quot;/dev/dbs/$dbName/password&quot; \
  &quot;/dev/dbs/$dbName/port&quot; \
  &quot;/dev/dbs/$dbName/user&quot; \
  &quot;/dev/dbs/$dbName/scheme&quot; \
  --region $REGION \
  --with-decryption \
  --query Parameters[*].Value \
  --output text | tr &quot;\t&quot; &quot; &quot;)

function set_parameters {
  database=&quot;$1&quot;
  database_host=&quot;$2&quot;
  database_pass=&quot;$3&quot;
  database_port=&quot;$4&quot;
  database_scheme=&quot;$5&quot;
  database_user=&quot;$6&quot;
}

set_parameters $dbInfo

if [ &quot;$database_scheme&quot; = &quot;mysql&quot; ];
then
  MYSQL_PWD=$database_pass mysql -h $database_host -P $listen_port -u $database_user $database_database
else
  PGPASSWORD=$database_pass psql -h $database_host -p $listen_port -U $database_user -d $database_database
fi
</code></pre>
<h3>stick to the naming convention</h3>
<p>This is the convention we've used for our parameters which has scaled ok. We've got 10-20 databases, ~50 services spanning 6+ environments organized with this structure.</p>
<pre><code>/$environment_name/databases/$database_name/{host,port,pass,user}
                            /databags/$service_name/{all,my,server,creds}
                            /other_sensitive_info/{foo,bar,baz}
</code></pre>
<p>We borrowed <code>databags</code> from our days using chef cookbooks &amp; encrypted databags. It basically just means that there's a bunch of parameters under keys belonging to a service.</p>
<p>This lets us scope access in a few different dimensions via IAM policies. We can say that a developer should have access to database secrets in a dev environment, but only service secret in prod.</p>
<pre><code>{
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Action&quot;: [
        &quot;ssm:GetParameters&quot;
    ],
    &quot;Resource&quot;: [
        &quot;arn:aws:ssm:us-east-2:123456123:parameter/dev/databases/*&quot;,
        &quot;arn:aws:ssm:us-east-2:123456123:parameter/prod/databags/myService/*&quot;
    ]
}
</code></pre>
<h3>you can't stuff big items into parameters</h3>
<p>We used to use the certificate cookbook to install certs on our hosts which required us to keep our certificates stored in this format</p>
<pre><code>{
  &quot;id&quot;: &quot;mail&quot;,
  &quot;cert&quot;: &quot;-----BEGIN CERTIFICATE-----\nMail Certificate Here...&quot;,
  &quot;key&quot;: &quot;-----BEGIN PRIVATE KEY\nMail Private Key Here...&quot;,
  &quot;chain&quot;: &quot;-----BEGIN CERTIFICATE-----\nCA Root Chain Here...&quot;
}
</code></pre>
<p>But you can't stuff this as json into a parameter due to the <a href="https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_Parameter.html">size limitations of 4096 characters</a></p>
<h3>use labels/pointers to help you rotate passwords</h3>
<p>password rotation is an inherently hard problem, but one strategy could be to use pointers (or what they now call labels) to secrets. for example, lets say you want to rotate an encryption key, but you have multiple services relying on it.</p>
<p>you could do something like</p>
<pre><code>/dev/databags/my-service/current     = 2 (index of actual secret)
                        /1           = 'secret 2018'
                        /2           = 'secret 2019'
</code></pre>
<p>Your applications need to know to follow the pointer and read the intended secret. You'd typically do this by regenerating a config file and bouncing your service.</p>
<p>Fortunately, <a href="https://aws.amazon.com/blogs/mt/use-parameter-labels-for-easy-configuration-update-across-environments/">You can do this natively now on AWS</a> with labels on parameters.
Rotating passwords can also be done <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html">using Lambda and Secrets Manager - check out this walkthrough here</a></p>
<h3>param store keeps versions of your parameters, but not if you delete them</h3>
<p>The docs indicate that parameters are versioned, and they are versioned while they exist. So if you accidentally delete a parameter, the history is gone with it. The consensus seems to be that you should export your parameter store database to S3 or Dynamodb but I haven't come across tools in this space yet.</p>
<h3>some related tools that support param-store</h3>
<p>secretly for exporting secrets into your environment, written in python - https://github.com/energyhub/secretly
parameter-store-exec - similar to secretly, written in go. for commands with access to secrets, written in go - https://github.com/cultureamp/parameter-store-exec
confd for putting into config files - https://github.com/kelseyhightower/confd
as part of a consul-template - https://github.com/hellofresh/consul-template-plugin-ssm
chamber for managing secrets including parameter store - https://github.com/segmentio/chamber</p>
<p>Hope these tips help, also interested to hear how other people have worked around param-store's nuisances and/or wrote cool integrations with it.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Managing DB users in Postgres &amp; MySQL</title>
      <link href="https://intricatecloud.io/2018/11/managing-db-users-in-postgres-mysql/"/>
      <updated>2018-11-04T05:20:25Z</updated>
      <id>https://intricatecloud.io/2018/11/managing-db-users-in-postgres-mysql/</id>
      <content type="html">
        <![CDATA[
      <p>A guide for when you have to add/remove/update/and/20/other/things in both mysql &amp; postgres databases, and you wind up getting lost trying to do seemingly simple things. I've been keeping a running doc where I keep snippets of SQL for those times when I have to remember, and I hope they help if you happen to be using both postgres and mysql and need to manage user access.</p>
<p>Below, we've got 10 frequently used SQL snippets for managing users.</p>
<ol>
<li>add a user</li>
<li>reset a password</li>
<li>checking users privileges</li>
<li>revoking privileges</li>
<li>remove a user</li>
<li>expire passwords/temporary credentials</li>
<li>checking expiration time</li>
<li>granting read-only access</li>
<li>checking if a user exists</li>
<li>some debug information that might help</li>
</ol>
<hr>
<p>If you want to play around with these scripts with a development database, start them up using docker and connect to it:</p>
<p>in postgres:</p>
<pre><code># start postgres in docker
docker run --rm --name pgsql -e POSTGRES_PASSWORD=password -p 5555:5432 -d postgres
e0e76a6af8b1f83cf91295ae5430cbf2ecd95337977b53653f312024d8aab3ad

# connect to postgres
psql -h 127.0.0.1 -p 5555 -U postgres -d postgres
</code></pre>
<p>in mysql:</p>
<pre><code># start mysql in docker
docker run --rm --name mysql -p3307:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql
# connect to mysql
mysql -h 127.0.0.1 -P 3307 -u root -p
</code></pre>
<hr>
<h1>1. add a user</h1>
<p>in postgres:</p>
<pre><code>CREATE USER '${user}' WITH PASSWORD '${password}';
GRANT ALL ON DATABASE '${db}' TO '${user}'; -- grants read/write to everything in this database instance
-- OR
GRANT CONNECT ON DATABASE '${db}' to '${user}'; -- only allows the user to connect, but nothing more.
</code></pre>
<p>* https://www.postgresql.org/docs/8.0/static/sql-createuser.html</p>
<p>in mysql:</p>
<pre><code>CREATE USER '${user}' IDENTIFIED BY '${password}';
GRANT ALL PRIVILEGES ON ${db}.* TO '${user}';
</code></pre>
<p>* https://dev.mysql.com/doc/refman/8.0/en/adding-users.html</p>
<h1>2. reset a password</h1>
<p>in postgres:</p>
<pre><code>ALTER ROLE '${user}' WITH PASSWORD '${pw}';
</code></pre>
<p>* https://www.postgresql.org/docs/8.0/static/sql-alteruser.html</p>
<p>in mysql:</p>
<pre><code>ALTER USER '${user}' IDENTIFIED BY '${pw}'
</code></pre>
<p>*heres 2 more ways of doing this: https://www.geeksforgeeks.org/mysql-change-user-password/</p>
<h1>3. checking a users privileges</h1>
<p>in postgres:</p>
<p>This will list all the databases and show you all the roles that have access to it.</p>
<pre><code>postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
</code></pre>
<p>This will show all the role names and the other roles they include.</p>
<pre><code>postgres=# \du
                                   List of roles
 Role name |                         Attributes                         | Member of
-----------+------------------------------------------------------------+-----------
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
</code></pre>
<p>This will show all the tables that a user has access to</p>
<pre><code>postgres=# \z
                                 Access privileges
 Schema |  Name  | Type  |     Access privileges     | Column privileges | Policies
--------+--------+-------+---------------------------+-------------------+----------
 public | foobar | table | postgres=arwdDxt/postgres+|                   |
        |        |       | dperez=r/postgres         |                   |
 public | test   | table | postgres=arwdDxt/postgres+|                   |
        |        |       | dperez=r/postgres         |                   |
(2 rows)
 ...
</code></pre>
<p>See the <a href="https://www.postgresql.org/docs/7.4/static/sql-grant.html">postgres docs on grants to decipher that description</a></p>
<p>in mysql:</p>
<pre><code>mysql&gt; SHOW GRANTS FOR '${user}';
+---------------------------------------------------------------------+
| Grants for root@localhost                                           |
+---------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO '${user}'@'localhost' WITH GRANT OPTION |
+---------------------------------------------------------------------+
</code></pre>
<h1>4. revoke privileges</h1>
<p>in postgres:</p>
<pre><code>REVOKE ALL PRIVILEGES ON DATABASE '${db}' from '${user}';
</code></pre>
<p>* https://www.postgresql.org/docs/9.1/static/sql-revoke.html</p>
<p>in mysql:</p>
<pre><code>REVOKE ALL PRIVILEGES ON '${db}.*' FROM '${user}';
</code></pre>
<p>*https://dev.mysql.com/doc/refman/8.0/en/revoke.html</p>
<h1>5. remove a user</h1>
<p>in postgres:</p>
<pre><code>DROP ROLE IF EXISTS '${user}';
</code></pre>
<p>You might see an error message like:
<code>ERROR: role &quot;testuser&quot; cannot be dropped because some objects depend on it DETAIL: $somethingUseful</code>
this means that your user has dependencies with other objects - things like privileges and other tables. In this case, you'd need to run:</p>
<pre><code>REVOKE ALL PRIVILEGES ON DATABASE '${db}' from '${user}';
DROP ROLE IF EXISTS '${user}';
</code></pre>
<p>If this fails, then try</p>
<pre><code>DROP OWNED BY '${user}';
DROP ROLE IF EXISTS '${user}'
</code></pre>
<p>* https://stackoverflow.com/questions/3023583/postgresql-how-to-quickly-drop-a-user-with-existing-privileges</p>
<p>The irony here is that you would typically use <code>DROP ROLE IF EXISTS</code> so that a script would not error out if the role did not exist. However, when the role does NOT exist, you get back a successful response. When the role DOES exist, it will likely have privileges and require you to run <code>REVOKE ALL PRIVILEGES</code> - and unfortunately you cannot do <code>REVOKE ALL ... IF EXISTS</code>. It kind of renders the whole <code>DROP ROLE IF EXISTS</code> convenience statement useless.</p>
<p>in mysql:</p>
<pre><code>DROP USER IF EXISTS '${user}';
</code></pre>
<p>Thank you for being so straightforward about it, mysql.</p>
<h1>6. expire a password / temporary credentials</h1>
<p>in postgres:</p>
<pre><code>ALTER ROLE '${user}' WITH PASSWORD '${password}' VALID UNTIL '${expiration_timestamp}';
</code></pre>
<p>Here, <code>expiration_timestamp</code> has the format <code>Nov 3 12:00:00 2018 +1</code> and means the time at which the password will no longer work.
*https://www.postgresql.org/docs/9.2/static/sql-alterrole.html</p>
<p>in mysql:</p>
<pre><code>ALTER USER '${user}' PASSWORD EXPIRE INTERVAL 1 DAY;
</code></pre>
<p>The statement seems to only allow <code>DAY</code> as the interval unit. So <code>INTERVAL 90 DAY</code> would be correct (note: no plural s in day).</p>
<h1>7. how to check expiration time of password</h1>
<p>Once you set a password expiration time, it would be useful to see what that time is.</p>
<p>in postgres:</p>
<pre><code>SELECT valuntil AS valid_until FROM pg_user WHERE usename = '${user}';
</code></pre>
<p>in mysql:</p>
<pre><code>SELECT DATE_ADD(password_last_changed, interval password_lifetime day) AS valid_until FROM mysql.user WHERE user = '${user}';
</code></pre>
<h1>8. granting read-only access</h1>
<p>in postgres:</p>
<pre><code>GRANT USAGE ON SCHEMA public TO '${user}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO '${user}';
</code></pre>
<p>* <a href="https://serverfault.com/questions/198002/postgresql-what-does-grant-all-privileges-on-database-do">related thread about what <code>grant all privileges on database</code> actually does</a>
You might find later on that if you add a table, this read-only user does not have access to read that table. In which case, run the following to make sure that you by default get privileges on tables:</p>
<pre><code>ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO '${user}';
</code></pre>
<p>* more info here - https://stackoverflow.com/questions/760210/how-do-you-create-a-read-only-user-in-postgresql</p>
<p>in mysql:</p>
<pre><code>GRANT SELECT ON '${db}'.* TO '${user}';
</code></pre>
<h1>9. check if a user exists</h1>
<p>in postgres:</p>
<pre><code>SELECT 1 FROM pg_roles WHERE rolname='${user}';
</code></pre>
<p>in mysql:</p>
<pre><code>SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = '${user}');
</code></pre>
<h1>10. get some debug info about your current user</h1>
<p>in postgres:</p>
<pre><code>postgres=# \conninfo
You are connected to database &quot;postgres&quot; as user &quot;postgres&quot; on host &quot;127.0.0.1&quot; at port &quot;5555&quot;.
postgres=# SELECT CURRENT_USER;
 current_user
--------------
 postgres
(1 row)

postgres=# SELECT CURRENT_USER(); -- you can't use it as a function
ERROR:  syntax error at or near &quot;(&quot;
LINE 1: SELECT CURRENT_USER();
</code></pre>
<p>in mysql:</p>
<pre><code>mysql&gt; select CURRENT_USER();
mysql&gt; select CURRENT_USER; -- both this and the above function return the same result
+----------------+
| current_user() |
+----------------+
| dperez@%       |
+----------------+
mysql&gt; status
--------------
mysql  Ver 14.14 Distrib 5.7.22-22, for osx10.11 (x86_64) using  EditLine wrapper

Connection id:    683
Current database: dpereztest
Current user:   dperez@10.162.9.100
SSL:      Cipher in use is DHE-RSA-AES128-SHA
Current pager:    less
Using outfile:    ''
Using delimiter:  ;
Server version:   8.0.11 Source distribution
Protocol version: 10
Connection:   127.0.0.1 via TCP/IP
Server characterset:  utf8mb4
Db     characterset:  utf8mb4
Client characterset:  utf8
Conn.  characterset:  utf8
TCP port:   5555
Uptime:     2 days 7 hours 55 min 53 sec

Threads: 3  Questions: 107602  Slow queries: 0  Opens: 285  Flush tables: 2  Open tables: 259  Queries per second avg: 0.534
--------------
</code></pre>
<p>I hope this helps as a useful guide the next time you need to manage users on a postgres or mysql database. There's dozens of more things you can do to customize your users and their access levels, but this guide should serve as a starting off point with links to the docs to help you do what you're trying to do.</p>
<p>If you're managing users and credentials across multiple teams, you might want to check out:</p>
<ul>
<li><a href="https://intricatecloud.wpengine.com/2018/10/managing-passwords-and-other-secrets-in-a-bigger-team/">things to be aware of when managing passwords among bigger teams.</a></li>
<li><a href="https://intricatecloud.wpengine.com/2018/10/a-few-lessons-learned-using-aws-parameter-store/">a recent experience storing secrets in AWS Parameter Store</a></li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>WebdriverIO tips: using $$(selector) vs browser.elements(selector)</title>
      <link href="https://intricatecloud.io/2018/11/webdriverio-tips-using-selector-vs-browser-elementsselector/"/>
      <updated>2018-11-12T21:41:34Z</updated>
      <id>https://intricatecloud.io/2018/11/webdriverio-tips-using-selector-vs-browser-elementsselector/</id>
      <content type="html">
        <![CDATA[
      <p>Last week, I started working on integrating a test suite previously built using Nightwatch, and making it work with webdriverIO. While I love all of webdriverIO's features like synchronous code when using their test runner and a REPL, there were a few things that I'd like to share which were a little hard to find in the docs or on a quick search.</p>
<p>Each day this week, I'll be posting one thing I've learned while getting started with webdriverIO. Here's the tip for today</p>
<h2>Using $$.selector vs browser.elements(selector)</h2>
<p>One of the first use cases I tackled in my test was finding all elements with a particular CSS class and then doing something to them. <a href="http://webdriver.io/api/utility/$$.html">The docs seem to indicate</a> that <code>browser.$$(selector)</code> is a shorthand for <code>browser.elements(selector)</code> and they <em>seem</em> to be synonymous except for a small difference:</p>
<p><code>$$(selector)</code> will return items that you can take an action like <code>.click()</code> on
<code>.elements(selector)</code> returns a WebElement JSON object (<a href="https://github.com/webdriverio/webdriverio/issues/2728">see more info about it on the github issue</a>) - but its basically a JSON object that contains an ID (looks like a decimal number).</p>
<p>If you try to click on the first element in a group using something like <code>browser.elements(selector)[0].click()</code>, you might get an error like <code>Can't call click on undefined</code> because it does not return an array, but an object containing a key called value which has Ids for those elements.</p>
<p>Here's an example of the value of <code>$$(selector)</code></p>
<pre><code>&gt; browser.$$(&quot;.link-gray-dark&quot;)
[ { ELEMENT: '0.15821111822631395-1',
    'element-6066-11e4-a52e-4f735466cecf': '0.15821111822631395-1',
    value: { ELEMENT: '0.15821111822631395-1' },
    selector: '.link-gray-dark',
    index: 0 },
  { ELEMENT: '0.15821111822631395-2',
    'element-6066-11e4-a52e-4f735466cecf': '0.15821111822631395-2',
    value: { ELEMENT: '0.15821111822631395-2' },
    selector: '.link-gray-dark',
    index: 1 },
    ....
</code></pre>
<p>and now compare that to <code>browser.elements(selector)</code></p>
<pre><code>&gt; browser.elements(&quot;.link-gray-dark&quot;)
{ sessionId: '651d1e513eb87326a67969d65bbd597c',
  value:
   [ { ELEMENT: '0.15821111822631395-1',
       'element-6066-11e4-a52e-4f735466cecf': '0.15821111822631395-1',
       selector: '.link-gray-dark',
       value: [Object],
       index: 0 },
     { ELEMENT: '0.15821111822631395-2',
       'element-6066-11e4-a52e-4f735466cecf': '0.15821111822631395-2',
       selector: '.link-gray-dark',
       value: [Object],
       index: 1 },
</code></pre>
<p>For the most part, I can do everything I want to do with <code>$$(selector)</code> since I'll usually try to find a group of elements and either click on them or input text on some of them.</p>
<p>One reason you might want to use <code>browser.elements(selector)</code> instead would be if you needed to use one of the webdriver Protocol methods like <code>elementIdClick(id)</code> or <code>elementIdName(id)</code>, and <code>elementIdScreenshot(id)</code> (which will take a screenshot of ONLY the element you want and not the whole page, neat!). Webdriver gives you a nice API so that you generally don't need to use these methods, but there are some nice features there if you do need to use them.</p>
<h3>Recap</h3>
<p>Prefer to use <code>$$(selector)</code> since it should be enough for the majority of your use cases.</p>
<p>If you need access to some of the <code>elementId*</code> methods like <a href="http://webdriver.io/api/protocol/elementIdClick.html"><code>elementIdClick(id)</code> that show up under the Protocol section in the docs</a>, then use <code>browser.elements(selector)</code>.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>WebdriverIO tips: using browser.debug() to help debug your tests</title>
      <link href="https://intricatecloud.io/2018/11/webdriverio-tips-using-browser-debug-to-help-debug-your-tests/"/>
      <updated>2018-11-13T19:34:50Z</updated>
      <id>https://intricatecloud.io/2018/11/webdriverio-tips-using-browser-debug-to-help-debug-your-tests/</id>
      <content type="html">
        <![CDATA[
      <p>Ever want to stop your Selenium tests halfway and try to see what your tests are seeing? Using .debug() helps but be aware of your test timeouts &amp; context around your code.</p>
<p>EDIT 10/2019 - I've published a new video to my Youtube Channel where I debug a test and show you how browser.debug() alongside the VS Code Debugger. Check it out!</p>
<p>https://youtu.be/wvvIz60DNp4</p>
<h3>timeouts</h3>
<p>Lets go over a simple use case. Going to a website and dropping you into the webdriverIO REPL:</p>
<pre><code>describe('a test', function() {
  it('runs', function() {
    browser.url('https://msn.com');
    browser.debug();
    expect(true).toBeTruthy();
  });
});
</code></pre>
<p>Running this test does exactly what I want. It drops me into the webdriverIO REPL so that I can start interacting with the page. I'm still in the middle of the test and the expectation hasn't run yet:</p>
<pre><code>[16:58:20]  DEBUG	Queue has stopped!
[16:58:20]  DEBUG	You can now go into the browser or use the command line as REPL
[16:58:20]  DEBUG	(To exit, press ^C again or type .exit)

&gt;
</code></pre>
<p>The first time you run this, you'll be disappointed to see feedback about a failing test due to a timeout. Something like</p>
<pre><code>&gt; F

0 passing (15.80s)
1 failing

1) a testsuite1 runs:
Error: Timeout - Async callback was not invoked within 10000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
running firefox
Error: Timeout - Async callback was not invoked within 10000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
    at &lt;Jasmine&gt;
    at ontimeout (timers.js:498:11)
    at tryOnTimeout (timers.js:323:5)
    at Timer.listOnTimeout (timers.js:290:5)
</code></pre>
<p>What just happened? Your test has a default timeout of 10 seconds as defined by Jasmine. Because we're in the middle of the test, Jasmine is still keeping track of the test execution time and will kill your test because it looks like its hanging. Normally, this is what you want because if you're waiting for a selector to appear on the page, and you're on the wrong page, you want the test to fail when it can't find it in time. In this situation where I want to debug my test, its annoying because I need to change this whenever I want to use <code>browser.debug()</code>.</p>
<p>You can change this in your <code>wdio.conf.js</code> file by changing the <code>jasmineNodeOpts</code></p>
<pre><code>// Options to be passed to Jasmine.
jasmineNodeOpts: {
    //
    // Change this to something really large...
    // but not too large
    defaultTimeoutInterval: 10000,
</code></pre>
<h3>losing context</h3>
<p>When you drop into the REPL, you lose a little bit of context. For example, with a test like this:</p>
<pre><code>describe('a test', function() {
  it('runs', function() {
    browser.url('https://msn.com');
    var foo = &quot;Bar&quot;;
    browser.debug();

    expect(true).toBeTruthy();
  });
});
</code></pre>
<p>Once you're in the REPL, you won't be able to see the value of <code>foo</code>.</p>
<pre><code>[17:38:54]  DEBUG	Queue has stopped!
[17:38:54]  DEBUG	You can now go into the browser or use the command line as REPL
[17:38:54]  DEBUG	(To exit, press ^C again or type .exit)

&gt; foo
evalmachine.&lt;anonymous&gt;:1
foo
^

ReferenceError: foo is not defined
    at evalmachine.&lt;anonymous&gt;:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
</code></pre>
<p>So the REPL is useful for being able to interact with the browser object while in the middle of your test, but not useful for inspecting your current context and the values of your variables. For that, we'd probably need to lean on the native node debugger, or as always..., some console.logs. You can however use it as a typical node REPL and set variables and print things to the console.</p>
<h3>Recap</h3>
<p>When using <code>browser.debug()</code> to use the webdriverIO REPL, there are 2 things you need to keep in mind</p>
<ul>
<li>your test framework (in this case Jasmine) has a global default timeout which will prevent you from using the REPL productively so remember to change it to a big number when you're trying to debug</li>
<li>you'll lose context so you won't be able to see the values of existing variables.</li>
</ul>
<hr>
<p>Last week, I started working on integrating a test suite previously built using Nightwatch, and making it work with webdriverIO. While I love all of webdriverIO’s features like synchronous code when using their test runner and a REPL, there were a few things that I’d like to share which were a little hard to find in the docs or on a quick search.</p>
<p>In case you missed it... Each day this week, I've been posting one thing I've learned while setting up webdriverIO. Check out my previous posts here:</p>
<ul>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-using-selector-vs-browser-elementsselector/">Using $$.(selector) vs browser.elements(selector)</a></li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>WebdriverIO tips: get text from a list of items</title>
      <link href="https://intricatecloud.io/2018/11/webdriverio-tips-get-text-from-a-list-of-items/"/>
      <updated>2018-11-14T23:49:06Z</updated>
      <id>https://intricatecloud.io/2018/11/webdriverio-tips-get-text-from-a-list-of-items/</id>
      <content type="html">
        <![CDATA[
      <p>There's a few ways to go about getting text from a list of elements, but there's a gotcha with webdriverIO that can make your tests a lot flakier.</p>
<p>The easiest way should be to:</p>
<pre><code>// get some of the headers on wikipedia.org
&gt; browser.getText(&quot;#mp-topbanner &gt; ul &gt; li&quot;)
[ 'Arts',
  'Biography',
  'Geography',
  'History',
  'Mathematics',
  'Science',
  'Society',
  'Technology',
  'All portals' ]
</code></pre>
<p>One exception that I seem to get intermittently with <code>browser.getText(selector)</code> (and also with <code>waitForVisible</code>) is the damn Invalid Argument error. Here's an example of the issue</p>
<pre><code>&gt; browser.url('http://webdriver.io')
&gt; browser.getText('nav &gt; ul &gt; li')
[ 'I/O',
  'Home',
  'Developer Guide',
  'API',
  'Contribute',
  '',
  'API Version',
  '' ]
&gt; browser.getText('nav &gt; ul &gt; li')
/Users/dperez/Documents/projects/tchdp/wdio-tips/node_modules/wdio-sync/build/index.js:357
            throw e;
            ^

Error: java.net.SocketException: Invalid argument
    at new RuntimeError (node_modules/webdriverio/build/lib/utils/ErrorHandler.js:143:12)
    at Request._callback (node_modules/webdriverio/build/lib/utils/RequestHandler.js:316:39)
    at Request.self.callback (node_modules/webdriverio/node_modules/request/request.js:185:22)
java.net.SocketException: Invalid argument
[chrome desktop #0-0] Error: An unknown server-side error occurred while processing the command.
</code></pre>
<p>The first call to <code>getText</code> succeeded, and the second call which ran immediately after, ran into an error. I believe the error message is coming from Selenium server, and it sends back a very helpful message about invalid arguments. It tends to happen when your selector returns too many elements. In the above example, the selectors were returning 8/9 elements. I've also seen it crap out even with 3 elements, so there's something else going on there.</p>
<p>Here's a workaround. Query for the elements and loop over them manually. I've found this to be a lot less flaky:</p>
<pre><code>&gt; $$(&quot;#mp-topbanner &gt; ul &gt; li&quot;).map(function(element){
    return element.getAttribute('innerText')
})
[ 'Arts',
  'Biography',
  'Geography',
  'History',
  'Mathematics',
  'Science',
  'Society',
  'Technology',
  'All portals' ]
</code></pre>
<h3>recap</h3>
<p>Selenium can be flaky, and while webdriverio does a good job at making writing tests a lot easier, it does have to deal with the webdriver API at the end of the day. If you're seeing <code>SocketException: Invalid argument</code>, its best to skip the convenience of <code>getText</code> and loop over your elements.</p>
<hr>
<p>Last week, I started working on integrating a test suite previously built using Nightwatch, and making it work with webdriverIO. While I love all of webdriverIO’s features like synchronous code when using their test runner and a REPL, there were a few things that I’d like to share which were a little hard to find in the docs or on a quick search.</p>
<p>In case you missed it... Each day this week, I've been posting one thing I've learned while setting up webdriverIO. Check out my previous posts here:</p>
<ul>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-using-selector-vs-browser-elementsselector/">Using $$.(selector) vs browser.elements(selector)</a></li>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-using-browser-debug-to-help-debug-your-tests/">Using browser.debug() to help debug your tests</a></li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>WebdriverIO tips: finding your errors when using waitUntil</title>
      <link href="https://intricatecloud.io/2018/11/webdriverio-tips-finding-your-errors-when-using-waituntil/"/>
      <updated>2018-11-15T19:57:18Z</updated>
      <id>https://intricatecloud.io/2018/11/webdriverio-tips-finding-your-errors-when-using-waituntil/</id>
      <content type="html">
        <![CDATA[
      <p>If you're loading a page and want to make sure that some elements are showing up before advancing, you'd be inclined to use <code>browser.waitUntil()</code>. While it does do the job, it holds onto the errors until the test times out.</p>
<p>In this example, I'd like to use <code>waitUntil</code> to check that multiple elements are visible</p>
<pre><code>browser.waitUntil(function() {
  return doesNotExist.$$('#elem-1').isVisible()
    &amp;&amp; browser.$$('#elem-2').isVisible()
})
</code></pre>
<p>Here's what I see when I run this inside a test:</p>
<pre><code>☁  wdio-tips  wdio

F

0 passing (15.30s)
1 failing

1) a testsuite1 runs:
Failed: Promise was rejected with the following reason: doesNotExist is not defined
running firefox
error properties: Object({ details: undefined, type: 'WaitUntilTimeoutError', shotTaken: true })
Error: Promise was rejected with the following reason: doesNotExist is not defined
    at waitUntil(&lt;Function&gt;) - index.js:312:3
</code></pre>
<p>The key thing to notice here is the test time of 15.3 seconds before it gave up with an error. <code>waitUntil</code> runs your function on an interval (default 500ms) for a total 10s timeout period. So that means that the function has run 20 times during those 10 seconds, but you only see the error message at the very end once it times out.</p>
<p>The annoying part about this is the amount of time it takes to get the feedback, but once you see the error message, you can fix it and double check the rest of it for syntax errors so that you don't spend all your time waiting 10s for that feedback. I think this is just the catch with using <code>browser.waitUntil</code> using their synchronous API.</p>
<hr>
<p>Last week, I started working on integrating a test suite previously built using Nightwatch, and making it work with webdriverIO. While I love all of webdriverIO’s features like synchronous code when using their test runner and a REPL, there were a few things that I’d like to share which were a little hard to find in the docs or on a quick search.</p>
<p>In case you missed it... Each day this week, I've been posting one thing I've learned while setting up webdriverIO. Check out my previous posts here:</p>
<ul>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-using-selector-vs-browser-elementsselector/">Using $$.(selector) vs browser.elements(selector)</a></li>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-using-browser-debug-to-help-debug-your-tests/">Using browser.debug() to help debug your tests</a></li>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-get-text-from-a-list-of-items/">Get text from a list of items</a></li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>WebdriverIO tips: element wrapped in div is not clickable</title>
      <link href="https://intricatecloud.io/2018/11/webdriverio-tips-element-wrapped-in-div-is-not-clickable/"/>
      <updated>2018-11-16T15:47:35Z</updated>
      <id>https://intricatecloud.io/2018/11/webdriverio-tips-element-wrapped-in-div-is-not-clickable/</id>
      <content type="html">
        <![CDATA[
      <p>Have you run into an error saying &quot;Element is not clickable at point&quot; when you're trying to click a button? You might be seeing this if you have a spinner that appears on your buttons or its a customized button (like a div as a button with inner styled elements). In these cases, you're kinda out of luck if you want selenium to be able to do it because it can't see the element that you want to click on. However... you can hack around it. While selenium isn't able to click the button obscured by another element, the browser can still do it and selenium can inject javascript to run inside the browser. Check out this example:</p>
<pre><code>var runInBrowser = function(argument) { 
  argument.click();
};
var elementToClickOn = browser.$(selector)
&gt; browser.execute(runInBrowser, elementToClickOn);
</code></pre>
<p><a href="http://webdriver.io/api/protocol/execute.html">.execute</a> to the rescue here. You can inject a snippet into the page, so as long as the browser can do it, you can get past the &quot;Element is not clickable&quot; error. This is a hack though and you should only use it sparingly when you need it, <code>$(element).click()</code> should still work the majority of the time. <a href="https://stackoverflow.com/questions/11908249/debugging-element-is-not-clickable-at-point-error">Check out this Stack Overflow discussion here if you're in this situation</a></p>
<hr>
<p>Last week, I started working on integrating a test suite previously built using Nightwatch, and making it work with webdriverIO. While I love all of webdriverIO’s features like synchronous code when using their test runner and a REPL, there were a few things that I’d like to share which were a little hard to find in the docs or on a quick search.</p>
<p>In case you missed it... Each day this week, I've been posting one thing I've learned while setting up webdriverIO. Check out my previous posts here:</p>
<ul>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-using-selector-vs-browser-elementsselector/">Using $$.(selector) vs browser.elements(selector)</a></li>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-using-browser-debug-to-help-debug-your-tests/">Using browser.debug() to help debug your tests</a></li>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-get-text-from-a-list-of-items/">Get text from a list of items</a></li>
<li><a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-finding-your-errors-when-using-waituntil/">finding your errors when using waitUntil</a></li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Writing quick and dirty scripts using bash</title>
      <link href="https://intricatecloud.io/2018/11/writing-quick-and-dirty-scripts-using-bash/"/>
      <updated>2018-11-27T05:42:03Z</updated>
      <id>https://intricatecloud.io/2018/11/writing-quick-and-dirty-scripts-using-bash/</id>
      <content type="html">
        <![CDATA[
      <p>You have a few lines you've been typing in your shell, and you'd like to just run it the way you run any other CLI including options. If you're writing a shell script, here's a few tips to help you get a head start.</p>
<ol>
<li>#! the shebang line</li>
<li>usage docs</li>
<li>flags and options</li>
<li>error handling</li>
<li>dealing with output</li>
</ol>
<p>This doc assumes you're using bash in an OSX/Linux environment.</p>
<h2>1. the shebang line</h2>
<p>You'll usually see a line at the top of shell scripts <code>#!/bin/bash</code> for example. This is called the shebang line - its a directive for the shell to run this file using the program at that path. For example, if this were a node.js script, I would have something like:</p>
<pre><code>#!/usr/bin/node

console.log('hello world')
</code></pre>
<p>Since we want to run this script using bash, we'll set the shebang line to bash. You could do <code>#!/bin/bash</code> - but this will default to your user's system install of bash. You might try <code>#!/usr/local/bin/bash</code> if you have another version of bash at that directory. It's hard to account for where your current shell may be - in this case, you can simply ask the environment where it should be with <code>env</code>.</p>
<pre><code>#!/usr/bin/env bash 
# will run the &amp;#x60;bash&amp;#x60; that is currently in your environment (i.e. in your $PATH). 
</code></pre>
<h2>2. usage docs</h2>
<p>One way a user might expect to see how to use the tool would be to just type the naked command, e.g. <code>mytool</code>. The following will print the usage docs for the tool if there are no arguments passed to the tool</p>
<pre><code>function usage {
    echo &quot;usage: &amp;#x60;basename &quot;$0&quot;&amp;#x60; /path/to/file&quot;
    echo ''
    echo 'ex. mytool /tmp/foobar'
    echo ''
    echo 'Does something useful to the target file'
    echo ''
    echo 'Options:'
    echo '-h : show this doc
}

if [ &quot;$#&quot; -lt 1 ]; then
  usage
fi
</code></pre>
<h2>2. options, flags, arguments, and env vars</h2>
<p>Once you want to set a CLI flag or an option, move to <code>getopts</code>. The syntax can be a little confusing, but hopefully this breakdown makes sense.</p>
<ul>
<li>one limitation is that you can only set short flags like <code>-x -V</code></li>
<li>the definition of all the CLI flags happens in the parameter to getops: <code>:ht:</code></li>
<li>the first <code>:</code> which means that you disable the default error handling of getopts</li>
<li>the second <code>h</code> which means that we have an option (<code>-h</code>) with no arguments</li>
<li>the third part is the <code>t:</code> (a colon follows the flag name) which means that the option (<code>-t</code>) requires an argument</li>
</ul>
<pre><code>while getopts &quot;:ht:&quot; opt; do
  	case $opt in
    	h)
      	usage
     	 ;;
    	t)
      	type=$OPTARG
      	;;
    	\?)
      	echo 'Invalid option: -$OPTARG'
     	 ;;
  	esac
done
shift $(($OPTIND - 1))
remaining_args=$@
</code></pre>
<p>In this example, we have a CLI with <code>-h -t</code> as options with <code>-t</code> requiring an argument. The last two lines allow you to get the rest of the arguments provided to your script. They all wind up in the <code>$@</code> variable.</p>
<p>If you ran <code>mytool -t foo /path/to/file</code> (with these options), you'd have the result:</p>
<pre><code>type=&quot;foo&quot;
remaining_args=&quot;/path/to/file&quot;
</code></pre>
<p>If you want to check if a variable is already defined, and abort if not, you can use bash conditionals.</p>
<pre><code>if [ -z &quot;$SOMETHING_IMPORTANT&quot; ]; then
  echo '$SOMETHING_IMPORTANT wasn't defined'
  usage
fi
</code></pre>
<p>There's a whole host of things you can check, <code>-z</code> is one of my more frequently used ones - see http://tldp.org/LDP/abs/html/comparison-ops.html</p>
<p>If you want to set default values for environment variables (and allow a user to override them if they want) - this will set <code>USE_HEADLESS_CHROME</code> to <code>0</code> if it was not defined already.  <code>USE_HEADLESS_CHROME=${USE_HEADLESS_CHROME:-0} </code></p>
<h2>3. Dealing with output</h2>
<p>Are you trying to run a long-running command, but you want to save the output for further debugging later and you also want to see it running? Enter <code>tee</code>! It lets you pipe your output to BOTH stdout &amp; a file at the same time.</p>
<pre><code>apt-get update -y | tee apt-get.log   # view and save the output
apt-get update -y &gt; /dev/null 2&gt;&amp;1    # ignore all output
apt-get update -y 2&gt;&amp;1 &gt; /dev/null    # Keep only stderr
</code></pre>
<p>See an interesting <a href="https://stackoverflow.com/questions/2342826/how-to-pipe-stderr-and-not-stdout">Stack Overflow discussion here about redirects in bash</a>.</p>
<h2>4. Error handling</h2>
<p>The first time you run your script, you'll likely see that even though some commands are failing, the script continues. No, I did not want to create a file named <code>404 Not Found</code>. /facepalm.</p>
<p><code>set -e</code> will cause your shell script to abort when any command fails. Conversely, if you want to ignore any errors and have the script continue, use <code>set +e</code> (which is the default)</p>
<pre><code>set -e
false
echo &quot;You won't see this line&quot;
</code></pre>
<p>Unfortunately, <code>set -e</code> won't work if you're using pipes and still want to abort the script whenever an error happens anywhere along the pipe. You'll have to use <code>set -o pipefail</code> to enable that behavior.</p>
<pre><code>ps aux | grep java | awk '{print $2}' | tee pids.log # grepping for java fails, but the pipe continues
</code></pre>
<p>Sometimes, you might expect an error to happen and you want to do something about it. <code>$?</code> will contain the exit code of the last command executed. You can even assign it to a variable to use to exit later.</p>
<pre><code>set +e                                  # so that errors do not cause the script to abort
apt-get install $somethingReallyNew     # I know this is going to fail
apt_exit_code=$?
if [ &quot;$apt_exit_code&quot; != 0 ];then  
    echo 'whoops, need to run apt-get update first'
    exit $apt_exit_code
fi
</code></pre>
<p>You can also include these options in your shebang line at the top of your script: <code>#!/usr/bin/env bash -eo pipefail</code></p>
<p>Bash is pretty powerful for small scripts available on any linux machine like seeing running processes named 'foo' - <code>ps | grep foo</code>. I'd much prefer to use a higher-level language like node.js or ruby, but for small scripts that do one small thing - its hard to beat bash. Hopefully with these guidelines, you can get started immediately slapping together a quick and dirty tool in bash.</p>
<hr>
<p>For more bash scripting resources, I've found these links particularly helpful:</p>
<ul>
<li>An interactive explainer of shell commands - https://explainshell.com</li>
<li>Writing Safe Shell Scripts - https://sipb.mit.edu/doc/safe-shell/</li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>3 things you might see in your logs once your site is public</title>
      <link href="https://intricatecloud.io/2018/12/3-things-you-might-see-in-your-logs-once-your-site-is-public/"/>
      <updated>2018-12-03T06:27:27Z</updated>
      <id>https://intricatecloud.io/2018/12/3-things-you-might-see-in-your-logs-once-your-site-is-public/</id>
      <content type="html">
        <![CDATA[
      <p>You've finished deploying your website to its new domain. You start to see your normal user traffic, but then you also notice funny patterns in your access logs. Here's a few examples of things you might see in your access logs once your site or API is public in production.</p>
<ol>
<li>Automated vulnerability scanners from all over the world</li>
<li>Crawlers</li>
<li>SQL Injection attempts</li>
</ol>
<h2>Scanners</h2>
<p>You know what regularly shows up? Scanners. <a href="https://geekflare.com/open-source-web-security-scanner/">Lots and lots of open source scanners</a>. IPs originating from all over the world China, Ukraine, Russia. Randomly throughout the week, we'll see scanner traffic. What does scanner traffic look like?</p>
<p>There's a few signs you can use to tell:</p>
<ul>
<li>You have slightly elevated error rates for a sustained period of ~30 minutes and suddenly dies off. A mix of 4xx's and 5xx's.
<img src="https://intricatecloud-content.s3.amazonaws.com/wp-content/uploads/2018/11/04064624/Screen-Shot-2018-11-04-at-1.46.07-AM.png" alt="elevated error rates" title="Elevated error rates"></li>
<li>It's requesting paths that don't exist in your application.</li>
<li>Some scanners use a specific User-Agent header that identifies itself as a scanner</li>
</ul>
<p>You might see requests like this...</p>
<pre><code>x.x.x.x - - [07/Apr/2018:02:50:27 +0000] &quot;GET /wp-json/oembed/1.0/embed?url=..%2F..%2F..%2F..%2F..%2F..%2F..%2FWindows%2FSystem32%2Fdrivers%2Fetc%2Fhosts%00&amp;format=xml HTTP/1.0&quot; 404 132 &quot;http://www.zzzz.yyy/xxxx&quot; &quot;Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.6 Safari/534.57.2&quot;
</code></pre>
<p>In there is an encoded URL: <code>../../../../../../../Windows/System32/drivers/etc/hosts&amp;</code>. This type of request looks mainly like a recon attempt to see if it can even get to files in that directory.</p>
<p>If you're running a wordpress site, always be prepared to receive a barrage of known exploits to your site. We're not even running a wordpress website and we get this type of thing in our access logs. Included there is a <a href="https://blog.sucuri.net/2014/09/slider-revolution-plugin-critical-vulnerability-being-exploited.html">revslider_show_image vulnerability to steal wp-config file from 2014</a>.</p>
<pre><code>| count | uri |
|-------|-------------------------------------------------------------------------------|
| 12 | /wp-admin/admin-ajax.php |
| 6 | /wp-admin/admin-ajax.php?action=revslider_show_image&amp;img=../wp-config.php |
| 6 | /wp-admin/tools.php?page=backup_manager&amp;download_backup_file=../wp-config.php |
| 3 | /wp-admin/wp-login.php?action=register |
| 2 | /help/wp/wp-admin/setup-config.php |
| 1 | /help/new/wp-admin/setup-config.php |
| 1 | /help/wp-admin/setup-config.php |
| 1 | /wp-admin/js/password-strength-meter.min.js?ver=4.9.1 |
| 1 | /wp-admin/js/password-strength-meter.min.js?ver=4.9.2 |
</code></pre>
<p>Depending on how your application is hosted, you may even see increased latencies during a scan:
<img src="https://intricatecloud-content.s3.amazonaws.com/wp-content/uploads/2018/11/04065335/Screen-Shot-2018-11-04-at-1.53.20-AM.png" alt="increased latency graph" title="increased latency graph"></p>
<h2>Crawlers</h2>
<p>These are innocuous. Just Google and Bing indexing content. Typical internet being the internet. This particular crawler is nice enough to include a User Agent header that sends you to a site that says exactly what they do with all the data they collect. Check it out - http://www.exensa.com/crawl</p>
<pre><code>x.x.x.x [30/Mar/2018:04:39:15 +0000] &quot;GET /robots.txt HTTP/1.1&quot; 404 136 &quot;-&quot; &quot;Barkrowler/0.7 (+http://www.exensa.com/crawl)&quot;
</code></pre>
<h3>SQL injection attacks</h3>
<p>You might see these recon attacks - you can take a known public URL and add a URL-encoded SELECT statement to see if anything funny comes out in the response. Like this:</p>
<pre><code>x.x.x.x [11/Apr/2018:16:09:37 +0000] &quot;GET /REDACTED&amp;response_mode=%28SELECT%20%28CHR%28113%29%7C%7CCHR%28107%29%7C%7CCHR%28120%29%7C%7CCHR%28107%29%7C%7CCHR%28113%29%29%7C%7C%28SELECT%20%28CASE%20WHEN%20%285423%3D5423%29%20THEN%201%20ELSE%200%20END%29%29%3A%3Atext%7C%7C%28CHR%28113%29%7C%7CCHR%28122%29%7C%7CCHR%28112%29%7C%7CCHR%2898%29%7C%7CCHR%28113%29%29%29&amp;response_type=code&amp;scope=openid HTTP/1.1&quot; 302 0 &quot;-&quot; &quot;Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.202.0 Safari/532.0&quot;

</code></pre>
<p>There's a SQL injection string in there that is URL encoded. If you decode it, you get this</p>
<pre><code>(SELECT (CHR(113)||CHR(107)||CHR(120)||CHR(107)||CHR(113))||(SELECT (CASE WHEN (5423=5423) THEN 1 ELSE 0 END))::text||(CHR(113)||CHR(122)||CHR(112)||CHR(98)||CHR(113)))
</code></pre>
<p>These are tests for injection. If the response contains something out of the ordinary, they know they can do it. Not entirely sure whats going on here though.</p>
<h2>Defend against it</h2>
<p>Here's a few things that you can use to keep most of these people away.</p>
<ul>
<li>
<p>Use security groups (in AWS) / iptables to limit inbound traffic only to the ports that you need. If only and 80/443 are open, you're primarily prone to web application attacks instead of SSH/FTP/DNS and the like. Here's an <a href="https://aws.amazon.com/whitepapers/aws-security-best-practices/">AWS Whitepaper on Security Best Practices</a></p>
</li>
<li>
<p>Periodically review your logs to see what's going on. If you see SQL injection attempts, see which calls have returned a 200/500 - this means something potentially useful has made it to attacker.</p>
</li>
<li>
<p>Outright reject anything thats not even remotely close to your applications URLs - For example, you'd want to block Windows paths when your application is hosted on Linux like paths containing <code>System32</code>, <code>cmd.exe</code>, <code>C:\\</code> in your access logs.</p>
</li>
<li>
<p>Run a scan against your application locally and be one step ahead of hackers. Know what you're exposed to first. Check out these <a href="https://geekflare.com/open-source-web-security-scanner/">open source scanners</a></p>
</li>
<li>
<p>Many of these problems go away if you <a href="https://intricatecloud.wpengine.com/2018/04/creating-a-serverless-static-website/">host your static website on AWS S3 - here's a guide I wrote if you're doing it for the first time</a></p>
</li>
<li>
<p>if you're looking for an alternative to Wordpress? See this guide for <a href="https://intricatecloud.wpengine.com/2018/06/part-1-build-test-deploy-and-monitor-a-static-website-building-a-blog-with-gatsby/">building a blog with gatsby.js</a></p>
</li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>9 books that helped me navigate my first time being a tech-lead</title>
      <link href="https://intricatecloud.io/2018/12/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead/"/>
      <updated>2018-12-11T06:25:21Z</updated>
      <id>https://intricatecloud.io/2018/12/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead/</id>
      <content type="html">
        <![CDATA[
      <p>The tech lead was moving to another team for a long-term assignment, and I took over as the engineering manager/team lead. From the outside, the tech lead's job seemed doable, but I quickly realized I was getting in over my head. Unfortunately, my team was responsible for a lot of centralized infrastructure as well as day-to-day technical operations. I had no tech lead training, and how could there be? I was certain that the tech lead role was so different across companies, how could there be guidelines to it? In my previous role as the senior engineer on my team, I felt capable of tackling larger projects, but I only ever had 1 project to tackle. Now, I needed to manage 3-5 projects for my small team of 5 engineers.</p>
<p>The best I could do was do as the last person did which only gets you far enough to keep your head above water. I realized that the only way for me to get past this would be to hit the books, and learn all the management that I never learned in college. I read a lot that year. More than the past 3 years combined. The most helpful books I read all boiled down to 3 areas of the job that I (like many others) struggled with: dealing with team &amp; individual performance, delegating, and making my team a great team to work on.</p>
<h2>Dealing with performance</h2>
<p>One thing I felt affected the team was an under-performing team member. Surely like many others, I hadn't ever seen an effective performance review myself, or seen how other leads dealt with performance on their team (Thats probably a good thing, but unhelpful for me). While I know at a high level what you're supposed to do, like talk about and deal with the problem, I struggled to actually do it - how could I, a new lead, give feedback to someone who was previously a peer on my team? It's definitely awkward the first few times! Fortunately, in this area, there are people much smarter than I who have shared their experiences in great deal to help you get past these types of problems.</p>
<ul>
<li><a href="https://www.radicalcandor.com/">Radical Candor: Be a Kick-Ass Boss Without Losing Your Humanity by Kim Scott</a>
<ul>
<li>If you want to focus on giving great feedback - this provides a pretty powerful mental model for giving and receiving feedback without feeling like an asshole. It's focused on being upfront with people, lest you be plotting against them or humiliating them.</li>
</ul>
</li>
<li><a href="https://www.amazon.com/Leading-Teams-10-Challenges-Solutions-ebook/dp/B014K0ONV2">Leading Teams: 10 challenges and solutions by Mandy Flint, Elisabet Vinberg Hearn</a> and <a href="https://www.amazon.com/dp/1491932058/ref=cm_sw_r_cp_apa_AIHbCbDG3ZGZG">Debugging Teams: Better Productivity through Collaboration by Brian W. Fitzpatrick, Ben Collins-Sussman</a>
<ul>
<li>If you want to focus on getting yourself un-stuck from some problems on your team - these 2 books go in-depth on common issues that teams might have, and some step-by-step guidelines for how to deal with them.</li>
<li>This was an area I didn't feel comfortable asking any of the other managers about. We had many small teams, and we were all very friendly so it was hard to find people you can talk to. The next best thing was seeing what other more successful leaders had done in these scenarios.</li>
</ul>
</li>
<li><a href="https://youtu.be/RXxHBSuW4lM">Manager Conversation with Low Performers at UMCB on Youtube</a>
<ul>
<li>It's very rare you'll ever get to shadow someone else's performance review - they're private and personal by nature (or you're HR). If you've ever wondered what a &quot;good&quot; conversation about poor performance could look like, this video helped me a TON! There's some other related videos there that will show you what NOT to do, but it is great to see how you can quickly diffuse an awkward situation.</li>
</ul>
</li>
</ul>
<p>Lesson #1 summary: Be incredibly explicit about your expectations <strong>with their job</strong> so that they can never say, &quot;How was I supposed to know?&quot;</p>
<h2>Delegating</h2>
<p>Another awkward part about my job was telling people what to do - our team had a mission that we were mostly aligned on, but we don't always get to work on cool stuff and the work still has to get done on time. I had seen other people do it well, I had seen others do it poorly - but I wouldn't have been able to explain to you why. I felt awkward the first few times saying, &quot;hey roger, can you take a look at this issue?&quot; only to have that developer come back with something that I wasn't expecting (see Lesson #1).</p>
<p>When I was a fellow engineer on the team, I felt capable of working on bigger projects and making sure that we shipped the right things, but now, I was also accountable for all the projects the team was working on, not just mine. I had about twice as many things to do now, and the typical-engineer-turned-manager in a bout of frustration might ask, &quot;When am i supposed to code if I'm stuck in meetings and dealing with people all the time?&quot; It was difficult to juggle all the projects that I was now responsible for, as well as do the work on critical projects, and plan cross-team initiatives, and insert 20 more things here.</p>
<ul>
<li><a href="https://www.amazon.com/dp/0789428903/ref=cm_sw_r_cp_apa_aVHbCb5YVMG1V">How to Delegate (Essential Managers Series) by Robert Heller</a></li>
<li><a href="https://www.amazon.com/dp/0814414745/ref=cm_sw_r_cp_apa_-PHbCb0PQNVWB">The Busy Manager's Guide to Delegation (Worksmart Series) by Richard Luecke, Perry McIntosh</a></li>
</ul>
<p>To summarize these books: Be incredibly explicit about your expectations <strong>with projects/tasks</strong> so that they can never say, &quot;How was I supposed to know?&quot;</p>
<p>While these two books sound a little cheesy, they gave me a great framework and process for delegating. After reading them, I started blocking out time on my calendar for going through our projects and trying to match people's goals and motivations with the work we had to do. A bunch of us got AWS certificates, one engineer earned with a promotion, and an intern joined us full-time. And we built great stuff too.</p>
<h2>Making a good team</h2>
<p>One way to build a better team is to see more teams and how they operate, and use them as guiding examples for building your own teams. The catch is, barring you leaving your job and working elsewhere, you won't get to see that many teams and so you might not even know what your team would look like at its best! I absolutely loved reading these books because they provided case studies of real teams with real stories across some high-profile companies. Some people had really crappy times at their job, others didn't and they explain why in-depth.</p>
<p>These books are more focused on software engineering teams:</p>
<ul>
<li><a href="https://www.thekua.com/atwork/2014/09/talking-with-tech-leads/">Talking with Tech Leads: From Novices to Practitioners by Patrick Kua</a>
<ul>
<li>Patrick Kua is a great speaker <a href="https://www.youtube.com/results?search_query=patrick+kua">with some of his talks available on Youtube</a> about technical leadership covering things like <a href="https://www.youtube.com/watch?v=CjgWwmBW-bc">What I wish I knew as a first time Tech Lead</a> and <a href="https://www.youtube.com/watch?v=N9UPW-2wL5U">Geek's Guide to Leading Teams</a></li>
</ul>
</li>
<li><a href="https://www.amazon.com/dp/149195177X/ref=cm_sw_r_cp_apa_WOHbCbCER0C65">Building Software Teams: Ten Best Practices for Effective Software Development by Joost Visser, Sylvan Rigal, Gijs Wijnholds, Zeeger Lubsen</a></li>
</ul>
<p>These books cover teams in general:</p>
<ul>
<li><a href="https://www.amazon.com/dp/0596518021/ref=cm_sw_r_cp_apa_nJHbCb6B32BVA">Beautiful Teams: Inspiring and Cautionary Tales from Veteran Team Leaders by Andrew Stellman, Jennifer Greene</a></li>
<li><a href="https://www.amazon.com/dp/B01HUER114/ref=cm_sw_r_cp_apa_JDHbCb3K7MX7T">Extreme Teams: Why Pixar, Netflix, Airbnb, and Other Cutting-Edge Companies Succeed Where Most Fail by Robert Bruce Shaw</a></li>
<li><a href="https://www.amazon.com/dp/149195227X/ref=cm_sw_r_cp_apa_6RHbCbX8RG0AA">Scaling Teams: Strategies for Building Successful Teams and Organizations by David Loftesness, Alexander Grosse</a></li>
</ul>
<p>To summarize these in one sentence: Be incredibly explicit about your expecatations <strong>with the team's culture</strong> so that they can never say, &quot;How as I supposed to know?&quot;</p>
<h2>Wrapping up</h2>
<p>I had a great time leading my team for over a year. While at times, it was incredibly daunting to think how I would get through a particularly problematic week, my team would stay on track and over time, we were able to move to more proactive work. There's many different areas in management where you could spend days and day learning, but if you do 1 thing and nothing else...</p>
<p><strong>Tell your team to be incredibly explicit about their expectations from you as their lead so that you can never say, &quot;How was I supposed to know?&quot;</strong></p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Can I use jest to run webdriverio tests?</title>
      <link href="https://intricatecloud.io/2019/03/can-i-use-jest-to-run-webdriverio-tests/"/>
      <updated>2019-03-22T05:50:52Z</updated>
      <id>https://intricatecloud.io/2019/03/can-i-use-jest-to-run-webdriverio-tests/</id>
      <content type="html">
        <![CDATA[
      <h1>Can I use jest to run webdriverio tests?</h1>
<p>You have a web app and you're using jest for unit testing your components. You're checking out webdriverio to run your UI tests in javascript. Webdriverio comes with its own test runner if you want to use it. But you're already using jest in your codebase. Can you use jest to run your tests using webdriverio?</p>
<p>Short answer: yes. Long answer: maybe not? There's a lot of things that jest does well, but what you need out of unit tests is not the same thing you need out of UI tests. Here's a few reasons why jest is a little awkward for UI testing.</p>
<h2>jest is really good at running your tests fast</h2>
<p>jest can run all of your spec files in parallel, but you can't really customize it (<a href="https://github.com/facebook/jest/issues/6484">see this github</a>). Consider this example...</p>
<pre><code>describe('user tests', () =&gt; {
  beforeAll(() =&gt; {
    browser.login(user)
  })

  test('user goes to home page', () =&gt; {
    expect(browser.getText(selector)).toEqual('home')
    // this needs to run before the next spec
    browser.navigateToPage('profile')
  })

  test('user goes to profile', () =&gt; {
    expect(browser.getText(selector)).toEqual('profile')
  })
})
</code></pre>
<p>In this example, we do more things while re-using (for better or for worse) the same browser session.</p>
<p>If tests need to run in order, you need to use <a href="https://jestjs.io/docs/en/cli#runinband">--runInBand</a> when running jest. Being able to run tests fast is part of the point with jest, but running tests sequentially means that you don't get some of those benefits.</p>
<p>Webdriverio strikes a good balance in running your tests in parallel. In your wdio.config.js, you can set the <a href="http://v4.webdriver.io/guide/testrunner/configurationfile.html">max_instances parameter</a> to set a cap on how many tests you can run in parallel, i.e. if you set it to 2, only 2 tests will run in parallel at once.</p>
<h2>jest manages globals well</h2>
<p>It gets difficult to do things like setup a browser session before your tests start, and providing an active browser session among your tests. Jest has a hook called <a href="https://jestjs.io/docs/en/configuration.html#globalsetup-string">globalSetup</a> where you can do some initialization work before your tests start running. However... there's a catch. See this example using global setup</p>
<pre><code>// setup.js
var webdriverio = require('webdriver')

module.exports = async () =&gt; {
  // setup browser session
  global.browser = webdriverio.remote({
    desiredCapabilities: {
      browserName: 'chrome'
    }
  })
};

// spec.js

test('it fails', async () =&gt; { 
  browser.url('google.com') // browser is undefined
})
</code></pre>
<p>Any globals defined in <code>globalSetup</code> can only be accessed within <code>globalTeardown</code>. jest does a really great job at isolating context from different tests and making sure your tests don't rely on some globally set state.</p>
<p>Unfortunately it tends to get a bit in the way occasionally. In this case, the workaround is to create a new browser instance in each spec file that you need it which may or may not be a nuisance to add everywhere like in this example.</p>
<pre><code>var browser = require('./webdriverio-wrapper') // all initialization is done here

describe('happy path test', () =&gt; {
  beforeEach(() =&gt; {
    browser.init() // here we create the browser session
  })
  afterEach(() =&gt; {
    browser.end()
  })
})
</code></pre>
<h2>You lose synchronous calls to webdriver methods.</h2>
<p>One of the nicest selling points for webdriverio is that you can make synchronous calls to browser*:</p>
<pre><code>var text = browser.getText(selector)
expect(text).toEqual('hello world')
</code></pre>
<ul>
<li>there is some black magic going on to make this possible. Unfortunately, one of the drawbacks here is losing the context of your promise as described in <a href="https://github.com/webdriverio-boneyard/wdio-sync/issues/45#issuecomment-284230211">this Github issue for wdio-sync</a></li>
</ul>
<p>Instead of the promise/callback nightmare just to assert text on an element like you do with the base <a href="https://seleniumhq.github.io/selenium/docs/api/javascript/index.html">selenium-webdriver javascript bindings</a></p>
<pre><code>test('check text', (done) =&gt; {
  webdriver.findElement(webdriver.By.css(selector)).then((element) {
    return element.getText()
  }).then((text) {
    expect(text).toEqual('hello world')
    done()
  })
})
</code></pre>
<p>In this example, we need to</p>
<ol>
<li>Use 2 promises in order to get the text from an element</li>
<li>Since our assertion is inside a <code>then</code> block, we need to explicitly call <code>done()</code> when our test has finished.</li>
</ol>
<h2>Wrapping up</h2>
<p>Jest does a lot of things well and is a go-to for unit testing. After trying to set it up with webdriverio and a legacy codebase, there were enough gotcha's with jest to warrant using webdriverio's built in test runner (<a href="https://jasmine.github.io/2.0/introduction">which is jasmine and thats a great tool as well</a>). UI tests may or may not do a good job of running parallel, and jest likes to run tests in parallel for speed. Your testing codebase might make use of globals, but the way jest manages your global context could make your tests a lot more verbose. But these are all things that make Jest great for unit tests. The biggest price to pay is losing out on synchronous calls to browser (can be done with async/await nowadays).</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Debugging webdriverio tests with VSCode</title>
      <link href="https://intricatecloud.io/2019/03/debugging-webdriverio-tests-with-vscode/"/>
      <updated>2019-03-27T01:49:42Z</updated>
      <id>https://intricatecloud.io/2019/03/debugging-webdriverio-tests-with-vscode/</id>
      <content type="html">
        <![CDATA[
      <p>https://youtu.be/66E7y12GQaE</p>
<p>^ Update - check out my video walkthrough where I follow along with the post and show you how I set it up.</p>
<p>https://youtu.be/wvvIz60DNp4</p>
<p>^ EDIT 10/2019 – I’ve published a new video to my Youtube Channel where I debug a test and show you how to use browser.debug() alongside the VS Code Debugger. Check it out!</p>
<p>Am I using the correct selector? Which element is it actually clicking on? Debugging tests with webdriverio can get frustrating when you’re trying to figure out why your test is sometimes clicking the wrong elements or just plain not working. There’s 3 things that can help you drill down:</p>
<ul>
<li>adding many console.log statements to your test</li>
<li>using a debugger to step through the test one line at a time</li>
<li>using webdriverio’s <code>browser.debug()</code> to get an interactive js session with the browser
<ul>
<li>while this seems like the obvious choice, <a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-using-browser-debug-to-help-debug-your-tests/">using browser.debug has its own limitations that I describe here</a></li>
</ul>
</li>
</ul>
<p>I searched and had found this post of getting <a href="http://blog.likewise.org/2017/02/debugging-a-javascript-webdriverio-project-in-vscode/">webdriverio tests running inside of vscode</a> to help me step through a test file line by line. That post is using an older version of node, and newer versions of node (v8.11.x+) throw an error like below:</p>
<pre><code>node --debug=127.0.0.1:5859

(node:29011) [DEP0062] DeprecationWarning: &amp;#x60;node --debug&amp;#x60; and &amp;#x60;node --debug-brk&amp;#x60; are invalid. Please use &amp;#x60;node --inspect&amp;#x60; or &amp;#x60;node --inspect-brk&amp;#x60; instead.
</code></pre>
<p>There's 2 parts to get this to work with newer versions of node: VSCode configuration and webdriverio configuration</p>
<h2>VSCode configuration</h2>
<p><a href="https://code.visualstudio.com/docs/editor/debugging#_launch-configurations">See here for creating a VSCode Launch Configuration</a>. Adding this file will allow you to <a href="https://code.visualstudio.com/docs/editor/debugging">run the VSCode debugger</a> and step your code line by line.</p>
<p>Heres a working .vscode/launch.json file that you can use and adapt to fit your needs</p>
<pre><code>{
  &quot;type&quot;: &quot;node&quot;,
  &quot;request&quot;: &quot;launch&quot;,
  &quot;name&quot;: &quot;Run in debug&quot;,
  &quot;port&quot;: 5859,
  &quot;timeout&quot;: 60000,
  &quot;runtimeExecutable&quot;: &quot;${workspaceRoot}/node_modules/.bin/wdio&quot;,
  &quot;cwd&quot;: &quot;${workspaceRoot}&quot;,
  &quot;console&quot;: &quot;integratedTerminal&quot;,
  &quot;args&quot;: [
      &quot;--spec&quot;, &quot;main.js&quot;
  // &quot;--spec main.js&quot; will be passed to your executable as
  // &quot;wdio '--spec main.js'&quot; (which isn't what you want)
  ],
  &quot;env&quot;: {
      &quot;DEBUG&quot;: &quot;1&quot; 
      // use an environment variable to be able
      // to toggle debug mode on and off
  }
</code></pre>
<p>This configuration will run your <code>runtimeExecutable</code> and by defining the <code>&quot;port&quot;</code> variable, it will attempt a connection to port 5859. Once that connection is successfully made, you'll be able to set breakpoints and step through your test.</p>
<h2>webdriverio configuration</h2>
<p>On the webdriverio side, we need to tell it to listen for connections from a debugger on port 5859.</p>
<p>In your <code>wdio.conf.js</code> file:</p>
<pre><code>exports.config = {
  debug: true,
  execArgv: ['--inspect-brk=127.0.0.1:5859],

  // other wdio configuration
  specs: ['some/specs/here'],
  suites: {
    ....
  }
  ....
}
</code></pre>
<p>This snippet will start webdriverio and start listening for connections from a debugger on 127.0.0.1:5859 (which you did in your VSCode configuration). The program will stop at this point to wait for a debugger to connect, and if nothing connects, the command will fail.</p>
<p>Once you run it successfully, you should see this type of output</p>
<pre><code>/Users/dperez/workspace/wdio/node_modules/.bin/wdio &quot;--spec&quot; &quot;main.js&quot;

Debugger listening on ws://127.0.0.1:5859/9698ad4c-8d7d-447f-a259-1c566cd511d6
Debugger attached.
</code></pre>
<p>You'll see the program pause at this point until a connection is made. If you never see &quot;Debugger attached&quot;, it means VSCode has not connected to your program, and so you can't set breakpoints and debug.</p>
<p>If you run this as part of your CI process (Gitlab/Jenkins), you can make debug mode configurable. You can use <code>env</code> vars in your .vscode/launch.json configuration.</p>
<pre><code>...
&quot;console&quot;: &quot;integratedTerminal&quot;,
&quot;env&quot;: {
  &quot;DEBUG&quot;: 1
}
</code></pre>
<p>This will run your program with that env var set by using:</p>
<pre><code>DEBUG=1 /Users/dperez/workspace/wdio/node_modules/.bin/wdio &quot;--spec&quot; &quot;main.js&quot;
</code></pre>
<p>Then in your wdio.conf.js file:</p>
<pre><code>exports.config = {
  debug: process.env.DEBUG === '1',
  execArgv: process.env.DEBUG === '1' ? ['--inspect-brk=127.0.0.1:5859'] : []
  ...
  // remaining wdio options
  ...
}
</code></pre>
<p>This way, the program will only wait to attach to the debugger when you run it inside of VSCode (where the environment variable is set), and everywhere else it will run normally without it.</p>
<h2>Wrapping up</h2>
<p>Add these snippets to your configuration if you need to step through your program and watch how the program is executing. It sure beats writing tons of console.logs all over your code.</p>
<ul>
<li>Interested in using webdriverio's <code>browser.debug()</code> mode? <a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-using-browser-debug-to-help-debug-your-tests/">check out my post on using wdio's debug mode to play with the browser</a>.</li>
<li>In case you’re here because you’re trying to solve the dreaded <a href="https://intricatecloud.wpengine.com/2018/11/webdriverio-tips-element-wrapped-in-div-is-not-clickable/">‘element is not clickable at point’ error message, check out this post here</a></li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Running webdriverio tests using headless chrome in Docker</title>
      <link href="https://intricatecloud.io/2019/05/running-webdriverio-tests-using-headless-chrome-inside-a-container/"/>
      <updated>2019-05-07T03:53:12Z</updated>
      <id>https://intricatecloud.io/2019/05/running-webdriverio-tests-using-headless-chrome-inside-a-container/</id>
      <content type="html">
        <![CDATA[
      <p>Using headless chrome for your UI tests works great out of the box on your laptop, but it won’t work out of the box when you're trying to run your tests in Docker. One recent work project was getting webdriverio tests successfully running in a Docker container as part of a Jenkins pipeline. Locally, our tests worked fine, but when we ran them in docker, they became super flaky with errors showing random Chrome crashes, or “Chrome is unreachable” errors. Hope is not lost though - There is in fact a proper incantation of options to pass to Chrome to get it running successfully (and without random crashes).</p>
<p><strong>tl;dr</strong> Here's a snippet of <code>wdio.conf.js</code> showing our Chrome configuration. To see why some of these need to be included, read on.</p>
<pre><code>// wdio.conf.js
...
browserName: 'chrome',
chromeOptions: {
  args: [
    '--disable-infobars',
    '--window-size=1280,800',
  ].concat((function() {
    return process.env.HEADLESS_CHROME === '1' ? [
      '--headless',
      '--no-sandbox',
      '--disable-gpu',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage'] : [];
  })()),
...
</code></pre>
<h3>--disable-dev-shm-usage</h3>
<p>Add this flag if you're seeing the error <a href="https://github.com/elgalu/docker-selenium/issues/20">(like this guy did) “Session deleted because of page crash from tab crash”</a> in your logs when your tests fail (or a message along the lines of a page crash), or Chrome is unreachable.</p>
<p><strong>Why do we need this?</strong></p>
<p>There’s a tmp folder that Chrome uses related to its internal memory management that by default tries to use the /dev/shm directory. I don't quite understand the internals of Chrome, but here's <a href="https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#tips">some details about this from the puppeteer project</a>. Anyway, Docker containers have a <a href="https://docs.docker.com/engine/reference/run/">default size of 64MB for /dev/shm (cmd+f for --shm-size)</a>. Chrome quickly runs out of space in there and your page will then crash, taking your tests down with it. No bueno. There’s two solutions to this:</p>
<p>The easiest way to deal with this problem is to just not use it. There are some small cons, but a good rule of thumb is that unless you're trying to parallelize multiple concurrent Chrome sessions within one container, you're probably safe to disable it by adding <code>--disable-dev-shm-usage</code> to your Chrome options.</p>
<p>If you are though (<a href="https://github.com/GoogleChrome/puppeteer/issues/1834#issuecomment-381106859">like this engineer</a>), then you'll need to increase the size of your /dev/shm. For Docker containers,</p>
<ul>
<li>there’s a flag for increasing the shared memory size <code>--shm-size=1G</code> that you can pass to your docker run command.</li>
<li>you can also mount /dev/shm from your host machine to the docker container via <code>-v /dev/shm:/dev/shm</code></li>
</ul>
<h3>--disable-setuid-sandbox --no-sandbox</h3>
<p>If you see errors failing to connect to Chrome <a href="https://github.com/GoogleChrome/puppeteer/issues/370">like this</a>, you might need these flags.  Chrome uses sandboxing in order to protect your machine from random content on the internet (<a href="https://chromium.googlesource.com/chromium/src/+/HEAD/docs/linux_sandboxing.md">see details here</a>). If you’re running Chrome inside of a <em>disposable</em> container, then your container is already acting as a sandbox for Chrome, and so you don’t quite <em>need</em> chrome’s sandbox (your mileage may vary here depending on what you’re trying to do).</p>
<p>I typically see these two flags passed along together in various writeups online. <a href="https://groups.google.com/a/chromium.org/forum/m/#!topic/chromium-discuss/kfhzPq_Al94">This answer from the Chromium team</a> suggests that <code>--disable-setuid-sandbox</code> was only used in older versions of chrome.</p>
<h3>--disable-gpu</h3>
<p>The official Puppeteer docs suggest that this flag is only needed on Windows. Various snippets online usually use <code>--disable-gpu</code> alongside <code>--headless</code> because an earlier version of headless chrome had bugs with it.</p>
<h3>--disable-infobars</h3>
<p>This flag can help a minor annoyance - if you take a screenshot of the browser in your tests, then you’ll see a bar at the top of your browser page saying that Chrome is being controlled by automated process. This also pushes elements farther down the page. Its usually fine, but if it gets in your way, then try using this option. This flag was at one point removed around <a href="https://github.com/GoogleChrome/puppeteer/issues/1765#issuecomment-356761402">Jan 2018</a>, and then <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=820453#c6">added back</a>.</p>
<h2>Wrapping up</h2>
<p>Yes, Chrome can run headlessly and it can run inside of a container, and be stable (for the most part). You do however need the right concoction of parameters to get it working right, but hopefully this saves you a few hours of debugging random page crashes. Try out those Chrome options in your configuration (they will also work if you're using puppeteer or other selenium/chrome combo).</p>
<p>Running into different errors? See <a href="https://intricatecloud.wpengine.com/2019/03/debugging-webdriverio-tests-with-vscode/">how to attach the VSCode debugger to step through your UI tests</a> to help you narrow down the issue.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Rerunning test suites with webdriverio and a failed suite reporter</title>
      <link href="https://intricatecloud.io/2019/05/rerunning-test-suites-with-webdriverio-and-a-failed-suite-reporter/"/>
      <updated>2019-05-15T04:29:04Z</updated>
      <id>https://intricatecloud.io/2019/05/rerunning-test-suites-with-webdriverio-and-a-failed-suite-reporter/</id>
      <content type="html">
        <![CDATA[
      <p>UI browser tests can be flaky, and it can be really frustrating to have to re-run a test suite because 1/50 tests failed and most likely, it'll work on the next run. webdriverio offers retries but only at the individual spec level <a href="http://v4.webdriver.io/guide/testrunner/retry.html">as seen on their docs</a> which doesn't help you <a href="https://github.com/webdriverio/webdriverio/issues/2521">re-run all the specs in 1 file</a>. webdriverio lets you hook into the test reporter <a href="http://v4.webdriver.io/guide/testrunner/reporters.html">using Custom Reporters</a>, and we can create a tool that lets us re-run the specs that failed</p>
<p>This is a solution proposed by an answer to <a href="https://github.com/webdriverio/webdriverio/issues/2521#issuecomment-367190751">this Github issue for webdriverio</a>. Since so many things are pluggable in the framework, we can hook into the test reporter, find all the tests that failed, get their filenames, and then re-run webdriverio with those file names.</p>
<h2>finding all the tests that failed:</h2>
<p>you can get the right info by responding to the <code>test:fail</code> and <code>end</code> events.</p>
<p>You can collect all the test failed events....</p>
<pre><code>this.on('test:fail', (data) =&gt; {
      this.failures.push(data);
    });
</code></pre>
<p>Then build a report at the end containing all the failed test files.</p>
<pre><code>this.on('end', () =&gt; {
  console.log(this.failures);

});
</code></pre>
<p>When each test fails, you'll get a test result object that looks like this:</p>
<pre><code>{ cid: '0-0',
  uid: 'test1spec0',
  event: 'test:fail',
  title: 'test1',
  pending: false,
  parent: 'testfile1',
  type: 'test',
  file: '',
  err: 
   { matcherName: '',
     message: 'Failed',
     stack: 'Error: Failed\n    at &lt;Jasmine&gt;\n    at UserContext.it (/path/to/testfile1.js:25:7)\n    at &lt;Jasmine&gt;',
     passed: false,
     expected: '',
     actual: '' },
  duration: 15294,
  runner: 
   { '0-0': 
      { maxInstances: 1,
        browserName: 'chrome',
        chromeOptions: [Object] } },
  specs: 
   [ '/path/to/testfile1.js' ],
  specHash: '3ad3fe8fe4af1ca17782a5b3fdc65e54' }
</code></pre>
<p>You can save it as JSON if you want to parse it with <code>jq</code> or another tool. You can also just grab the info you need, like all the file names with tests the failed:</p>
<pre><code>// Save as JSON
let dir = path.resolve(this.outputDir);
let filepath = path.join(dir, 'failed-suite-reporter.json');
mkdirp.sync(dir);
fs.writeFileSync(filepath, JSON.stringify(this.failures));

// Save filenames
dir = path.resolve(this.outputDir);
filepath = path.join(dir, 'failed-specs-to-rerun.txt');
const specs = _.uniq(_.flatMap(this.failures, (f) =&gt; f.specs)).join(',');
fs.writeFileSync(filepath, specs);
</code></pre>
<p>That txt file will look like <code>/path/to/testfile1.js,/path/to/testfile2.js</code> for example.</p>
<h2>Re-running failed tests</h2>
<p>At this point, we have a listing of all the test files that had failed tests, and those results are saved to a file. Now we'll need a wrapper script that runs our wdio suite, and then re-runs it if it fails.</p>
<p>You can add a script to your repo, and link it to the <code>npm test</code> shortcut.</p>
<pre><code>wdio --spec all_my_specs/**
test_exit_code=$?

if [[ $test_exit_code != 0 ]]; then
  failed_spec_files='./output/failed-specs-to-rerun.txt'
  wdio --spec $(cat $failed_spec_files);
  exit $?
fi

exit $test_exit_code
</code></pre>
<p>Something to be aware of:</p>
<p>If you're using another test reporter already that puts a bunch of files in an <code>outputDir</code>, such as the <a href="http://v4.webdriver.io/guide/reporters/junit.html">JUnit Reporter</a>, re-running the tests will also <strong>overwrite</strong> the files from the first run.</p>
<p>That means, if you have 50 specs and 1 of them failed, and you re-run the 1 that failed, your JUnit report will only show the 1 test. Depending on how you use your test report, you might have to make a copy of the files in your <code>outputDir</code> before re-running the tests again.</p>
<p>tl;dr here's a snippet of what you need in a failed test suite reporter.</p>
<pre><code>const events = require('events');
const path = require('path');
const fs = require('fs');
const mkdirp = require('mkdirp');
const _ = require('lodash');

// Pulled from this very helpful Github comment
// https://github.com/webdriverio/webdriverio/issues/2521#issuecomment-367190751
class FailedSuiteReporter extends events.EventEmitter {
  static get reporterName() {
    return 'FailedSuiteReporter';
  }

  constructor(baseReporter, config, options = {}) {
    super();
    this.failures = [];
    this.outputDir = options.outputDir || './output/';

    this.on('test:fail', (data) =&gt; {
      this.failures.push(data);
    });

    this.on('end', () =&gt; {
      const specs = _.uniq(_.flatMap(this.failures, (f) =&gt; f.specs)).join(',');

      let dir = path.resolve(this.outputDir);
      let filepath = path.join(dir, 'failed-suite-reporter.json');
      mkdirp.sync(dir);
      fs.writeFileSync(filepath, JSON.stringify(this.failures));

      dir = path.resolve(this.outputDir);
      filepath = path.join(dir, 'failed-specs-to-rerun.txt');

      fs.writeFileSync(filepath, specs);
      console.log('Wrote report to ', filepath);
    });
  }
}

/**
 * Expose Custom Reporter
 */
exports = module.exports = FailedSuiteReporter;

</code></pre>
<h2>Wrapping up</h2>
<p>There’s no support for re-running failed suites in webdriverio out of the box which can make re-running flaky test suites take a lot longer. However, using Custom Reporters, we can get the information we need in order to re-run tests on our own. You can grab that test reporter above and include it in your project to start re-running only the test suites that failed.</p>
<p>A few other helpful resources:</p>
<ul>
<li>
<p>Seeing errors with Chrome crashing randomly when running your tests in Docker? <a href="https://intricatecloud.wpengine.com/2019/05/running-webdriverio-tests-using-headless-chrome-inside-a-container/">Check out this post</a> to see how to configure Chrome for docker.</p>
</li>
<li>
<p>Sometimes, you just need to step through your tests with a debugger. <a href="https://intricatecloud.wpengine.com/2019/03/debugging-webdriverio-tests-with-vscode/">See here for how to connect it to VSCode</a></p>
</li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Adding Google Sign-in to your webapp - Hello World</title>
      <link href="https://intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-pt-1/"/>
      <updated>2019-07-12T04:20:52Z</updated>
      <id>https://intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-pt-1/</id>
      <content type="html">
        <![CDATA[
      <p>[embed]https://youtu.be/KwOmVpd1DUA[/embed]
*Check out the video to see me follow along with the code.</p>
<p>Gotten stuck in a rabbit hole figuring out how to add &quot;Log In with Google&quot; to your web app?  In this series, I'll cover:</p>
<ul>
<li>why you might want to use the Sign-In for Website JS library, and getting started with it</li>
<li>Adding the library via javascript</li>
<li>adding it to an existing project (angular &amp; react)</li>
</ul>
<p>Their docs give you a decent place to start if you know what you're looking for, but there's a few areas where their docs and other guides online can be confusing. There's also minimal guidance as to how to use it within existing projects.</p>
<ul>
<li>Am I supposed to be following the <a href="https://developers.google.com/identity/protocols/OAuth2UserAgent#example">Google Identity docs</a> or do I need <a href="https://developers.google.com/identity/sign-in/web/">Google Sign-in for Web?</a>?</li>
<li>Wait, why are those 2 guides so different? They even load different libraries!</li>
<li>This should be simple! I'm just trying to load a users avatar!!</li>
</ul>
<p>The answer to &quot;How do I add Log in with Google?&quot; boils down to 1 question you need to be able to answer: What exactly are you trying to do?</p>
<ul>
<li>Do you just want to be able to see the users name and picture, maybe their email? Use the <a href="https://developers.google.com/identity/sign-in/web/">Sign-In for Websites library</a> (full walkthrough below).</li>
<li>Do you have your own user database and want to offer Google login as an extra option? This is Federated Login and you'd want to use <a href="https://developers.google.com/identity/protocols/OpenIDConnect">their OpenID Connect protocol</a>. This is used by platforms like auth0, firebase, oneidentity.</li>
<li>Do you want to be able to interact with the users Google account and do things like see their calendar, or create a Google doc? You'll need to use <a href="https://developers.google.com/identity/protocols/OAuth2UserAgent#example">their OAuth workflow</a>.</li>
</ul>
<p>For the purposes of this series, I'm going to explore when you should use the Sign-In for Websites library which uses the OpenID Connect protocol (also used by Federated Login systems) - this library can be easily misunderstood, but is still incredibly useful. <strong>The goal of this authentication library is to let you identify a user with their Google account. Thats it.</strong></p>
<h2>So when should I use it?</h2>
<p>Good reasons to use it:</p>
<ul>
<li>you have some content stored on a backend service that you deliver via an API only after the user has signed in</li>
<li>you only have to support Google/G-Suite users</li>
<li>you dont need to authorize users (e.g. allow some users to be admins)</li>
<li>your site is public</li>
<li>you have a single-page app</li>
</ul>
<p>Use cases where it's worth considering federated login:</p>
<ul>
<li>you have <strong>pre-existing user content</strong> stored on a backend service</li>
<li>you have an internal site that you want to restrict to users from a specific domain. (e.g. only users from @example.com should see it)</li>
<li>you want to prevent people from seeing your page unless they've logged in. The best you can do with this library is show/hide elements on the page if the user logged in, but thats enough if you only load data from an API after a user has logged in.</li>
</ul>
<p>The library is designed to be used with HTML/JS and only interacts with your page via the &quot;Sign in with Google&quot; button. You can integrate this with other frameworks like Angular/React which I'll be covering in the next part of this series.</p>
<h2>Adding Google Sign-in step-by-step</h2>
<h3>1. Hello World HTML</h3>
<p>To start with, all you need is an index.html file.</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;

&lt;head&gt;
  &lt;title&gt;Google Auth Demo&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;Welcome to the Demo&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3>2. Add the Google Sign-In Button</h3>
<p>Before adding in the button, you first need to create a client ID/secret which is a way of identifying that you, the developer, are allowing users to get the identity information from Google and delivered to your website.</p>
<h3>Creating credentials for Sign-In</h3>
<ul>
<li>Go to the Google API Console - https://console.developers.google.com/apis/dashboard</li>
<li>Create a new project, or use an existing project if you already have one set up.</li>
<li>Then click on Credentials -&gt; Create Credentials -&gt; OAuth Client ID</li>
</ul>
<p>Here's what I put in for this demo:</p>
<p>Name: google-auth-demo
Authorized Javascript Origins: http://localhost:8080
Authorized Redirect URIs: empty</p>
<p>*A note about the Javascript origins: if you have a plain HTML file that you load in your browser using a file path like /home/dperez/index.html, this won't work. You'll need to &quot;serve&quot; your website on your computer so that you have a URL, even if its just localhost. You can use <code>python -m SimpleHTTPServer 8080</code> (which is commonly available) to serve up your current directory or you can use an npm package like <a href="https://www.npmjs.com/package/http-server">http-server</a>.</p>
<p>You'll then get a Client ID &amp; Client Secret. These are 2 identifiers for your Oauth Client. At this point, you have authorized that Client ID to accept logins and return you the users information. Copy them down. If you accidentally click out of the screen, you can always copy them again later.</p>
<h3>Add the library + credentials + button to your HTML</h3>
<p>In your index.html page, add the following to your HTML page:</p>
<pre><code>...
&lt;head&gt;
  ...
  &lt;meta name=&quot;google-signin-client_id&quot; content=&quot;your-client-id-goes-here&quot;&gt;
  &lt;script src=&quot;https://apis.google.com/js/platform.js&quot; async defer&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;Welcome to the Demo&lt;/h1&gt;
  &lt;div class=&quot;g-signin2&quot;&gt;&lt;/div&gt;
&lt;/body&gt;
</code></pre>
<p>The script tag grabs the library from Google that reads your <code>&lt;meta&gt;</code> tag to use as the Client ID, and then automatically re-styles the button with the CSS class <code>.g-signin2</code>.</p>
<p>At this point, if you refresh your page, you should see a pretty Sign-In button. If you click it, you'll go through your Google Login flow in a popup, and you'll finally land back on your site, and the button will say, &quot;Signed In&quot;.</p>
<p>Great, we're most of the way there! But in its current form, this is still useless.</p>
<h2>3. Identify the user</h2>
<ol>
<li>Update the g-signin2 div to include the <code>data-onsuccess</code> attribute:</li>
</ol>
<pre><code>&lt;div class=&quot;g-signin2&quot; data-onsuccess=&quot;onSignIn&quot;&gt;&lt;/div&gt;
</code></pre>
<p>That attribute contains the name of the function that will be called once a user has successfully logged in with Google.</p>
<ol start="2">
<li>Create a function called &quot;onSignIn&quot;.</li>
</ol>
<pre><code>&lt;body&gt;
  ...
  &lt;script&gt;
    function onSignIn(googleUser) {
      // get user profile information
      console.log(googleUser.getBasicProfile())
    }
  &lt;/script&gt;
&lt;/body&gt;
</code></pre>
<p>Your <code>onSignIn</code> function will be called with an argument containing the information provided by Google. If you refresh the page, you'll notice that</p>
<ol>
<li>You're automatically signed in</li>
<li>The button gets updated very shortly after refresh (1s delay)</li>
</ol>
<p>If you open up your javascript console, you'll see the users information printed:</p>
<pre><code>{
  Eea: &quot;108716981921401766503&quot;
  Paa: &quot;https://lh3.googleusercontent.com/-y_ba58pC4us/AAAAAAAAAAI/AAAAAAAAAYE/wMGKOxlWR90/s96-c/photo.jpg&quot;
  U3: &quot;perez.dp@gmail.com&quot;
  ig: &quot;danny perez&quot;
  ofa: &quot;danny&quot;
  wea: &quot;perez&quot;
}
</code></pre>
<p>This is the basic profile containing a series of values that identify me as a user. Under any normal circumstances, this object would have fields like <code>name</code> or <code>email</code>, but for some unknown reason (I wish I had the answer but <a href="https://github.com/google/google-api-javascript-client/issues/440">they havent given an answer either</a>), its gibberish - but at least its consistent and hasn't changed.</p>
<p>You can either get the data directly by doing <code>googleUser.getBasicProfile()['U3']</code> or use the more human-readable approach by using the functions like <code>googleUser.getBasicProfile().getName()</code> or <code>googleUser.getBasicProfile().getEmail()</code>. (<a href="https://developers.google.com/identity/sign-in/web/reference#googleusergetid">See here for the javascript client api reference docs</a>)</p>
<h2>4. Sign out</h2>
<p>Add a sign out button after the user has signed in by adding a button in your index.html and the click handler in your javascript.</p>
<p>index.html</p>
<pre><code>&lt;body&gt;
...
  &lt;button onclick=&quot;signOut()&quot;&gt;Sign out&lt;/button&gt;
&lt;/body&gt;
</code></pre>
<pre><code>function signOut() {
  gapi.auth2.getAuthInstance().signOut().then(function() {
    console.log('user signed out')
  })
}
</code></pre>
<p>Great! At this point, we've added a Sign-In with Google button and we can identify the user by name/email.  Also, thats it. Thats all this library can help you do.</p>
<p>...but what about basic things like saving users data to the backend? or showing an admin page?! Why isn't this in the docs?! That'll be covered on the next post coming 7/22 - using the js library to add it to your site without HTML.</p>
<p>Update: <a href="https://www.intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-using-the-js-library/">Here's the link for part 2 of the series - Using the js library</a></p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Adding Google Sign-in to your webapp - using the JS library</title>
      <link href="https://intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-using-the-js-library/"/>
      <updated>2019-07-22T05:19:41Z</updated>
      <id>https://intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-using-the-js-library/</id>
      <content type="html">
        <![CDATA[
      <p>In the first part of the series, we decided to use the Google Sign-In for websites library to allow you to show some info about the user using javascript. In that part, we use the default Google Sign-In workflow with a default button.</p>
<p>In this part, we'll be going over how to use the <code>gapi</code> library to configure Sign-In and then actually sign-in the user, as well as a few snippets for handling some common user scenarios.</p>
<h2>create your own sign in button</h2>
<ol>
<li>Load the api.js library instead of platform.js :shrug: (not sure why they're different)</li>
</ol>
<p>Change <code>https://apis.google.com/js/platform.js</code> to <code>https://apis.google.com/js/api.js?onload=onLibraryLoaded</code></p>
<p>Here, we configure our Sign-In client once the library has loaded using the <code>?onload=onLibraryLoaded</code> callback that you can provide via the URL. <code>api.js</code> will add a global variable called <code>gapi</code>.</p>
<ol start="2">
<li>Add a button to your index.html with a button click handler</li>
</ol>
<p><code>&lt;button onclick=&quot;onSignInClicked()&quot;&gt;Sign in with button onClick&lt;/button&gt;</code></p>
<ol start="2">
<li>Add the following to your script tag in index.html to handle the button click</li>
</ol>
<pre><code>function onLibraryLoaded() {
    gapi.load('auth2', function() {
        gapi.auth2.init({
            client_id: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
            scope: 'profile'
        })
    })
}

function onSignInClicked() {
    gapi.load('auth2', function() {
        gapi.auth2.signIn().then(function(googleUser) {
          console.log('user signed in')
        }, function(error) {
            console.log('user failed to sign in')
        })
    })
}
</code></pre>
<p>We can then access the <code>gapi.auth2</code> library and initialize it using our <code>client_id</code> from the Developer Console.</p>
<h2>how to handle some common user scenarios</h2>
<ul>
<li>listening for when the user has signed in</li>
<li>checking if the user is logged in</li>
<li>getting the users info</li>
<li>only allow users from a particular domain to log in</li>
<li>only allow certain users to log in</li>
<li>hide content until after a user logs in</li>
<li>sending your ID token to a backend (if you have one)</li>
</ul>
<h3>using sign-in listeners</h3>
<p>In the above example, we can only run some code after we initialize and sign in the user. But what if you have different parts of the page in different files, each showing something different depending on the user? (this might be the case if you're using components)</p>
<pre><code>class UserAvatarComponent extends React.Component {
    ...
    componentDidMount() {
        gapi.load('auth2', function() {
            gapi.auth2.isSignedIn.listen(function(isSignedIn) {
                console.log('user signed in ', isSignedIn)
                this.setState({status: isSignedIn})
            })
        })
    }    
}
</code></pre>
<h3>checking if the user is signed in</h3>
<pre><code>function isUserSignedIn() {
  gapi.load('auth2', function() {
      var isSignedIn = auth2.isSignedIn.get();
      console.log('is signed in? ', isSigned In)
  })
}
</code></pre>
<p>A few things to note about using this function:</p>
<ul>
<li>The Sign-In library will, by default, sign you in automatically if you've already signed in.</li>
<li>If you refresh the page, even after the user has signed in, then on first laod, you'll get  <code>auth2.isSignedIn.get() === false</code></li>
<li>After the user is signed in automatically (usually takes a sec), then <code>auth2.isSignedIn.get() === true</code></li>
<li>Depending on how you handle your login UI, your user might see that they are not logged in for a hot second. It's helpful to use the <code>isSignedIn.listen()</code> callback if you want to know the precise moment this happens.</li>
</ul>
<h3>getting the users info</h3>
<pre><code>function showCurrentUserInfo() {
  gapi.load('auth2', function() {
      var googleUser = auth2.currentUser.get()
      console.log('users info ', googleUser)
  })
}
</code></pre>
<h3>only allowing users from a particular domain to login</h3>
<p>This is a little bit of a hack and is probably easy to circumvent, but you can use the <code>getHostedDomain()</code> method to get the G-Suite domain the user comes from. If the user does not have a G-Suite domain, then it'll be blank.</p>
<pre><code>function onSignIn(googleUser) {
    if(googleUser.getHostedDomain() !== 'mysite.com') {
        // show a Not Authorized message
    } else {
        // show the users dashboard
    }
}
</code></pre>
<h3>only allowing certain users to login</h3>
<p>This is even more of a hack, but seems to be the only you can do it from within javascript. You really shouldn't. Don't know why I'm including it. The brute method.</p>
<pre><code>function onSignIn(googleUser) {
    var profile = googleUser.getBasicProfile()
    if(profile.getEmail() === 'admin@example.com' ||
       profile.getEmail() === 'client@example.com') {
           // show the user dashboard
    } else {
        // show a Not Authorized message
    }
}
</code></pre>
<h3>hiding content until after the user logs in</h3>
<p>This is also a hack. This is easy to workaround if you fidget with the CSS in your browser, but can work if it fits your use case. The reason this is a bad idea is that in a static website, all of the information thats available in your HTML is available to the user. DO NOT USE THIS if you have actual sensitive information to hide. It is a good candidate for showing your favorite cat pictures.</p>
<pre><code>&lt;body&gt;
...
  &lt;div id=&quot;greeting&quot;&gt;
    You are not authorized to view this content
  &lt;/div&gt;
  &lt;div id=&quot;dashboard&quot;&gt;
    ...
  &lt;/div&gt;
  &lt;script&gt;
    // hide initially
    $('#dashboard').hide()
    
    function onSignIn(googleUser) {
      setTimeout(function() {
        // show the content
        $('#greeting').hide()
        $('#dashboard').show()
      }, 1000);
    }
  &lt;/script&gt;
&lt;/body&gt;
</code></pre>
<h3>send the token to identify the user (not their ID)</h3>
<p>If you're making a backend request, you'll want to send the users ID token to your backend as an Authorization header. On your backend, you'd then be able to validate and decode the ID token (<a href="https://developers.google.com/identity/sign-in/web/backend-auth">see here for examples</a>).</p>
<pre><code>$.ajax({
  url: 'myapi/example',
  headers: {'Authorization': googleUser.getAuthResponse().id_token)},
})
</code></pre>
<h2>Conclusion</h2>
<p>In this post, we've seen how you can configure the Google Sign-In library via javascript, and use it to do things like get the users info and check if they're signed in (theres some nuances to be aware of with the login flow). For the last part of this series, we'll look at some examples of how you might use google sign-in in a React and Angular application. <a href="https://www.intricatecloud.io/2019/08/adding-google-sign-in-to-your-webapp-a-react-example/">Check it out here</a></p>
<p>Demo code is available on <a href="https://github.com/intricatecloud/google-sign-in-demo/blob/master/gapi-demo.html">Github intricatecloud/google-sign-in-demo</a>. Replace <code>YOUR_CLIENT_ID</code> with your client ID, and you'll be able to see the sign in buttons in action.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Adding Google Sign-in to your webapp - a React example</title>
      <link href="https://intricatecloud.io/2019/08/adding-google-sign-in-to-your-webapp-a-react-example/"/>
      <updated>2019-08-07T02:12:25Z</updated>
      <id>https://intricatecloud.io/2019/08/adding-google-sign-in-to-your-webapp-a-react-example/</id>
      <content type="html">
        <![CDATA[
      <p>[embed]https://www.youtube.com/watch?v=iND-Epl8nz0[/embed]
*Check out the video to see me follow along with the code.</p>
<p>In this next part of the series, I'll be walking you through an implementation of google sign-in with a simple react app and a bonus react-router example.</p>
<p>Up until now, we've seen 2 different hello world examples of how to add google sign-in on the front-end - using plain HTML and vanilla JS. It's been all nice and dandy for a hello world, but one thing thats been missing while I was figuring out google sign-in is what a working implementation looks like - especially in React.</p>
<p>*There is a <a href="https://github.com/anthonyjgrove/react-google-login">react-google-login component</a> that configures all of google sign-in behind a <code>&lt;GoogleLogin&gt;</code> tag. It's quite useful and I've used it in a few instances - my one complaint is that you can't get at the return value of the <code>gapi.auth2.init()</code> method. This post will show whats going on under the covers if you prefer not to use a library.</p>
<h2>creating a new react app with google sign-in</h2>
<p>First - create the app <code>create-react-app google-auth-demo</code>. The files we'll mainly be working with are App.js and index.html.</p>
<p>Add the google sign-in script tag to your <code>public/index.html</code></p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>
  ...
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://apis.google.com/js/api.js<span class="token punctuation">"</span></span> <span class="token attr-name">async</span> <span class="token attr-name">defer</span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>
  ...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span></code></pre>
<h2>add the login button</h2>
<p>In App.js - add some state to keep track of when the user has signed in</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token literal-property property">isSignedIn</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<p>Add the button to the component</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App-header<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>logo<span class="token punctuation">}</span></span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App-logo<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>logo<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
    
          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">You are not signed in. Click here to sign in.</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">
          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loginButton<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">Login with Google</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Wait, how do I avoid showing this if the user is signed in? We can use the state to conditionally show it.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token function">getContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>isSignedIn<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">hello user, you're signed in </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">You are not signed in. Click here to sign in.</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loginButton<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">Login with Google</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
    <span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
  
<span class="token punctuation">}</span>

<span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>      
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App-header<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>logo<span class="token punctuation">}</span></span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App-logo<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>logo<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span><span class="token plain-text">Sample App.</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><span class="token plain-text">

        </span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">           
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<ul>
<li>Since conditionals are a little hard to write with inline JSX, I've pulled out the conditional block to another method to provide the component that we want.</li>
</ul>
<p>At this point, you'll have a button that does nothing (the best type of button) and you'll see the &quot;You are not signed in&quot; message</p>
<h2>add sign-in</h2>
<p>To finish setting up google sign-in, you'll want to initialize the library using <code>gapi.auth2.init()</code>. A good place to do that is inside of <code>componentDidMount()</code> callback.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token function">componentDidMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  window<span class="token punctuation">.</span>gapi<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token string">'auth2'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>auth2 <span class="token operator">=</span> gapi<span class="token punctuation">.</span>auth2<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token literal-property property">client_id</span><span class="token operator">:</span> <span class="token string">'YOUR_CLIENT_ID'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>To use the default styling, use the <code>gapi.signin2.render</code> method when initializing your component.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token function">onSuccess</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token literal-property property">isSignedIn</span><span class="token operator">:</span> <span class="token boolean">true</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token function">componentDidMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  window<span class="token punctuation">.</span>gapi<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token string">'auth2'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>auth2 <span class="token operator">=</span> gapi<span class="token punctuation">.</span>auth2<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token literal-property property">client_id</span><span class="token operator">:</span> <span class="token string">'YOUR_CLIENT_ID.apps.googleusercontent.com'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    window<span class="token punctuation">.</span>gapi<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token string">'signin2'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// render a sign in button</span>
      <span class="token comment">// using this method will show Signed In if the user is already signed in</span>
      <span class="token keyword">var</span> opts <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
        <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">50</span><span class="token punctuation">,</span>
        <span class="token literal-property property">onsuccess</span><span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">onSuccess</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span>
      gapi<span class="token punctuation">.</span>signin2<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token string">'loginButton'</span><span class="token punctuation">,</span> opts<span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>When using this method, the button will automatically show whether you're signed in, but the <code>onSuccess</code> callback won't actually run unless the user clicks it when it says &quot;Sign In&quot;. Otherwise, you are logged in automatically. One way to hook into the end of that auto sign in process is by adding a callback to the promise returned by <code>gapi.auth2.init</code>:</p>
<pre class="language-javascript"><code class="language-javascript">window<span class="token punctuation">.</span>gapi<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token string">'auth2'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>auth2 <span class="token operator">=</span> gapi<span class="token punctuation">.</span>auth2<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token literal-property property">client_id</span><span class="token operator">:</span> <span class="token string">'YOUR_CLIENT_ID.apps.googleusercontent.com'</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>

  <span class="token keyword">this</span><span class="token punctuation">.</span>auth2<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token literal-property property">isSignedIn</span><span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>auth2<span class="token punctuation">.</span>isSignedIn<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<h2>making a &quot;protected&quot; route</h2>
<p>If you're using react-router and you want to add a &quot;protected&quot; route to your React app, you can hijack the <code>render</code> prop of a <code>&lt;Route&gt;</code>. You can do something like this:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token function">authCheck</span><span class="token punctuation">(</span><span class="token parameter">props<span class="token punctuation">,</span> Component</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>auth2<span class="token punctuation">.</span>isSignedIn<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Component</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">UnauthorizedPage</span></span><span class="token punctuation">/></span></span>

<span class="token punctuation">}</span>

<span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token operator">...</span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Route</span></span> <span class="token attr-name">path</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/home<span class="token punctuation">"</span></span> <span class="token attr-name">render</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">authCheck</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> HomePage<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">/></span></span>
  <span class="token operator">...</span>
<span class="token punctuation">}</span></code></pre>
<p>By hooking into the render property on <code>&lt;Route&gt;</code>, you can dynamically define what component will load when you try to access that Route.</p>
<p>This is the strategy employed by the <a href="https://www.npmjs.com/package/react-private-route">react-private-route project</a> library to make it a little bit easier to write, definitely worth checking out.</p>
<p>If you'd like to check out what this pattern looks like, you can check out my video below to see an example app with private routes:
[embed]https://youtu.be/BC4QEliL-4Y[/embed]</p>
<h1>conclusion</h1>
<p>If you're implementing google sign-in in a React app - check out <a href="https://github.com/intricatecloud/google-sign-in-demo/tree/master/react/google-auth-demo">my github repo intricatecloud/google-sign-in-demo</a> to see all the code above in a working setup.</p>
<p>Throughout this 3-part series, we've covered going from <a href="https://www.intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-pt-1/">a hello-world example of google sign-in</a>, to <a href="https://www.intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-using-the-js-library/">using the javascript library</a> to do some hacky things. Now, we've reviewed all the code you need to integrate with the Google Sign-In button.</p>
<p>EDIT: 12/2020 - I've made a new tutorial to walk you through stepping up your user's experience by <a href="https://www.intricatecloud.io/2020/12/passwordless-sign-in-with-google-one-tap-for-web/">setting up passwordless login with Google One Tap Sign In for Web</a></p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>That time I needed to create 20k short links, and how I did it on AWS</title>
      <link href="https://intricatecloud.io/2019/09/that-time-i-needed-to-create-20k-shortlinks-and-how-to-create-them-on-aws/"/>
      <updated>2019-09-08T06:25:03Z</updated>
      <id>https://intricatecloud.io/2019/09/that-time-i-needed-to-create-20k-shortlinks-and-how-to-create-them-on-aws/</id>
      <content type="html">
        <![CDATA[
      <p>This problem landed on my lap awhile ago, and I've seen it come up time and time again so I wanted to share how we went about it. Our marketing team was submitting one of our products for a review for a client, it has a ton of content. There was a long list of deep-links in the product that we wanted the reviewers to see, but we needed to create shortlinks so they could embed them on some doc.</p>
<p>There wound up being a total of 20k shortlinks, which meant that:</p>
<ul>
<li>I needed to be able to upload 20,000 in one go, it needed a scriptable API</li>
<li>whatever we used needed to be around for at least 6 months to be available during a certain review period</li>
<li>the links needed to work, and be available</li>
<li>we needed to be able to create the redirects from a Google Sheet</li>
<li>they wanted to know if/when links were clicked</li>
</ul>
<p>We ultimately landed on a solution with just AWS that comes out dirt cheap - but there were a few things we had to consider first.</p>
<h2>Why not just use something like bit.ly?</h2>
<p>For this specific case - based on their pricing, we'd be in the enterprise range under the &quot;Contact Us for a Quote&quot; plan which puts us north of $400/year (and a &quot;quick call&quot; with an account manager) for something I only need once.</p>
<p>$400/year sounds steep for something that ...should.... be simple and cheap. 301 redirects have been around since 1999 (20 years ago!) - why isnt this a solved problem! (it kinda is, but it isnt)</p>
<p>Even outside of enterprisey considerations,</p>
<ul>
<li>bitly comes with its own limits for the free tier, and even doing something like bulk redirects needs to be on their enterprise plan.</li>
<li>30/month for their basic plan is a bit steep for some 301s, and we can do it for cheaper. (you trade off some features though, primarily analytics - if you dont care about that, fantastic!)</li>
</ul>
<p>Apart from that, its a good opportunity to learn the ins &amp; outs of AWS, it doesnt involve a lot of services. If you're familiar with Cloudformation or Terraform, you can set it all up that way.</p>
<h2>How do you build it?</h2>
<p>We worked out a solution using Cloudfront + S3, where your Objects have a Website-Redirect-Location metadata attached (<a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/how-to-page-redirect.html">see docs here</a>). We built it out in an afternoon, and had all 30k redirects generated and working, and it costs just a few bucks/month to run.</p>
<p>Let's say you own short.url and you want to be able to have short.url/2019barbecue redirect to a Facebook page.</p>
<p>At a high level, take a look at this architecture (images courtesy of cloudcraft.co):</p>
<p><img src="/cms-content/shortlinks-on-aws-11.png" alt="cloudcraft architecture of shortlinks on aws"></p>
<p>You point your domain (which can be hosted anywhere, doesnt need to be route53) at a Cloudfront distribution that sits in front an S3 bucket served as a website. In that bucket are many objects, with the Website Redirect Location metadata set to redirect to our target url - short.url/2019barbecue redirects to example.com</p>
<h3>Are your redirects business critical?</h3>
<p>Maybe you're running a launch and you need people to click a link. You are now responsible for uptime and so AWS' uptime is your uptime.</p>
<p>In this case, your availability is the minimum of either Cloudfront or S3 (99.9%) - you do, however, have the benefit of a globally distributed CDN… and a highly durable and replicated object store… with failover to another region in case it craps out - not to mention its cheap-ish to implement.</p>
<h3>Do you want analytics?</h3>
<p>Bitly analytics are pretty neat - thats a tradeoff since its something you'll need to do yourself - but if you already have an analytics/monitoring platform, you can get your data through there. For things on my personal site, I don’t care for seeing analytics - i just want to share a customized short link.</p>
<p>Out of the box, you get some level of Cloudwatch monitoring around Cloudfront including error rates, data in/out, and total requests. (<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/monitoring-using-cloudwatch.html#monitoring-console.distributions">full list here</a>)</p>
<p>If you want more info, then you need to dump Cloudfront access logs to another S3 bucket and use Athena (<a href="https://aws.amazon.com/premiumsupport/knowledge-center/analyze-logs-athena/">the docs for that here</a>) for some quick analytics to see things like source IP, referer, requested path, response code</p>
<h2>Any gotchas i should be aware of?</h2>
<p>Be mindful of updating where a redirect points to. 301 redirects CAN be cached indefinitely by your browser since the spec indicates a 301 means Permanent Redirect.</p>
<p>If someone has already clicked your link before, it's possible that they may be taken to the old location for some period of time. In practice, I've seen a long tail of requests to the old value of the redirect kinda like this:</p>
<p><img src="/cms-content/long-tail-graph.png" alt="graph of decreasing requests vs time"></p>
<p>302 redirects are meant to be temporarily cached, so it has less issues. Some architectures limit you to 301 redirects, others give you the option to do a 302. In this case, Cloudfront + S3 only lets you do 301s and thats just a constraint we need to deal with unless we want to throw Lambda in the mix as well.</p>
<h2>Wrapping up</h2>
<p>If you find yourself needing to create some customized short links or redirects, consider using Cloudfront + S3 to serve them using the architecture above - you get all the benefits of an enterprise-grade solution at a fraction of the cost if you're already familiar with AWS.</p>
<p>I'm working on a series of posts to thoroughly explain how to set up short links on AWS. Next up, I'll be covering how to set up the short links in the console, and then how you could do it in terraform.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Create your own short link redirects using CloudFront + S3</title>
      <link href="https://intricatecloud.io/2019/09/create-your-own-short-link-redirects-using-cloudfront-s3/"/>
      <updated>2019-09-22T02:57:05Z</updated>
      <id>https://intricatecloud.io/2019/09/create-your-own-short-link-redirects-using-cloudfront-s3/</id>
      <content type="html">
        <![CDATA[
      <p>Get branded short links under your own domain for dirt-cheap by self-hosting it on AWS using CloudFront + S3. It's a no-code server-less solution to get redirects on the cheap and in this guide, I'll detail how to set it up. If you're interested in some good reasons why - check out my <a href="https://www.intricatecloud.io/2019/09/that-time-i-needed-to-create-20k-shortlinks-and-how-to-create-them-on-aws/">first post on it here</a>.</p>
<p>In this guide, I'll go over</p>
<ul>
<li>what do our redirects/short links need to be able to do</li>
<li>how to set it up via the console if thats what you're comfortable with, or doing it via the AWS node SDK.</li>
<li>How to check that its working</li>
<li>A few things to check when it doesn't (it never really works the first time, doesn't it?)</li>
</ul>
<h2>What do we need out of our short links?</h2>
<ul>
<li>I need to be able to create custom links to redirect</li>
<li>I need to be able to change them later</li>
<li>I want to see traffic to the redirect, and how many times they've been clicked (this will be part of another post)</li>
</ul>
<p>Technical Requirements:</p>
<ul>
<li>Highly Available, Redundant (99.9% SLA)</li>
<li>Serve HTTPS 301 redirects</li>
<li>Serverless. Cloudfront + S3 means we don't have any servers to manage.</li>
</ul>
<p>This is the architecture we're going to set up on AWS:</p>
<p><img src="/cms-content/shortlinks-on-aws-11.png" alt="cloudcraft diagram showing cloudfront + s3 with replication"></p>
<p>In short, we have...</p>
<ul>
<li>a CloudFront CDN which gives us free HTTPS for our domain (courtesy of AWS Certificate Manager)</li>
<li>An S3 bucket with Cross-Replication enabled to another bucket in another region</li>
<li>Each redirect is stored as an object with the <code>Website-Redirect-Location</code> metadata pointing to our redirect</li>
</ul>
<h2>Setting it up via the console</h2>
<h3>Step 1: Set up Cloudfront + S3</h3>
<p>I've set up this diagram for one of my domains - gotothat.link so I'll show you how I've set that up.</p>
<p><img src="/cms-content/r53-gotothatlink-image.png" alt="registered domains on Route53"></p>
<p>I own gotothat.link using Route53 as my registrar. You don't need to have your domain on AWS Route53 if you want to host your short links on AWS - if you have one already via something like GoDaddy or Namecheap, thats fine too, you can use that.</p>
<p>Cloudfront will be providing an HTTPS URL that we can use using S3 as an origin which will serve our redirects. Cloudfront will have its logs piped to another bucket, and we can get some metrics out of that.
<img src="/cms-content/cloudfront-gotothatlink.png" alt="cloudfront properties"></p>
<p><img src="/cms-content/s3-origin-cloudfront.png" alt="s3 origin properties"></p>
<p>An S3 bucket will be used to store the actual redirects.</p>
<p>For an in-depth guide on setting up those 2 services, you can <a href="https://www.intricatecloud.io/2018/04/creating-a-serverless-static-website/">check out my post which walks you through setting up via the console</a> or <a href="https://www.intricatecloud.io/2018/04/creating-your-serverless-website-in-terraform-part-2/"> if you want to set it up with terraform</a>.</p>
<p>Once you have Cloudfront deployed and connected to your S3 bucket, we can  add our redirects.</p>
<h3>Step 2: Add a redirect</h3>
<p>Create an empty file and upload it to the console, with Website-Redirect-Location metadata pointing to the URL you want to redirect to. In this case, I've uploaded an empty file named <code>not-a-rick-roll</code> and added the metadata to point to a particular YouTube video.</p>
<p>You can use the default permissions, default storage type, and no need for encryption.</p>
<p><img src="/cms-content/s3-upload-metadata.png" alt="aws s3 upload file with metadata screen"></p>
<p>In the screenshot, you'll see theres a warning under the Metadata header - <code>You cannot modify object metadata after it is uploaded.</code> I'm sure theres a reason this is included, but I've been able to just go into the object's metadata and change the values from the screen below.</p>
<p><img src="/cms-content/object-metadata-redirect.png" alt="s3 object and metadata"></p>
<p>By default, S3 will add a Content-Type metadata to your object when you upload it. So you can see in the screenshot that the Content-Type has been set to <code>binary/octet-stream</code> which implies this link will return some file. Its worth noting in this case, that by adding the Website-Redirect-Location metadata to your object, S3 will serve up a HTTP 301 Redirect to your target redirect - a 301 response only needs to include a Location header specifying the redirect. So it doesn't quite matter what the Content-Type is on your object. You can leave it alone, or delete it.</p>
<p>Thats it! Assuming you have many more of these, you can even automate the creation of your redirects.</p>
<h3>Step 3: Automating it</h3>
<p>If you use the CLI, you can script it using the AWS CLI - <code>aws s3 cp not-a-rick-roll s3://gotothat.link/not-a-rick-roll --website-redirect &quot;https://www.youtube.com/watch?v=dQw4w9WgXcQ&quot;</code></p>
<p>If you have your redirects stored in a CSV, you can parse it and upload it using the node aws-sdk - check out this example:</p>
<pre><code># redirects.csv

notarickroll,https://youtube.com/video
no/really/not/a/rickroll/video, https://youtube.com/video
</code></pre>
<pre class="language-js"><code class="language-js"><span class="token comment">// redirects.js</span>

<span class="token keyword">const</span> parse <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'csv-parse/lib/sync'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token constant">AWS</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'aws-sdk'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> s3 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AWS<span class="token punctuation">.</span>S3</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> redirects <span class="token operator">=</span> <span class="token function">parse</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span><span class="token string">'redirects.csv'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
redirects<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">redirect</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Uploading '</span><span class="token punctuation">,</span> redirect<span class="token punctuation">)</span>
    s3<span class="token punctuation">.</span><span class="token function">putObject</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token literal-property property">Body</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
        <span class="token literal-property property">Bucket</span><span class="token operator">:</span> <span class="token string">'gotothat.link'</span><span class="token punctuation">,</span>
        <span class="token literal-property property">Key</span><span class="token operator">:</span> redirect<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token literal-property property">Metadata</span><span class="token operator">:</span> <span class="token punctuation">{</span>
            <span class="token string-property property">'Website-Redirect-Location'</span><span class="token operator">:</span> redirect<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token keyword">return</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'done'</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<h2>Verify your redirects</h2>
<p>You can check to see that its working by using good ol' <code>curl</code> to make a request to your site - gotothat.link, and verify that you get back a 301 redirect for that object. This is what the output of that command looks like:</p>
<pre><code>☁  intricate-web  curl -i https://gotothat.link/not-a-rick-roll
HTTP/2 301
content-length: 0
location: https://www.youtube.com/watch?v=dQw4w9WgXcQ
date: Sun, 22 Sep 2019 01:52:57 GMT
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 96b6c9282feceea8aa00c25902322bb6.cloudfront.net (CloudFront)
x-amz-cf-pop: EWR53-C1
x-amz-cf-id: rNwzh3rL-ErWpi0YuDrqQcCvvmbqa_ZWj7cPl71WrPPi1vlFbW0siA==
</code></pre>
<p>You now have URL redirects served out of your own domain so you can do things like gotothat.link/not-a-rick-roll (check it out - its totally not a rick roll 😎)</p>
<h2>Troubleshooting</h2>
<p>If you change a redirect and you don't see it taking effect, there might be one of two things happening:</p>
<ul>
<li>If you're trying this in your browser, you might have a cached version of the redirect. Open an incognito window and try it there.</li>
<li>Cloudfront is still sending you back a cached version of the redirect. You'll have to use <code>curl</code> to inspect the request so you can see whats happening.</li>
</ul>
<pre><code>☁  intricate-web  curl -i https://gotothat.link/not-a-rick-roll
HTTP/2 301
content-length: 0
location: https://www.youtube.com/watch?v=dQw4w9WgXcQ
date: Sun, 22 Sep 2019 01:52:57 GMT
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 c67ae9899d89f9402837da3a0ead9442.cloudfront.net (CloudFront)
x-amz-cf-pop: EWR53-C1
x-amz-cf-id: 7pDL0XgVjGmdMqcHf7tSsRILd0WfZpihpLF66SYVJLlzqfCJG2PMRg==
age: 4
</code></pre>
<p>In this example, we want to pay attention to the <code>x-cache</code> &amp; <code>age</code> response header. In this case, it says <code>Hit from cloudfront</code> and the <code>age == 4</code>. The age is the time that object has been in the cache. If you've updated your redirect recently, and you're still seeing an old redirect in your response headers, then you'll need to invalidate your Cloudfront cache. You can do that from the Invalidations page</p>
<p><img src="/cms-content/invalidations-cloudfront.png" alt="the Invalidations page"></p>
<p>You'll know it updated when the <code>age</code> response header shows something recent.</p>
<p>There you have it! By this point, you should have your own domain sending you back redirects that you can monitor yourself. Now, this isn't the whole story because theres still a feature or two that we need to set up.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Setting up automatic failover for your static websites hosted on S3</title>
      <link href="https://intricatecloud.io/2019/10/setting-up-automatic-failover-for-your-static-websites-hosted-on-s3/"/>
      <updated>2019-10-01T05:19:10Z</updated>
      <id>https://intricatecloud.io/2019/10/setting-up-automatic-failover-for-your-static-websites-hosted-on-s3/</id>
      <content type="html">
        <![CDATA[
      <p>Up until late last year, you couldn't set up automatic failover in CloudFront to look at a different bucket. You could only have one Primary origin at a time, which meant that you needed additional scripts to monitor and trigger failover. (It was one of <a href="https://www.intricatecloud.io/2018/04/gotchas-with-cloudfront-s3-architecture/">CloudFront's gotchas I had written about previously</a>)</p>
<p>Then AWS dropped the bombshell during re:invent 2018 that Cloudfront can now support it by using Origin Groups - <a href="https://aws.amazon.com/about-aws/whats-new/2018/11/amazon-cloudfront-announces-support-for-origin-failover/">Announcement</a></p>
<p>In this post, I'll show how you can configure this for an existing site via the console.</p>
<h2>Adding an origin group to your Cloudfront distribution</h2>
<p>I was working on this for one of my websites - gotothat.link - its not a static website, but its just a site that serves a bunch of redirects so all the same rules apply.</p>
<h3>Step 1 - Configure replication on your S3 bucket</h3>
<p>Go to the replication settings page for your S3 bucket.
<img src="/cms-content/s3-replication-settings-page.png" alt="S3 Bucket Replication Settings page"></p>
<p>If you haven't yet enabled Versioning, it will tell you that you're required to do so.</p>
<p>Enable replication for the entire bucket by clicking Add Rule. For our use case, we don't need to encrypt the replicated contents.</p>
<p><img src="/cms-content/s3-replication-rule-page.png" alt="replication rule page"></p>
<p>You can follow the prompt to create a new bucket in us-west-1 - N. California.</p>
<p><img src="/cms-content/replication-bucket-options.png" alt="replication bucket options"></p>
<p>Once you've created the S3 bucket, make sure to update the bucket policy on your new replication bucket to allow the static website hosting to work:</p>
<pre><code>{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: &quot;*&quot;,
            &quot;Action&quot;: &quot;s3:GetObject&quot;,
            &quot;Resource&quot;: &quot;arn:aws:s3:::gotothat.link-west-1/*&quot;
        }
    ]
}
</code></pre>
<p>For the IAM role, I'll let the console create one for me - this is whats generated automatically for you</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
    <span class="token property">"Version"</span><span class="token operator">:</span> <span class="token string">"2012-10-17"</span><span class="token punctuation">,</span>
    <span class="token property">"Statement"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token property">"Action"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
                <span class="token string">"s3:Get*"</span><span class="token punctuation">,</span>
                <span class="token string">"s3:ListBucket"</span>
            <span class="token punctuation">]</span><span class="token punctuation">,</span>
            <span class="token property">"Effect"</span><span class="token operator">:</span> <span class="token string">"Allow"</span><span class="token punctuation">,</span>
            <span class="token property">"Resource"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
                <span class="token string">"arn:aws:s3:::gotothat.link"</span><span class="token punctuation">,</span>
                <span class="token string">"arn:aws:s3:::gotothat.link/*"</span>
            <span class="token punctuation">]</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span>
            <span class="token property">"Action"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
                <span class="token string">"s3:ReplicateObject"</span><span class="token punctuation">,</span>
                <span class="token string">"s3:ReplicateDelete"</span><span class="token punctuation">,</span>
                <span class="token string">"s3:ReplicateTags"</span><span class="token punctuation">,</span>
                <span class="token string">"s3:GetObjectVersionTagging"</span>
            <span class="token punctuation">]</span><span class="token punctuation">,</span>
            <span class="token property">"Effect"</span><span class="token operator">:</span> <span class="token string">"Allow"</span><span class="token punctuation">,</span>
            <span class="token property">"Resource"</span><span class="token operator">:</span> <span class="token string">"arn:aws:s3:::gotothat.link-west-1/*"</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>
<span class="token punctuation">}</span></code></pre>
<p>Once you click Save, you should see this panel appear now when you're on the Management page of your S3 bucket.</p>
<p><img src="/cms-content/s3-replication-dashboard.png" alt="S3 management console - replication settings page"></p>
<h3>Step 2: Replicate data</h3>
<p>Replication is now enabled. But we're not quite done.</p>
<p>When you enable replication, any objects created/modified from that point forward are then replicated to your target bucket. Any existing objects are NOT automatically transferred over - you have to do a one-time sync when you first set it up.</p>
<p>If all you have in your bucket is your website files and nothing else, then you can do an <code>aws s3 sync s3://source.bucket s3://target.bucket</code> to import all your existing objects. Depending on how much is in there, it can take awhile.</p>
<p>If you have some objects in your source S3 bucket that require you use the <code>Website-Redirect-Location</code> metadata (this is needed if you want to <a href="https://www.intricatecloud.io/2019/09/that-time-i-needed-to-create-20k-shortlinks-and-how-to-create-them-on-aws/">serve redirects out of your domain like this</a>):</p>
<ul>
<li>You cannot do a sync with the replicated bucket using something like <code>aws s3 sync s3://my-bucket-name s3://my-bucket-name-us-west-1</code>. While the sync may succeed, your redirects will not. This is annoying.</li>
<li>The S3 docs state that <code>Website-Redirect-Location</code> isn't copied over when using a sync command, although it doesn't specify why. <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html">See docs here</a></li>
<li>However, Objects that are automatically replicated will keep their Website-Redirect-Location metadata. But any files that are <code>aws s3 sync</code>'d between 2 buckets will not have that metadata.</li>
</ul>
<p>Last step is to go to the replicated bucket, select Properties, and enable Versioning + Static Website Hosting for it. If you forget this, you won't receive the 301 redirect.</p>
<p>At this point, you should have a bucket in another region, with all the same contents as your primary bucket, with static website hosting enabled.</p>
<h2>Configure Failover in CloudFront</h2>
<p>Create a new origin with an origin id like <code>gotothatlink-failover-prod</code> and use the URL of the form <code>gotothat.link-west-1.s3-website-us-west-1.amazonaws.com</code></p>
<p>Create a new origin group and add your two origins at the prompt. The primary origin will be our original S3 bucket, and the secondary origin will be the new failover origin we created.</p>
<p>For the failover criteria, we can check all the 5xx status codes since that would imply issues happening in S3. You can check 404s if you want to protect against someone accidentally deleting all your data, or 403 Forbidden errors to protect against accidental bucket policy changes.</p>
<p><img src="/cms-content/cloudfront-origin-group.png" alt="origin group settings"></p>
<p>Next, go to the Behaviors tab, and make sure to update the behavior to route all paths to your Origin Group (and not your Origin). Otherwise, even if it fails over, it will still send requests to your primary origin.</p>
<p><img src="/cms-content/cloudfront-behaviors-update.png" alt="edit behavior settings page"></p>
<p>After you've set these changes, you'll have to wait for the CloudFront distribution to finish updating (could take up to a half hour).</p>
<h2>Test it by &quot;accidentally&quot; trashing your primary bucket</h2>
<p>Since I've enabled failover on 404 errors, I'll run a test where I delete everything in my bucket. I ran <code>aws s3 rm --recursive s3://gotothat.link/</code>.</p>
<p>And now to save you boatloads of time - here is every error I ran into when setting this up.</p>
<h3>Troubleshooting:</h3>
<ul>
<li>You get a 404 - Cloudfront is still looking at your primary bucket. Turns out I hadn't updated the Behaviors for the CloudFront distribution - remember you need to change the behavior to stop looking at the primary origin and look at the <strong>origin group</strong>.</li>
<li>You get a 403 - Your replicated bucket has incorrect permissions. See the section above labeled &quot;<em>Configure replication on your S3 bucket</em>&quot; to see the required bucket policy.</li>
<li>You now get a 200 - your object doesn't have the <code>Website-Redirect-Location</code> metadata associated with it
<ul>
<li>Take a look at the replicated object by doing the following - note that it includes <code>WebsiteRedirectLocation</code></li>
<li>If you've used <code>aws s3 sync</code> to do a one-time replication of your objects - see the section &quot;Step 2: Replicating data&quot; on why that might have been a problem.</li>
</ul>
</li>
</ul>
<pre><code>☁  intricate-web  aws s3api get-object --bucket gotothat.link-west-1 --key newvideo  newvideo-1
{
    &quot;AcceptRanges&quot;: &quot;bytes&quot;,
    &quot;LastModified&quot;: &quot;Sun, 22 Sep 2019 05:21:08 GMT&quot;,
    &quot;ContentLength&quot;: 0,
    &quot;ETag&quot;: &quot;\&quot;d41d8cd98f00b204e9800998ecf8427e\&quot;&quot;,
    &quot;VersionId&quot;: &quot;dynNjEGPeE3.z6kCCp.zgvVlbYkA.h3X&quot;,
    &quot;ContentType&quot;: &quot;binary/octet-stream&quot;,
    &quot;WebsiteRedirectLocation&quot;: &quot;https://youtube.com/video&quot;,
    &quot;Metadata&quot;: {},
    &quot;ReplicationStatus&quot;: &quot;REPLICA&quot;
}
</code></pre>
<h2>Wrapping up</h2>
<p>If you're doing it via the console, this doesn't take long to set up. Its a handful of steps that you can do in &lt; 30 mins that will give you extra confidence that your site is backed up, and still functional even if S3 goes down (it usually has a high-profile outage at least once a year).</p>
<p>With this solution in place, you have an HTTPS website served from a global CDN with automatic failover in case of an S3 outage or data loss in your primary bucket so that you don't need to take any downtime. All this for just a few bucks a month. :mindblown:</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Using Angular innerHtml to display user-generated content without sacrificing security</title>
      <link href="https://intricatecloud.io/2019/10/using-angular-innerhtml-to-display-user-generated-content-without-sacrificing-security/"/>
      <updated>2019-10-29T04:38:21Z</updated>
      <id>https://intricatecloud.io/2019/10/using-angular-innerhtml-to-display-user-generated-content-without-sacrificing-security/</id>
      <content type="html">
        <![CDATA[
      <p>Why do some of your styles no longer work when using <code>[innerHtml]</code> to show some HTML content? Angular comes with a built-in html sanitizer <code>DomSanitizer</code>, as a security feature, thats used whenever you use <code>[innerHtml]</code>. Its a great feature - but has a pretty annoying bug/feature in that if you have elements with inline styles, the styles wind up getting removed from your page. Judging by the amount of questions on how to turn it off - it clearly has its shortcomings. That said, I do appreciate the Angular devs building this in to protect the majority of us from doing something silly in their webapp.</p>
<p>The easiest approach is to disable DOMSanitizer and the <a href="https://stackoverflow.com/questions/39628007/angular2-innerhtml-binding-remove-style-attribute/39630507">recommended approach on Stack Overflow</a> is to create a <code>safeHtml</code> pipe so that you could write <code>&lt;div [innerHtml]=&quot;content | safeHtml&quot;&gt;</code>. It's a few lines of code if you just want to drop it in - <a href="https://stackoverflow.com/questions/39628007/angular2-innerhtml-binding-remove-style-attribute/39630507">see the answer here</a>.</p>
<p>But before jumping right into a solution that disables a security feature thats on by default, lets think about what we're doing for a second.</p>
<h2>Why disable it at all?</h2>
<p>The use case that I (and apparently many others) have is that I want to display user generated HTML content - ranging from small snippets to entire HTML pages. The issue is that <code>DomSanitizer</code> is stripping too much out of my HTML, and there's no way to configure its rules. In my case, I had <code>&lt;span style=&quot;background-color:red&quot;&gt;</code> spans with inline style attributes as well as a <code>&lt;style&gt;</code> block.</p>
<p><code>DomSanitizer</code> has its own rules about what is considered &quot;safe&quot; HTML. Its not wrong - I'm sure they have a good reason and they provide a safe default. As an example, Take a look at the <a href="https://github.com/angular/angular/blob/master/packages/core/src/sanitization/html_sanitizer.ts#L69">source for DomSanitizer</a> that shows what HTML attributes are &quot;whitelisted&quot;. I have both <code>&lt;style&gt;</code> tags and inline styles on element - both of which it doesn't like and so it gets removed out of my HTML. No, I have no control over the source HTML.</p>
<p>:( Thats annoying. In this case, I kinda have to throw the baby out with the bathwater. I have to turn it off completely so that it works for what I need, but I lose all the Angular guardrails. No bueno.</p>
<h2>bypassing DomSanitizer</h2>
<p>DomSanitizer offers a few methods to allow you to bypass only certain parts of your content - but it doesn't quite work for user-generated HTML. Here's why.</p>
<p><strong>sanitizer.bypassSecurityTrustScript(content)</strong> - <code>content</code> is expected to be javascript content and not HTML containing javascript content. Now it would be wonderful if I could keep this part because I don't expect my user-generated HTML to contain <code>&lt;script&gt;</code> tags (and they're cleaned out at the server-side) but I don't have the option of use the script sanitizer on my HTML content.</p>
<p><strong>sanitizer.bypassSecurityTrustStyles(content)</strong> - <code>content</code> is expected to be CSS content, and not HTML containing a <code>&lt;style&gt;</code> tag. I can't use this method because my HTML contains style elements that are getting stripped. Welp. :shrug:</p>
<p><strong>sanitizer.bypassSecurityTrustHtml(content)</strong> - <code>content</code> is expected to be an HTML snippet or an entire page and it leaves all your content untouched - which is NOT RECOMMENDED at all. Unfortunately, I have no other option around it.</p>
<h2>So then, how can you stay safe from XSS attacks in Angular?</h2>
<p>If you find yourself in this predicament where you have to bypass <code>DomSanitizer</code> - hopefully I can save you some time and show you what I've found.</p>
<p>Yes, you'll have to pull yet-another-dependency into your app, but the open-source JS community comes to the rescue. The following libraries were popular on npm &amp; github, worked on both the server-side and client-side, with a configurable sanitizer using whitelists/blacklists and other cool features. Definitely worth checking out.</p>
<p><a href="https://github.com/apostrophecms/sanitize-html">punkave/sanitize-html</a> A popular option with a very succint and obvious name. Under recent active development.</p>
<p><a href="https://github.com/cure53/DOMPurify">cure53/dompurify</a>
DOMPurify has a publicly available security audit report from 2015 which has just 2 major vulnerabilities for IE9. Also worth a read if you want to learn what a DOM Clobbering attack is. <a href="https://cure53.de/pentest-report_dompurify.pdf">Check it out here.</a></p>
<h2>how to use it in place of DomSanitizer</h2>
<p>Here's what my <code>safeHtml</code> Pipe looks like:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> Pipe<span class="token punctuation">,</span> PipeTransform <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DomSanitizer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/platform-browser'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> DOMPurify <span class="token keyword">from</span> <span class="token string">'dompurify'</span><span class="token punctuation">;</span>

@<span class="token function">Pipe</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'safeHtml'</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">SafeHtmlPipe</span> <span class="token keyword">implements</span> <span class="token class-name">PipeTransform</span> <span class="token punctuation">{</span>

  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter"><span class="token keyword">protected</span> <span class="token literal-property property">sanitizer</span><span class="token operator">:</span> DomSanitizer</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
 
 <span class="token keyword">public</span> <span class="token function">transform</span><span class="token punctuation">(</span>value<span class="token operator">:</span> any<span class="token punctuation">,</span> <span class="token literal-property property">type</span><span class="token operator">:</span> string<span class="token punctuation">)</span><span class="token operator">:</span> any <span class="token punctuation">{</span>
     <span class="token keyword">const</span> sanitizedContent <span class="token operator">=</span> DOMPurify<span class="token punctuation">.</span><span class="token function">sanitize</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
     <span class="token keyword">return</span> angular<span class="token punctuation">.</span><span class="token function">bypassSecurityTrustHtml</span><span class="token punctuation">(</span>sanitizedContent<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>You can lean on an html-sanitizing library to clean your content first, so that you can bypass the sanitizer without losing the benefits of the XSS protection.</p>
<h2>Wrapping up</h2>
<p>Just a reminder - you can never trust the client/browser. Always sanitize on the server-side where you can guarantee that you don't save any malicious content.</p>
<p>If you're using nodejs on the server-side, then this will work perfectly for you. If you find yourself needing to sanitize html on the client-side however, bring in one of the sanitizing libraries so you get some protection from XSS attacks when a built-in like DomSanitizer doesn't meet your needs.</p>
<p>I'm interested to hear from other people who solved this problem - leave a comment if you took a different approach, or just disabled it entirely!</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Avoiding burnout as a new tech lead</title>
      <link href="https://intricatecloud.io/2019/12/avoiding-burnout-as-a-new-tech-lead/"/>
      <updated>2019-12-08T22:14:00Z</updated>
      <id>https://intricatecloud.io/2019/12/avoiding-burnout-as-a-new-tech-lead/</id>
      <content type="html">
        <![CDATA[
      <p>Does this sound familiar? The best dev on the team is promoted to being a tech lead, despite not having any management experience. That dev then all of a sudden has to deal with:</p>
<ul>
<li>Angela, the senior dev who broke up with her partner and is emotionally devastated and needs to take a few days</li>
<li>Oscar, the new dev, saying he needs a few more days to refactor a logging module that needs a &quot;cleaner&quot; interface.</li>
<li>Kevin the PM who keeps making these damn status meetings</li>
<li>Michael, the sales guy who keeps promising features that don't exist yet</li>
</ul>
<p>You spend all your time drowning in problems, trying to keep everyone happy. You found out that multiple teams were depending on your work only after it all fell behind. People start looking to you for answers, and you don't have any. You stopped caring. You're burnt out.  I've been there myself. :wave: hi.</p>
<p>Almost as if the presence of programming skills also indicated that the person can manage a project with many other people. There are, at least, a good chunk of people (developers included) who honestly believe this. It sounds wrong, doesn't it?</p>
<h2>How I started as a lead</h2>
<p>First time I became a lead, I had no prep other than being a teammate and so my only goal for my first few months was to do everything the last person did - like I imagine the person before them did, and the person before them... I also started reading everything I could about <a href="https://www.intricatecloud.io/2018/12/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead/">being a tech lead - check out my list here</a></p>
<p>I also decided to keep taking on the same amount of work I usually did, in addition to my tech lead responsibilities. Sound familiar? :raises_hand: I</p>
<p>They were in a lot of meetings, so I felt I had to be in a lot of meetings. That blew apart my schedule for the week because I couldn't work on some relatively important things, and so schedules fell behind quickly after that. I would stay late at the office (seemed to be common among tech leads or maybe just consultants), and spend long evenings catching up on extra work after people started leaving for the day. In my bachelor days, this was fine - I had no other responsibilities and coding was fun - thats the excuse anyway.</p>
<p>All this meant that my days were filled with meetings and other &quot;administrivia&quot;, my evening/nights were filled with coding, and I started burning myself out over time.</p>
<p>Anyway, long story short.</p>
<h2>Here's a couple tips to help you avoid burnout as a new lead</h2>
<h3>1. Plan your time carefully</h3>
<p>Block out time on your calendar to manage your week, set hard boundaries between when you start/end the day (especially if you work remotely) and stick to it.</p>
<p>You probably don't need to attend every meeting thats on your calendar. But if for some reason, you are required, depending on the meeting, I'll multi-task and be a &quot;fly on the wall&quot; and just listen, other times I'll have to be an active participant.</p>
<p>If you need to set aside a day to do some hands-on dev work, put it in the calendar and decline everything else. Unless you're working on life/death scenarios, it could probably wait until later. Some people have No Meeting Thursdays, Code Review Afternoons... Call it whatever you want.</p>
<p>When performance review season rolls around, its even more helpful to block out time to prepare everyone's review. I've tried to write all my reviews the night before in one week for my team, and it wound up taking me HOURS for each person as I tried to remember everything they did.</p>
<h3>2. Stay out of the critical path</h3>
<p>The critical path in a project is the set of tasks that NEED to be accomplished in order for something to work. For example - if you're building a feature that adds a button to your site, adding a linter or a CSS theme to your project is not required for that button to work. The button itself is obviously the important work.</p>
<p>As a tech lead, you gotta stop picking up any of that important work. If its a time sensitive thing, and you aren't able to get it done in time, it'll have some downstream consequences, and start blocking other projects from getting done. Thats what your team is for - they can go heads down on the work.</p>
<p>But what if some of your team doesn't know how to make changes to your CI/CD pipeline, or the front-end dev hasn't worked on backend SQL queries? Its on you as the lead to support the team to do the work - like that time we hired a junior dev who knew python and just a little javascript to do front-end work in an Angular app. He had never worked with a full-blown framework with all the bells and whistles before, but we had bugs to fix and no time to kill, and sure enough, with some training and mentoring, he got up to speed and started cranking through bugs.</p>
<p>Now, its important you stay technically involved at some level, but your focus will be on the big picture, like making sure that important features are getting shipped, and less on the small details, like why is <code>[1,2,3].flatMap</code> throwing an error.</p>
<p>Want to make your week easier? Try these to start.</p>
<ul>
<li>start with blocking out a 4 hour chunk one day a week for heads-down coding time and decline everything. See how it goes and you can adjust depending on your workload.</li>
<li>Take a look at upcoming work that you would usually do like adding a new API endpoint to a backend, and offer to pair with another dev so that they can learn how to do that work.</li>
</ul>
<p>If you're a new tech lead, definitely check out some of the <a href="https://www.intricatecloud.io/2018/12/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead/">9 books that helped me navigate my first time being a tech lead</a>. I'd like to hear from you - what were some of the things that frustrated you when you first became a tech lead?</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Creating a simple npm library to use in and out of the browser</title>
      <link href="https://intricatecloud.io/2020/02/creating-a-simple-npm-library-to-use-in-and-out-of-the-browser/"/>
      <updated>2020-02-09T22:07:33Z</updated>
      <id>https://intricatecloud.io/2020/02/creating-a-simple-npm-library-to-use-in-and-out-of-the-browser/</id>
      <content type="html">
        <![CDATA[
      <p>Sharing some reusable javascript is hard - even moreso if you're trying to share between node and the browser. I'm <em>just</em> trying to share some 10 lines of code. Why is this taking me more than a day to figure out?! Its times like these I have to talk myself out of doing a copy/paste. I know its wrong, and reusable packages are better, but ugh... at what cost.</p>
<ul>
<li>Some JS code is meant to run in the browser, some JS code can only run on the server-side.</li>
<li>es6 != es2016, es5 != 2015, esNext/es8, HOW OFTEN IS THIS CHANGING?! <a href="https://codeburst.io/javascript-wtf-is-es6-es8-es-2017-ecmascript-dca859e4821c">I found this explanation helpful</a></li>
<li>Webpack? Babel? I didn't think I'd need to deal with this just to share some code!</li>
</ul>
<p>I mean, I get it. But why do I need 147 MB in node_modules just to share 10 lines of code!</p>
<p>Recently at work, I tried to extract some 20-something lines of javascript into a re-usable npm library to share between a few projects. I thought it would be easy and straightforward, and when I found out it wasn't, I started putting together this post to help guide people through what it takes to build a reusable npm package.</p>
<p>In this guide, I'm going to create a react app that loads random images of dogs, cats, and goats. Why those 3? :shrug: They were 3 random things I found on <a href="https://github.com/public-apis/public-apis">public-apis</a>. Then I'll take that code, and turn it into a re-usable npm package that can be used in the browser and node with webpack, babel, and typescript.</p>
<p>I'm also including the <a href="https://github.com/intricatecloud/reusable-js-demo">intricatecloud/reusable-js-demo repo</a> here so you can take a look at the project. Best way to follow along is to <a href="https://github.com/intricatecloud/reusable-js-demo/commits/master">look at each commit</a> to see each change that I'm specifically making (they are also linked after each section in the post). I've shortened some of the interesting bits here for brevity.</p>
<h2>Building Random Animal Demo</h2>
<h3>Step 1. Create the app and run it</h3>
<pre class="language-shell-session"><code class="language-shell-session"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">npx create-react-app random-animal-demo</span></span>
<span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash"><span class="token builtin class-name">cd</span> random-animal-demo</span></span>
<span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash"><span class="token function">npm</span> start</span></span></code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/582d2b1c7a3aafdc678f91a70e9f4de025765c84">full commit 582d2b</a></p>
<h3>Step 2. Hook it up to some apis</h3>
<p>In the app, I'm going to display a dog, cat, and goat picture. I'll tweak some of the boilerplate in the react demo app. I took the default react stuff that was there, and refactored it to show an image and some text. Now I'll integrate these free apis by using <code>axios</code> to make requests (it works in the browser, and in node).</p>
<p>We'll use these APIs which allow us to call them from the browser.</p>
<p>https://aws.random.cat/meow
https://random.dog/woof.json
http://placegoat.com/200</p>
<p>You'll notice each API works slightly differently. The cat API returns a JSON object with a file property. The dog API returns a JSON object with a url property. And the goat API returns the image itself. I added some logic to <code>componentDidMount()</code> to run it on a timer so that it cycles every few seconds</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token constant">DOG</span> <span class="token operator">=</span> <span class="token number">0</span>
<span class="token keyword">const</span> <span class="token constant">CAT</span> <span class="token operator">=</span> <span class="token number">1</span>
<span class="token keyword">const</span> <span class="token constant">GOAT</span> <span class="token operator">=</span> <span class="token number">2</span>

<span class="token keyword">switch</span><span class="token punctuation">(</span>newIndex<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">case</span> <span class="token constant">CAT</span><span class="token operator">:</span>
    axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'https://aws.random.cat/meow'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> imageSrc <span class="token operator">=</span> response<span class="token punctuation">.</span>data<span class="token punctuation">.</span>file
        <span class="token keyword">const</span> text <span class="token operator">=</span> <span class="token string">'CAT'</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">currentAnimal</span><span class="token operator">:</span> <span class="token punctuation">{</span>imageSrc<span class="token punctuation">,</span> text<span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token keyword">case</span> <span class="token constant">DOG</span><span class="token operator">:</span>
    <span class="token operator">...</span></code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/3dd98c59542c0cb083802db871fdf66a1d8c10e0">full commit 3dd98c5</a></p>
<h3>Step 3: Choose how you want to import your file</h3>
<p>Once you want to pull the logic into another file, you have to decide how you want to import it. There are a few options for how you want to import it:</p>
<h4>ES6 Imports - if you want to use <code>import AnimalApi from 'animal-api'</code></h4>
<p>animal-api.js</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    <span class="token function-variable function">getDog</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">...</span><span class="token punctuation">.</span>
    <span class="token function-variable function">getCat</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">...</span><span class="token punctuation">.</span>
    <span class="token function-variable function">getGoat</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">...</span><span class="token punctuation">.</span>
<span class="token punctuation">}</span></code></pre>
<h4>ES6 Destructured Import - if you want to use <code>import { getDog, getCat, getGoat } from 'animal-api'</code></h4>
<p>animal-api.js</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">getCat</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">...</span><span class="token punctuation">.</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">getDog</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">...</span><span class="token punctuation">.</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">getGoat</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">...</span><span class="token punctuation">.</span></code></pre>
<h4>CommonJS - if you want to use <code>const AnimalApi = require('animal-api')</code></h4>
<p>animal-api.js</p>
<pre class="language-javascript"><code class="language-javascript">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
    getDog<span class="token punctuation">,</span> getCat<span class="token punctuation">,</span> getGoat
<span class="token punctuation">}</span></code></pre>
<h4>When would you choose one over the other?</h4>
<p>If your app only needs to work in a browser, and only in the context of React (or Angular2+ or environment that uses ES6 modules), then you're fine with using an ES6 import.</p>
<p>If your lib is meant to be used in the browser, and you need to include it in a vanilla JS HTML app, you need to use a bundler like webpack to bundle your app as a lib.</p>
<p>If you use webpack and take advantage of code splitting and tree shaking, you can use ES6 Destructured imports. What this means is rather than include all of lodash in your app, you can only include the functions you want and you'll have a smaller built app.</p>
<p>If you're writing an app or a library that needs to run in BOTH the browser, and in node, then you'll need to produce a few different versions of your library: one meant for the browser (as a script tag), one as an es6 module, and one for using in node.</p>
<p>For this guide, we're going to be writing an ES6 module, so we can target using <code>import AnimalApi from 'animal-api'</code>.</p>
<h3>Step 4: Extract some of this out into an npm library</h3>
<p>Would you really pull some of this out into a lib? Probably not. But this is an example of how complex something that looks so simple could be. So bear with me.</p>
<p>I can modify my App.js file to use this new file:</p>
<pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>import React from 'react';
<span class="token prefix unchanged"> </span>import axios from 'axios';
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>import AnimalApi from './animal-api';
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>import './App.css';
</span>
<span class="token unchanged"><span class="token prefix unchanged"> </span>class App extends React.Component {
</span>@@ -28,23 +29,19 @@ class App extends React.Component {

<span class="token unchanged"><span class="token prefix unchanged"> </span>      switch(newIndex) {
<span class="token prefix unchanged"> </span>        case CAT:
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>          axios.get('https://aws.random.cat/meow').then((response) => {
<span class="token prefix deleted">-</span>            const imageSrc = response.data.file
<span class="token prefix deleted">-</span>            const text = 'CAT'
<span class="token prefix deleted">-</span>            this.setState({currentAnimal: {imageSrc, text}})
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>          AnimalApi.getCat().then((animal) => {
<span class="token prefix inserted">+</span>            this.setState({currentAnimal: animal})
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>          })
<span class="token prefix unchanged"> </span>          break;
<span class="token prefix unchanged"> </span>        case DOG:
<span class="token prefix unchanged"> </span>        ...
</span></code></pre>
<p>src/animal-api.js</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">getCat</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'https://aws.random.cat/meow'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> imageSrc <span class="token operator">=</span> response<span class="token punctuation">.</span>data<span class="token punctuation">.</span>file
        <span class="token keyword">const</span> text <span class="token operator">=</span> <span class="token string">'CAT'</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span>imageSrc<span class="token punctuation">,</span> text<span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token operator">...</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    getDog<span class="token punctuation">,</span>
    getCat<span class="token punctuation">,</span>
    getGoat
<span class="token punctuation">}</span>
</code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/37c3a3ab56c7f23db43d0b34bffdfc1b911b9f44">full commit 37c3a3a</a></p>
<p>Next, we're going to move this logic to its own npm package.</p>
<h2>How to package your library so that it can be used</h2>
<h3>Create a new npm library locally</h3>
<p>Create a new folder outside of your React project for the npm package we're going to make.</p>
<pre class="language-shell"><code class="language-shell">$ <span class="token function">mkdir</span> animal-api
$ <span class="token builtin class-name">cd</span> animal-api
$ <span class="token function">npm</span> init</code></pre>
<p>You can use all the defaults for the npm init command which creates a package.json file like this:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
  <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"animal-api"</span><span class="token punctuation">,</span>
  <span class="token property">"version"</span><span class="token operator">:</span> <span class="token string">"1.0.0"</span><span class="token punctuation">,</span>
  <span class="token property">"description"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token property">"main"</span><span class="token operator">:</span> <span class="token string">"index.js"</span><span class="token punctuation">,</span>
  <span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token property">"test"</span><span class="token operator">:</span> <span class="token string">"echo \"Error: no test specified\" &amp;&amp; exit 1"</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token property">"author"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token property">"license"</span><span class="token operator">:</span> <span class="token string">"ISC"</span>
<span class="token punctuation">}</span></code></pre>
<p>Then I'll copy paste all of the code thats in my random-animal-demo into animal-api/index.js, ready to be used with <code>import AnimalApi from 'animal-api'</code>.</p>
<p>Because this is a library, we'll want to add some tests to this to make sure this library is working. I like using jest, so I'll pull it in: <code>npm install --save-dev jest</code></p>
<p>I'll then create animal-api/spec/index.spec.js:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> AnimalApi <span class="token keyword">from</span> <span class="token string">'./index'</span>

<span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'animal-api'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'gets dogs'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> AnimalApi<span class="token punctuation">.</span><span class="token function">getDog</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">animal</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
                <span class="token function">expect</span><span class="token punctuation">(</span>animal<span class="token punctuation">.</span>imageSrc<span class="token punctuation">)</span><span class="token punctuation">.</span>not<span class="token punctuation">.</span><span class="token function">toBeUndefined</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token function">expect</span><span class="token punctuation">(</span>animal<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toEqual</span><span class="token punctuation">(</span><span class="token string">'DOG'</span><span class="token punctuation">)</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
   <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/6d797c27855dde59161c699b0a3c24885dd53a43">full commit 6d797c</a></p>
<p>Now run <code>jest</code>:</p>
<pre><code>☁  animal-api [master] ⚡ jest
 FAIL  spec/index.spec.js
  ● Test suite failed to run

    /Users/dperez/workspace/animal-api/spec/index.spec.js:1
    ({&quot;Object.&lt;anonymous&gt;&quot;:function(module,exports,require,__dirname,__filename,global,jest){import AnimalApi from '../index';
                                                                                                    ^^^^^^^^^

    SyntaxError: Unexpected identifier

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1059:14)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.834s
Ran all test suites.
</code></pre>
<p>And I thought this would have been the easiest part. Apparently, <code>jest</code> doesn't like the use of the <code>import</code> statement.</p>
<p>At the moment, with just an index.js file and jest, its going to be running inside a node.js environment where <code>import</code> is not yet supported. (This walkthrough is using node 10).</p>
<p>This is where <code>babel</code> comes into play. <code>babel</code> will transpile (&quot;translate/compile&quot;) your js files from ES6 (where you can use <code>import</code>) and ES5 which node.js supports. Jest has a section on enabling <a href="https://jestjs.io/docs/en/getting-started#using-babel">babel support here</a>. We'll have to install a few more packages, and a little configuration.</p>
<p>Run <code>npm install --save-dev babel-jest @babel/core @babel/preset-env</code> and then add babel.config.js</p>
<p>babel.config.js</p>
<pre class="language-javascript"><code class="language-javascript">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">presets</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">[</span>
      <span class="token string">'@babel/preset-env'</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span>
        <span class="token literal-property property">targets</span><span class="token operator">:</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">node</span><span class="token operator">:</span> <span class="token string">'current'</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Then when you run <code>jest</code>, you get to the next error</p>
<pre><code>☁  animal-api [master] ⚡ jest
 FAIL  spec/index.spec.js
  ● Test suite failed to run

    Cannot find module 'axios' from 'index.js'
</code></pre>
<p>Aha, progress. Now it knows how to read the <code>import</code> statement, and the test runs. Now i'm missing a dependency - axios. <code>npm install --save axios</code>. Now re-run jest and all green!</p>
<pre><code>☁  animal-api [master] ⚡ jest
 PASS  spec/index.spec.js
  animal-api
    ✓ gets dogs (161ms)
    ✓ gets cats (69ms)
    ✓ gets goats

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        1.148s
Ran all test suites.
</code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/fafc23a29290be2a13e9fae71810d6178cf9d451">full commit fafc23a</a></p>
<p>Next up, we'll go through a few scenarios for how to include this package in another project</p>
<h3>Scenario 1: A lib that only needs to work in a browser, and you're using React/Angular2+ (ES6 modules).</h3>
<p>I'm at a good spot now where I can try including this library in my react app. Rather than publishing the npm package publicly, I'll just &quot;install&quot; it from the folder on my machine. I'll switch back to my react app and run <code>npm install ../animal-api</code></p>
<p>My package.json now looks like this:</p>
<pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>    "@testing-library/user-event": "^7.2.1",
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    "animal-api": "file:../animal-api",
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>    "axios": "^0.19.2",
</span></code></pre>
<p>I can try using it from the React app. Back to App.js</p>
<pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>import React from 'react';
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>import axios from 'axios';
<span class="token prefix deleted">-</span>import AnimalApi from './animal-api';
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>import AnimalApi from 'animal-api';
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>import './App.css';
</span></code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/a80289849ed9845286372e72db87192cb4aa2a44">full commit a802898</a></p>
<p>Boom! Everything works! Fantastic, I've now got a publish-able package. By coincidence, theres already an <code>animal-api</code> published to npm so I wont bother adding yet-another-one. If all you need to do is share some code thats going to be used in a React/Angular framework (where you can use <code>import</code>), then you can stop right here. Move on with your life and be grateful you don't need to read Scenario 2.</p>
<h2>Scenario 2. Using the library in a browser using a script tag</h2>
<p>This is where we want to create a file that be included as a script tag.</p>
<p>In your animal-api directory, create index.html, run a local webserver with <code>npx http-server</code> and visit your page <code>http://localhost:8080</code>:</p>
<p>index.html</p>
<pre class="language-html"><code class="language-html">...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>imageSrc<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>./index.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
    AnimalApi<span class="token punctuation">.</span><span class="token function">getDog</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">animal</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'#imageSrc'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> animal<span class="token punctuation">.</span>imageSrc
        document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'#text'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> animal<span class="token punctuation">.</span>text
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/df79f052f82ecfacb266824d61eabdd570c790f9">full commit df79f05</a></p>
<p>The first error I get with this is:</p>
<pre><code>index.js:1 Uncaught SyntaxError: Cannot use import statement outside a module
index.html:11 Uncaught ReferenceError: AnimalApi is not defined
    at index.html:11
(anonymous) @ index.html:11
</code></pre>
<p>This is saying that the browser can't run your file that contains import statements. We used <code>babel</code> before so that <code>jest</code> was able to use the <code>import</code> keyword. We have to do something similar here. The only difference is that we need to save the &quot;transpiled&quot; output so we can include it in the browser. Since we already have <code>babel</code> as a dependency in <code>animal-api</code>, we can use it to convert to a UMD module</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token function">npm</span> <span class="token function">install</span> --save-dev @babel/plugin-transform-modules-umd @babel/core @babel/cli</code></pre>
<p>Then we can run <code>babel index.js -d lib</code> which will create a lib/index.js file ready to be consumed as a script tag in your browser. Now I can update my script tag to point to this other file.</p>
<pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>    &lt;/body>
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>&lt;script src="./index.js">&lt;/script>
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>&lt;script src="./lib/index.js">&lt;/script>
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>&lt;script>
<span class="token prefix unchanged"> </span>    AnimalApi.getDog().then(function(animal) {
</span></code></pre>
<p>Once I refresh the page, one error goes away, the other one stays.</p>
<pre><code>index.html:11 Uncaught ReferenceError: AnimalApi is not defined
    at index.html:11
</code></pre>
<p>We haven't configured the babel plugin that creates the UMD module to use the name that we want. Because our file is named <code>index.js</code>, we'll update our babel.config.js to this:</p>
<p>babel.config.js</p>
<pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>module.exports = {
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    plugins: [
<span class="token prefix inserted">+</span>        ["@babel/plugin-transform-modules-umd", {
<span class="token prefix inserted">+</span>        exactGlobals: true,
<span class="token prefix inserted">+</span>        globals: {
<span class="token prefix inserted">+</span>          index: 'AnimalApi'
<span class="token prefix inserted">+</span>        }
<span class="token prefix inserted">+</span>      }]
<span class="token prefix inserted">+</span>    ],
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>    presets: [
</span></code></pre>
<p>Re-run <code>babel index.js -d lib</code>, and refresh the page</p>
<p>And now I get a new error! Progress!</p>
<pre><code>Uncaught TypeError: AnimalApi.getDog is not a function
    at index.html:8
</code></pre>
<p>This one is a head scratcher. Whatever babel is doing to this file, I'm losing access to my functions. Lets take a look at what the transpiled file looks like <code>lib/index.js</code>. In this file, I notice 2 things:</p>
<pre class="language-javascript"><code class="language-javascript">global<span class="token punctuation">.</span>AnimalApi <span class="token operator">=</span> mod<span class="token punctuation">.</span>exports<span class="token punctuation">;</span>
<span class="token operator">...</span>
<span class="token keyword">var</span> _default <span class="token operator">=</span> <span class="token punctuation">{</span>
    getDog<span class="token punctuation">,</span>
    getCat<span class="token punctuation">,</span>
    getGoat
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  _exports<span class="token punctuation">.</span>default <span class="token operator">=</span> _default<span class="token punctuation">;</span></code></pre>
<p>This suggests that there is a <code>.default</code> property on my AnimalApi global object. This is a small detail thats usually invisible when you're using the <code>import axios from 'axios'</code> syntax. An ES6 package will provide a &quot;default&quot; export, and when you use the <code>import</code> statement, it knows to look at <code>.default</code> property for you, so you don't need to write it.</p>
<p>Lets update our code to match that:</p>
<pre class="language-javascript"><code class="language-javascript">AnimalApi<span class="token punctuation">.</span>default<span class="token punctuation">.</span><span class="token function">getDog</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">animal</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>Now, when we refresh the browser, we get our next error:</p>
<pre><code>index.js:36 Uncaught TypeError: Cannot read property 'get' of undefined
    at Object.getDog (index.js:36)
    at index.html:8
</code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/5da97d81bda90d5e8d2e16415bfc7c5bf12b28f3">full commit 5da97d8</a></p>
<p>This is failing to call <code>axios.get</code> because <code>axios</code> is undefined. All we did was transpile the index.js file. The code for axios isn't there, and the browser doesn't know that its supposed to search your node_modules directory for it (nor can it).</p>
<p>What we need to do is create one js file, that has all the dependencies of that library built into it, so that you only need to add the 1 script tag to your site. Babel can't handle your dependencies, it can only translate.</p>
<p>We now need to introduce our 2nd tool: webpack!</p>
<p>We'll use webpack to take a look at our file, and everything that is import'd/require'd will then get &quot;packed&quot; into one file.</p>
<p>We'll install webpack</p>
<pre class="language-shell"><code class="language-shell">$ <span class="token function">npm</span> <span class="token function">install</span> --save-dev webpack webpack-cli</code></pre>
<p>And add the following to webpack.config.js</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">entry</span><span class="token operator">:</span> <span class="token string">'./index.js'</span><span class="token punctuation">,</span>
  <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">path</span><span class="token operator">:</span> path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'lib'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token literal-property property">filename</span><span class="token operator">:</span> <span class="token string">'animal-api.js'</span><span class="token punctuation">,</span>
    <span class="token literal-property property">library</span><span class="token operator">:</span> <span class="token string">'AnimalApi'</span><span class="token punctuation">,</span>
    <span class="token literal-property property">libraryTarget</span><span class="token operator">:</span> <span class="token string">'var'</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This config will look at <code>./index.js</code> and create <code>./lib/animal-api.js</code> which will make the variable <code>AnimalApi</code> available within your javascript, using the <code>var</code> target.</p>
<p>Now run <code>webpack</code> and you'll notice that <code>lib/animal-api.js</code> got a lot bigger, with more gibberish.</p>
<p>Go back to your <code>index.html</code> and update the script tag:</p>
<pre class="language-diff"><code class="language-diff"><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>&lt;script src="./lib/index.js">&lt;/script>
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>&lt;script src="./lib/animal-api.js">&lt;/script>
</span></code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/8f865c50ee9184e6fdd316076d41f1450cecd3b0">full commit 8f865c5</a></p>
<p>Refresh the page, and success! The data shows up! If thats all you needed, then great. You can stop here. Otherwise....</p>
<h2>Scenario 3: Using the library in both the browser AND node</h2>
<p>I'll create a plain javascript file that I'll run with node: <code>node node-test.js</code></p>
<p>node-test.js</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> AnimalApi <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./index.js'</span><span class="token punctuation">)</span>

AnimalApi<span class="token punctuation">.</span><span class="token function">getCat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">animal</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>animal<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>I get this error:</p>
<pre><code>/Users/dperez/workspace/animal-api/index.js:1
(function (exports, require, module, __filename, __dirname) { import axios from 'axios';
                                                                     ^^^^^

SyntaxError: Unexpected identifier
</code></pre>
<p>We saw this when we tried to run our index.spec.js file with jest before using babel. We're trying to import an ES6 module with <code>require</code> which won't work because <code>import</code> is part of ES6 and node is running against ES5 (for node 10 anyways). We built a <code>lib/index.js</code> using babel earlier in Scenario 1 which contains an ES5 module (that was transpiled from ES6). Lets use that instead:</p>
<p>I now get this error:</p>
<pre><code>☁  animal-api [master] ⚡ node node-test.js
/Users/dperez/workspace/animal-api/node-test.js:3
AnimalApi.getCat().then(animal =&gt; {
          ^

TypeError: AnimalApi.getCat is not a function
</code></pre>
<p>We saw this earlier in the browser, and saw that we needed to add <code>AnimalApi.default.getCat()</code> because of the way that babel transpiles your ES6 module to a UMD module. Once we update it....</p>
<pre><code>☁  animal-api [master] ⚡ node node-test.js
{ logo: 'https://purr.objects-us-east-1.dream.io/i/v0SoPzk.jpg',
  text: 'CAT' }
</code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/3cdea8737f6db1593e48678f2f0e5e61a1198aa2">full commit 3cdea87</a></p>
<p>Success! We can use the ES5 module <code>lib/index.js</code> that was created by babel in order to use the library in node.js.</p>
<h2>Scenario 4: You want to use Typescript in your library, but still include it as normal in a React/node project</h2>
<p>What if you like using Typescript and would rather create your library using Typescript, but still use it in other non-typescript projects? No fear, there's an easy solution.</p>
<p>I'm going to add an <code>index.ts</code> file which contains the library, only using Typescript. Here's what it looks like compared to our index.js file. We added an <code>interface</code> and added a function signature.</p>
<pre class="language-diff"><code class="language-diff"><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>interface Animal {
<span class="token prefix inserted">+</span>    imageSrc: string
<span class="token prefix inserted">+</span>    text: string
<span class="token prefix inserted">+</span>}
<span class="token prefix inserted">+</span>
<span class="token prefix inserted">+</span>const getCat = (): Promise&lt;Animal> => {
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>    return axios.get('https://aws.random.cat/meow').then((response) => {
<span class="token prefix unchanged"> </span>        const imageSrc = response.data.file
<span class="token prefix unchanged"> </span>        const text = 'CAT'
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>        return {imageSrc, text}
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>        return {imageSrc, text} as Animal
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>    })
<span class="token prefix unchanged"> </span>}
</span></code></pre>
<p>If we try to run this <code>.ts</code> file straight from react by adding:</p>
<p>App.js</p>
<pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>import React from 'react';
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>import AnimalApi from 'animal-api';
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>import AnimalApi from 'animal-api/index.ts';
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>import './App.css';
</span></code></pre>
<p>When we refresh the app, we see this error:</p>
<pre><code>Module parse failed: The keyword 'interface' is reserved (3:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import axios from 'axios';
|
&gt; interface Animal {
|     logo: string
|     text: string
</code></pre>
<p>The problem is that React doesn't know what to do with your <code>.ts</code> file. So now you need to convert your <code>index.ts</code> file into something that React can read which would be either an ES6 module, or an ES5 module.</p>
<p>Typescript is able to do this without the help of babel. We can compile our file to an ES5 module that we can then <code>import</code> or <code>require</code>. First, install typescript <code>npm install --save-dev typescript</code></p>
<p>Add a tsconfig.js to configure your Typescript compiler</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
    <span class="token property">"compilerOptions"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token property">"outDir"</span><span class="token operator">:</span> <span class="token string">"./dist"</span><span class="token punctuation">,</span>
        <span class="token property">"lib"</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token string">"ES2015"</span><span class="token punctuation">,</span> <span class="token string">"ES2016"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token property">"sourceMap"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
        <span class="token property">"noImplicitAny"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
        <span class="token property">"target"</span><span class="token operator">:</span> <span class="token string">"es5"</span><span class="token punctuation">,</span>
        <span class="token property">"module"</span><span class="token operator">:</span> <span class="token string">"commonjs"</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<p>When we run <code>tsc</code>, we'll get <code>dist/index.js</code> and that file we'll be able to include from our react project or <code>node-test.js</code> file.</p>
<p>index.html</p>
<pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>import React from 'react';
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>import AnimalApi from 'animal-api';
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>import AnimalApi from 'animal-api/dist/index';
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>import './App.css';
</span></code></pre>
<p>Check out the <a href="https://github.com/intricatecloud/reusable-js-demo/commit/26251248f2e6a4de43eb853db7ddb10203153dbb">full commit 2625124</a></p>
<p>Now when you refresh your pages, success! Now, for the last bit...</p>
<h2>Scenario 5: You want to use Typescript to build a lib included as a script tag</h2>
<p>When we want to include a library or project using a script tag, we need to use a bundler to make sure that our projects dependencies are included with our library. We used webpack earlier to do this, and without much mucking around, we were able to produce a js bundle that can be included as a <code>&lt;script src=&quot;./lib/animal-api.js&quot;&gt;</code></p>
<p>We'll update webpack.config.js to add a Typescript loader which will be able to read your Typescript file and do things with it. We'll still use webpack to bundle it.</p>
<p>Run <code>npm install --save-dev ts-loader</code> to install the loader and then update the webpack config.</p>
<p>webpack.config.js</p>
<pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>module.exports = {
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>  entry: './index.js',
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>  entry: './index.ts',
<span class="token prefix inserted">+</span>  module: {
<span class="token prefix inserted">+</span>    rules: [
<span class="token prefix inserted">+</span>      {
<span class="token prefix inserted">+</span>        test: /\.ts$/,
<span class="token prefix inserted">+</span>        use: 'ts-loader',
<span class="token prefix inserted">+</span>        exclude: /node_modules/,
<span class="token prefix inserted">+</span>      }
<span class="token prefix inserted">+</span>    ]
<span class="token prefix inserted">+</span>  },
<span class="token prefix inserted">+</span>  resolve: {
<span class="token prefix inserted">+</span>    extensions: ['.ts', '.js'],
<span class="token prefix inserted">+</span>  },
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>  output: {
</span></code></pre>
<p>Thats all we have to add to our webpack configuration to get this project built and bundled. Once you run <code>webpack</code>, you'll get a new file <code>./lib/animal-api.js</code>. Go back to your test <code>index.html</code> file to test it out. Thats it!</p>
<p>Check out the last <a href="https://github.com/intricatecloud/reusable-js-demo/commit/cc18f80383093e00451b990bebd9dfdcce1e08b8">full commit cc18f80</a></p>
<p>Theres a few things to be aware of. Our very simple library has multiple entry points that need to be maintained.</p>
<ul>
<li>If you can consume ES6 modules using <code>import AnimalApi from 'animal-api'</code> then that will rely on your <code>./index.js</code> file.</li>
<li>If you want to consume your ES6 module in the browser, you need to use the <code>./lib/animal-api.js</code> file which will make AnimalApi globally available using the babel plugin, and bundles all the dependencies along with it using webpack.</li>
<li>If you want to use your ES6 module in node, then you need to use <code>./lib/index.js</code>, the UMD module that we built with babel.</li>
<li>If you want to use your Typescript file from another Typescript project, you need to use <code>./lib/index.ts</code></li>
<li>If you want to use your Typescript file in a node.js environment, you have to use <code>tsc</code> to compile it down to an ES5 module that you can then re-use.</li>
<li>If you want to use your Typescript file in a <code>&lt;script src=&quot;mylib.js&quot;&gt;</code>, you'll need to use <code>webpack</code> with <code>ts-loader</code> to compile your typescript, bundle all the dependencies, and produce a UMD bundle.</li>
</ul>
<p>Both your source files, and built files can be distributed with your npm package and left to the user to choose which one they need. But it does require you to put a fair amount of tooling in place to create that convenience.</p>
<h2>Wrapping up</h2>
<p>Remember, you can follow along commit by commit by checking out the repo here <a href="https://github.com/intricatecloud/reusable-js-demo">intricatecloud/reusable-js-demo</a>. Drop a ⭐️ if you found it helpful!</p>
<p>Thanks for reading along, hope this helped you understand the differences between the different module types, and how to go about extracting a library from our application that we can use both in and outside of the browser.</p>
<p>If you're interested in more things react - definitely check out my in-depth guide to <a href="https://www.intricatecloud.io/2019/08/adding-google-sign-in-to-your-webapp-a-react-example/">adding google sign-in to a react app</a>, or my guide to <a href="https://www.intricatecloud.io/2018/04/creating-your-serverless-website-in-terraform-part-2/">deploying a static website using S3 + Cloudfront with terraform</a></p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>How to handle API errors in your web app using axios</title>
      <link href="https://intricatecloud.io/2020/03/how-to-handle-api-errors-in-your-web-app-using-axios/"/>
      <updated>2020-03-10T09:39:07Z</updated>
      <id>https://intricatecloud.io/2020/03/how-to-handle-api-errors-in-your-web-app-using-axios/</id>
      <content type="html">
        <![CDATA[
      <p><strong>Note:</strong> If you want to see how to handle these in React, take a look at my new post on that here - <a href="https://www.intricatecloud.io/2021/06/handling-async-errors-with-axios-in-react/">handling async errors with axios in react.</a></p>
<p>Whenever you're making a backend API call with axios, you have to consider what to do with the <code>.catch()</code> block of your promise. Now, you might think that your API is highly available and it'll be running 24/7. You might think that the user workflow is pretty clear, your javascript is sane, and you have unit tests. So when you stare at the catch block when making requests using <code>axios</code> you might think - &quot;Well... I'll just console.log it. That'll be fine.&quot;</p>
<pre class="language-javascript"><code class="language-javascript">axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/my-highly-available-api'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">response</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token comment">// do stuff</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token parameter">err</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token comment">// what now?</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>But there are so many many many more things that are outside of your control that could throw errors when making API requests. And you probably don't even know that they're happening!</p>
<p>This post deals mainly with errors you see in the browser. Things can get pretty funny looking on the back end too - just take a look at <a href="https://www.intricatecloud.io/2018/12/3-things-you-might-see-in-your-logs-once-your-site-is-public/">3 things you might see in your backend logs</a></p>
<p>Below are 3 types of errors that could appear, and how to handle it, when using axios.</p>
<h2>Catching axios errors</h2>
<p>Below is a snippet I've started including in a few JS projects.</p>
<pre class="language-javascript"><code class="language-javascript">axios<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
        <span class="token comment">// do good things</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token parameter">err</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">.</span>response<span class="token punctuation">)</span> <span class="token punctuation">{</span>
	  <span class="token comment">// client received an error response (5xx, 4xx)</span>
	<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">.</span>request<span class="token punctuation">)</span> <span class="token punctuation">{</span>
	  <span class="token comment">// client never received a response, or request never left</span>
	<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
	  <span class="token comment">// anything else</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>Each condition is meant to capture a different type of error.</p>
<h3>Checking error.response</h3>
<p>If your error object contains a <code>response</code> field, that means your server responded with a 4xx/5xx error. Usually this is the error we're most familiar with, and is most straightforward to handle.</p>
<p>Do things like a show a 404 Not Found page/error message if your API returns a 404. Show a different error message if your backend is returning a 5xx or not returning anything at all. You might think your well-architected backend won't throw errors - but its a matter of when, not if.</p>
<h3>Checking error.request</h3>
<p>The second class of errors is where you don't have a response but there's a request field attached to the error. When does this happen? This happens when the browser was able to make a request, but for some reason, it didn't see a response.</p>
<p>This can happen if:</p>
<ul>
<li>you're in a spotty network (think underground subway, or a building wireless network)</li>
<li>if your backend is hanging on each request and not returning a response on time</li>
<li>if you are making cross-domain requests and you're not authorized to make the request</li>
<li>if you're making cross-domain requests and you are authorized, but the backend API returns an error</li>
</ul>
<p>One of the more common versions of this error had a message &quot;Network Error&quot; which is a useless error message. We have a front-end and a backend api hosted on different domains, so every backend API call is a cross-domain request.</p>
<p>Due to security constraints on JS in the browser, if you make an API request, and it fails due to crappy networks, the only error you'll see is &quot;Network Error&quot; which is incredibly unhelpful. It can mean anything from &quot;your device doesn't have internet connectivity&quot; to &quot;your OPTIONS returned a 5xx&quot; (if you make CORS requests). The reason for &quot;Network Error&quot; is described well <a href="https://stackoverflow.com/a/19325710">here on this StackOverflow answer</a></p>
<h3>All the other types of errors</h3>
<p>If your error object doesn't have the <code>response</code> or <code>request</code> field on it, that means its not an axios error and theres likely something else wrong in your app. The error message + stack trace should help you figure out where its coming from.</p>
<h2>How do you fix it?</h2>
<h3>Degrading the user experience</h3>
<p>This all depends on your app. For the projects I work on, for each of the features that use those endpoints, we degrade the user experience.</p>
<p>For example, if the request fails, and the page is useless without that data, then we have a bigger error page that will appear and offer users a way out - which sometimes is only a &quot;Refresh the page&quot; button.</p>
<p>Another example, if a request fails for a profile picture in a social media stream, we can show a placeholder image and disable profile picture changes, along with a toaster message explaining why the &quot;update profile picture&quot; button is disabled. However, showing an alert saying &quot;422 Unprocessable Entity&quot; is useless to see as a user.</p>
<h3>Spotty networks</h3>
<p>The web client I work on is used in school networks which can be absolutely terrible. The availability of your backend barely has anything to do with it, the request sometimes fail to leave the school network.</p>
<p>To solve these types of intermittent network problems, we added in <a href="https://github.com/softonic/axios-retry">axios-retry</a>. This solved a good amount of the errors we were seeing in production. We added this to our axios setup:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> _axios <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'axios'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> axiosRetry <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'axios-retry'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> axios <span class="token operator">=</span> _axios<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment">// https://github.com/softonic/axios-retry/issues/87</span>
<span class="token keyword">const</span> <span class="token function-variable function">retryDelay</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">retryNumber <span class="token operator">=</span> <span class="token number">0</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> seconds <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> retryNumber<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> randomMs <span class="token operator">=</span> <span class="token number">1000</span> <span class="token operator">*</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> seconds <span class="token operator">+</span> randomMs<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token function">axiosRetry</span><span class="token punctuation">(</span>axios<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">retries</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
    retryDelay<span class="token punctuation">,</span>
    <span class="token comment">// retry on Network Error &amp; 5xx responses</span>
    <span class="token literal-property property">retryCondition</span><span class="token operator">:</span> axiosRetry<span class="token punctuation">.</span>isRetryableError<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> axios<span class="token punctuation">;</span></code></pre>
<p>We were able to see that 10% of our users (which are in crappy school networks) were seeing sporadic &quot;Network Errors&quot; and that dropped down to &lt;2% after adding in automatic retries on failure.</p>
<p><img src="/cms-content/nr-screenshot.png" alt="chart showing count of Network Errors"></p>
<p>^ This is a screenshot of our errors is the count of &quot;Network Errors&quot; as they appear in NewRelic and are showing &lt;1% of requests are erroring out.</p>
<p>Which leads to my last point:</p>
<h3>Add error reporting to your front-end</h3>
<p>Its helpful to have front-end error/event reporting so that you know whats happening in prod <strong>before</strong> your users tell you. At my day job, we use NewRelic Browser (paid service - no affiliation, just a fan) to send error events from the front-end. So whenever we catch an exception, we log the error message, along with the stack trace (although thats sometimes useless with minified bundles), and some metadata about the current session so we can try to recreate it.</p>
<p>Other tools that fill this space are <a href="https://github.com/getsentry/sentry-javascript/tree/master/packages/browser">Sentry + browser SDK</a>, <a href="https://docs.rollbar.com/docs/browser-js">Rollbar</a>, and a whole bunch of other useful ones <a href="https://github.com/cheeaun/javascript-error-logging">listed here</a></p>
<h2>Wrapping up</h2>
<p>If you get nothing else out of this, do one thing.</p>
<p>Go to your code base now, and review how you're handling errors with axios.</p>
<ul>
<li>Check if you're doing automatic retries, and consider adding <code>axios-retry</code> if you aren't</li>
<li>Check that you're catching errors, and letting the user know that something has happened. <code>axios.get(...).catch(console.log)</code> isn't good enough.</li>
</ul>
<p>So. How do you handle <em>your</em> errors? Let me know in the comments.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Talking to people - the hard part of being a tech lead</title>
      <link href="https://intricatecloud.io/2020/03/talking-to-people-the-hard-part-of-being-a-tech-lead/"/>
      <updated>2020-03-24T06:00:48Z</updated>
      <id>https://intricatecloud.io/2020/03/talking-to-people-the-hard-part-of-being-a-tech-lead/</id>
      <content type="html">
        <![CDATA[
      <p>Today, I stared at a to-do on my Trello board saying &quot;schedule 1-1s with your team.&quot; Its been on my list for 2 weeks now, and even though I see it every day, I keep putting it off.</p>
<p>I recently became tech lead of my team after the previous lead left suddenly. This wasn't my first time filling in the tech lead's shoes, and I've tried to study a lot about being a lead (<a href="https://www.intricatecloud.io/2018/12/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead/">check out my recommended book list for new tech leads</a>). One of the first items on any leads list, according to &quot;The Internet(tm)&quot;, is to set up 1-1s with people who reported to me. Now, we all understand its a best practice that we should do, but how much we follow through it on can be questionable, kinda like saying you'll take a Cod Liver oil supplement, but the bottle has been sitting there, unopened on your kitchen counter, reminding you of how it tastes gross so you don't do it.</p>
<p>As I stared at the item on my Trello board, I decided to procrastinate it. I've already wasted 15 minutes by thinking about why I don't want to do it. Instead, I'll take an easy issue off the list for this sprint, to get me a quick fix - like a sugary drink that only makes you thirstier. Its familiar, readily available, and gives me the cycle of satisfaction of pushing code out to production.</p>
<h2>Why did I keep pushing it out?</h2>
<p>I thought devs would hate 1-1s. They just want to code without getting interrupted by &quot;management&quot; asking silly questions about your happiness. Why would I want to add another meeting to their calendar? They're senior so surely they'll speak up if they need something. So I can procrastinate this.</p>
<p>Maybe theres someone who I feel doesn't really like me or vice versa (it happens, we're only human), and I'll have to have a 1-1, face-to-face conversation with them, and boy, is that going to be awkward, so I'll procrastinate.</p>
<p>There might be someone on the team who's not quite carrying their weight. I might have to bring up something about it, but they're going to respond poorly and it'll ruin my day. Giving negative feedback is so emotionally draining, and I hate conflict, so I'll procrastinate.</p>
<h2>So how do you solve these issues?</h2>
<p>You know what helps? Talking to your team. Regularly. In private. Team meetings dont count, nor do retros.</p>
<p>Why?</p>
<p>You might have a senior dev who leaves your company because he felt people didn't care about his hard work. You thought he was doing just fine, he should have said something, right? He probably didn't have a chance to and felt uncomfortable bringing it up because they thought you would think its stupid and didn't care. If only you had a chance to regularly catch up with him and let him that it would be the opposite, he would have mentioned it.</p>
<p>You might have a low-performing dev who doesn't know they're not carrying their weight. They think they're doing a great job because no one has said anything about it yet - no news is good news, right! If only you had a chance to talk to them regularly, you could give them feedback and prevent the rest of the team from resenting both that dev for creating more work for them, and the manager for not doing anything about it.</p>
<p>You also won't have two product managers breathing down your neck, because your team was supposed to deliver an important feature for another team, but that got delayed because a dev had to re-do another dev's work in order to finish a new feature. If only you talked to your team.</p>
<p>So how do you do it? Easy - set aside a consistent time where you and your team mate can have a private conversation</p>
<ul>
<li>about their compensation, and employment</li>
<li>about their work</li>
<li>about any problems with other devs</li>
</ul>
<p>...so, fine. I guess I'll just schedule my 1-1s to talk with my team today.</p>
<p>For other helpful resources for new tech leads, check out:</p>
<ul>
<li><a href="https://www.intricatecloud.io/2019/12/avoiding-burnout-as-a-new-tech-lead/">tips for avoiding burn out as a new tech lead</a></li>
<li><a href="https://www.intricatecloud.io/2018/12/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead/">9 books that helped me navigate my first time as a tech lead</a></li>
</ul>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Passwordless sign-in with Google One Tap for Web</title>
      <link href="https://intricatecloud.io/2020/12/passwordless-sign-in-with-google-one-tap-for-web/"/>
      <updated>2020-12-02T05:04:15Z</updated>
      <id>https://intricatecloud.io/2020/12/passwordless-sign-in-with-google-one-tap-for-web/</id>
      <content type="html">
        <![CDATA[
      <p>I sign in with my Google account everywhere I can to avoid having yet-another-password on another random website. I've been seeing an upgraded experience on some sites (maybe I'm just noticing now) like Trello/Medium where you can sign-in with Google with one click on the page without getting redirected. Turns out its called One Tap for Web and its Google's passwordless sign-in option and you can use it on your own website. I took it for a spin and set it up on a hello world React example and here's what I found, warts and all.</p>
<p>You can also watch me live-code this walkthrough over on my Youtube channel - check it out! [embed]https://www.youtube.com/watch?v=qS4dY7syQwA [/embed]</p>
<h2>Create a new react app and add a sign-in state</h2>
<p>Start with a bare react app... <code>npx create-react-app one-tap-demo</code></p>
<p>For this example app, I'll be using the JS library to initialize the one-tap prompt. The reference guide shows you how to add it <a href="https://developers.google.com/identity/one-tap/web/guides/load-one-tap-client-library">using mostly HTML</a>, but if you're using a front-end framework, its easier to use just JS to configure it.</p>
<p>Add some state to keep track of when the user has signed in</p>
<pre class="language-diff"><code class="language-diff">function App() {
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>   const [isSignedIn, setIsSignedIn] = useState(false)
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>   return (
<span class="token prefix unchanged"> </span>       &lt;div className="App">
<span class="token prefix unchanged"> </span>           &lt;header className="App-header">
<span class="token prefix unchanged"> </span>               &lt;img src={logo} className="App-logo" alt="logo" />
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>                { isSignedIn ? &lt;div>You are signed in&lt;/div> : &lt;div>You are not signed in&lt;/div>}
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>           &lt;/header>
<span class="token prefix unchanged"> </span>       &lt;/div>
<span class="token prefix unchanged"> </span>   )
</span>}</code></pre>
<h2>Add the GSI</h2>
<p>Add the Google Sign-In library (also called GSI) dynamically when your application starts</p>
<pre class="language-diff"><code class="language-diff">function App() {
<span class="token unchanged"><span class="token prefix unchanged"> </span>   const [isSignedIn, setIsSignedIn] = useState(false)
</span>
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>     const initializeGsi = () => {
<span class="token prefix inserted">+</span>        google.accounts.id.initialize({
<span class="token prefix inserted">+</span>            client_id: 'insert-your-client-id-here',
<span class="token prefix inserted">+</span>        });
<span class="token prefix inserted">+</span>        google.accounts.id.prompt(notification => {
<span class="token prefix inserted">+</span>            console.log(notification)
<span class="token prefix inserted">+</span>        });
<span class="token prefix inserted">+</span> }
<span class="token prefix inserted">+</span>
<span class="token prefix inserted">+</span>    useEffect(() => {
<span class="token prefix inserted">+</span>        const script = document.createElement('script')
<span class="token prefix inserted">+</span>        script.src = 'https://accounts.google.com/gsi/client'
<span class="token prefix inserted">+</span>        script.onload = initializeGSI()
<span class="token prefix inserted">+</span>        script.async = true;
<span class="token prefix inserted">+</span>        document.querySelector('body').appendChild(script)
<span class="token prefix inserted">+</span>    }, [])
</span>
}</code></pre>
<p>There's 2 API calls - one to configure the library, and one to show the user the prompt (after the library has been configured). To see all possible configuration options, <a href="https://developers.google.com/identity/one-tap/web/reference/js-reference#IdConfiguration">check the reference here</a>.</p>
<p>I added it as a <code>useEffect</code> hook with <code>[]</code> as an arg, so that it only runs once after the first render. <a href="https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect">ref</a></p>
<p>When you refresh the page, if you got it all right the first time, you'll see the prompt.</p>
<h3>Get a Client ID from your Google Developer Console</h3>
<p>Follow this guide for adding your <a href="https://developers.google.com/identity/one-tap/web/guides/get-google-api-clientid">Get your Google API client ID</a></p>
<p>When I refreshed the page after following the steps and adding my client id to the app, I got this error message:</p>
<pre><code>[GSI] Origin is not an authorized javascript origin
</code></pre>
<p>For me, it meant that my Google OAuth Client ID is misconfigured. I missed the &quot;Key Point&quot; on the official walkthrough - which only applies if we're using localhost as our domain.</p>
<ul>
<li>Confirm that your site URL (i.e. <code>http://localhost:3000</code>) is added as both an authorized Javascript origin and as a valid redirect URI, in the Google OAuth Client Console.</li>
<li><strong>IMPORTANT</strong> You ALSO need to add <code>http://localhost</code> as an authorized Javascript origin. This only seems necessary in development when you might be using a different port in your URL (we are).</li>
</ul>
<h2>Using the sign-in button</h2>
<p>Now when you refresh the page, it should be working, and you should see the prompt. If you don't see the prompt, check your developer console for errors, and if that doesn't help - see the section on Debugging below.</p>
<p>IF THIS IS YOUR FIRST TIME DOING THIS, DO <strong>NOT</strong> CLICK THE X BUTTON ON THE PROMPT BEFORE READING THIS WARNING!</p>
<p><strong>WARNING 1:</strong> Clicking the X button on the one-tap prompt dismisses the prompt. If you refresh the page after this, you won't see the button come back. Why?</p>
<p>The One Tap library has some additional side effects around dismissing the prompt. If you've clicked the X button, a cookie has been added to your domain called <code>g_state</code>. Here's a screenshot of where you can find it - if you clear that cookie value, you'll see the prompt come back.
<img src="/cms-content/g-state-cookie.png" alt="showing g state cookie value in developer console"></p>
<p><strong>WARNING 2:</strong> Clicking the X button <strong>more than once</strong> will enter you into an Exponential Cool Down mode - <a href="https://developers.google.com/identity/one-tap/web/guides/features#exponential_cool_down">see the reference here</a>. What does that mean?</p>
<ul>
<li>You cannot clear cookies or use an incognito window to get around it (at least I couldn't). It seems to be based on your browser and website (maybe IP?), its not clear. If you accidentally run into this, time to take a break. Or try a different browser/website URL.</li>
<li>After I dismissed it, I couldn't see it for 10-15 minutes, although the table on the developer guide suggests I wouldn't see it for 2 hours. In any case, its an annoying thing to have to run into during development.</li>
</ul>
<h2>Debugging issues with One Tap sign-in</h2>
<p>The developer guide suggests this as example code for your prompt. But it flys over an important detail that the reason WHY your prompt did not display, or got skipped, or got dismissed is also in the <code>notification</code> object.</p>
<pre class="language-js"><code class="language-js">google<span class="token punctuation">.</span>accounts<span class="token punctuation">.</span>id<span class="token punctuation">.</span><span class="token function">prompt</span><span class="token punctuation">(</span><span class="token parameter">notification</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>notification<span class="token punctuation">.</span><span class="token function">isNotDisplayed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">||</span> notification<span class="token punctuation">.</span><span class="token function">isSkippedMoment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                  <span class="token comment">// continue with another identity provider.</span>
      <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>There are 3 types of notification moments - <code>display</code>, <code>skipped</code>, <code>dismissed</code> and each one has their own list of possible reasons, with its own API call to figure it out - <a href="https://developers.google.com/identity/one-tap/web/reference/js-reference#PromptMomentNotification">see here for the full list</a>. If you're having problems with the button and you don't know why, it might be helpful to use the snippet below to see what those reasons look like:</p>
<pre class="language-diff"><code class="language-diff">google.accounts.id.prompt(notification => {
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>      if (notification.isNotDisplayed()) {
<span class="token prefix inserted">+</span>          console.log(notification.getNotDisplayedReason())
<span class="token prefix inserted">+</span>      } else if (notification.isSkippedMoment()) {
<span class="token prefix inserted">+</span>          console.log(notification.getSkippedReason())
<span class="token prefix inserted">+</span>      } else if(notification.isDismissedMoment()) {
<span class="token prefix inserted">+</span>          console.log(notification.getDismissedReason())
<span class="token prefix inserted">+</span>      }
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>       // continue with another identity provider.
<span class="token prefix unchanged"> </span>   });
</span></code></pre>
<p>One of the reasons you might see is <code>opt_out_or_no_session</code>. This can mean that</p>
<ul>
<li>A. your user has &quot;opted out&quot; of the prompt by dismissing it. You can try clearing the <code>g_state</code> cookie that might be on your domain, if you dismissed it by accident</li>
<li>B. your user does not have a current Google session in your current browser session.
<ul>
<li>While this is a passwordless sign-on via Google, it does require you to have signed into Google (presumably with a password) at some earlier point.</li>
<li>If you are using an Incognito window, make sure you sign in to Google within that window.</li>
</ul>
</li>
</ul>
<h2>Now that your user has signed in</h2>
<p>Once you have selected your account and signed in with no errors, its time to hook it into your React app. If you've used the Google Sign-In for Websites library before (<a href="https://www.intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-pt-1/">see here for my guide on setting it up</a>), there's an API to let you get at the user's info. But with the One Tap Sign-In for Web library, you only get the users id token (aka their JWT token).</p>
<p>That means you need to decode the ID token to get the users info. We can do that by adding the jwt-decode library with  <code>npm install --save jwt-decode</code></p>
<p>To do all this, add a callback to your initialize block:</p>
<pre class="language-diff"><code class="language-diff"><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span> import jwt_decode from 'jwt-decode'
</span>
function App() {
<span class="token unchanged"><span class="token prefix unchanged"> </span>   const [isSignedIn, setIsSignedIn] = useState(false)
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>   const [userInfo, setUserInfo] = useState(null)
<span class="token prefix inserted">+</span>   const onOneTapSignedIn(response => {
<span class="token prefix inserted">+</span>       setIsSignedIn(true)
<span class="token prefix inserted">+</span>       const decodedToken = jwt_decode(response.credential)
<span class="token prefix inserted">+</span>       setUserInfo({...decodedToken})
<span class="token prefix inserted">+</span>   })
</span>
<span class="token unchanged"><span class="token prefix unchanged"> </span>    const initializeGsi = () => {
<span class="token prefix unchanged"> </span>       google.accounts.id.initialize({
<span class="token prefix unchanged"> </span>           client_id: 'insert-your-client-id-here',
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>            callback: onOneTapSignedIn
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>       });
<span class="token prefix unchanged"> </span>       ...
<span class="token prefix unchanged"> </span>}
<span class="token prefix unchanged"> </span>   ...
<span class="token prefix unchanged"> </span>   return (
<span class="token prefix unchanged"> </span>       &lt;div className="App">
<span class="token prefix unchanged"> </span>           &lt;header className="App-header">
<span class="token prefix unchanged"> </span>               &lt;img src={logo} className="App-logo" alt="logo" />
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>               { isSignedIn ?
<span class="token prefix inserted">+</span>                   &lt;div>Hello {userInfo.name} ({userInfo.email})&lt;/div> :
<span class="token prefix inserted">+</span>                   &lt;div>You are not signed in&lt;/div>
<span class="token prefix inserted">+</span>               }
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>           &lt;/header>
<span class="token prefix unchanged"> </span>       &lt;/div>
<span class="token prefix unchanged"> </span>   )
</span>}</code></pre>
<p>To see all the user info that is available to you, <a href="https://developers.google.com/identity/one-tap/web/reference/js-reference#credential">see the dev guide here</a></p>
<h2>Signing out</h2>
<p>The docs suggest adding a <code>&lt;div id=&quot;g_id_signout&quot;&gt;&lt;/div&gt;</code> to your page, but its not clear if the library is supposed to create a sign-out button for you. I think the answer is no, because I tried it, and nothing happens.</p>
<p>My current theory is that signing-out is meant to be left to your own application, and can be as easy as refreshing the page.</p>
<ul>
<li>In this post, I'm only using the One-Tap button on the front-end since I have no login system myself. Whenever you refresh the page, you'll see the prompt even if you just finished signing in.</li>
<li>If I wanted to integrate this with an existing login system, &quot;sign-out&quot; would mean signing out of my own application (and not out of my google account)</li>
<li>This flow works out as long as you dont enable the auto sign-in option.</li>
</ul>
<pre class="language-diff"><code class="language-diff"><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span> const signout = () => {
<span class="token prefix inserted">+</span>     // refresh the page
<span class="token prefix inserted">+</span>     window.location.reload();
<span class="token prefix inserted">+</span> }
</span>
return (
<span class="token unchanged"><span class="token prefix unchanged"> </span>   &lt;div className="App">
<span class="token prefix unchanged"> </span>       &lt;header className="App-header">
<span class="token prefix unchanged"> </span>           &lt;img src={logo} className="App-logo" alt="logo" />
<span class="token prefix unchanged"> </span>           { isSignedIn ?
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>               &lt;div>
<span class="token prefix inserted">+</span>                   Hello {userInfo.name} ({userInfo.email})
<span class="token prefix inserted">+</span>                   &lt;div className="g_id_signout"
<span class="token prefix inserted">+</span>                       onClick={() => signout()}>
<span class="token prefix inserted">+</span>                       Sign Out
<span class="token prefix inserted">+</span>                    &lt;/div>
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>              &lt;/div> :
<span class="token prefix unchanged"> </span>              &lt;div>You are not signed in&lt;/div>
<span class="token prefix unchanged"> </span>           }
<span class="token prefix unchanged"> </span>       &lt;/header>
<span class="token prefix unchanged"> </span>   &lt;/div>
</span>)
</code></pre>
<h2>Limitations of the One Tap Sign-In button</h2>
<p>Also lost in the developer guide is this comment for the prompt snippet <code>// continue with another identity provider.</code> which brings us to the section on limitations of this sign-in prompt.</p>
<ul>
<li>The One Tap Sign-In button only works on Chrome &amp; Firefox across Android, iOS, macOS, Linux, Window 10. If you have users on Safari or Edge, they won't see the prompt. When I try it, I get a Not Displayed error with reason <code>opt_out_or_no_session</code> :shrug:</li>
<li>If your users accidentally dismiss the prompt (see the warning above if you missed it the first time), they will also see <code>opt_out_or_no_session</code> as the Not Displayed reason, and they will be unable to sign-in.</li>
<li>The library (and the interface itself) is different from the Google Sign-In for Web library. The One Tap library uses <code>google.accounts.id.initialize()</code> to initialize the app, and the other one uses <code>gapi.auth2.init()</code> - That seems like a missed opportunity to put both login systems behind the same interface.</li>
<li>There's no sign out button - the snippet thats mentioned in the docs doesn't seem to do anything. My guess is that a sign-out button could mean refreshing the page which would cause the prompt to appear again, effectively signing you out.</li>
</ul>
<p>It's highlighted on the main page of the dev docs, but you can't use this library alone. I did it here for the purposes of a hello world example, but its meant to be an upgrade to your login experience.</p>
<h2>Try it out</h2>
<p>I've pushed this sample code to github available on <a href="https://github.com/intricatecloud/google-one-tap-web-demo">intricatecloud/google-one-tap-web-demo</a>. Instructions to run the demo in the README.</p>
<p>It's worth taking a look at how some of these other sites have implemented the login workflow. Sign in to Google in your current browser session, and then visit medium.com to see the prompt in action.</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>5 free coding games where you write javascript to play</title>
      <link href="https://intricatecloud.io/2021/06/5-free-games-where-you-write-javascript-to-play/"/>
      <updated>2021-06-13T04:30:52Z</updated>
      <id>https://intricatecloud.io/2021/06/5-free-games-where-you-write-javascript-to-play/</id>
      <content type="html">
        <![CDATA[
      <p>If you're itching to write some code, but you can't stand the thought of another MEAN, MERN, or PERN stack, here's a few resources to help you explore javascript using a slightly different approach - games!</p>
<p>Last week, I saw a Reddit comment thread that was asking about coding games - games where you have to write code to play the game. I've heard of a couple over the years, but never explored the genre. I went down the rabbit hole over the weekend, and was amazed at a world I didn't know existed. Here's the games that really stood out to me, and they range from beginner-friendly to advanced  development knowledge required.</p>
<h3>Flexbox Froggy &amp; Grid Garden</h3>
<p><img src="/cms-content/Untitled.png" alt="Flexbox Froggy Screenshot">
<img src="/cms-content/Untitled-1.png" alt="Grid Garden Screenshot"></p>
<p>These two games are friendly for those that are learning CSS. Make a frog move around the screen with <a href="https://flexboxfroggy.com/">Flexbox Froggy</a> or plant some carrots with <a href="https://cssgridgarden.com/">Grid Garden</a>. Even as a dev with decent CSS - some of these took me a minute to figure out. Its not often that you use CSS to control a game!</p>
<p>These games have a bit of chatter around them online so there are also resources for helping you if you get stuck, so you can search for &quot;grid garden answers&quot; to make your way!</p>
<h3>CodeCombat</h3>
<p><img src="/cms-content/Untitled-2.png" alt="Code Combat Screenshot"></p>
<p><a href="https://codecombat.com/home">CodeCombat</a> is an RPG-like game where you have a character that you control with Javascript, Python, or C++ (and Coffeescript!). The game is beginner-friendly for those learning JS or Python. You progress through levels writing different bits of code, and you can level up your skills and abilities as you go through each level. I was a huge fan of Diablo and this feels like that kind of RPG except rather than mouse-clicking all over the screen, you write some Javascript to beat the level.</p>
<h3>CodinGame</h3>
<p><img src="/cms-content/Untitled-3.png" alt="CodinGame Screenshot"></p>
<p>If you thought CodeCombat was fun - get a load of <a href="https://www.codingame.com/start">CodinGame</a>. Its pretty feature packed. You go through a series of challenges where you need to write or edit some code to make things happen. As you complete each challenge, you can see your progress on a map. You get achievements for your progress, and access to CodeClash. A CodeClash is a multiplayer challenge where you and a few other people participate in a code challenge - where you try to write code to get the most tests to pass the fastest.</p>
<p>There's a leaderboard, a chat, and you can see other people's submissions (on the Code Clash anyway). They also have practice challenges that have easy/medium/hard difficulty - they have something for everyone here. Easy to spend a fun 20 minutes here.</p>
<h3>Untrusted</h3>
<p><img src="/cms-content/untrusted-screenshot.png" alt="Untrusted Screenshot"></p>
<p>I'm not sure if <a href="https://alexnisnevich.github.io/untrusted/">Untrusted</a> is a play on Unchartered the game, but this game was pretty fun to get started on.</p>
<p>This one is definitely for intermediate-advanced JS developers. Make your way through various puzzles using javascript to help break the puzzle you're in, and move to the next level. The concept and game play was pretty fun and my favorite (maybe a close second behind CodinGame). You have to be able to read Javascript code, and you should at least be good at getting your JS to do the <em>wrong</em> thing (🙋‍♂️ I'm a pro at this by now).</p>
<h3>Capture the Flag &amp; CTFLearn</h3>
<p>I've always heard of capture the flag events, most in the context of First-Person Shooter games, but I had no idea what a cybersecurity CTF is! A Capture the Flag (CTF) event is where teams are presented with coding or hacking challenges where they need to search for a flag, and then submit it when they find it for points. Most points win.</p>
<p>This is best suited towards someone (or teams) with experience in web technologies, although they do have more beginner-friendly challenges.</p>
<p>What really got me interested in this was running across the Live Overflow channel on YouTube where he goes into what a live CTF event actually looks like behind the scenes. He cut out all the boring parts of it but really goes into how his team participates in the event. I thought it would have been that you can show up and participate, but maybe not! Check it out on YouTube here.</p>
<p>[embed]https://www.youtube.com/watch?v=DGuRI_SPZYg&amp;t=516s[/embed]</p>
<p>Typically, CTFs are organized by someone and run during some time period. Teams are able to register to participate. The host would usually provide a live environment for hacking or provide files that you'd need to solve the challenge. <a href="https://ctftime.org/">CTF Time</a> is a useful listing of CTF events around the global, around the year.</p>
<p><img src="/cms-content/ctf-learn-screenshot.png" alt="CTF Learn Screenshot"></p>
<p>If you don't want to participate in a live event with a team, <a href="https://ctflearn.com/">CTFLearn</a> is a way to practice Capture-the-Flag style challenges across a variety of topics in the systems space. I did a few bits of the Forensics challenge on CTFLearn and it really got me scratching my head for a bit - definitely a fun challenge, but made me question whether I knew how to solve it or not.</p>
<p>I hope at least one of these games piques your interest, there's something here for every difficulty level. If you do try one of these out - leave a comment or hit me up on Twitter <a href="https://twitter.com/intricatecloud">@intricatecloud</a> and let me know what you thought!</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Handling async errors with Axios in React</title>
      <link href="https://intricatecloud.io/2021/06/handling-async-errors-with-axios-in-react/"/>
      <updated>2021-06-15T04:52:14Z</updated>
      <id>https://intricatecloud.io/2021/06/handling-async-errors-with-axios-in-react/</id>
      <content type="html">
        <![CDATA[
      <p>Have you ever been stuck on what looks like an empty page, and you ask yourself, &quot;Am I supposed to be seeing something yet?&quot;, only for it to appear like 30 seconds later? Or maybe you've clicked on a button and you're not sure whether its processing or not (like on checkout pages). If thats what your own app feels like then, read on. In this guide, I'll walk you through 4 scenarios you should handle when working with APIs using axios &amp; react.</p>
<ul>
<li>Handling requests that sometimes take longer than usual and leave the user looking at an empty page</li>
<li>Handling requests that have errored and you want to give the user a way out</li>
<li>Handling a possible timeout where the request is taking significantly longer than usual and giving the user an updated loading message so they see the page isn't frozen</li>
<li>Handling a definite timeout where you want to abort the request to give the user a more specific error message</li>
</ul>
<p>I'll start with a very innocent example - When the <code>ResultsList</code> component loads, it request some data, and displays it in the UI.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">ResultsList</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>results<span class="token punctuation">,</span> setResults<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
  
  <span class="token comment">// run on load</span>
  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>apiUrl<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">response</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token function">setResults</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>data<span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token parameter">err</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
  
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token punctuation">{</span> results<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">result</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>result<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span>
    <span class="token punctuation">)</span>
  <span class="token punctuation">}</span></code></pre>
<p>The data from the API is being stored in the <code>results</code> field of the component's state. It starts as an empty array, and gets replaced with the actual results when the data is fetched.</p>
<p>This is buggy. Here's why.</p>
<h2>1. Handling long response times</h2>
<p>Not all users will have a great connection, even if your backend is stable, and that request may take a little longer to complete than usual. In this example, there's no feedback to the user that something is happening, and when there are issues, the page will be empty for a lot longer than you're expecting.</p>
<p>It will make your users ask, &quot;Am I supposed to be seeing something yet?&quot;</p>
<p>This question can be solved with a few snippets:</p>
<pre class="language-diff"><code class="language-diff">const ResultsList = () => {
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>  const [results, setResults] = useState(null)
</span>
...

<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>  const getListItems = () => {
<span class="token prefix inserted">+</span>    if(results) {
<span class="token prefix inserted">+</span>      return results.map(result => {
<span class="token prefix inserted">+</span>        return &lt;li>{result.name}&lt;/li>
<span class="token prefix inserted">+</span>    })
<span class="token prefix inserted">+</span>    } else {
<span class="token prefix inserted">+</span>      return (
<span class="token prefix inserted">+</span>        &lt;div>
<span class="token prefix inserted">+</span>          &lt;i class="fas fa-spinner fa-spin">&lt;/i>
<span class="token prefix inserted">+</span>          Results are loading...
<span class="token prefix inserted">+</span>       &lt;/div>
<span class="token prefix inserted">+</span>      )
<span class="token prefix inserted">+</span>    }
<span class="token prefix inserted">+</span>   }
<span class="token prefix inserted">+</span>
<span class="token prefix inserted">+</span>  return (
<span class="token prefix inserted">+</span>    &lt;div>
<span class="token prefix inserted">+</span>      &lt;ul>{getListItems()}&lt;/ul>
<span class="token prefix inserted">+</span>    &lt;/div>
<span class="token prefix inserted">+</span>    )
</span><span class="token unchanged"><span class="token prefix unchanged"> </span> }
</span></code></pre>
<p>There are 2 changes</p>
<ul>
<li>Rather than initialize <code>results</code> to an empty array <code>[]</code>, its initialized to null.</li>
<li>I can then check if I should show a loading message, or if I should show the list with data.</li>
</ul>
<p>Pro-tip: Add spinners. They're so easy. And fun. Your users will be less confused. I say this as someone who was too lazy to add spinners.</p>
<h2>2. Handling errors</h2>
<p>When something goes wrong, the easy options are to write it to the console, or the show user an error message. The best error message is one that can tell the user how to fix whatever just happened. I have more details on the axios error object on <a href="https://www.intricatecloud.io/2020/03/how-to-handle-api-errors-in-your-web-app-using-axios/">this post here</a></p>
<p>The easy option is to show the user <em>something</em> useful on any error, and offer them a way to fix things.</p>
<pre class="language-diff"><code class="language-diff">const ResultsList = () => {
<span class="token unchanged"><span class="token prefix unchanged"> </span> const [results, setResults] = useState(null)
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span> const [error, setError] = useState(null)
</span>
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span> const loadData = () => {
<span class="token prefix inserted">+</span>   return axios.get(apiUrl).then(response => {
<span class="token prefix inserted">+</span>     setResults(response.data)
<span class="token prefix inserted">+</span>     setError(null)
<span class="token prefix inserted">+</span>   }).catch(err => {
<span class="token prefix inserted">+</span>     setError(err)
<span class="token prefix inserted">+</span>   })
<span class="token prefix inserted">+</span> }
</span>
<span class="token unchanged"><span class="token prefix unchanged"> </span> // run on load
<span class="token prefix unchanged"> </span> useEffect(() => {
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>   loadData()
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>    ...
</span><span class="token unchanged"><span class="token prefix unchanged"> </span> }, [])
</span>
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span> const getErrorView = () => {
<span class="token prefix inserted">+</span>   return (
<span class="token prefix inserted">+</span>     &lt;div>
<span class="token prefix inserted">+</span>       Oh no! Something went wrong.
<span class="token prefix inserted">+</span>       &lt;button onClick={() => loadData()}>
<span class="token prefix inserted">+</span>         Try again
<span class="token prefix inserted">+</span>.      &lt;/button>
<span class="token prefix inserted">+</span>     &lt;/div>
<span class="token prefix inserted">+</span>   )
<span class="token prefix inserted">+</span> }
</span>
<span class="token unchanged"><span class="token prefix unchanged"> </span> return (
<span class="token prefix unchanged"> </span>   &lt;div>
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    &lt;ul>
<span class="token prefix inserted">+</span>      {  error ? 
<span class="token prefix inserted">+</span>        getListItems() : getErrorView() 
<span class="token prefix inserted">+</span>      }
<span class="token prefix inserted">+</span>    &lt;/ul>
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>    &lt;ul>{getListItems()}&lt;/ul>
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>   &lt;/div>
<span class="token prefix unchanged"> </span>   )
<span class="token prefix unchanged"> </span> }
</span></code></pre>
<p>Here, I've added an <code>error</code> field to the component's state where I can keep track of errors and conditionally show an error message to the user. I give them a painfully generic error message, but I offer a way out via a button to Try again which will retry the request.</p>
<p>When the request succeeds, the error is cleared from the state, and the user sees the right info. Wonderful.</p>
<h2>3. Handling a possible timeout</h2>
<p>You get extra bonus points if you add this. Its not that its hard, its just that its quite considerate. Every once in awhile, a user will come across a loading spinner, and they'll wonder - &quot;Is it frozen and the icon is just spinning or does it really take this long?&quot;</p>
<p>If a request is taking awhile, you can give the user a little feedback in the form of, &quot;This is taking longer than usual...&quot;</p>
<pre class="language-diff"><code class="language-diff"><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span> const TIMEOUT_INTERVAL = 60 * 1000
</span>
const loadData = () => {
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>   if (results) setResults(null)
</span>
<span class="token unchanged"><span class="token prefix unchanged"> </span>   // make the request
<span class="token prefix unchanged"> </span>   axios.get(apiUrl).then(response => {
<span class="token prefix unchanged"> </span>     setResults(response.data)
<span class="token prefix unchanged"> </span>     setError(null)
<span class="token prefix unchanged"> </span>   }).catch(err => {
<span class="token prefix unchanged"> </span>     setError(err)
<span class="token prefix unchanged"> </span>   })
</span>
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>   // show an update after a timeout period
<span class="token prefix inserted">+</span>   setTimeout(() => {
<span class="token prefix inserted">+</span>     if (!results &amp;&amp; !error) {
<span class="token prefix inserted">+</span>       setError(new Error('Timeout'))
<span class="token prefix inserted">+</span>     }
<span class="token prefix inserted">+</span>   }, TIMEOUT_INTERVAL)
</span>}

const getErrorView = () => {
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>   if (error.message === 'Timeout') {
<span class="token prefix inserted">+</span>     &lt;div>This is taking longer than usual&lt;/div>
<span class="token prefix inserted">+</span>   } else {
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>     return (
<span class="token prefix unchanged"> </span>       &lt;div>
<span class="token prefix unchanged"> </span>         Oh no! Something went wrong.
<span class="token prefix unchanged"> </span>         &lt;button onClick={() => loadData()}>
<span class="token prefix unchanged"> </span>          Try again
<span class="token prefix unchanged"> </span>         &lt;/button>
<span class="token prefix unchanged"> </span>       &lt;/div>
<span class="token prefix unchanged"> </span>     )
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    }
</span>}</code></pre>
<p>You can set a timer for whichever <code>TIMEOUT_INTERVAL</code> you want. When the timer executes, check whether the data has already loaded, and show the extra error message. If there are no results yet, and no errors, we're still waiting for data so you can show the updated loading message.</p>
<h2>4. Handling a definite timeout</h2>
<p>If you know a request is supposed to be quick, and you want to give the user quick feedback, you can set a timeout in the axios config.</p>
<pre class="language-diff"><code class="language-diff">const loadData = () => {
<span class="token unchanged"><span class="token prefix unchanged"> </span>   if (results) setResults(null)
</span>
<span class="token unchanged"><span class="token prefix unchanged"> </span>   // make the request
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    axios.get(apiUrl, { timeout: TIMEOUT_INTERVAL }).then(response => {
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>    axios.get(apiUrl).then(response => {
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>     setResults(response.data)
<span class="token prefix unchanged"> </span>     setError(null)
<span class="token prefix unchanged"> </span>   }).catch(err => {
<span class="token prefix unchanged"> </span>     setError(err)
<span class="token prefix unchanged"> </span>   })
</span>		...
}
const getErrorView = () => {
<span class="token unchanged"><span class="token prefix unchanged"> </span>   if (error.message === 'Timeout') {
<span class="token prefix unchanged"> </span>     &lt;div>This is taking longer than usual&lt;/div>
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    } else if (error.message.includes('timeout')) {
<span class="token prefix inserted">+</span>      &lt;div>This is taking too long. Something is wrong - try again later.&lt;/div>
<span class="token prefix inserted">+</span>    } else {
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>     return ...
<span class="token prefix unchanged"> </span>   }
</span>}</code></pre>
<p>Axios will throw an error here when the request rejects and it'll have an error message like <code>timeout of 30000ms exceeded</code></p>
<h2><strong>Get started</strong></h2>
<p>Take a look at your React projects that are working with APIs and review how you're handling errors. These tips are an easy way to give your users with a more resilient experience. If you've tried it out or have different ways of approaching your errors, let me know in the comments!</p>

    ]]>
      </content>
    </entry>
  
    
    <entry>
      <title>Teaching a 1-credit CS course at Cornell Tech</title>
      <link href="https://intricatecloud.io/2023/06/teaching-a-1-credit-cs-course-at-cornell-tech/"/>
      <updated>2023-06-22T05:17:10Z</updated>
      <id>https://intricatecloud.io/2023/06/teaching-a-1-credit-cs-course-at-cornell-tech/</id>
      <content type="html">
        <![CDATA[
      <p>In 2021, I joined Cornell Tech as a Visiting Instructor, to trial out a class called CS 5356 Building Startup Systems. It started off as a 1-credit course that met for 1:15 hours a week. The goal of the class was to cover all the practical aspects of building software and software engineering. It is where you take the theoretical skills of writing code and connect it to real world problems solved through the web.</p>
<p>Cornell Tech also has an entrepreneurial program called Startup Studio that puts students in multi-disciplinary teams to build the basis of a startup, and in my class, they can develop a working prototype of their idea using tools like React &amp; Firebase.</p>
<p>This was my first time teaching in a university setting. Years ago, I ran a non-profit to help teach underserved teenagers in NYC how to code with robotics, and now, the big difference is that I'm teaching adults. Surprisingly, the age doesn't make that big of a difference!</p>
<p>I enjoyed the experience of teaching, of watching students connect one idea in their head to another in real time. Students have big aspirations and the energy to do it. But the experience also had its downsides, so I thought it'd be helpful to share out my experience for other technical instructors &amp; aspiring students (both in and out of university settings).</p>
<h2>On Teaching</h2>
<p>When I joined in 2021, we were still in the middle of the pandemic so my very first experience was teaching a class remotely over Zoom to ~45 students who had registered for the class.</p>
<p>Since it was a 1-credit course, my class started midway through the semester and lasted for 8 weeks. My class ran for an hour and 15 minutes each Monday at 3pm.</p>
<p>At my day job at Amplify Education, I'm used to working remotely and presenting in front of a room of blank squares with your initials as an avatar. So I didn't mind it too much and I had fun with it - like this pic where I use the backdrop of the conference room in The Office 🙂.</p>
<p><img src="/cms-content/Screen-Shot-2023-06-22-at-12.28.49-AM.png" alt="a screencap of a zoom recording of one of my classes"></p>
<p>My biggest worry while preparing for the class was that I wouldn't have enough content to fill the time slot. It was my biggest dread. So I prepared a ton of content to go through in each session. Then reality would smack me in the face when I realize how hard it is to cover whatever I thought I would be able to cover.</p>
<h2>On Teaching Programming</h2>
<p>Since I'm teaching a grad level class, I assume that students can understand code and at least be able to put ideas together with code. And that's true to an extent. One of the nice things about Cornell Tech is that you can enroll even if you don't have a CS background, as long as you're able to figure it out on your own and catch up.</p>
<p>Unfortunately, for me, that means the experience range in the class goes from people who have never heard of git all the way to people who have already launched a web-based business. I have some students who have only taken Data Structures &amp; Algorithms, others who only know Python with Jupyter Notebooks. One student shared that their intro to CS class was C++ and it was mostly focused on coding for biostats.</p>
<p>Multiple students would say, &quot;I only know python for machine learning&quot;, worried about how they might perform in my class.</p>
<p><img src="/cms-content/Screen-Shot-2023-06-22-at-12.54.53-AM.png" alt="a survey that shows 9 percent of students in my class are very new to programming"></p>
<p>From a student's point of view, their only exposure to the industry is via their college experience, after all, that's what they are paying for. But some colleges are stuck with outdated ways and techniques of building software, and it doesn't line up with whats needed in the industry. The gap between writing some numpy scripts in Jupyter code and creating a product that people can use is huge.</p>
<p>It is impossible to cater to both ends - I tried and failed miserably.</p>
<p>My first 3 months of my first programming job was a mind opener in topics that never came up in school. Things like version control, continuous integration, environments was all a new concept to me and never mentioned in my college - so this was what I wanted cover in my course.</p>
<p>I also learned that many teachers who teach programming or other CS classes are not always teachers first. They do other work as part of their job at the university and the class is just one part of it, and for some - its their least favorite and required thing to do (and I get why!). They may not even have a background in teaching at all. One of my professors hadn't programmed professionally in years. His experience seemed outdated even in an intro class.</p>
<p>But I can't really talk either... I have no formal background in teaching either!</p>
<h1>On Learning Programming</h1>
<p>Its one thing to learn the syntax of a language, and its something else to use the language to do something more than console.log(). Lots of people struggle with both.</p>
<p>Lectures are a terrible format for learning. And the alternative where students do exercises during class falls apart quickly. Some students like to work in groups, some students want to work individually. There's a lot of slackers out there and people try to avoid them.</p>
<p>Some students work better when they can review material ahead of time. Some students just want to hear what you have to say that day. Some students rely on the Zoom recording, so they can be less alert during the actual class. Some students like a video format. Other students prefer a text-based format.</p>
<p>And holy moly, its a lot of work to put this all together from scratch. The crux of the problem is, I don't know how much I can reasonably cover within a semester's time, and I can make small course corrections based on feedback - but I can't make big shifts in the order I teach things mid-way through the semester (not easily anyway).</p>
<p>Students pay a pretty penny to get an education from a university, but given the structure and the incentives, universities (in general) don't have strong incentives to provide quality instruction. Cornell Tech pays a pretty penny to get professional industry leaders to teach their courses, and I think they have a good cross-section of practical experience + teaching experience. But students have very varied experiences across schools.</p>
<p>So students come in expecting to learn from people who are at the top of their game. And its possible they may get someone who is good at what they do, but can't communicate at all. Or you get someone who can teach, but they don't do the work professionally and might be behind the times.</p>
<h2>On Grading</h2>
<p>This was the hardest part to come to terms with. Early on, I told myself repeatedly - &quot;I'm not out here to ruin anyones life.&quot; But at the same time, I had to define a bar for what constitutes acceptable work.</p>
<p>Grading is incredibly subjective. There is no standard for grades - especially for a class that you're creating. And the ranges will vary wildly from class to class. For one professor, an A+ means you have to go above and beyond whats expected of you in the class. For others, an A+ means you completed all the work that was expected of you. It is up to the teachers discretion to grade the students understanding of the material - this is hard to do.</p>
<p>The best advice I got was to base it on a real situation. If a student was to mention during a job interview that they took this class at Cornell Tech, and showed up, and didn't know any of the lingo, the interviewers would doubt the usefulness of their degree. So for me, an A/A+ meant that I believed you had enough to hold your own during a technical interview.</p>
<p>That said, some students are (obviously) very opinionated about how they should be graded. Some students think they should be graded on effort, others believe the amount of time spent should be a factor. Others want to do the work when they feel like it without penalty.</p>
<p>There was one F in my class this first semester but it was a student that had meant to drop the course. Because it was a 1-credit course, I tried to make it very easy with a minimal amount of work. I started the course with ~45 students and finished with 33 by the end of the semester.</p>
<p>Here's how they did
<img src="/cms-content/Screen-Shot-2023-06-22-at-1.33.13-AM.png" alt="grade distribution of the class"></p>
<p>Maybe in a future post - I'll talk about the way I structured the course and why from an instructor's perspective.</p>

    ]]>
      </content>
    </entry>
  
</feed>