tag:blogger.com,1999:blog-28594190170827774762024-03-19T04:47:49.589-04:00Matthew D Fuller - BlogMatt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.comBlogger81125tag:blogger.com,1999:blog-2859419017082777476.post-9403800873195729582015-12-07T20:30:00.000-05:002015-12-08T12:20:15.682-05:00AWS Lambda: "Occasionally Reliable Caching"One of the biggest misconceptions I've heard from developers working with Lambda for the first time is that every execution of a Lambda function is entirely isolated and independent. AWS may be slightly responsible for this, as their <a href="https://aws.amazon.com/lambda/faqs/">documentation</a> states:
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<blockquote class="tr_bq">
<i>Each AWS Lambda function runs in its own isolated environment, with its own resources and file system view. AWS Lambda uses the same techniques as Amazon EC2 to provide security and separation at the infrastructure and execution levels.</i></blockquote>
The trouble with this statement is that, while each function may be running in an isolated environment, the <i>executions</i> of that function have access to the same memory and disk resources. I'll skip right to an example.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">var str = 'hello';</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">exports.handler = function(event, context) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>console.log(str);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>str = 'it\'s me';</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>context.succeed();</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">};</span><br />
<br />
Create a new Lambda function using this code and then run it twice. Here's the output:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">START RequestId: 031528d9-8eea-22e5-b1e5-13516c9e3426 Version: $LATEST</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">2015-12-07T22:37:50.125Z<span class="Apple-tab-span" style="white-space: pre;"> </span>031528d9-8eea-22e5-b1e5-13516c9e3426<span class="Apple-tab-span" style="white-space: pre;"> </span>hello</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">END RequestId: 031528d9-8eea-22e5-b1e5-13516c9e3426</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">REPORT RequestId: 031528d9-8eea-22e5-b1e5-13516c9e3426<span class="Apple-tab-span" style="white-space: pre;"> </span>Duration: 10.91 ms<span class="Apple-tab-span" style="white-space: pre;"> </span>Billed Duration: 100 ms <span class="Apple-tab-span" style="white-space: pre;"> </span>Memory Size: 128 MB<span class="Apple-tab-span" style="white-space: pre;"> </span>Max Memory Used: 8 MB</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">START RequestId: 4ac77131-8aca-11e3-874c-cb361dfcaaf1 Version: $LATEST</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">2015-12-07T22:38:10.101Z<span class="Apple-tab-span" style="white-space: pre;"> </span>4ac77131-8aca-11e3-874c-cb361dfcaaf1<span class="Apple-tab-span" style="white-space: pre;"> </span>it's me</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">END RequestId: 4ac77131-8aca-11e3-874c-cb361dfcaaf1</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">REPORT RequestId: 4ac77131-8aca-11e3-874c-cb361dfcaaf1<span class="Apple-tab-span" style="white-space: pre;"> </span>Duration: 8.49 ms<span class="Apple-tab-span" style="white-space: pre;"> </span>Billed Duration: 100 ms <span class="Apple-tab-span" style="white-space: pre;"> </span>Memory Size: 128 MB<span class="Apple-tab-span" style="white-space: pre;"> </span>Max Memory Used: 9 MB</span><br />
<br />
Run the function again in 30 minutes. It will output "hello" again because the container had to be reinitialized.<br />
<br />
Not so isolated, right? To be fair, AWS <a href="https://forums.aws.amazon.com/thread.jspa?messageID=686261">does claim</a> that any code outside of the handler function is treated as global code and is only initialized once per container. However, I've seen numerous developers mistakenly add code outside of the handler that should remain private. As Lambda becomes more ubiquitous, the distinction is critical for both performance and security.<br />
<br />
<h3>
Performance</h3>
Because code outside of the handler is only initialized once, this is the perfect spot for initializing Node modules, making database connections, etc. Additionally, it's also where global caching can be added. Take the following sample code:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">var CACHE = {};</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">exports.handler = function(event, context) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (CACHE[event.item]) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>return context.succeed(CACHE[event.item]);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// Lookup object in database</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>db.find(event.item, function(err, item){</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (err) return context.fail(err);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>CACHE[event.item] = item;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>context.succeed(item);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>});</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">};</span><br />
<br />
Adding caching outside of the handler allows cached items to be shared across multiple executions of the same function. Because AWS does not guarantee that each execution will occur on the same container (due to scaling or lack of use), I've nicknamed this form of caching "Occasionally Reliable Caching<span style="background-color: white; color: #222222; font-family: "arial" , sans-serif; font-size: 16px; line-height: 19.2px;">™</span>" (ORC).<br />
<br />
<h3>
Security</h3>
As you can imagine, there are important security considerations here as well. Take the following hypothetical code:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">var creditCard;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">exports.handler = function(event, context) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>creditCard = {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>number: event.credit_card_number,</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>exp: event.credit_card_expiration,</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>code: event.credit_card_security_code</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>};</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// Connect to payment system to verify</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>payments.verify(creditCard, function(err){</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (err) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>context.fail('Invalid card');</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>} else {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>context.succeed('Payment okay');</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>})</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">};</span><br />
<br />
While this is honestly just as bad as storing credit card information in a global variable on a web server, outside of the request handler, the distinction isn't as clear or as well-known with Lambda. In the example above, if there are multiple executions occurring simultaneously, there is no guarantee as to which card is being verified.<br />
<br />
<i>This is an extreme example to indicate an obvious code error, but also note that Lambda is not yet PCI certified, so shouldn't be used for this kind of data in the first place.</i><br />
<br />
As developers begin working more with Lambda, I expect this post to become unneeded; but until Lambda's execution model is more well-understood, it is important to keep in mind.<br />
<br />
<i>Enjoy this post? Want to learn more about Lambda? <a href="http://www.amazon.com/gp/product/B016JOMAEE">Pre-order my complete Lambda book on Amazon</a> for only $3.99!</i>Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com3tag:blogger.com,1999:blog-2859419017082777476.post-64243962267308935842015-11-02T20:40:00.001-05:002015-11-20T16:34:14.116-05:00AWS Lambda Pricing Calculator<a href="https://aws.amazon.com/lambda/pricing/">The AWS Lambda pricing scheme</a> is a bit confusing to understand. To help easily calculate the cost of running Lambda functions, I've embedded my simple AWS Lambda pricing calculator below. (<a href="https://s3.amazonaws.com/lambda-tools/pricing-calculator.html" target="_blank">Click here</a> to open in a new window).
<br />
<br />
<iframe frameborder="0" height="660px" scrolling="no" src="https://s3.amazonaws.com/lambda-tools/pricing-calculator.html" style="overflow: hidden;" width="100%"></iframe>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div style="text-align: center;">
<span style="font-family: "verdana" , sans-serif; font-size: large;"><b>Pre-Order the Complete Lambda Guide</b></span></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.amazon.com/gp/product/B016JOMAEE?*Version*=1&*entries*=0" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://s3.amazonaws.com/lambda-tools/book-cover.png" width="216" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<span style="font-family: "verdana" , sans-serif;"><a href="http://www.amazon.com/gp/product/B016JOMAEE?*Version*=1&*entries*=0">Click Here</a> to pre-order for only $3.99! The Lambda guide covers everything you need to know to get started with Lambda and deep dives into scores of topics.</span></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com2tag:blogger.com,1999:blog-2859419017082777476.post-56385739946618263132015-05-12T00:22:00.002-04:002015-05-12T00:24:09.367-04:00YouTube's Content ID Program is Leading to Hundreds of Distorted Variations of Songs and ShowsRecently while looking for a song on YouTube, I came across what appeared to be the song I was searching for, but with a slightly-off pitch. I assumed it was a fluke and moved on. However, over the past few months I have noticed hundreds of videos, both of popular songs and hit television shows, that have been distorted in some way or another. There are songs that have been slowed down or sped up by ~5%, videos that are missing the top or bottom 20%, videos that have black boxes over parts of the video, audio that has had its pitch altered, videos where the original source is shrunk to half its size and embedded within some background, the list goes on.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
After a bit of digging, it became apparent that this was almost certainly an attempt by the uploader to defeat YouTube's Content ID recognition system. For those who aren't familiar, Content ID is an automated system used by YouTube to detect copyrighted works within videos uploaded to the site. In most cases, Content ID will alert the uploader that the work is copyrighted and give them the option to swap the audio, or display advertisements on the video (proceeds go to the rights holders). While this is arguably a workable approach to copyright detection, it has mis-identified countless videos in the past (<a href="http://arstechnica.com/tech-policy/2012/08/how-youtube-lets-content-companies-claim-nasa-mars-videos/">example</a> <a href="https://www.techdirt.com/articles/20130227/20563322144/youtubes-contentid-trolls-claim-copyright-lots-gameplay-videos-hope-no-one-complains-collect-free-money.shtml">another</a> <a href="http://www.adweek.com/socialtimes/youtube-content-id-false-copyright-claims/91474">another</a>). Besides the constant false positives and the lack of recourse for users accused of a violation, another major issue exists with Content ID: it struggles to detect derivatives of an original work, leading to multiple copies, all of them distorted in some way.</div>
<div>
<br /></div>
<div>
Given that Content ID is an automated system, it lends itself to the constant back-and-forth battles that exist with such technologies; for every attempt YouTube takes to curb copyright violations, the violators will seek ways to defeat the system. This very issue is now littering YouTube with thousands, if not millions, of defective copies of original works.</div>
<div>
<br /></div>
<div>
Take the hit show, "Shark Tank," as an example.</div>
<div>
<br /></div>
<div>
Here's a copy of a recent episode where about 10% of the left side of the video has been removed: <a href="https://www.youtube.com/watch?v=O9wPE-GZpQI">https://www.youtube.com/watch?v=O9wPE-GZpQI</a>.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg_gvY8b3Ei5Qnn5Rb1JFPePx_Ur8HL_3KPEtyR8C0AfneMUF9wXGymHEbxeLd-AcEw3_WasOjfDkAUXUmwsvXgx4Bs-ry4_o7JRMPj1NSlswjy-OHSGH9K_XVQ331wWKa28MHr3OnZ6w/s1600/Screenshot+2015-05-12+00.10.34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="237" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg_gvY8b3Ei5Qnn5Rb1JFPePx_Ur8HL_3KPEtyR8C0AfneMUF9wXGymHEbxeLd-AcEw3_WasOjfDkAUXUmwsvXgx4Bs-ry4_o7JRMPj1NSlswjy-OHSGH9K_XVQ331wWKa28MHr3OnZ6w/s400/Screenshot+2015-05-12+00.10.34.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Here's an episode where the entire video has been framed by a black box and translucent lines: <a href="https://www.youtube.com/watch?v=9kJq9hNgPrw">https://www.youtube.com/watch?v=9kJq9hNgPrw</a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1RLSNX5W8me697vYDgTyQdyEdfejmQli3K5iNFjU27KA4biBx_5Ky3CbM0_dadaN5HO-zNk7Cxafkf0FvgkC3Wh1Ze4kxTmrCeTt2FszUIblbVkq4nsjg81OpoG-P8TQaHEyOTYPBeHw/s1600/Screenshot+2015-05-12+00.08.09.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" height="238" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1RLSNX5W8me697vYDgTyQdyEdfejmQli3K5iNFjU27KA4biBx_5Ky3CbM0_dadaN5HO-zNk7Cxafkf0FvgkC3Wh1Ze4kxTmrCeTt2FszUIblbVkq4nsjg81OpoG-P8TQaHEyOTYPBeHw/s400/Screenshot+2015-05-12+00.08.09.png" width="400" /></a></div>
<div>
<br />
In this episode, the uploader shrunk the entire video within a large black border: <a href="https://www.youtube.com/watch?v=bmg5oqXWskY">https://www.youtube.com/watch?v=bmg5oqXWskY</a>.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwuyovQsjISplUxQ8HbYeYBDojs8N3Oa8UZzgo3W8aOr6l0wAUihsBHAx1qf8V3LyCzffc4hsBhaxwAKpAxXMSssdBOAyH0HdTwHn99RN8FOOtGzd8pRZDCEk3KUmQZhrH1k1fSYN8gPE/s1600/Screenshot+2015-05-12+00.11.35.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="237" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwuyovQsjISplUxQ8HbYeYBDojs8N3Oa8UZzgo3W8aOr6l0wAUihsBHAx1qf8V3LyCzffc4hsBhaxwAKpAxXMSssdBOAyH0HdTwHn99RN8FOOtGzd8pRZDCEk3KUmQZhrH1k1fSYN8gPE/s400/Screenshot+2015-05-12+00.11.35.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br />
Here's an even more annoying example where the actual video only makes up about 40% of the box, while the rest if filled with a background pattern: <a href="https://www.youtube.com/watch?v=JB78NFhmIYs">https://www.youtube.com/watch?v=JB78NFhmIYs</a>.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsPeYqdNhNrxueUIL-HXkGHhcOB-CKsDUPnwO_anjmvO3hpNJFjtmZgaInqR9rd1phGHj1-PR1mtdsWJjJ5kQCxvJ7jNzf-LDNcVRqxb8grDZCmikbXP9rCRk8Y23jZg59UDCoDyh-zBk/s1600/Screenshot+2015-05-12+00.12.30.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="237" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsPeYqdNhNrxueUIL-HXkGHhcOB-CKsDUPnwO_anjmvO3hpNJFjtmZgaInqR9rd1phGHj1-PR1mtdsWJjJ5kQCxvJ7jNzf-LDNcVRqxb8grDZCmikbXP9rCRk8Y23jZg59UDCoDyh-zBk/s400/Screenshot+2015-05-12+00.12.30.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
In this copy, the uploader has added a giant image background behind the original video, which has also been cropped: <a href="https://www.youtube.com/watch?v=fsSlp9b2P4Q">https://www.youtube.com/watch?v=fsSlp9b2P4Q</a>.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1yjTCJsCDYO8NnSSU8gz9m7RHbitP_pvHx0OSAHuDaG0GG41ggpZ96ythjziZWldLhWLQHqxMVENxYYF-RG9ngo1QDXHvlpDismETn7ps7nTSwXM5vmtRDs5iPLISJ0i8ml1Ewaz75KQ/s1600/Screenshot+2015-05-12+00.13.27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="237" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1yjTCJsCDYO8NnSSU8gz9m7RHbitP_pvHx0OSAHuDaG0GG41ggpZ96ythjziZWldLhWLQHqxMVENxYYF-RG9ngo1QDXHvlpDismETn7ps7nTSwXM5vmtRDs5iPLISJ0i8ml1Ewaz75KQ/s400/Screenshot+2015-05-12+00.13.27.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
When it comes to audio changes, pitch shifting on Taylor Swift's music has become so rampant, that <i>not</i> having pitch shift has become a selling point:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrC4PwABKOiqhAFZPt64vNo4aNQd2uGv293SjBye147tsJiT4GcPp9814RTH42_ot3HFEyJP8x4KGpNAY0fyK8JAzx0Tnh-RwQcfBzPsRyXUbBw0bBZuePay5tpRTw2vK4jb-vDNaP3I4/s1600/Screenshot+2015-05-12+00.14.27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="82" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrC4PwABKOiqhAFZPt64vNo4aNQd2uGv293SjBye147tsJiT4GcPp9814RTH42_ot3HFEyJP8x4KGpNAY0fyK8JAzx0Tnh-RwQcfBzPsRyXUbBw0bBZuePay5tpRTw2vK4jb-vDNaP3I4/s400/Screenshot+2015-05-12+00.14.27.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGtB5-hMMAb6jAkyUceKEFwu7WSGilw9qE5Fkcw8JcrI1zRPXx0vewo4kwgDVZNM1y6lTU2xaiYADF7BLZ6ZRpri65BQlNxyHGJVSq7_54ncJRvLbJVF9ow19IpAD3-G3n9CU7DvcDuJw/s1600/Screenshot+2015-05-12+00.14.54.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="77" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGtB5-hMMAb6jAkyUceKEFwu7WSGilw9qE5Fkcw8JcrI1zRPXx0vewo4kwgDVZNM1y6lTU2xaiYADF7BLZ6ZRpri65BQlNxyHGJVSq7_54ncJRvLbJVF9ow19IpAD3-G3n9CU7DvcDuJw/s400/Screenshot+2015-05-12+00.14.54.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Unfortunately, I doubt that Content ID will ever be able to detect all of the variations of videos and songs that are copyrighted. Because these works have now been altered, sometimes to an almost laughable extent, I'm afraid that the outcome for the original artists is actually <i>worse</i> than if the originals were allowed to remain. Now, instead of hearing Taylor Swift's "Welcome to New York" the way she intended, I've heard about a hundred different variations, some fast, some slow, some higher pitch, some low. It remains to be seen how YouTube will respond to these videos, but my guess is that they will continue to adjust Content ID. Eventually, new uploads of Justin Bieber may sound like this: <a href="https://www.youtube.com/watch?v=bidHnEekXpE">https://www.youtube.com/watch?v=bidHnEekXpE</a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div style="text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/bidHnEekXpE/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/bidHnEekXpE?feature=player_embedded" width="320"></iframe></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-68875705358454729652015-04-08T16:22:00.000-04:002015-04-13T22:54:43.278-04:00More Graceful Deployments with CodeDeployUPDATE: As user /u/Andy_Troutman on Reddit kindly pointed out to me, the AWS team has already considered functionality very similar to this. They even wrote a similar script (long before I wrote this one). If you want the more "official" version, here it is: <a href="https://github.com/awslabs/aws-codedeploy-samples/tree/master/load-balancing/elb">https://github.com/awslabs/aws-codedeploy-samples/tree/master/load-balancing/elb</a><br />
<br />
If you haven't used AWS CodeDeploy before, it's a new service aimed at automating deployments across a fleet of EC2 instances. It works using an agent on the instance that polls AWS for new changes to application code. When a change is detected (you or a CI tool triggered a deployment), the instance downloads the new code and runs a series of steps you define in a YAML file. These steps can include installing dependencies, validating that the service is running, and pretty much anything you can fit in a script. CodeDeploy can be configured to deploy the code to all instances at once, one at a time, or in percentage groups.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
Ideally, a new deployment revision should not cause any loss of traffic. However, once the application is installed (the code is unzipped and copied to the correct location), most services must be restarted for the changes to take effect. Personally, I use a simple Node.js process running with "forever" for 90% of my projects. When CodeDeploy finishes installing the code, I have to run "forever stop" and "forever start" for the changes to be applied. This takes about 500 to 4000 milliseconds depending on how large the application is and whether it has to make database connections or perform other startup procedures. During this time, traffic is obviously rejected and the load balancer returns a "503 - Backend Service at Capacity" error to the client.</div>
<div>
<br /></div>
<div>
Although ELBs do have health check options (and theoretically, you could set it to have a 1 second ping), the application restart still causes a instantaneous cut-off of all connections, followed by a failure of the health check. Until the check fails, the ELB is still sending traffic, which could amount to hundreds of connections for a highly trafficked service.</div>
<div>
<br /></div>
<div>
The solution to this issue is to tell the load balancer to stop sending the instance traffic and then wait for the existing connections to drain before restarting the application. Honestly, CodeDeploy could easily implement a simple option for "remove instance from the load balancer when deploying," but I've decided to recreate the effect using the start, stop, and validate scripts used by the agent.</div>
<div>
<br /></div>
<h3>
Overview</h3>
<div>
In the next several steps, I'm going to script out the process of: 1) De-registering the instance from the load balancer 2) performing the application restart, and 3) Re-registering it once the health check passes.</div>
<div>
<br /></div>
<h3>
Instance Preparation</h3>
<div>
Your instances must have the AWS command line tools installed. This can be done on the AMI (recommended) or during the bootstrap process. Additionally, you could even add it as a "BeforeInstall" hook (just know that it will run the install command before every deployment until it's removed).</div>
<div>
<br /></div>
<div>
Additionally, the instances are going to need to be able to make the necessary API calls to AWS to register and deregister themselves from the load balancer. I've allowed this using IAM roles (you are using IAM roles, right?) in CloudFormation below:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace;">{</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Effect" : "Allow",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Action" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"elasticloadbalancing:DeregisterInstancesFromLoadBalancer",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"elasticloadbalancing:RegisterInstancesWithLoadBalancer"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>],</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Resource" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>{</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Fn::Join": [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>[</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"arn:aws:elasticloadbalancing:",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>{ "Ref" : "AWS::Region" },</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>":",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>{ "Ref":"AWS::AccountId" },</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>":loadbalancer/your-elb-name"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">}</span></div>
</div>
<div>
<br /></div>
<div>
You can also modify the instance's IAM role directly from the console and use the policy generator to give it the same permissions.</div>
<div>
<br /></div>
<h3>
ELB Preparation</h3>
<div>
You should also enable connection draining on your ELB and set the time to whatever is appropriate for your application (if you're just serving webpages, 30 seconds is probably fine; if users are uploading files to your service, you may want to increase it).</div>
<div>
<br /></div>
<h3>
CodeDeploy Files</h3>
<div>
Now that your instances have the correct permissions, you can include the code in your scripts to gracefully remove them from the ELB before running the application restart. Your scripts may differ considerably, but I have the following appspec.yml file:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace;">version: 0.0</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">os: linux</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">files:</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> - source: /</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> destination: /path/to/install/location</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">hooks:</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> AfterInstall:</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> - location: deployment/stop.sh</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> runas: user</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> ApplicationStart:</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> - location: deployment/start.sh</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> runas: user</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> ValidateService:</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> - location: deployment/validate.sh</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"> runas: user</span></div>
</div>
<div>
<br /></div>
<div>
When a deployment is triggered, CodeDeploy runs the "ApplicationStop" script, downloads your artifact, runs the "BeforeInstall" script, copies the files to the correct location, runs the "AfterInstall" script, then the "ApplicationStart" script, and then finally the "ValidateService" script. As you can see, they are not all required, and I have not made use of every one.</div>
<div>
<br /></div>
<div>
Once the artifact is downloaded and unzipped, the "AfterInstall" script is run, which I've configured to remove the instance from the ELB, wait for the connections to drain, then stop my application:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace;">#!/bin/bash</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"># Get the instance-id from AWS</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">INSTANCEID=$(curl http://169.254.169.254/latest/meta-data/instance-id)</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"># Remove the instance from the load balancer</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">aws elb deregister-instances-from-load-balancer --load-balancer-name elb-name --instances $INSTANCEID --region us-east-1</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"># Let connections drain for 30 seconds (replace with your drain time)</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">sleep 30</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"># Now stop the server</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">forever stop /path/to/process.js</span></div>
</div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
At this point, the instance is successfully removed from the ELB, connections have been drained, and you can do whatever is needed to restart your app without worrying about loosing requests. My start.sh script restarts the server:</div>
<div>
<br /></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">#!/bin/bash</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">forever start path/to/process.js</span></div>
<div>
<br /></div>
<div>
Finally, you should add validation to ensure your app is actually running before you re-attach the instance to the ELB. I've done this in the validate.sh script:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace;">#!/bin/bash</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"># Wait for however long the service takes to be responsive</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">sleep 10;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">res=`curl -s -I localhost/ping | grep HTTP/1.1 | awk {'print $2'}`</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">echo $res;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">if [ $res -eq 200 ]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>then</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span># Get the instance-id from AWS</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>INSTANCEID=$(curl http://169.254.169.254/latest/meta-data/instance-id)</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span># Add the instance back to the ELB</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>aws elb register-instances-with-load-balancer --load-balancer-name elb-name --instances $INSTANCEID --region us-east-1</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span># Wait for the instance to be detected by the ELB (set this to the health check interval)</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sleep 10</span></div>
<div>
<span class="Apple-tab-span" style="white-space: pre;"><span style="font-family: Courier New, Courier, monospace;"> </span></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>exit 0;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>else</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>exit 1;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">fi</span></div>
</div>
<div>
<br /></div>
<div>
If everything is successful, CodeDeploy will complete this step and move to the next instance (assuming you're deploying one at a time). If not, the deployment will fail but the instance will remain removed from the ELB. You can either re-trigger a deployment with a fix or rollback to a previously working one.</div>
<div>
<br /></div>
<h3>
<span style="font-family: inherit;">Additional Thoughts</span></h3>
<div>
Depending on the size of your application, this may not fully replace proper A-B stack deployments that includes switching DNS. If you only have a few servers, taking one offline will increase the load on the others substantially. Finally, these steps will increase the time of your deployments by 30 seconds to a few minutes per server. If you have 100 servers, consider using the "percentage at a time" deployment method, but balance this will the increased load on the remaining servers.</div>
<div>
<br /></div>
<div>
<br /></div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com1tag:blogger.com,1999:blog-2859419017082777476.post-75772480635023077872015-03-04T14:59:00.001-05:002015-03-04T14:59:41.562-05:00Update Your AWS ELBs to Protect Against the FREAK VulnerabilityThe <a href="http://www.theguardian.com/technology/2015/mar/04/freak-attack-leaves-millions-of-apple-and-google-users-vulnerable-to-hackers">recently announced "FREAK" vulnerability</a> is yet another blight on SSL. Fortunately, AWS has been quick as always to issue another cipher update. To protect your AWS Elastic Load Balancers from FREAK, follow these steps.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
1. Navigate to your ELB in the EC2 console and select the "Listeners" tab.</div>
<div>
2. Click on "Cipher."</div>
<div>
3. Change the dropdown to the 2015-02 cipher policy.</div>
<div>
4. Save.</div>
<div>
<br /></div>
<div>
That's it! Your ELBs will now be protected.</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-66828199605656924672015-02-11T17:30:00.000-05:002015-02-18T21:39:54.817-05:00AWS CodeDeploy: An In-Depth First LookAt Amazon's 2014 re:invent conference in Las Vegas, they announced <a href="http://aws.amazon.com/codedeploy/">CodeDeploy</a>, a tool designed to simplify the process of deploying applications to groups of servers, sometimes numbering in the hundreds. The primary objective of CodeDeploy is to make deployments consistent, repeatable, and integrated with existing AWS services (you can complain about vendor lock-in now, but AWS is doing a great job of providing value for that lock-in).<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
I took a few hours to setup CodeDeploy and documented issues I ran into. This post is a result of a few hours of playing with the service and trying to get it running on a Ubuntu 12.04 Server (despite 14.04 being the only "officially" supported version.</div>
<div>
<br /></div>
<h3>
First Impressions</h3>
<div>
At first glance, CodeDeploy really seems like a game changer; it's built by Amazon, integrated with their services, and a convenient way to do rolling, all-at-once, or grouped deployments. Once I started working with CodeDeploy, it felt like a solid product once I got past the first few issues. Of course, given its recent release, it also lacks a lot of support or online discussion, which left me manually digging through error logs and support forums for dependencies. While the documentation is pretty decent, there are currently only about thirty questions in the AWS forums about CodeDeploy. The biggest issue I found was that I had to manually add an alternative source for ruby2.0 on Ubuntu 12.04 and install it myself before continuing - but this was not the fault of CodeDeploy.</div>
<div>
<br /></div>
<h3>
IAM Setup</h3>
<div>
CodeDeploy requires a moderate amount of setup to get working properly. The biggest error-prone aspect is creating the appropriate IAM roles for both the CodeDeploy service and the instances. First, I created the CodeDeploy IAM role with the following policy:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">{</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"PolicyName" : "AWSCodeDeployPolicy",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"PolicyDocument" : {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Statement": [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>{</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Action": [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"autoscaling:PutLifecycleHook",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"autoscaling:DeleteLifecycleHook",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"autoscaling:RecordLifecycleActionHeartbeat",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"autoscaling:CompleteLifecycleAction",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"autoscaling:DescribeAutoscalingGroups",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"autoscaling:PutInstanceInStandby",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"autoscaling:PutInstanceInService",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"ec2:Describe*"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>],</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Effect": "Allow",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Resource": "*"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
</div>
<div>
<br /></div>
<div>
This allows CodeDeploy to access the tags and autoscaling groups it needs to in order to create applications and deployment configurations.</div>
<div>
<br /></div>
<div>
Next, I created the instance IAM role. It is important to remember that the CodeDeploy service needs access to the autoscaling and EC2 resources listed above while the instance itself only needs access to the S3 bucket containing the CodeDeploy agent and whatever bucket you store your final compressed file in.</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">{</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Effect" : "Allow",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Action" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"s3:Get*",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"s3:List*"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>],</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Resource" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"arn:aws:s3:::aws-codedeploy-us-east-1/*",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"arn:aws:s3:::your-bucket/path/*"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
</div>
<div>
<br /></div>
<div>
Here's a good place to tell you what I did wrong. Being security conscious, I thought I could get away with giving the instance role GetObject permissions only. My existing deployment strategy only requires this permission to pull the file from S3. However, apparently CodeDeploy tries to list the file and its ACL before downloading, which results in an error without the additional permissions. Lesson learned.</div>
<div>
<br /></div>
<h3>
The CodeDeploy Agent</h3>
<div>
The next step was to get the agent installed on the Ubuntu Server instance. Amazon provides its own "Amazon Linux" if you're looking for an officially AWS-supported AMI, but I'm much more familiar with Debian-based distros, so I chose to stick with that. When you launch your instance, make sure you either give it a descriptive tag or place it in an autos-scaling group.</div>
<div>
<br /></div>
<div>
Installing the CodeDeploy agent on Ubuntu 12.04 proved to be a bit more difficult than the documentation reveals for 14.04 (again, not the fault of CodeDeploy, just my own need to use an older version). According to AWS, all you have to do is run:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">sudo apt-get update</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">sudo apt-get install awscli</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">sudo apt-get install ruby2.0</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">cd /home/ubuntu</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">sudo aws s3 cp s3://bucket-name/latest/install . --region region-name</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">sudo chmod +x ./install</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">sudo ./install auto</span></div>
</div>
<div>
<br /></div>
<div>
(<a href="http://docs.aws.amazon.com/codedeploy/latest/userguide/how-to-run-agent.html#how-to-run-agent-install-ubuntu">http://docs.aws.amazon.com/codedeploy/latest/userguide/how-to-run-agent.html#how-to-run-agent-install-ubuntu</a>)</div>
<div>
<br /></div>
<div>
However, if you try that, you'll notice it fails at the third line with:</div>
<div>
<br /></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">E: Couldn't find any package by regex 'ruby2.0'</span></div>
<div>
<br /></div>
<div>
There is a yet-unanswered forum post about this <a href="https://forums.aws.amazon.com/thread.jspa?messageID=595079">here</a>.</div>
<div>
<br /></div>
<div>
I decided to get Ruby installed another way. After getting it installed via rvm and rerunning the install script, it failed again, this time with:</div>
<div>
<br /></div>
<div>
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;">"Dependency not satisfiable: ruby2.0"</span></div>
<div>
<br /></div>
<div>
So, I finally installed Ruby by adding an alternative source from Brightbox as documented <a href="https://www.brightbox.com/docs/ruby/ubuntu/">here</a>. In case that's ever not available, here were the steps:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">sudo apt-get install software-properties-common</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">sudo apt-add-repository ppa:brightbox/ruby-ng</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">sudo apt-get update</span></div>
</div>
<div>
<br /></div>
<div>
Finally, I ran the install script yet again and it worked!</div>
<div>
<br /></div>
<h3>
Preparing the Application</h3>
<div>
The application I wanted to deploy was a simple Node.js web app. It runs on the server using "forever," a daemon that keeps the process running in the background. To prepare it for CodeDeploy, I had to add an appspec.yml file and two scripts: a start and stop script.</div>
<div>
<br /></div>
<div>
The appspec.yml file looked very simple:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">version: 0.0</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">os: linux</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">files:</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> - source: /</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> destination: /usr/local/projects/source</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">hooks:</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> AfterInstall:</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> - location: deployment/stop.sh</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> runas: root</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> ApplicationStart:</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> - location: deployment/start.sh</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> runas: root</span></div>
</div>
<div>
<br /></div>
<div>
Keep in mind that the YAML file is super-particular about spacing. There's an <a href="http://docs.aws.amazon.com/codedeploy/latest/userguide/app-spec-ref.html#app-spec-ref-spacing">entire section</a> devoted to it on the AWS docs.</div>
<div>
<br /></div>
<div>
Next, I added the start and stop scripts to the deployment directory of the project. Obviously they can be much more complex than this, but I'm trying to keep it relatively simple:</div>
<div>
<br /></div>
<div>
start.sh:</div>
<div>
<br /></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#!/bin/sh</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">forever start /usr/local/projects/source/server.js --flags --here;</span></div>
<div>
<br /></div>
<div>
stop.sh:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#!/bin/sh</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">forever stopall</span></div>
</div>
<div>
<br /></div>
<div>
Like I said, super simple, but it works. The basic premise of this is that CodeDeploy will execute each file that you provide during the correct lifecycle event, as defined by the appspec file. Besides "AfterInstall" and "ApplicationStart," there are also "ApplicationStop," "BeforeInstall," and "ValidateService." AWS provides explanations <a href="http://docs.aws.amazon.com/codedeploy/latest/userguide/app-spec-ref.html#app-spec-ref-structure">here</a>, but keep in mind that "Install" purely means copying files to the right directories. In my example, "AfterInstall" means that CodeDeploy will wait until the files have been copied before stopping the previous running instance.</div>
<div>
<br /></div>
<div>
Once all of this has been done, create a compressed file of your choice (zip, tar, and tar.gz are supported on Linux, zips for Windows). Put the file in the same S3 bucket that you gave your instance permissions to earlier.</div>
<div>
<br /></div>
<h3>
CodeDeploy Console</h3>
<div>
Within the AWS console, you can now setup your application. To do this, head to the CodeDeploy page and create a new application. Provide a name and a deployment group name. The console doesn't make this clear, but the difference is that you can have multiple deployment groups belonging to an application. For example, you could have an app called "node-app" and create a "node-app-a" deployment group and then later create a "node-app-b" group which would help with A-B style deployments.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3qNe0TZSKxKdOfllSrZIyXPMFY78_EoTfcKkXNCPSdbaztENDsT33zobQdYvfOO8U6W-MXqTGJyM7Ay6aG9Y905e4cXGwOhRj88QN9Oky7nRuMcxmKJCDJfJ__id66dyOyhaETFPQvPE/s1600/Screenshot+2015-02-11+16.58.54.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3qNe0TZSKxKdOfllSrZIyXPMFY78_EoTfcKkXNCPSdbaztENDsT33zobQdYvfOO8U6W-MXqTGJyM7Ay6aG9Y905e4cXGwOhRj88QN9Oky7nRuMcxmKJCDJfJ__id66dyOyhaETFPQvPE/s1600/Screenshot+2015-02-11+16.58.54.png" height="361" width="400" /></a></div>
<div>
<br /></div>
<div>
In the tags section, enter either the autoscaling group or the tags you created earlier. If everything is successful, you should see the instance count increase.</div>
<div>
<br /></div>
<div>
The next section, Deployment Configuration, allows you to determine how you want your apps deployed. This is not really relevant when you only have one server, but it becomes very helpful if you have multiple servers. If you choose "one at a time," AWS will go to each server, attempt to deploy your app, and stop if any servers fail along the way. With all at once or half at a time, CodeDeploy will run in parallel accordingly. This is a much faster, but also much more dangerous option.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTKXjCt4j9KyYwh_MktGJlMwWDAh2hOa_tgRMChD8suC6CZmHlXLfzWZOu06wVqWb3DO-A26FPuPRYuHJAMVEiGjidscn-xm6D7hE8vovLykcv7wbZFHXd5asHxhGJPLPk6ns8wqpzZJo/s1600/Screenshot+2015-02-11+17.03.10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTKXjCt4j9KyYwh_MktGJlMwWDAh2hOa_tgRMChD8suC6CZmHlXLfzWZOu06wVqWb3DO-A26FPuPRYuHJAMVEiGjidscn-xm6D7hE8vovLykcv7wbZFHXd5asHxhGJPLPk6ns8wqpzZJo/s1600/Screenshot+2015-02-11+17.03.10.png" height="246" width="400" /></a></div>
<div>
<br /></div>
<div>
The service role should be the role created earlier with the necessary permissions. This role can be re-used for every application, as the permissions are the same regardless.</div>
<div>
<br /></div>
<div>
Finally, the application can be created. The next page is a bit confusing because it does not contain any action buttons. Instead, it says to use the command line to upload an application. Instead of doing that, head back to the main CodeDeploy page and click on "Deployments."</div>
<div>
<br /></div>
<div>
On this page, select your application from the list, then select the group name, paste the full S3 URL to your source into the box, and select your deployment method. Then, click deploy.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJym9EJHhS-XCIQVKmgLdlf5qJ_zr3YJibhxXg5I7L0Y9oOYx9vPJ9rc2Ogpm4IB4uolFzzmtEA_YjrxxZARABAQ7ZVAq_sYVFuN7eY1jY69bzb3OXCU2_2gYxjvXxyx-fVFZLVz4Bmx4/s1600/Screenshot+2015-02-11+17.09.31.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJym9EJHhS-XCIQVKmgLdlf5qJ_zr3YJibhxXg5I7L0Y9oOYx9vPJ9rc2Ogpm4IB4uolFzzmtEA_YjrxxZARABAQ7ZVAq_sYVFuN7eY1jY69bzb3OXCU2_2gYxjvXxyx-fVFZLVz4Bmx4/s1600/Screenshot+2015-02-11+17.09.31.png" height="400" width="380" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
You can then see the results of the deployment.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgahg7swHue5kk7nO5mntdykXLwY5VZW0OfF5ltVM5bIY3uS-PeU7a4vVHZvdDlwGzs4C1UPujdTyXWMBVA_Y24D1LdWQKBaynf8AKhMtNmdBJI2jQhDeQlQHVfq4Ipf5OazC-ADWUuHpQ/s1600/Screenshot+2015-02-11+17.14.51.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgahg7swHue5kk7nO5mntdykXLwY5VZW0OfF5ltVM5bIY3uS-PeU7a4vVHZvdDlwGzs4C1UPujdTyXWMBVA_Y24D1LdWQKBaynf8AKhMtNmdBJI2jQhDeQlQHVfq4Ipf5OazC-ADWUuHpQ/s1600/Screenshot+2015-02-11+17.14.51.png" height="125" width="400" /></a></div>
<div>
<br /></div>
<div>
<h3>
Potential Issues</h3>
</div>
<div>
Besides the ruby dependency issue I mentioned above, I also ran into a very ambiguous error message:</div>
<div>
<br /></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">UnknownError: Not Opened for Reading</span></div>
<div>
<br /></div>
<div>
This message really didn't tell me what was happening, but after logging into the instances, going to the /opt/codedeploy-agent/deployment-root directory and finding that all of the source files contained an XML error from S3 instead of the actual files, I was able to debug it. Be sure that you use all of the permissions listed above for the instance role or you might run into the same problems.<br />
<br /></div>
<h3>
Other Options and Thoughts</h3>
<div>
Besides deploying from an S3 object, you can also tie into GitHub. While this could work, I prefer to have a 100% working source file before actually deploying to an instance. I still use Jenkins to pull my changes from GitHub, install dependencies (node modules for my apps), run tests, zip everything up, and put it on S3. Once that's done, I can launch a new deployment from the console, or even have Jenkins use the AWS CLI to launch a deployment pointed at the file it just uploaded. While I could certainly install node modules as part of the pre-install hooks on the instance itself, that is much more error prone and slower as well.<br />
<br />
UPDATE: AWS has also informed me that there is an <a href="https://github.com/awslabs/aws-codedeploy-plugin">open-source Jenkins CodeDeploy plugin</a> available. I've installed the plugin and it works quite nicely; you can easily specify the application name, deployment group, and deployment policy from within Jenkins. Then, it executes as a build step with the same exit codes as Jenkins. Essentially, you can push to GitHub, copy to Jenkins, run tests, then execute a CodeDeploy deployment all as a result of one push (assuming you have the appropriate webhooks).</div>
<div>
<br /></div>
<div>
Overall, CodeDeploy worked very well once I got it working. I was able to deploy my app multiple times in a row without issues and even tested out the "one at a time" feature with an autoscaling group. Everything worked as expected. While I don't think CodeDeploy will be a complete replacement for a tool like Jenkins or other CI suites, it does remove the last few steps and make them more tightly integrated to AWS. I highly reccommend you try CodeDeploy out, but definitely do it in a test environment first until you have the process down to a science.</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com1tag:blogger.com,1999:blog-2859419017082777476.post-41002083558308885742015-01-28T20:44:00.000-05:002015-01-28T20:44:02.025-05:00NYC Blizzard 2015I am going to break with my traditional technology-based posts to share some images I took during the "blizzard" this past Monday. NYC only wound up getting about eight inches of snow - a lot less than expected.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU2H_xdwiA-YcTZ2OGAZ9b87NTVAcCDwTaVnAVRTnC5rdJfx08gPOgQNIRs5hkQ1gdZvRp2YZUVIpqv46UlgFmlbQ-Db7ZFJ_p0SCVDJbQvfV3e8bdFS4kmQiqzab_PQrDytajy6UBnZI/s1600/IMG_2634.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU2H_xdwiA-YcTZ2OGAZ9b87NTVAcCDwTaVnAVRTnC5rdJfx08gPOgQNIRs5hkQ1gdZvRp2YZUVIpqv46UlgFmlbQ-Db7ZFJ_p0SCVDJbQvfV3e8bdFS4kmQiqzab_PQrDytajy6UBnZI/s1600/IMG_2634.jpg" height="403" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG9C5dQTZjRzu2iDH8uOMFkotXZHOcRbZP0xlAylR-_z62AZvqI_pCd-eu184Vb3w2HzR20oZZq2-4phOi_8i0O2aS8BAhEvFSGMGHtoKxcI_aVa3p-qxm8FeGxIep5-Del5CZcbAGkdQ/s1600/IMG_2647.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG9C5dQTZjRzu2iDH8uOMFkotXZHOcRbZP0xlAylR-_z62AZvqI_pCd-eu184Vb3w2HzR20oZZq2-4phOi_8i0O2aS8BAhEvFSGMGHtoKxcI_aVa3p-qxm8FeGxIep5-Del5CZcbAGkdQ/s1600/IMG_2647.jpg" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEfR3CQ-o4Ox_qk7IekYf0gfgiaSjaFzNwKwjHtl7xIRICwsQyLi5k7i9St_cJXEq48-5V82pnH_3-kuTUT-WSja1-ZdHxf9xLgCiBGK_2lhtVkWalCvaZ3K9N4lbmKMBxqMYJy_QEf-o/s1600/IMG_2664.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEfR3CQ-o4Ox_qk7IekYf0gfgiaSjaFzNwKwjHtl7xIRICwsQyLi5k7i9St_cJXEq48-5V82pnH_3-kuTUT-WSja1-ZdHxf9xLgCiBGK_2lhtVkWalCvaZ3K9N4lbmKMBxqMYJy_QEf-o/s1600/IMG_2664.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCPzSDj3DH098V6eRtXSs1jH7H0qGuc6BkeJWvhd-Bl5VC2Vh1Rm_fl2wJ3S1M6TvxaKrk7D33HKwhLW6MiGyecVp4TyDDecMEBkHhhIhGZe4Dkf4xwE5zKzQzGhLeRCt2ROZRVg6F2g8/s1600/IMG_2670.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCPzSDj3DH098V6eRtXSs1jH7H0qGuc6BkeJWvhd-Bl5VC2Vh1Rm_fl2wJ3S1M6TvxaKrk7D33HKwhLW6MiGyecVp4TyDDecMEBkHhhIhGZe4Dkf4xwE5zKzQzGhLeRCt2ROZRVg6F2g8/s1600/IMG_2670.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVNK7a_q8oNv41NCnaET1uqcE4Zr3R2KxTfeSDzlLg6owJIRf86apEZnq_eaBhIdWs3_4U-IU5eMKeL1FHPNx6OBXHjmuK4373gHgy_zXP25KRCiDXidvl1HBOPKGgHIFDHc8DXH6x1TQ/s1600/IMG_2674.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVNK7a_q8oNv41NCnaET1uqcE4Zr3R2KxTfeSDzlLg6owJIRf86apEZnq_eaBhIdWs3_4U-IU5eMKeL1FHPNx6OBXHjmuK4373gHgy_zXP25KRCiDXidvl1HBOPKGgHIFDHc8DXH6x1TQ/s1600/IMG_2674.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRjqF8mkVYkatxPu9GIpKRmAnU4FH65xZOpdYAWAg_EdE94sAMRQcdJdZKkutPmDxEtotMqVgt7AdlThyphenhyphen2GCYk1_d8nu0eP7t-ZWNuEGLrw5sz_q-A-LhWzZZ80E6cti-6R4-tWWx8PbU/s1600/IMG_2701.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRjqF8mkVYkatxPu9GIpKRmAnU4FH65xZOpdYAWAg_EdE94sAMRQcdJdZKkutPmDxEtotMqVgt7AdlThyphenhyphen2GCYk1_d8nu0eP7t-ZWNuEGLrw5sz_q-A-LhWzZZ80E6cti-6R4-tWWx8PbU/s1600/IMG_2701.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjm9yIfCk2HzTnldy37x08UgPWrGgPgqxJbqQgtDsvajswU4H0YeQYeLIBtU0mw5aJI9fIm6uJkNZz2-Nq1rAb0bh_f3-TG_CkXsQeZ3y2X8JmCJrvbzkwgTzExz_jJXU492xvzeur9jHg/s1600/IMG_2702.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjm9yIfCk2HzTnldy37x08UgPWrGgPgqxJbqQgtDsvajswU4H0YeQYeLIBtU0mw5aJI9fIm6uJkNZz2-Nq1rAb0bh_f3-TG_CkXsQeZ3y2X8JmCJrvbzkwgTzExz_jJXU492xvzeur9jHg/s1600/IMG_2702.JPG" height="640" width="426" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTcO3PWIeFBHrNWrsldPTzypm9tzYdy3Yxams5FWUGxr6QeTqtQ65t1RtzrNBfhk9h_24veCZCxtjEmjt3fdO63v3poamcwT2-egAcmQZ0O4UlKSuqU8n8Re6Nk54TRQv_SKxtjVfaSo0/s1600/IMG_2704.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTcO3PWIeFBHrNWrsldPTzypm9tzYdy3Yxams5FWUGxr6QeTqtQ65t1RtzrNBfhk9h_24veCZCxtjEmjt3fdO63v3poamcwT2-egAcmQZ0O4UlKSuqU8n8Re6Nk54TRQv_SKxtjVfaSo0/s1600/IMG_2704.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQgLn2O-o7PfZAMtUi6O7PkjSgtwWglzwXPk4scKyYSSh6a5Ky2glNjOfibvhWtkHAzcJ16cAiUQyPtVobmLi7OCjjXzaVeUwfprqUaMRC7gNF9qaDPNtDOIhaH25CddumOCciTXNdi5U/s1600/IMG_2708.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQgLn2O-o7PfZAMtUi6O7PkjSgtwWglzwXPk4scKyYSSh6a5Ky2glNjOfibvhWtkHAzcJ16cAiUQyPtVobmLi7OCjjXzaVeUwfprqUaMRC7gNF9qaDPNtDOIhaH25CddumOCciTXNdi5U/s1600/IMG_2708.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGoS1TPBOVu_ZNMnfxuhnOsGBLzoX5hJGcLhqSwCbirU33-myZjYLQkAT_2f55fz_SdfOAgdMohiEYhaBnEhctWnsxR3n8MY6wPprk0_77bbXeISooI902wfQFCFAD6lemvF3KVqq_tFA/s1600/IMG_2710.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGoS1TPBOVu_ZNMnfxuhnOsGBLzoX5hJGcLhqSwCbirU33-myZjYLQkAT_2f55fz_SdfOAgdMohiEYhaBnEhctWnsxR3n8MY6wPprk0_77bbXeISooI902wfQFCFAD6lemvF3KVqq_tFA/s1600/IMG_2710.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwLLaNEfGsqwNXJdjS3l9YM6lY-owbfh9x-LbTdlkS-zS4vUOMvNdJmshNf-9xN_TWJUpvOsjlNDLxKl8cgRAxb-Ci2H0kvg1D9ZCz5m7ZWmRXyhg9XDeNsM_EISH6mw9pqu30hQFeXXk/s1600/IMG_2714.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwLLaNEfGsqwNXJdjS3l9YM6lY-owbfh9x-LbTdlkS-zS4vUOMvNdJmshNf-9xN_TWJUpvOsjlNDLxKl8cgRAxb-Ci2H0kvg1D9ZCz5m7ZWmRXyhg9XDeNsM_EISH6mw9pqu30hQFeXXk/s1600/IMG_2714.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8AimiIkUn___yz7OdnlUfOhO9djwE1lHiThHeLRKtnsrKWi0u1WPYPBAexMi5qEwkR3CCMmf2DvKrAzmNL-IOpRBtVWOC87Ba1C3VkcTnVZwrep-zVmcqSQGLAndU08QMIYLIvZGjRBc/s1600/IMG_2718.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8AimiIkUn___yz7OdnlUfOhO9djwE1lHiThHeLRKtnsrKWi0u1WPYPBAexMi5qEwkR3CCMmf2DvKrAzmNL-IOpRBtVWOC87Ba1C3VkcTnVZwrep-zVmcqSQGLAndU08QMIYLIvZGjRBc/s1600/IMG_2718.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRH6PILEDxhMLzPOlnNmgZHA_d5NliG6d35RVf9-z2PjpKbpF9N5TAVW3i2KV2Flr3OuzBKAxsLbG4601n7pVOSOypD8P2RsqcVU-r7K0_Si9hyphenhyphenY_8KxmIayNTbNsw_68MiWooAlIFj3A/s1600/IMG_2725.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRH6PILEDxhMLzPOlnNmgZHA_d5NliG6d35RVf9-z2PjpKbpF9N5TAVW3i2KV2Flr3OuzBKAxsLbG4601n7pVOSOypD8P2RsqcVU-r7K0_Si9hyphenhyphenY_8KxmIayNTbNsw_68MiWooAlIFj3A/s1600/IMG_2725.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgplrhr4-uW5TuRTRF84cT3YJt6qkGDl1VPWmnCarzutqyZGsE-ztawk66stksj0U1q1p0Wh7O4rnHIgVnCqNYEZ9iNuuTNmPQFYQah3QjPrJpOWr7RCjXHmaaqw51HsCMgSl1taXqNv4o/s1600/IMG_2751.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgplrhr4-uW5TuRTRF84cT3YJt6qkGDl1VPWmnCarzutqyZGsE-ztawk66stksj0U1q1p0Wh7O4rnHIgVnCqNYEZ9iNuuTNmPQFYQah3QjPrJpOWr7RCjXHmaaqw51HsCMgSl1taXqNv4o/s1600/IMG_2751.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgONHPLOAXGcrHrr49C8j-dkejonDdL7Ck220vzAt-6qyaUkEUHeWmk1AqCI1_36t-dDljOYvZ6VI2pMMEjUR1f9KKdrDvZaAL2Ps4GHm9DZ4ux2bDGQNw3JfXtIFNTSpF44sipeTdT7xU/s1600/IMG_2763.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgONHPLOAXGcrHrr49C8j-dkejonDdL7Ck220vzAt-6qyaUkEUHeWmk1AqCI1_36t-dDljOYvZ6VI2pMMEjUR1f9KKdrDvZaAL2Ps4GHm9DZ4ux2bDGQNw3JfXtIFNTSpF44sipeTdT7xU/s1600/IMG_2763.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF8TpI50qP5lDVjZD3qJ_VR4AHbg4GqAdsRCJVjMsk6HQFhPzKAvk1jjprD5TvG2tqTTBf5pP-TYUwP4pHwwnTrFIVN7wjcAfPo2aNEn0n9sgZ0gLbCFkyEWLYKkmdDd6q-H2F7aCZvzc/s1600/IMG_2782.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF8TpI50qP5lDVjZD3qJ_VR4AHbg4GqAdsRCJVjMsk6HQFhPzKAvk1jjprD5TvG2tqTTBf5pP-TYUwP4pHwwnTrFIVN7wjcAfPo2aNEn0n9sgZ0gLbCFkyEWLYKkmdDd6q-H2F7aCZvzc/s1600/IMG_2782.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA7U9Qz0IHcJnycAxFWAfi_YeP6saAnccCtA43ZsQdtqDcSVrj-ZLgKA76pRBcgRl7rXrcQsQlsLgsNftIENkjt9nHPdjP5qJoSu_6N3aG7SKtO6vOl2Sle8tDRMgaCF9szMEZXZRLApM/s1600/IMG_2783.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA7U9Qz0IHcJnycAxFWAfi_YeP6saAnccCtA43ZsQdtqDcSVrj-ZLgKA76pRBcgRl7rXrcQsQlsLgsNftIENkjt9nHPdjP5qJoSu_6N3aG7SKtO6vOl2Sle8tDRMgaCF9szMEZXZRLApM/s1600/IMG_2783.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVvJ46fwP-Si0IHgvbo52rr_wTO5D0cHRFAF6ibsWpZEgqJnvE_4-C_iMlgAgKQQRlj8NfXY_rnxs-Bxt48J66b2wj9yQ-82O7xZam7gRtWnzFt6LvTQm2GivYg_kfd_3IINs37f2cWOQ/s1600/IMG_2785.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVvJ46fwP-Si0IHgvbo52rr_wTO5D0cHRFAF6ibsWpZEgqJnvE_4-C_iMlgAgKQQRlj8NfXY_rnxs-Bxt48J66b2wj9yQ-82O7xZam7gRtWnzFt6LvTQm2GivYg_kfd_3IINs37f2cWOQ/s1600/IMG_2785.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKhw7nWPA6mIN7IawVCfA5aS6Ui46_dSneOu_oLEN6CKnGvjsb-1qTFROjlmCJq__8Mw1yqJu8e-HqQh66GKVg8WlNL2sEt02AjHVh8ACJ4BTaJlzF5Lye1FTfWQSBFQd3x0WVaCakplk/s1600/IMG_2801.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKhw7nWPA6mIN7IawVCfA5aS6Ui46_dSneOu_oLEN6CKnGvjsb-1qTFROjlmCJq__8Mw1yqJu8e-HqQh66GKVg8WlNL2sEt02AjHVh8ACJ4BTaJlzF5Lye1FTfWQSBFQd3x0WVaCakplk/s1600/IMG_2801.JPG" height="640" width="426" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzrvAm_af64bd9FsbhyiDxFl_k0-L791Vt-R7MadivLf63uf_OvVGUojlLwKbK3IvWRdquYTlwkNnsRCPf89eAO4IcQOzZ6eBu81ou5JZuBcskwZTiGpH86gE9UkZurY6UdO1MIyxXLgE/s1600/IMG_2807.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzrvAm_af64bd9FsbhyiDxFl_k0-L791Vt-R7MadivLf63uf_OvVGUojlLwKbK3IvWRdquYTlwkNnsRCPf89eAO4IcQOzZ6eBu81ou5JZuBcskwZTiGpH86gE9UkZurY6UdO1MIyxXLgE/s1600/IMG_2807.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhivKxVJk0aUq2OmBkCvZZTaRvPkUXACJf_jgPFK-gaHutv0dGs6oU5tJAVExv_SeDdmC2OW5YE9EOmxa4XqbadO8WbehqZDapjXvOlvXuX5WIthFRCkE8YvwV-sOtZmT8zazZHwejUsnk/s1600/IMG_2811.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhivKxVJk0aUq2OmBkCvZZTaRvPkUXACJf_jgPFK-gaHutv0dGs6oU5tJAVExv_SeDdmC2OW5YE9EOmxa4XqbadO8WbehqZDapjXvOlvXuX5WIthFRCkE8YvwV-sOtZmT8zazZHwejUsnk/s1600/IMG_2811.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhf0fYZDSh4sxWyzWpcD1w59yPthSHSVrai5oC9eOaevNqr8xMj8wkkYS8K2q-SnGPtyxS9guny6Ktv_7gdtlpq6OC6wlVYeQ1NNHd-niXOQuZumLsMdPGw52m7lLTF1xF5cfq1ICRzCLI/s1600/IMG_2816.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhf0fYZDSh4sxWyzWpcD1w59yPthSHSVrai5oC9eOaevNqr8xMj8wkkYS8K2q-SnGPtyxS9guny6Ktv_7gdtlpq6OC6wlVYeQ1NNHd-niXOQuZumLsMdPGw52m7lLTF1xF5cfq1ICRzCLI/s1600/IMG_2816.JPG" height="640" width="426" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrnuL4rvksEjvsvtOtvmfKpg8UVsQW94MnQionIBBtUtKogh501fKyGdHKGa2nnZP2Bl8UFYmBP1_gCbk-pgH9TsrEp-J5Ya8NYIrh79F9zi9z7frGuR4ZVU0ERft0Uc1H2gR1cKj1tnQ/s1600/IMG_2818.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrnuL4rvksEjvsvtOtvmfKpg8UVsQW94MnQionIBBtUtKogh501fKyGdHKGa2nnZP2Bl8UFYmBP1_gCbk-pgH9TsrEp-J5Ya8NYIrh79F9zi9z7frGuR4ZVU0ERft0Uc1H2gR1cKj1tnQ/s1600/IMG_2818.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUgOV3llcH5UYSzwKF9Kjf-Hbu_OpYk-xbgdbkOYBrQAcbrB2ViAOzjr319ijTwBkcMQ13Zyn1OO1cUwJhA27VG5Fq-N7as7h6NTAEqgOYbLL2tFMyFLyrXL4CCUa6Wu5nFOugs-ECgoI/s1600/IMG_2821.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUgOV3llcH5UYSzwKF9Kjf-Hbu_OpYk-xbgdbkOYBrQAcbrB2ViAOzjr319ijTwBkcMQ13Zyn1OO1cUwJhA27VG5Fq-N7as7h6NTAEqgOYbLL2tFMyFLyrXL4CCUa6Wu5nFOugs-ECgoI/s1600/IMG_2821.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnKve-FQYy-SbCwN_Bat4uir52qDpMo1AFY-N4GA6smkcRyOBcK4cdLgezMe357ONvxUNk1DhiiHruXMa1RkLLMvZDJsiY22OsiD4G7KIgdvHFgDW_raMRM3eWPBUZAYfA2hIfVWBioC8/s1600/IMG_2824.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnKve-FQYy-SbCwN_Bat4uir52qDpMo1AFY-N4GA6smkcRyOBcK4cdLgezMe357ONvxUNk1DhiiHruXMa1RkLLMvZDJsiY22OsiD4G7KIgdvHFgDW_raMRM3eWPBUZAYfA2hIfVWBioC8/s1600/IMG_2824.JPG" height="426" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg44fkhbFecCb9E14qPEZfSEegTLqm8TAkWmgXOyHuTZaOtsxn6xOwg7DBNeGQKwqzNxJvDJ8l9yMkPX70-ddAENCm0eCtDA8kIgi4FpkJQhkMB-qwyIGMVYJQJFc-gNytNcr4902YuJI/s1600/IMG_2834.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg44fkhbFecCb9E14qPEZfSEegTLqm8TAkWmgXOyHuTZaOtsxn6xOwg7DBNeGQKwqzNxJvDJ8l9yMkPX70-ddAENCm0eCtDA8kIgi4FpkJQhkMB-qwyIGMVYJQJFc-gNytNcr4902YuJI/s1600/IMG_2834.JPG" height="426" width="640" /></a></div>
<div>
<br /></div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-23456815523637742872015-01-15T09:13:00.002-05:002015-01-15T09:13:45.284-05:00Using IAM Roles and S3 to Securely Load Application CredentialsMany applications require certain information to be provided to them at run-time in order to utilize additional services. For example, applications that connect to a database require the database connection URL, a port, a username, and a password. Developers frequently utilize environment variables, which are set on the machine on which the application is running, to provide these credentials to the underlying code. Some developers, against all recommendations, will hard-code the credentials into an application, which then gets checked into git, distributed, etc.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
Ideally, the credentials required by an application should not be hard-coded at all, or even accessible to processes outside of the one running the application itself. To achieve this, the application must determine what additional credentials it needs and load them prior to starting its main command.</div>
<div>
<br /></div>
<div>
Many applications that run on Amazon's Web Services platform have the added advantage of being hosted on EC2 instances that can assume specific IAM roles. An IAM role is essentially a definition of access rights that are provided to a particular AWS resource (an EC2 instance in this case). AWS takes care of generating temporary credentials for that instance, rotating them, and ensuring they are provided only to the assigned instance. Additionally, the AWS command line tools and various AWS language-specific SDKs will detect that they are being run on an instance using an IAM role and automatically load the necessary credentials.</div>
<div>
<br /></div>
<div>
As developers, we can take advantage of IAM roles to provide access to credentials that are stored in a private S3 bucket. When the application loads, it will use its IAM role to download the credentials and load them into the environment variables of the process. Then, wherever they are needed, they can simply be called by accessing the environment variable that has been defined.</div>
<div>
<br /></div>
<div>
As an example of this setup, here are the steps I would take to run a Node.js web server that requires some database credentials:</div>
<div>
<br /></div>
<div>
1. Create an S3 bucket called "organization-unique-name-credentials".</div>
<div>
<br /></div>
<div>
2. If you plan to have multiple applications, create a new folder for each within the bucket: "organization-unique-name-credentials/web-app," "organization-unique-name-credentials/app-two," etc. Ensure the proper access rights to each for your existing AWS users.</div>
<div>
<br /></div>
<div>
3. Set encryption on the bucket (you can use either AWS' key or your own).</div>
<div>
<br /></div>
<div>
4. Create a file called credentials.json that looks like this:</div>
<div>
<br /></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">{</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "DB_URL" : "some-database-connection-string.com",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "DB_PORT" : 3306,</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "DB_USER" : "app_user",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "DB_PASS" : "securepass"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<br /></div>
<div>
5. Upload the file to the right S3 bucket and folder (be sure to enable encryption or the upload will fail, assuming you required it for the bucket)</div>
<div>
<br /></div>
<div>
6. Create an IAM role for your instance. In the IAM console, click "Roles," then create a new role, enter a name, select "EC2 Service Role," and give it the following policy (add any other rights the app may need if it accesses other AWS resources):</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">{</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "Statement": [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "Effect": "Allow",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "Action": [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "s3:GetObject"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> ],</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "Resource": [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> "arn:aws:s3:::organization-unique-name-credentials/web-app/credentials.json"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> ]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> }</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> ]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
</div>
<div>
<br /></div>
<div>
7. Launch your EC2 instance, selecting the role you just created.</div>
<div>
<br /></div>
<div>
8. In your code, do the following (node pseudo-code):</div>
<div>
<br /></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">var AWS = require('aws-sdk);</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">AWS.config.region = 'us-east-1';</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">var s3 = new AWS.S3();</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">var params = {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Bucket: 'organization-unique-name-credentials',</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Key: 'web-app/credentials.json'</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">s3.getObject(params, function(err, data) {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> if (err) {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> console.log(err);</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> } else {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> data = JSON.parse(data.Body.toString());</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> for (i in data) {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> console.log('Setting environment variable: ' + i);</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> process.env[i] = data[i];</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> }</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> // Load database via db.conn({user:process.env['DB_USER'], password:process.env[</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">'DB_PASS']}); etc...</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> }</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">});</span></div>
</div>
<div>
<br /></div>
<div>
9. Run the app, and you will notice that the environment variables are downloaded from S3 and are set before the database connection is attempted.</div>
<div>
<br /></div>
<div>
If you're using Node.js, I made a module that does exactly this: https://www.npmjs.com/package/secure-credentials</div>
<div>
<br /></div>
<div>
If you're not using Node.js, this technique can be applied to any language that AWS has an SDK for. While it isn't 100% hacker-proof (if someone managed to log into your instance as root, he or she could still modify the source code to display the credentials), but combined with other AWS security mechanisms such as security groups, VPCs with proper network ACLs, etc. it can certainly help. Additionally, it keeps credentials out of the source code.</div>
<div>
<br /></div>
<div>
One final note: if you're running this app locally to test, your machine will obviously not have an EC2 IAM role. When testing locally, it's okay to use AWS keys and secrets, but be sure to keep them in a separate file that is excluded with .gitignore.</div>
<div>
<br /></div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-24549385920791026742015-01-13T11:07:00.001-05:002015-01-13T11:07:28.056-05:00AWS Cross-Account IAM Roles in CloudFormationThe AWS documentation is relatively sparse when it comes to creating specific IAM role types using CloudFormation. It describes the process of setting up standard roles, attaching roles to instances, etc. but doesn't mention that all of the other role types can also be created using CloudFormation.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
For example, when you log into the AWS console and click on "IAM," you see a number of different roles you can create:</div>
<div>
<br /></div>
<div>
AWS Service Roles</div>
<div>
Role for Cross-Account Access</div>
<div>
Role for Identity Provider Access</div>
<div>
<br /></div>
<div>
However, these role types are merely just different adaptations of the same concept. In the following steps, I'll show how to create a Cross-Account Role using CloudFormation.</div>
<div>
<br /></div>
<div>
1. Add the following to the "Resources" section of your CloudFormation template:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">"CrossAccountRole" : {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Type" : "AWS::IAM::Role",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Properties" : {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"AssumeRolePolicyDocument" : {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Statement" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>{</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Effect" : "Allow",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Principal" : {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"AWS": "arn:aws:iam::ACCOUNT_NUMBER_HERE:root"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>},</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Action" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"sts:AssumeRole"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">},</span></div>
</div>
<div>
<br /></div>
<div>
2. Add another resource for the policy:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">"CrossAccountPolicy" : {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Type" : "AWS::IAM::Policy",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Properties" : {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"PolicyName" : "IAMInstancePolicy",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"PolicyDocument" : {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Statement" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>{</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Effect" : "Allow",</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Action" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"*"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>],</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Resource" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"*"</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>},</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>"Roles" : [</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>{ "Ref" : "CrossAccountRole" }</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>]</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">},</span></div>
</div>
<div>
<br /></div>
<div>
3. Adjust the account number and resources as needed:</div>
<div>
<br /></div>
<div>
This policy gives admin access to any account you specify. To restrict permissions, change the statement section of the policy document as desired.</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-34959931191313696002015-01-05T10:40:00.001-05:002015-01-05T10:40:09.224-05:00Quickly Find What is Using Disk Space on LinuxHere's a quick command to find out what folder is consuming the most space on Linux. This will also sort the results to show the most space-consuming folders at the bottom:<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
<span style="background-color: #eeeeee; color: #222222; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif; font-size: 13px; line-height: 20px; white-space: pre-wrap;">du -sh * | sort -h</span></div>
<div>
<span style="background-color: #eeeeee; color: #222222; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif; font-size: 13px; line-height: 20px; white-space: pre-wrap;"><br /></span></div>
<div>
Run this in the current directory to find the most consuming subdirectory.</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-29299478776340765492014-10-30T14:57:00.000-04:002014-10-30T14:57:05.658-04:00Node.js "Error: too many parameters at queryparse"If you've been using Express' body-parser and attempting to process large data submissions (aka forms with more than 1000 elements or 1000 sub-elements, you may have run into the following error:<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
<div>
Error: too many parameters</div>
<div>
at queryparse (/project/node_modules/body-parser/lib/types/urlencoded.js:120:17)</div>
<div>
at parse (/project/node_modules/body-parser/lib/types/urlencoded.js:64:9)</div>
</div>
<div>
<br /></div>
<div>
This is due to the fact the urlencode defaults to 1000 parameters by default. If you have a large form or just an abnormally large JSON submission, you'll need to increase this limit by doing the following:</div>
<div>
<br /></div>
<div>
var bodyParser = require('body-parser');</div>
<div>
<br /></div>
<div>
<div>
app.use(bodyParser.urlencoded({</div>
<div>
extended: false,</div>
<div>
<span class="Apple-tab-span" style="white-space: pre;"> </span>parameterLimit: 10000,</div>
<div>
<span class="Apple-tab-span" style="white-space: pre;"> </span>limit: 1024 * 1024 * 10</div>
<div>
}));</div>
<div>
app.use(bodyParser.json({</div>
<div>
extended: false,</div>
<div>
<span class="Apple-tab-span" style="white-space: pre;"> </span>parameterLimit: 10000,</div>
<div>
<span class="Apple-tab-span" style="white-space: pre;"> </span>limit: 1024 * 1024 * 10</div>
<div>
}));</div>
</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
This will allow you to provide up to 10,000 parameters (increase as needed) and 10 MB of data (also adjustable).</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com2tag:blogger.com,1999:blog-2859419017082777476.post-4732216051157877142014-10-14T20:21:00.000-04:002014-10-15T09:39:57.005-04:00How to Disable SSLv3 on AWS Elastic Load BalancersIn a blog post today, Google announced that a vulnerability in SSLv3 had been found that could allow attackers to intercept data that had previously been assumed to be secured. Luckily, a very small portion of the web (IE6 users on Windows XP) still use SSLv3, so it can safely, for the most part, be disabled to mitigate the risk from this issue.<br />
<div>
<br /></div>
<div>
http://googleonlinesecurity.blogspot.com/2014/10/this-poodle-bites-exploiting-ssl-30.html<br />
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
UPDATE 10/15: As Andrew and Julio point out in the comments below, AWS has since updated their default cipher security policies. Replace steps 5 and 6.<br />
<br /></div>
<div>
To modify the ciphers on AWS ELBs, follow the following steps:</div>
<div>
<br /></div>
<div>
1) Log into the AWS console and click on "Load Balancers."</div>
<div>
2) Find the load balancer that handles your site's traffic (you shouldn't need to worry about internal VPC LBs, etc.)</div>
<div>
3) Click the "Listeners" tab</div>
</div>
<div>
4) Find the HTTPS/443 listener and click "Edit" under the cipher column</div>
<div>
<strike>5) Change the option to "Custom"</strike></div>
<div>
<strike>6) Uncheck the SSLv3 option</strike><br />
5) Change the policy to "ELBSecurityPolicy-2014-10" which disables SSLv3 for you.</div>
<div>
6) Save.</div>
<div>
<br /></div>
<div>
This should be sufficient to mitigate this risk with the information that is currently known.</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com4tag:blogger.com,1999:blog-2859419017082777476.post-81397493032162542002014-10-14T17:31:00.000-04:002014-10-14T17:31:11.400-04:00Redirect HTTP to HTTPS Behind AWS Elastic Load Balancer - Node.js and Apache<h3>
Apache</h3>
When you enable HTTPS for your website, you should enforce that HTTPS is being used by automatically redirecting users who access your site over HTTP to the HTTPS version. When using Apache, this is done via a host redirect entry with mod_rewrite:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"><VirtualHost *:80></span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"># Beginning declarations here</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">RewriteEngine On</span><br />
<span style="font-family: Courier New, Courier, monospace;">RewriteCond %{HTTPS} off</span><br />
<span style="font-family: Courier New, Courier, monospace;">RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI}</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"># Rest of entry here...</span><br />
<span style="font-family: Courier New, Courier, monospace;"></VirtualHost></span><br />
<br />
<h3>
Apache Behind an ELB</h3>
However, in the Amazon Web Services world, most applications utilize the load balancer as an SSL-termination point. This means that the traffic is decrypted at the load balancer and all traffic being sent to the final destination instances is actually sent over HTTP (from a security standpoint, this is generally an accepted practice as the load balancer and instance are in the same network, and thus secure from eavesdropping). If you used the rule above, your users would wind up in an endless redirect loop.<br />
<br />
To fix this, the "RewriteCond" needs to be changed to:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">RewriteCond %{HTTP:X-Forwarded-Proto} !https</span><br />
<br />
This rule will check to see which protocol the user was using before he or she hit the load balancer - which is what HTTPS redirection should use.<br />
<br />
<h3>
Node.js</h3>
When using Node.js, the same thing can be accomplished by inserting some middleware that listens for all traffic and redirects the non-HTTPS requests to their HTTPS equivalents.<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">var myApp = express () ,</span><br />
<span style="font-family: Courier New, Courier, monospace;">myServer = http.createServer(myApp);</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">myApp.use(function redirectHTTP(req, res, next) {</span><br />
<span style="font-family: Courier New, Courier, monospace;"> if (!req.secure) {</span><br />
<span style="font-family: Courier New, Courier, monospace;"> return res.redirect('https://' + req.headers.host + req.url);</span><br />
<span style="font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace;"> next();</span><br />
<span style="font-family: Courier New, Courier, monospace;">});</span><br />
<br />
<h3>
Node.js Behind an ELB</h3>
Just as in our first Apache example, this works great until it is placed behind a load balancer. If you're using a load balancer, you need to make some modifications. Instead of "req.secure", do the following:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">if (req.headers['x-forwarded-proto'] && req.headers['x-forwarded-proto'].toLowerCase() === 'http') {</span><br />
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>return res.redirect('https://' + req.headers.host + req.url);</span><br />
<span style="font-family: Courier New, Courier, monospace;">}</span><br />
<br />
Using these techniques, you should be able to enforce the new HTTPS version of your website. Remember - if you serve both an HTTPS and an HTTP version of your site, the HTTPS is rendered pointless unless you also redirect the HTTP requests.<br />
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-72939356686669609902014-09-28T13:32:00.000-04:002014-09-28T13:32:00.041-04:00Bringing Mint's Interface into the 21st CenturyIf you use the online financial planning app, Mint, you've probably wondered what decade it was designed in. Just browsing through the site presents a clutter of different styles, an incredible use of box-shadow, and more border-radius than should be permissible by law. It's time for an upgrade (Intuit, if you're reading this, please hire some designers!).<br />
<div>
<br /></div>
<div>
Now I am by no means a graphic designer. In fact, I'd argue that my understanding of colors, patterns, and other design principles is on par with most grade schoolers. However, in about three hours of work on a lazy Sunday afternoon, I was able to turn this:</div>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJTdLyfLHUn_VsIhKV1JpcM1-u8K1mZtLyKPW0pa1WuLxDpxvES5CK7RQjNWXA8beRe__In07_QKIX2S9M1OqrPzwfnzyyVLzbxG61VOFDm6P9EqKWuqZPlZQX4-ahriwL-AYx4mKn2Nc/s1600/mint0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJTdLyfLHUn_VsIhKV1JpcM1-u8K1mZtLyKPW0pa1WuLxDpxvES5CK7RQjNWXA8beRe__In07_QKIX2S9M1OqrPzwfnzyyVLzbxG61VOFDm6P9EqKWuqZPlZQX4-ahriwL-AYx4mKn2Nc/s1600/mint0.png" height="578" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Into this:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc82SJJ-22xCIwYbgDW5TsQhx1fUf2oZ12nWC9OpFdELEPN0DjLzC49uDXkbE-1PZarQe5Q2b7mUB_msHQbh9PA8gyyLX4JMg1J43q21r6cy11lMFCdkDD7GLv_ZDblSZV0yXHQdj4eTc/s1600/mint1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc82SJJ-22xCIwYbgDW5TsQhx1fUf2oZ12nWC9OpFdELEPN0DjLzC49uDXkbE-1PZarQe5Q2b7mUB_msHQbh9PA8gyyLX4JMg1J43q21r6cy11lMFCdkDD7GLv_ZDblSZV0yXHQdj4eTc/s1600/mint1.png" height="534" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
As you can see, not a ton has changed; my goal was not to completely design a new site. However, as I'll show in the next screenshots, I've tried to unify the interface a bit, get rid of the overzealous use of shadows, gradients, and rounded borders, as well as add a bit of white space to spread things out a bit. I also tried to flatten some of the progress bars to get rid of the outdated "Web 2.0" gloss effect.</div>
<br />
For the transactions page, I've simply added white space to make things seem less cramped (pardon the gray box, I had to box out some of my own personal info):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-zmUWv45o3TjYrPUmYgLlMEV6XPgrli3YlOn64IqomcaRez3G9zC3tMXGT1GwNhPAQkwc1ynkp1rW2__8SYJAxjvHeyJP77XVRuptMAbKVMr8XUjs146aOEyK3ZkUG3W8mM5-RTQre2M/s1600/mint2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-zmUWv45o3TjYrPUmYgLlMEV6XPgrli3YlOn64IqomcaRez3G9zC3tMXGT1GwNhPAQkwc1ynkp1rW2__8SYJAxjvHeyJP77XVRuptMAbKVMr8XUjs146aOEyK3ZkUG3W8mM5-RTQre2M/s1600/mint2.png" height="348" width="640" /></a></div>
<br />
Within the budgets page, I've gotten rid of all sorts of rounded glossy boxes and replaced them with whitespace and flat progress bars.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHjaohWSjvGuQTnnJTVS92RJb9Dh46WkGoMDNBXHjxP-X_FEvjIwjlnUaer4dO1q2dFn_9WUk_iCefuxBbw3JpX-H21ciYS-2K_0FC-zuR-j0AvCnheGR2QwbNWVhdZnpyv0kOI3b7wUY/s1600/mint3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHjaohWSjvGuQTnnJTVS92RJb9Dh46WkGoMDNBXHjxP-X_FEvjIwjlnUaer4dO1q2dFn_9WUk_iCefuxBbw3JpX-H21ciYS-2K_0FC-zuR-j0AvCnheGR2QwbNWVhdZnpyv0kOI3b7wUY/s1600/mint3.png" height="640" width="606" /></a></div>
<br />
Not much has changed in the "Trends" page, but I did get rid of a lot of the cramped feeling and spread the content out a bit.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnOXqo-pCTAXuI3m0eLQQNikZ-o8fhnTgNG3a2onLTngNvfD417TVo1WPBIumshRLIJuRwFjkfkIirTCfYWes2PszgrNAaP4iFWPPR3B8k3gjA32pejVMWQech06g8o-CzZuaRip4F8KA/s1600/mint4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnOXqo-pCTAXuI3m0eLQQNikZ-o8fhnTgNG3a2onLTngNvfD417TVo1WPBIumshRLIJuRwFjkfkIirTCfYWes2PszgrNAaP4iFWPPR3B8k3gjA32pejVMWQech06g8o-CzZuaRip4F8KA/s1600/mint4.png" height="436" width="640" /></a></div>
<br />
Finally, the "Investments" page was given more whitespace as well and a wider width for content:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkgRvyh-bSML37nPjAQjcLRumHM4SohYr92zJb2971oMLXWVVCxw6YUn1v_pCd6oJJ-yBAOSicv4ufieIPqPReto2cdLHZx8bZDWhuuoxTxXji9IiyDVKAXZ4MVBkw6f6eFiELW3NC5eo/s1600/mint5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkgRvyh-bSML37nPjAQjcLRumHM4SohYr92zJb2971oMLXWVVCxw6YUn1v_pCd6oJJ-yBAOSicv4ufieIPqPReto2cdLHZx8bZDWhuuoxTxXji9IiyDVKAXZ4MVBkw6f6eFiELW3NC5eo/s1600/mint5.png" height="348" width="640" /></a></div>
<br />
Here is a list of other little changes I've made:<br />
<br />
<br />
<ul>
<li>Increased the overall width to 1200px from the original 900. This gives us more room to work and added whitespace.</li>
<li>Changed all the popups to have sharp edges rather than rounded corners</li>
<li>Replaced the shadows with clean lines</li>
<li>Made all the menus square with straight hover edges rather than rounded corners</li>
<li>All the progress bars are flat now</li>
<li>Removed the bulky hover effects on the budgets page</li>
<li>Removed ads on the homepage and investments page</li>
<li>Removed the annoying Norton Certified seal in the footer</li>
<li>Widened the transactions and their details</li>
<li>A bunch of other small touches</li>
</ul>
<div>
I did not:</div>
<div>
<ul>
<li>Get rid of the "Ways to Save" page. This is how Mint makes money and I don't want to destroy their revenue stream.</li>
<li>Modify the "Goals" page. It is so poorly designed right now with background images and borders and popups I wasn't brave enough to touch it.</li>
<li>Change the chart or graph styles for trends</li>
</ul>
<div>
Now, for a disclaimer: I do not guarantee that this will work for everyone's account. I tried to test it as much as possible, but there are a lot of hidden pieces of functionality within Mint that I may have missed. Finally, this code is all written out below. It's entirely CSS, so you can decide which parts you want to use. This code will not steal your account info or do anything else malicious.</div>
<div>
<br /></div>
<div>
I also don't make any guarantees about supporting this in the future. Mint could change their site tomorrow and break all of this. Fortunately, it's very easy to undo (just erase the user styles).</div>
<div>
<br /></div>
<h3>
So how can you get this?</h3>
</div>
<div>
<br /></div>
<div>
It's all CSS! I use an extension called "StyleBot" for Chrome (<a href="https://chrome.google.com/webstore/detail/stylebot/oiaejidbmkiecgbjeifoejpgmdaleoha?hl=en">download link</a>), but Firefox has similar extensions as well. Simply copy and paste the following styles into the extension by opening the extension while on mint.com, then clicking "Edit CSS" at the bottom, and then pasting it in.</div>
<div>
<br /></div>
<br />
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#brokerage-link {</span></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#filters-wrapper {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> box-shadow: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> margin-left: 40px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#filters-wrapper.closed {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-bottom: 0px solid lightgray;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> margin-bottom: 25px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#filters-wrapper.open {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-bottom: 1px solid lightgray;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#graphSelectionNav {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 100%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#graphSelectionNav ul li {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#ira-link {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#menu-category li.active, #menu-categoryTypeFilter li.active {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#menu-category ul {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#menu-compare ul {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#menu-compare ul li.active {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-accounts ul li h3 {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-bottom: 1px solid #DFDFDF;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> box-shadow: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-advice {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-budget table div.bar {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #ededed;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> height: 20px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-budget table div.bar span {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #3FAF3B;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> font-weight: normal;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-bottom: 4px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-top: 3px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> text-shadow: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-budget table tr {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> height: 40px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-budget table tr.overbudget div.bar span {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #ED5D51;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-budget table tr.warning div.bar span {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #EFBE2E;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-budget thead th.bar {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 70%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-budget.module table thead th.budget {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding: 0 11px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-goals table th.name {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 40%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-goals table th.next-step {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 40%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-investments table tr {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> height: 40px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#module-offers {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#month-line {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> height: 500px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#overview-left-column {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> margin: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 302px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#planning_group {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding: 5px 0 10px 250px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#pop-categories, #pop-rules, #pop-tags, #pop-hotspot-overlay {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#rollover-link {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#search-filters small {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#transaction-ad {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#trust {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txn-column-accounts {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> min-width: 240px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 20%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txn-detail {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txnEdit #actions {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> left: 698px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txnEdit-basic tbody td.date, #txnEdit-basic-multi tbody td.date {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> max-width: 12%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txnEdit-form fieldset, #txnEdit-form-multi fieldset {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 726px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txnEdit-form, #txnEdit-form-multi {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> top: 48px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 765px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txnEdit-mt-account label.txn-edit-labels.checknumber {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 90px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txnEdit-note, #txnEdit-note-multi {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 588px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txnEdit-split-icon {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> margin-left: -9px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#txnEdit-toggle.noattachcol, #txnEdit-toggleP {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> left: 350px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 765px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#ways_to_invest_control {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">#wrapper {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 1200px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.custom-filters-action-wrapper {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> left: -130px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> top: 10px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.filters-top-line {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> display: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.module .module-menu .menu-wrapper {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.module-reminders-content .timeline .chart .date {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 25px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.module-reminders-content .timeline .chart .dates {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 810px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.module-reminders-content .timeline .chart .divider-line {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 830px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.module-reminders-content .timeline .chart .graph {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #3FAF3B;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.nav #activeMenuItemGreenBar {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 238px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.overview {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 100%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.overviewPage .column-left {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> box-shadow: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.overviewPage .column-main {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 95%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.overviewPage .module .module-menu {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> left: 823px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.pageContents > div {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 100%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage #incomeEE-list ul.ee_header {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-bottom: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage #timeline {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding: 15px 0 5px 260px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage .allocate_to_goals a.button {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage .budget-summary {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> box-shadow: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage .budget_group {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding: 5px 0 10px 260px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage .right_col {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 310px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage div.ee_list ul.ee_header {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-bottom: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage div.leftcolumn {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 240px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage ul.planning_items .edit-details {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding: 8px 0 0 1px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage ul.planning_items .over-under-budget-text {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> margin: 6px 4px 0 0;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage ul.planning_items div.status span.progress_bar {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #3FAF3B ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border: none ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> font-weight: normal ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-bottom: 4px ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-top: 3px ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> text-shadow: none ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage ul.planning_items div.status span.total_bar {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #ededed ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> height: 20px ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage ul.planning_items li {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> height: 70px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage ul.planning_items li div.status span.progress_bar.full, .planningPage #incomeBudget-list-body li.overbudget div.status span.progress_bar.full {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #ED5D51 ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage ul.planning_items li.hover {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding: 7px 35px 0px 12px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.planningPage ul.planning_items li.warning div.status span.progress_bar {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #EFBE2E ;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.productPageContent {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 100%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">.txnEdit-btn {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 2px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">a#txnEdit-category_picker.noattachcol, a#txnEdit-category_picker-multi.noattachcol, a#txnEdit-category_picker, a#txnEdit-category_picker-multi {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> left: 600px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> top: 15px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">a.find_all span.find_all_wrapper {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">a.find_all, a.split {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border: 1px solid #D8D8D8;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> margin-bottom: 5px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> margin-top: 10px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-bottom: 10px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-left: 10px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">a.find_all:hover, a.split:hover {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #D8D8D8;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">body div#graph-container {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-left: 80px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div#column-accounts.column {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 100%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div#column-accounts.column ul li div.result-number {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> left: 227px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div#column-accounts.column ul li li {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding: 10px 10px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div#column-transactions div.controls#controls-top a.button {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div#flash-container {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> margin-left: 10px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding-left: 90px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div#main {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 1200px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div#main.trends-main {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 100%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div.column#column-accounts ul li, div.column#column-accounts ul li li {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div.column#column-content {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 900px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div.column#column-transactions {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 98%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div.inspector {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div.left-nav {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 240px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div.pop.newpop {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div.premium-filters {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 850px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div.right-column {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> box-shadow: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 945px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">div.txn-edit-group {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 598px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">table.account {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 100%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">table.transactions {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 766px</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">table.transactions tbody td {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> padding: 12px 4px 12px 3px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">td.column.accounts {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-right: 1px solid #E1E6DD;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> box-shadow: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 240px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">td.column.details {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">td.column.details-budgets {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">td.column.transactions {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> width: 70%;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">ul.horizontal-bar li {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #3FAF3B;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">ul.horizontal-bar li.debt {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #ED5D51;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">ul.vertical-bar li a {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #3FAF3B;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">ul.vertical-bar li a.debt {</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background: none;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> background-color: #ED5D51;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> border-radius: 0px;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span></div>
</div>
</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-32001923964397829052014-09-09T09:41:00.001-04:002014-09-09T09:41:51.970-04:00OS X Fuse Input/output error FixIf you're using OS X Fuse to mount a remote disk on a Mac device, you will occasioanlly receive the following error:<br />
<br />
ssh: Input/output error<br />
<br />
This has happened to me several times when allowing my Mac to sleep with the disk still mounted. When this happens, any operation on the disk seems to fail. I've managed to fix it with the following steps:<br />
<br />
1) Shut down the remote server / disconnect it to prevent corruption (optional)<br />
2) Attempt to unmount the disk cleanly from your Mac: umount /tmp/ssh (where /tmp/ssh is the location of the mounted disk)<br />
3) If that fails, find the process ID of the SSHFS process: ps aux | grep sshfs<br />
4) Kill that process: sudo kill <PID><br />
5) Remove the old folder: rmdir /tmp/ssh (rm -rf /tmp/ssh if it fails)<br />
6) Remount everything again:<br />
<br />
mkdir /tmp/ssh<br />
sshfs user@host:/ /tmp/ssh -ocache=no -onolocalcaches -ovolname=ssh<br />
<br />
This should fix the Input/Output errors and allow you to cleanly remount.<br />
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-18044618980512168242014-09-07T14:28:00.002-04:002014-09-07T14:28:32.205-04:00Monitoring a Scaling Infrastructure at AviaryI recently wrote a blog post for Aviary, where I am employed as a Server Engineer. It details how we setup monitoring to automatically scale with our infrastructure and covers tools like Nagios, StatsD, CollectD, and Graphite:<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
<a href="http://blog.aviary.com/posts/2014/9/1/monitoring-a-scaling-infrastructure-at-aviary">http://blog.aviary.com/posts/2014/9/1/monitoring-a-scaling-infrastructure-at-aviary</a></div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-25238343205841536542014-09-01T23:22:00.000-04:002014-09-01T23:22:34.764-04:00Why Security is Important for Developer OperationsAfter working at my current gig for about a year now (internship plus full-time), I wanted to take some time to outline my experiences transitioning to more of a developer operations role from my existing security background. The transition has been fairly straight-forward, as much of the work I do currently touches security in some aspect. However, the biggest point I've noticed is how much security considerations are involved when developing tools for a traditional developer operations requirement.<div>
<br /></div>
<div>
<div>
While I am a big proponent of at least some security training for everyone in a technical role, it remains to be seen just how much is "enough." Obviously, as the realm of technical fields continues to expand at its current pace, having every member of a technical team fully trained in the security of the applications they are developing is impossible. Yet it is also imperative that they at least understand the risks associated with these applications; doing so should be a requirement of a good developer.</div>
<div>
<br /></div>
<div>
As a "Developer Operations Engineer" (a role whose title is still rather undefined and unstandardized), I generally focus on several categories of projects: infrastructure development, monitoring and incident response, and deployments and the application lifecycle. While this is a highly compressed view of my role, each of these has a dizzying array of security concerns. While some companies offload much of these concerns to a security team, smaller companies need to remain vigilant of their impact.</div>
</div>
<div>
<br /></div>
<div>
In most modern startup environments, infrastructure development typically refers to everyone's favorite buzzword: the cloud. Amazon Web Services, Microsoft Azure, Google's App Engine, the list goes on. The security concerns associated with the cloud are not the focus of this post, but I do want to highlight places where security is especially important. Almost every one of these services has security enhancements that are not used as often as they should be. For example, AWS's Virtual Private Cloud is not only free, but can also greatly improve security when used properly. Yet quickly starting instances from EC2 still requires less hassle and so remains the more common choice. Another example is the use of security groups. AWS's security groups are infinitely customizable, yet simply opening a port to the world (0.0.0.0) is a tempting simpler option. While hosted infrastructure providers like Amazon and Google abstract a lot of security work away from the customer, good security practices still require active participation.</div>
<div>
<br /></div>
<div>
Monitoring and incident response is perhaps the area in which a lack of security can have the biggest impact. While many "DevOps" engineers view monitoring in terms of system performance, monitoring must also cover system security. Disk space, CPU utilization, and memory usage are all important indicators of a healthy system, but so too are login attempts, changes to file permissions, and unauthorized outgoing network connections. A good monitoring platform must also monitor for security events that could signal an intrusion or potential breach of security. In the same light, the response to a security event should also contain a viable plan for mitigating the same risk in the future.</div>
<div>
<br /></div>
<div>
Finally, the deployment of applications is a critical component of the security of an infrastructure. Because the main goal of deployments from a developer operation's standpoint is automation, any security bugs introduced once tend to be replicated. For this reason, it is imperative that the deployment process and any actors in it (Jenkins, AWS S3, etc.) are fully secured and audited often. Vulnerabilities that are present here can expand exponentially when deployments are pushed.</div>
<div>
<br /></div>
<div>
The role of developer operations or server engineering is rapidly changing and expanding. While it does, it is important, if not necessary, to include security in the expansion and ensure that those building a company's most critical technological parts are also trained in protecting them.</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-45778351235182532512014-09-01T20:37:00.002-04:002014-09-01T20:37:46.498-04:00How to Enable Two-Factor Authentication for iCloud (And Why You Should)This past weekend was not a good one for at least twenty or so celebrities, as well as Apple's iCloud service. According to a number of reports, a large number of female celebrities had personal photos released from their private iCloud accounts after a hacker was able to gain access to them. While we don't know all the details yet, it is likely that a combination of social engineering, weak passwords, and publicly available security question answers merged to allow the attacker access. In light of these events, it's a great idea to review the security of your account to avoid any accidental exposure of your private information.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
<h3>
Two-Factor Authentication</h3>
</div>
<div>
One of the easiest ways to secure your iCloud account is to use two-factor authentication. With this feature enabled, an attacker will not be able to log into your account unless he or she also has access to your phone. To set it up, first head over to your Apple ID security page here: https://appleid.apple.com/account/manage/security</div>
<div>
<br /></div>
<div>
Make sure that two-factor authentication is setup. If it is not, begin the process by clicking the link. You will be asked for several pieces of information, including a phone number and answers to your security questions. You will also be asked to provide the code texted to you as well as given a very long reset code which should be treated as if it is your password (in other words, don't write it down some place that will be lost, and don't save it on your desktop).</div>
<div>
<br /></div>
<div>
Once you enter all this information, two-factor authentication will be setup. Congratulations, you've just made your iCloud account ten thousand times more secure!</div>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3jY9eLwrAGCUKxzu9pgIVMEb2NlGpHgtUTx3vXjgA9RQrJ5W9Esm4EA8OFxGTMoQ3IUtkybKmasYseKaDbTHc3Cquo33J76Y4uCh8vi-U8GFNsOzQ03VRr2COESkyeJBxr6Vx-DaKCmY/s1600/Screen+Shot+2014-09-01+at+8.26.02+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3jY9eLwrAGCUKxzu9pgIVMEb2NlGpHgtUTx3vXjgA9RQrJ5W9Esm4EA8OFxGTMoQ3IUtkybKmasYseKaDbTHc3Cquo33J76Y4uCh8vi-U8GFNsOzQ03VRr2COESkyeJBxr6Vx-DaKCmY/s1600/Screen+Shot+2014-09-01+at+8.26.02+PM.png" height="90" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i>iCloud Two-Factor Authentication</i></div>
<span id="goog_1030172235"></span><br />
<br />
<h3>
Security Questions</h3>
For most accounts, the only thing that stands between a hacker resetting your password and maintaining your security is a strong set of password security questions. What good are these questions if an attacker can find the answers on Facebook? This is a good time to check your questions and make sure they are hacker-proof. Here are some tips:<br />
<br />
<ol>
<li>Security questions should be simple for you to remember but complex for anyone else</li>
<li>The answers to your questions should not be something an attacker can find on Facebook, Twitter, your blog, or on your abandoned MySpace page.</li>
<li>Be very specific in your answers. If the question asks where you met your significant other (a common question), don't use "Los Angeles" as the answer. Instead, provide a specific street name, a friend's name who introduced you, etc.).</li>
<li>Don't choose simple questions. "What is your favorite color?" and "Where were you born?" are ridiculously simple questions for attackers. Choose more complex questions that ask for information not found in your online profiles.</li>
</ol>
<div>
Securing iCloud is relatively simple and doing so can go a long way in securing your personal photos and documents. Unfortunately, leaks like the one that happened this weekend are going to occur. But by following these guidelines, at least you can make the hacker's efforts less rewarding, and in most cases, stop them entirely.</div>
</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com1tag:blogger.com,1999:blog-2859419017082777476.post-2105512682473494412014-07-11T19:00:00.000-04:002014-07-11T19:00:03.156-04:00Changing Releases and Tags on GitHub to a Different CommitI recently came across a situation where I accidentally created a release on GitHub, pointed at a specific tag which was tagged to a commit. I fixed the changes, committed, but wanted the release to point to the new commit instead of the old one with the error. Unfortunately, GitHub doesn't have a way to do this online, but it can be done through a combination of command line git and the GitHub website.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
First, make sure your repo is up-to-date with "git fetch." This will pull down the tag you made, including the wrong one.</div>
<div>
<br /></div>
<div>
git fetch</div>
<div>
<br /></div>
<div>
Next, delete the old tag by running:</div>
<div>
<br /></div>
<div>
git tag -d [tag-name-here]</div>
<div>
<br /></div>
<div>
So, for example, I did:</div>
<div>
<br /></div>
<div>
git tag -d v1.1.0</div>
<div>
<br /></div>
<div>
Next, commit and push your latest changes including the change you want the release to point to:</div>
<div>
<br /></div>
<div>
git add folder/file</div>
<div>
git commit -m "message here"</div>
<div>
git push -u origin master</div>
<div>
<br /></div>
<div>
Finally, push your tag changes as well:</div>
<div>
<br /></div>
<div>
git push origin :[tag-name-here]</div>
<div>
<br /></div>
<div>
If your tag has the same name as a branch, run:</div>
<div>
<br /></div>
<div>
git push origin :refs/tags/[tag-name-here]</div>
<div>
<br /></div>
<div>
Now, open GitHub in your browser and navigate to the "releases" page. Your release should now be listed as a "Draft" because its tag has been deleted.</div>
<div>
<br /></div>
<div>
You can now discard this release and create a new one with the same name pointed at the most recent commit.</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-194136326922726042014-07-02T21:00:00.000-04:002014-07-02T21:00:02.134-04:00Use google-passport Authentication in Node.js Projects without Google+<div>
NPM passport-google and 400 Error "OpenID auth request contains an unregistered domain"</div>
<div>
<br /></div>
If you've been using the Node.js module "passport-google" for authentication in your projects you will now notice that new projects are receiving an error stating:<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
"400 That's an error. OpenID auth request contains an unregistered domain."</div>
<div>
<br /></div>
<div>
This issue is due to the fact that Google has deprecated their OpenID API for new domains beginning in May 2014. Old projects which had previously been used should not have an issue (although you should upgrade at some point), but new projects will not be allowed to authenticate.</div>
<div>
<br /></div>
<div>
There are two fixes for this:</div>
<div>
<br /></div>
<div>
1) Convert your application to using the new, Google+ sign-in. This will require users to have a Google+ profile and approve your application to access it. The passport-google-plus module located on GitHub can do just that: <a href="https://github.com/sqrrrl/passport-google-plus">https://github.com/sqrrrl/passport-google-plus</a></div>
<div>
<br /></div>
<div>
2) Convert your application to use OAuth2.0 signin. Your users will not need to have a Google+ profile and this new method is the closest match to the old. The passport-google-oauth module can help with this: <a href="https://github.com/jaredhanson/passport-google-oauth">https://github.com/jaredhanson/passport-google-oauth</a></div>
<div>
<br /></div>
<div>
If you choose the second option, be sure to only provide the userinfo scope (and not the google.plus scope):</div>
<div>
<br /></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">passport.authenticate('google', { scope: ['https://www.googleapis.com/auth/userinfo.email'] })</span></div>
<div>
<br /></div>
<div>
One additional note is that you will now be required to register your application at <a href="https://console.developers.google.com/">https://console.developers.google.com</a> and create a client ID and secret (which are used in the passport module).</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com1tag:blogger.com,1999:blog-2859419017082777476.post-44309249813613741842014-06-27T10:16:00.000-04:002014-06-27T10:16:20.464-04:00JavaScript Print Date in YYYY-MM-DD HH:mm:ss Format With Leading ZerosPrinting out a JavaScript date with leading zeros is a bit more complex than it should be.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
<div>
var now = new Date();</div>
<div>
var dateToPrint = now.getFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2) + '-' + ('0' + now.getDate()).slice(-2) + ' ' + ('0' + (now.getHours() + 1)).slice(-2) + ':' + ('0' + now.getMinutes()).slice(-2) + ':' + ('0' + now.getSeconds()).slice(-2);</div>
</div>
<div>
<br /></div>
<div>
The .slice(-2) method allows us to add a leading "0" to every date (including those already containing two characters) and then slice out the last two.</div>
<div>
<br /></div>
<div>
For example, if it is 1PM, the time is 13 (getHours() + 1), but then written as 013, then sliced to 13.</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com1tag:blogger.com,1999:blog-2859419017082777476.post-35370814666628461152014-06-24T19:30:00.000-04:002014-06-24T19:30:00.834-04:00Sending CollectD Metrics to GraphiteThere are no shortage of tutorials on setting up collectd as an agent on a machine. However, I have found little help in the way of describing how to setup a centralized collectd collection server that aggregates statistics from multiple clients and sends them to Graphite. This post will help you do just that. It focuses on Ubuntu, but the instructions are universally applicable.<br />
<br />
First, let's setup the central collectd server. This could be on the same machine as your Graphite server, but on large production environments, it is not recommended.<br />
<br />
The Ubuntu collectd repositories do not contain the necessary write_graphite plugin, so you must download and install collectd manually. Download the source from the website at: http://collectd.org/download.shtml<br />
<br />
Next, run:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">tar jxf collectd-version.tar.bz2</span><br />
<span style="font-family: Courier New, Courier, monospace;">cd collectd-version</span><br />
<span style="font-family: Courier New, Courier, monospace;">./configure</span><br />
<span style="font-family: Courier New, Courier, monospace;">make all install</span><br />
<br />
Once collectd is installed, modify the /opt/etc/collectd.conf file to contain the following:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">Hostname "hostname"</span><br />
<span style="font-family: Courier New, Courier, monospace;">FQDNLookup true</span><br />
<span style="font-family: Courier New, Courier, monospace;">BaseDir "/opt/collectd/var/lib/collectd"</span><br />
<span style="font-family: Courier New, Courier, monospace;">PIDFile "/opt/collectd/var/run/collectd.pid"</span><br />
<span style="font-family: Courier New, Courier, monospace;">PluginDir "/opt/collectd/lib/collectd"</span><br />
<span style="font-family: Courier New, Courier, monospace;">TypesDB "/opt/collectd/share/collectd/types.db"</span><br />
<span style="font-family: Courier New, Courier, monospace;">Interval 10</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">LoadPlugin network</span><br />
<span style="font-family: Courier New, Courier, monospace;"><Plugin network></span><br />
<span style="font-family: Courier New, Courier, monospace;">Listen "*" "12345"</span><br />
<span style="font-family: Courier New, Courier, monospace;"></Plugin></span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">LoadPlugin interface</span><br />
<span style="font-family: Courier New, Courier, monospace;"><Plugin interface></span><br />
<span style="font-family: Courier New, Courier, monospace;">Interface "eth0"</span><br />
<span style="font-family: Courier New, Courier, monospace;"></Plugin></span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">LoadPlugin write_graphite</span><br />
<span style="font-family: Courier New, Courier, monospace;"><Plugin write_graphite></span><br />
<span style="font-family: Courier New, Courier, monospace;"><Node "graphing"></span><br />
<span style="font-family: Courier New, Courier, monospace;">Host "localhost"</span><br />
<span style="font-family: Courier New, Courier, monospace;">Port "2003"</span><br />
<span style="font-family: Courier New, Courier, monospace;">Protocol "tcp"</span><br />
<span style="font-family: Courier New, Courier, monospace;">LogSendErrors true</span><br />
<span style="font-family: Courier New, Courier, monospace;">Prefix "collectd."</span><br />
<span style="font-family: Courier New, Courier, monospace;">StoreRates true</span><br />
<span style="font-family: Courier New, Courier, monospace;">AlwaysAppendDS false</span><br />
<span style="font-family: Courier New, Courier, monospace;">EscapeCharacter "_"</span><br />
<span style="font-family: Courier New, Courier, monospace;"></Node></span><br />
<span style="font-family: Courier New, Courier, monospace;"></Plugin></span><br />
<br />
Make adjustments for your network as needed.<br />
<br />
Now, we are going install the collectd agent on the client and then tell it to send the metrics to the collectd server (not Graphite).<br />
<br />
The clients do not need the write_graphite plugin and can use the older version of Collectd that ships with the repositories. On each client, run:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">sudo apt-get install collectd collectd-utils</span><br />
<br />
Then, cd into /etc/collectd. Backup the collectd.conf file as collectd.conf.bkp or similar and then create a new collectd.conf file. In it, enable the plugins you want and also add the following:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">Hostname "hostname"</span><br />
<span style="font-family: Courier New, Courier, monospace;">FQDNLookup true</span><br />
<span style="font-family: Courier New, Courier, monospace;">BaseDir "/var/lib/collectd"</span><br />
<span style="font-family: Courier New, Courier, monospace;">PIDFile "/var/run/collectd.pid"</span><br />
<span style="font-family: Courier New, Courier, monospace;">PluginDir "/usr/lib/collectd"</span><br />
<span style="font-family: Courier New, Courier, monospace;">TypesDB "/usr/share/collectd/types.db"</span><br />
<span style="font-family: Courier New, Courier, monospace;">Interval 10</span><br />
<span style="font-family: Courier New, Courier, monospace;">#Timeout 5</span><br />
<span style="font-family: Courier New, Courier, monospace;">ReadThreads 5</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">LoadPlugin network</span><br />
<span style="font-family: Courier New, Courier, monospace;"><Plugin network></span><br />
<span style="font-family: Courier New, Courier, monospace;">Server "collectd.domain.com" "12345"</span><br />
<span style="font-family: Courier New, Courier, monospace;"></Plugin></span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">LoadPlugin cpu</span><br />
<span style="font-family: Courier New, Courier, monospace;">LoadPlugin load</span><br />
<span style="font-family: Courier New, Courier, monospace;">LoadPlugin disk</span><br />
<span style="font-family: Courier New, Courier, monospace;">LoadPlugin memory</span><br />
<span style="font-family: Courier New, Courier, monospace;">LoadPlugin processes</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">Include "/etc/collectd/filters.conf"</span><br />
<span style="font-family: Courier New, Courier, monospace;">Include "/etc/collectd/thresholds.conf"</span><br />
<br />
Be sure to configure the network plugin with your collectd server information.<br />
<br />
Now, if you log into Graphite, you should see that your clients are sending all of their statistics to the collectd server which is then sending them to Graphite.<br />
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com2tag:blogger.com,1999:blog-2859419017082777476.post-9103168137327401822014-05-11T17:35:00.001-04:002014-05-11T17:35:06.738-04:00Cast Reddit to Your ChromecastI just finished up a weekend project called "Castddit.com." The premise is to be able to replace any Reddit URL with "castddit" and have the images from that subreddit sent to your Chromecast in an image-slideshow fashion.<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script><br />
<div>
<br /></div>
<div>
I have released a beta version at: <a href="http://castddit.com/">http://castddit.com</a>.</div>
<div>
<br /></div>
<div>
Some of the best subreddits for this platform include the safe-for-work pron networks. You can see some demos here:</div>
<div>
<br /></div>
<div>
<a href="http://castddit.com/r/earthporn">http://castddit.com/r/earthporn</a></div>
<div>
<a href="http://castddit.com/r/cityporn">http://castddit.com/r/cityporn</a></div>
<div>
<br /></div>
<div>
It also works well on <a href="http://castddit.com/r/pics">/r/pics</a> and <a href="http://castddit.com/r/funny">/r/funny</a>.</div>
<div>
<br /></div>
<div>
If you're interested in how I made the site, check out my <a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial.html">Chromecast tutorials</a>, which I wrote while making this site.</div>
<div>
<br /></div>
<div>
Feel free to critique the site and let me know what you think!</div>
Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com0tag:blogger.com,1999:blog-2859419017082777476.post-57932140194916073352014-05-04T16:10:00.000-04:002014-08-26T08:51:55.590-04:00Chromecast Development Tutorial - Your Own First App<span style="font-size: large;"><b>This guide is being moved to: <a href="http://chromecasthub.com/developers/">http://chromecasthub.com/developers/</a></b></span><br />
<span style="font-size: large;"><b>Please visit the site for complete tutorials and more resources for Chromecast programming!</b></span><br />
<i><br /></i>
<i>Note: This is part of a multi-part series on developing web applications that integrate Google's Chromecast SDK. I am working on making a complete resource for web development with the Chromecast, so consider this a pre-release version and please comment if you see any issues or have recommendations on how to improve the tutorial.</i><br />
<i><a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial.html">Part 1 - Introduction</a></i><br />
<i><a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial-getting.html">Part 2 - Getting Started</a></i><br />
<i><a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial-hello.html">Part 3 - Hello World</a></i><br />
<i><a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial-your.html">Part 4 - Your Own First App</a></i><br />
<i><br /></i>
<br />
<h1 dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 10pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 48px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">4</span><span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> Your Own First App</span></h1>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Now that you’ve seen an official app from Google, we are going to begin writing our own app from scratch. Start a new project folder, called “MyFirstApp” within the /var/www directory of your server. Within this folder, create a file called “index.html” and then open it for editing within your editor of choice.</span></div>
<b id="docs-internal-guid-42922ae2-c8d9-b637-602f-8c53ad4b4422" style="font-weight: normal;"><br /></b>
<br />
<h2 dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 10pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 17px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Including the Cast Chrome Sender API</span></h2>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">In order to communicate with the Chromecast extension within Chrome, you will need to include the Chrome Sender API library. This is done very simply by including the following JavaScript within the <head> of your page like so:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><head></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></head></span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<h2 dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 10pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 17px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">A First Page</span></h2>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The code sample below shows a very basic first page that includes the Chrome Sender API, a custom JavaScript file called “script.js,” and a button. In the following steps, we will use JQuery to handle a button click, so take note that JQuery is also included within the page’s <head>. (Note: it is common practice to place the JQuery at the end of the page body so that it does not slow down the page load, but for simplicity’s sake, I am including it in the <head>.)</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">index.html</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><!DOCTYPE html></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><html></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><head></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> <title>My First App</title></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> <script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></head></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><body></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> <form></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> <button type="button" id="castme">Click To Cast</button></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </form></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> <script type="text/javascript" src="script.js"></script></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></body></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></html></span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Save the index.html file and create another file in the root directory called “script.js.” This is where we will be placing all of the custom code to interact with the Chromecast, as well as code to handle the button click (which will eventually cast an image to the Chromecast). Within the script.js file, add the following:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">$( document ).ready(function(){</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> var loadCastInterval = setInterval(function(){</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> if (chrome.cast.isAvailable) {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log('Cast has loaded.');</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> clearInterval(loadCastInterval);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> initializeCastApi();</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> } else {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log('Unavailable');</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> }</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> }, 1000);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">});</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Let’s look at some key parts of this script. To start, it uses JQuery’s document.ready function to execute the code when the page has loaded. Secondly, it sets a one second (1000 milliseconds) interval which attempts to connect to the Cast API. If the API has not yet loaded, the loop will try again after a second. If it has, it calls the function “initializeCastApi()” and stops looping (clearInterval). I have also added some console logging so that we can debug within the browser.</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">If you save the files, browse to your page, and open the Chrome Developer Tools (within the browser, not the Chromecast Developer Tools), you should see the following:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<img alt="Screen Shot 2014-04-30 at 10.45.09 PM.png" height="391px;" src="https://lh3.googleusercontent.com/x1iu7xBLo4XXStfudfXrOUe_Jp0bo2-2bhKZrhCKVp66Qp1V4o-wD4AWV8UFr2o9ICZUkQN4GAGRiWVRIBhmhqsSeWwKHHQTKq3bfAbbRwnsIJJ-SsrBDqciZ2C3tYB9YA" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="568px;" /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Figure 4-1 - Initial Initialization</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Notice that there is a ReferenceError because we have not coded the iniitalizeCastApi function yet. However, you should see “Cast has loaded.” Congratulations, you’ve initialized your first Chromecast app!</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">From here, there is a lof of code to type out in order to get even the most basic functionality. This is because the Chromecast API requires all sorts of functions for success and failures. I will do my best to walk you through each step. Begin by creating the initializeCastApi() function directly below your previous code:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function initializeCastApi() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> var applicationID = chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID;</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> var sessionRequest = new chrome.cast.SessionRequest(applicationID);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> var apiConfig = new chrome.cast.ApiConfig(sessionRequest,</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> sessionListener,</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> receiverListener);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> chrome.cast.initialize(apiConfig, onInitSuccess, onInitError);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">};</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Immediately, you can see that there are now a number of functions we will need to create. But first, let’s understand this code.</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The main objective of this code is to initialize a session with the Chromecast. To do this, the Chromecast requires an applicationID. This ID is provided via the cast API as the DEFAULT_MEDIA_RECEIVER_APP_ID (the ID of the Chromecast). For now, don’t worry about this ID; it is simply a unique ID associated with the Chromecast. However, later this ID will be customized when custom “receiver applications” (code that customizes the look and feel on the Chromecast) are used.</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Once the applicationID is assigned, it is passed into the sessionRequest method which creates a session with the Chromecast. Next, an apiConfig object is created by passing the sessionRequest (just created), a sessionListener function (not yet created), and a receiverListener function (also not yet created) into the ApiConfig function of the Cast API:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">var apiConfig = new chrome.cast.ApiConfig(sessionRequest,</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> sessionListener,</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> receiverListener);</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Finally, the last step is to use the apiConfig object that was just created, along with success and error functions (onInitiSuccess and onInitError), to initialize the Chromecast setup.</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The next step is to create the two callback functions, sessionListener and receiverListener, which we passed to the ApiConfig method earlier. Also, don’t forget about the onInitSuccess and onInitError functions, we will be creating those shortly as well.</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<h2 dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 10pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 17px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Sessions</span></h2>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">A session, very simply put, is a period of time during which a user has connected to, and is interacting with, the Chromecast API. To use YouTube as an example, if you cast a video from the YouTube video page, you have created a session with the Chromecast. Browsing to a different page, then coming back to YouTube, or refreshing the original page does not change that session. The session exists until it is either destroyed by the user (disconnecting via the Cast extension), destroyed programmatically, or destroyed via a connection error. Now, we are going to create the sessionListener function which was passed to the ApiConfig method earlier. First, add a global JavaScript variable called “session” at the top of your code:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">var session = null;</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">$( document ).ready(function(){</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">...</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Then, add a sessionListener function at the bottom of your script.js file:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function sessionListener(e) {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> session = e;</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log('New session');</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> if (session.media.length != 0) {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log('Found ' + session.media.length + ' sessions.');</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> }</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">This code establishes a session if one does not exist; but if a session does exist, it will log that fact. We will dive deeper into session management later in this book.</span></div>
<h2 dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 10pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 17px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Receivers</span></h2>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The Chromecast API works by using a sender (the application we are writing) and a receiver (the application running on the Chromecast). The sender sends instructions to the receiver which then executes them. The “receiverListener” callback function, which was passed to the ApiConfig method earlier, is responsible for ensuring a valid receiver application is available. In other words, it makes sure that a Chromecast on the network is capable of receiving instructions. Add a receiverListener as follows:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function receiverListener(e) {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> if( e === 'available' ) {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log("Chromecast was found on the network.");</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> }</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> else {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log("There are no Chromecasts available.");</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> }</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<h2 dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 10pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 17px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Success and Error Functions</span></h2>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Remember earlier when we passed “onInitiSuccess” and “onInitError” to the initialize method? Well now we are going to define what should happen when the initialization either succeeds or fails. Add the following functions at the end of the script.js file:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function onInitSuccess() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log("Initialization succeeded");</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function onInitError() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log("Initialization failed");</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">You can now save the file and test your work. Reloading the page should result in the following appearing within the JavaScript console of the Developer Tools:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<img alt="Screen Shot 2014-05-01 at 12.27.31 AM.png" height="379px;" src="https://lh4.googleusercontent.com/uebkbd9RdTUQE5TN3NTgHrb1_oVdn8q49ZFplJ0whLth6pxAUsCsj59IHh6KcVp9EAde2Xq6s01ncYdJGy6BhPIPgwKg9176jk0W4t_1h4SU6HP33nNzJpARO9OHCzEe9g" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="550px;" /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Figure 4-2 - Successful Initialization</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<h2 dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 10pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 17px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Involving the User</span></h2>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The next step involves user interaction in order to complete successfully. Because of security concerns, your application cannot simply commandeer a user’s Chromecast and begin casting on its own. Instead, Google requires that you prompt the user (by launching the Chromecast extension dialog) to select a device to which the media should be sent.</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<img alt="Screen Shot 2014-05-01 at 12.38.05 AM 1.png" height="154px;" src="https://lh6.googleusercontent.com/c1spb3FTatJ-WwUiV_D4x8kDdu_2lEJD9DLVy_pTnj9Q5lPI6_nQR28OxsEPflVdXEUBmlnLDcxf6-59haL5XwMBbDMMufWs9Kp-3lOyIv4E6IdFpallJ7KGi4qeSrq0HA" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="297px;" /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Figure 4-3 - The Familiar Cast Extension Popup</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">In order to launch this dialog, we need a function called “launchApp” to be called when when we want to launch the casting. We can do that by attaching a click event handler to the button we created earlier:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">$('#castme').click(function(){</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> launchApp();</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">});</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Notice the addition of the “launchApp()” call when the button is clicked. Now, create the “launchApp” function:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function launchApp() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log("Launching the Chromecast App...");</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> chrome.cast.requestSession(onRequestSessionSuccess, onLaunchError);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The second line of the function calls the requestSession method of the cast object. This method takes two arguments: onRequestSessionSuccess, which is called when a session has been successfully created (i.e. the user selected a Chromecast from the dialog), and onLaunchError, which is called when the session fails for some reason (most commonly when the user does not select a Chromecast from the popup).</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">In order to successfully test these new additions, we first need to create the two functions referenced above. Eventually, we will be adding a lot more functionality to the onRequestSessionSuccess function, but for now we will simply assign the session to the session variable and then log the result. The error function will simply log what happened if the user does not select a device from the popup. Here is the new code:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function onRequestSessionSuccess(e) {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log("Successfully created session: " + e.sessionId);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> session = e;</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function onLaunchError() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log("Error connecting to the Chromecast.");</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">This page can now be tested in the browser. Reload the page and click the button and you should see the popup appear. If you pick a Chromecast, you should see the success message. If you do not, you should see the error.</span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<img alt="Screen Shot 2014-05-03 at 11.10.33 AM.png" height="357px;" src="https://lh3.googleusercontent.com/xiEgh3Bk3PY3z1yt65eFt0NYRetuxpAHdwdv77OtbuLEzr4aniC6MPhHUjs82eE4MsOINRaj0zSYJBT-1DxALhivcQDuPwzyls1fEZLSDb9TNhkEdTeoDnhyHA0tG78M7Q" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="491px;" /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Figure 4-4 - Clicking the Cast Button</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Notice that the text of the popup no longer says “Cast this Tab,” but instead says “Cast <website name> to…”. This is because the official Cast API is being invoked. If you select your Chromecast from the list, you will now see the success message in the console. Clicking out of the window without selecting a device will cause the error message to be displayed.</span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<img alt="Screen Shot 2014-05-03 at 11.20.16 AM.png" height="350px;" src="https://lh6.googleusercontent.com/IcD496GGWTBL8rbiXOX5wZdkpGz_FQujTJ94C4lN4JfHeu5esAX5TTPX4hk05GYZvQsm7vLO8gV6JhsJpEyCTS1tmPbTccluQ1NVuUVaf1a8yfaGEt__jkEvB5VsI5B4jQ" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="483px;" /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Figure 4-5 - Successful Casting</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<img alt="Screen Shot 2014-05-03 at 11.21.22 AM.png" height="349px;" src="https://lh5.googleusercontent.com/LPLv5nHhKMk7fNF3WfXSBlS-ASCQkhJ7-LRNiRyoAqpg3zO3iuz7NdD-t3x_z2krsopN_6_FESmvSbqoL58WVbplh-YL-ha_ONAhkX2tWhjTVAEPHB1ZJDvDDn9YYqC8ww" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="480px;" /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Figure 4-6 - Unsuccessful Casting</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">We now have a session with the Chromecast! This means that we can send media to the device to display on the user’s television (hasn’t the Internet come a long way?).</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<h2 dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 10pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 17px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Casting Media</span></h2>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">There are a number of moving parts when it comes to casting media to the device once a session has been established, so I am just going to start out with the shortest amount of code that can cast an image, then walk through the pieces. First, edit the onRequestSessionSuccess function to call a function called “loadMedia” on success:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function onRequestSessionSuccess(e) {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log("Successfully created session: " + e.sessionId);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> session = e;</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> loadMedia();</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Then, add the following code to the end of the script file:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function loadMedia() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> if (!session) {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log("No session.");</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> return;</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> }</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> var mediaInfo = new</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">chrome.cast.media.MediaInfo('http://i.imgur.com/IFD14.jpg');</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> mediaInfo.contentType = 'image/jpg';</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> var request = new chrome.cast.media.LoadRequest(mediaInfo);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> request.autoplay = true;</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> session.loadMedia(request, onLoadSuccess, onLoadError);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function onLoadSuccess() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log('Successfully loaded image.');</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function onLoadError() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log('Failed to load image.');</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The first function, “loadMedia,” is the function called when a session is successfully initialized. The first thing it does is make sure that a valid session is set. If not, there is nothing to cast, so it returns without doing anything. However, if a session does exist, a new “mediaInfo” object is created by calling chrome.cast.media.MediaInfo and passing in a URL of an image (movies, GIFs, and other media can be passed in, but for now let’s stick with images). This image URL is one of a cute kitten hosted on Imgur.</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Once created, mediaInfo can be passed into the LoadRequest method of chrome.cast.media. This is assigned to the request object which is finally passed into the session.loadMedia method. Although this sounds complex or as if it is additional, unnecessary work, the methodology will become clear later when more attributes are added to the image and when custom receiver applications are used.</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The final two functions are simply success and failure callbacks which log the result of casting the image. Now that everything is in place, let’s try to cast! Save the page, reload, and then cast to your device. If everything goes right, you should see an image of a kitten on your television. (Note: if the image URL no longer works in the distant future, simply replace it with any .jpg image URL.)</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<img alt="Screen Shot 2014-05-03 at 12.03.09 PM.png" height="349px;" src="https://lh6.googleusercontent.com/FW8fFv5Dh-LunGSQz9GpfKAWPRyQ2WkX6L5wBRzPs4xq4rAw4jTNjDByNLnZnyUER3aq1aZdzeP_7GCQGwJdK8KsFpKhbAG0iTnQkamyK3-xxCV8ZC67AghSKSIyWIx_fA" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="481px;" /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Figure 4-7 - Successfully Casting an Image</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<img alt="Screen Shot 2014-05-03 at 12.33.09 PM.jpg" height="263px;" src="https://lh6.googleusercontent.com/sok9WT59f7MSFby5f17F1genpZT5B8dDw9iWwbfpkZnY2m2C1n1jnsh9wrzgPcd6AEp6dzDgzPpjYjdsnWctVS26gXaJhIP5oNy_BcgqYxsMw-FvHT6dcsAY7jnsmQY6Zw" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="447px;" /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Figure 4-8 - Image Casting to Television</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<h2 dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 10pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 17px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Ending the Session</span></h2>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The final step in the application is to stop the casting session. A user can initiate this process by clicking the “Stop Casting” button within the Chrome extension, but in many cases, you will want to stop a session programmatically, or provide a button within your web application that can stop the session. To do this, first add a button to your index.html file:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">index.html</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">…</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><form></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> <button type="button" id="castme">Click To Cast</button></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> <button type="button" id="stop">Click To Stop</button></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></form></span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">…</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Now, add a button click event handler in your script:</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: bold; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">script.js</span></div>
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">$('#stop').click(function(){</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> stopApp();</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">});</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function stopApp() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> session.stop(onStopAppSuccess, onStopAppError);</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function onStopAppSuccess() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log('Successfully stopped app.');</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
<br />
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">function onStopAppError() {</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> console.log('Error stopping app.');</span></div>
<div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Courier New'; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">}</span></div>
</td></tr>
</tbody></table>
</div>
<span style="font-family: Arial; font-size: 15px; line-height: 22.5px; text-align: justify; white-space: pre-wrap;">This code follows the same conventions we have seen earlier, so I won’t explain every aspect, but note that the button click event handler calls the stopApp function, which calls the stop method of the session object, passing in success and error functions as callbacks. Try reloading the page, casting the image, then clicking stop. Congratulations, you’ve written your first app!</span><br />
<br />
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com9tag:blogger.com,1999:blog-2859419017082777476.post-24170922186593911862014-05-04T16:03:00.003-04:002014-08-26T08:51:46.230-04:00Chromecast Development Tutorial - Hello World<span style="font-size: large;"><b>This guide is being moved to: <a href="http://chromecasthub.com/developers/">http://chromecasthub.com/developers/</a></b></span><br />
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71"></span><br />
<span style="font-size: large;"><b>Please visit the site for complete tutorials and more resources for Chromecast programming!</b></span><br />
<i><br /></i>
<i><br /></i>
<i>Note: This is part of a multi-part series on developing web applications that integrate Google's Chromecast SDK. I am working on making a complete resource for web development with the Chromecast, so consider this a pre-release version and please comment if you see any issues or have recommendations on how to improve the tutorial.</i><br />
<i><a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial.html">Part 1 - Introduction</a></i><br />
<i><a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial-getting.html">Part 2 - Getting Started</a></i><br />
<i><a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial-hello.html">Part 3 - Hello World</a></i><br />
<i><a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial-your.html">Part 4 - Your Own First App</a></i><br />
<i><br /></i>
<br />
<i><br /></i>
<span style="font-family: 'Trebuchet MS'; font-size: 48px; vertical-align: baseline; white-space: pre-wrap;">3</span><span style="font-family: 'Trebuchet MS'; font-size: 21px; vertical-align: baseline; white-space: pre-wrap;"> Hello World</span><br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71"><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">The first program in a new language is almost always the “Hello World” program. For that reason, we are going to be using Google’s demo app called “CastHelloVideo” to get our first app up and running. To begin, log into your development server and clone the GitHub repository located at: https://github.com/googlecast/CastHelloVideo-chrome.</span></span></div>
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71">
</span>
<br />
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none; width: 624px;"><colgroup><col width="*"></col></colgroup><tbody>
<tr style="height: 0px;"><td style="background-color: #efefef; border-bottom: solid #000000 0px; border-left: solid #000000 0px; border-right: solid #000000 0px; border-top: solid #000000 0px; padding: 7px 7px 7px 7px; vertical-align: top;"><div dir="ltr" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span style="background-color: transparent; font-family: 'Courier New'; font-size: 13px; vertical-align: baseline; white-space: pre-wrap;">user@test:~$git clone https://github.com/googlecast/CastHelloVideo-chrome</span></div>
</td></tr>
</tbody></table>
</div>
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71">
</span>
<br />
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71"><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">Move the folder into your /var/www directory so that you can access it via your IP address (or domain if you have configured one).</span></span></div>
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71">
</span>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: justify;">
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71"><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">At this point, you should be able to point your browser at your IP address and see your “CastHelloVideo-chrome” folder.</span></span></div>
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71">
</span>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71"><img alt="Screen Shot 2014-04-29 at 11.49.15 PM.png" height="336px;" src="https://lh4.googleusercontent.com/UQ1j82uzoZzsUbQbNi6IxA1sSpA8lqjjT_BPLu3pf3jDW99H-OxEK7WmFu6AS7hKYoTi5z4uOx_NSbqRZANOd5MqBrYM80res5-o2dNHifCtB8MYkPo2cJS3Qh-dJaiSBw" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="483px;" /><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;"></span></span></div>
<span id="docs-internal-guid-42922ae2-c8d3-e0b4-f7fd-681a09ecff71">
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="font-family: Arial; font-size: 13px; font-style: italic; vertical-align: baseline; white-space: pre-wrap;">Figure 3-1 - HelloWorld Running</span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="font-family: Arial; font-size: 13px; font-style: italic; vertical-align: baseline; white-space: pre-wrap;"><br /></span></div>
<div dir="ltr" style="line-height: 1.5; margin-bottom: 0pt; margin-top: 0pt; text-align: left;">
<span style="font-family: Arial; font-size: 13px; font-style: italic; vertical-align: baseline; white-space: pre-wrap;"><span id="docs-internal-guid-42922ae2-c8d4-4d53-9302-379213b54f85"><span style="font-size: 15px; font-style: normal; vertical-align: baseline;">Click into the folder and you should see the application. Play around for a bit and ensure that you can connect to your Chromecast and successfully cast a video. In the coming chapters, we will be building an application from the ground up that will have similar features, but for now, you can look at the source code or simply understand the various controls.</span></span></span><br />
<span style="font-family: Arial; font-size: 13px; font-style: italic; vertical-align: baseline; white-space: pre-wrap;"><span style="font-size: 15px; font-style: normal; vertical-align: baseline;"><br /></span></span>
<span style="font-family: Arial; font-size: 13px; font-style: italic; vertical-align: baseline; white-space: pre-wrap;"><span style="font-size: 15px; font-style: normal; vertical-align: baseline;"><a href="http://blog.matthewdfuller.com/2014/05/chromecast-development-tutorial-your.html">Continue on to Part 4 Here</a></span></span></div>
</span><script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16909264-11]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>Matt Fullerhttp://www.blogger.com/profile/08695836860792504116noreply@blogger.com1