<?xml version="1.0" encoding="UTF-8"?>
<rss version='2.0' xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ritesh Patel</title>
    <description>Hi, I am Ritesh Patel. I live in a beautiful town surrounded by the mountains. C&amp;O Canal is few miles away. State parks are only a distance away &amp; bike trails galore. It is home sweet home Frederick, MD. A passionate developer. Love to cook. Enjoy playing &quot;Bollywood Tunes&quot; on my harmonica. Apart from that just a normal guy. </description>
    <link>https://riteshpatel.silvrback.com/feed</link>
    <atom:link href="https://riteshpatel.silvrback.com/feed" rel="self" type="application/rss+xml"/>
    <category domain="riteshpatel.silvrback.com">Content Management/Blog</category>
    <language>en-us</language>
      <pubDate>Mon, 09 Oct 2017 16:00:31 -1100</pubDate>
    <managingEditor>flow.with.ritesh@gmail.com (Ritesh Patel)</managingEditor>
      <item>
        <guid>https://riteshpatel.silvrback.com/ansible-tower-cloudformation#33842</guid>
          <pubDate>Mon, 09 Oct 2017 16:00:31 -1100</pubDate>
        <link>https://riteshpatel.silvrback.com/ansible-tower-cloudformation</link>
        <title>Ansible Tower &amp; CloudFormation</title>
        <description>DevOps Rain with CloudFormation!</description>
        <content:encoded><![CDATA[<p>In this article I am penning down what I learned about &quot;automating Ansible Tower install&quot; - using a CloudFormation template. </p>

<p>Remember, it&#39;s not &quot;automating with Ansible&quot; instead it&#39;s &quot;how to deploy Ansible Tower with a CloudFormation Template&quot;. I have used RHEL7.4 for this template. </p>

<p>First things first. A very brief introduction on CloudFormation template</p>

<p><strong>Few words about CloudFormation</strong></p>

<blockquote>
<p>AWS CloudFormation gives developers and systems administrators an easy way to create and manage a collection of related AWS resources, provisioning and updating them in an orderly and predictable fashion</p>
</blockquote>

<p>It is cool, really! You can write your templates in JSON or YAML. And, so quite honestly I haven&#39;t had to deal with YAML, but nothing to it except it&#39;s very picky about how you indent your code :D :D. But we will catch malformed template by validating the template. It&#39;s super easy.</p>

<p>On a side note, I use Atom to write my templates, but you are free to use an editor of your choice. I also am a huge fan of VSCode.</p>

<p><strong>And a bit about template syntax</strong></p>

<p>A CloudFormation template has,</p>

<ul>
<li>Description - describes a template</li>
<li>Metadata - has information about ParameterGroups, Parameters &amp; Parameter Labels</li>
<li>Mappings - Region maps, Region AMI maps etc..</li>
<li>Conditions - Some boolean conditions (optional)</li>
<li>Parameters - parameters with default values (if any). </li>
<li>Resources - Definition of AWS resources to be spun up by the template</li>
<li>Outputs - Output from newly spun resources</li>
</ul>

<p>AWS has decent tutorials / listing of templates to get familiarized with the CloudFormation syntax. YAML or JSON, pick your poison :D</p>

<p><strong>How I write my template</strong></p>

<p>This is not mandatory, but I am a developer first and so I like to use appropriate prefix to identify parameters, resources etc...I will identify parameters with a prefix &quot;p&quot; and resources with &quot;r&quot;. So here is how it goes. Very simple example of a parameter and a resource.</p>
<div class="highlight"><pre><span></span><span class="c1">#  parameter definition</span>
<span class="l l-Scalar l-Scalar-Plain">pAuthorName</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">Description</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">Who is the author?</span>
    <span class="l l-Scalar l-Scalar-Plain">Type</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">String</span>
    <span class="l l-Scalar l-Scalar-Plain">Default</span><span class="p p-Indicator">:</span> <span class="s">&#39;ritesh</span><span class="nv"> </span><span class="s">patel&#39;</span>

<span class="c1"># resource definition</span>
<span class="l l-Scalar l-Scalar-Plain">rElasticIP</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">Type</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">AWS::EC2::EIP</span>
    <span class="l l-Scalar l-Scalar-Plain">Properties</span><span class="p p-Indicator">:</span>
      <span class="l l-Scalar l-Scalar-Plain">InstanceId</span><span class="p p-Indicator">:</span> <span class="kt">!Ref</span> <span class="l l-Scalar l-Scalar-Plain">rEC2InstanceAnsibleTower</span>
      <span class="l l-Scalar l-Scalar-Plain">Domain</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">vpc</span>
</pre></div>
<p>Don&#39;t worry about the details of defining a resource. We will dissect resources in a few. </p>

<hr>

<p><strong>How to validate a template?</strong></p>

<p>I use AWS CLI to validate my templates. It&#39;s super easy with only one requirement. You must install and configure AWS CLI.</p>
<div class="highlight"><pre><span></span>aws cloudformation validate-template --profile <span class="nv">$PROFILE</span> --region <span class="nv">$REGION</span> --template-body <span class="nv">$TEMPLATE</span>
</pre></div>
<p>$PROFILE is your AWS credentials, $REGION is your AWS region and $TEMPLATE is the template file you wish to validate. That&#39;s it.</p>

<p>Now let&#39;s start building our template for Ansible Tower.</p>

<hr>

<p><strong>Important parameters for Ansible Tower instance</strong></p>

<p>In this section I will mainly focus on important parameters (a.k.a must haves) for Ansible Tower.</p>

<p><em>Network Parameters</em></p>

<p>These are the parameters for VPC, subnets &amp; availability zones.</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">Label</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">default</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">Network Configurations</span>
    <span class="l l-Scalar l-Scalar-Plain">Parameters</span><span class="p p-Indicator">:</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pVPC</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pSubnet</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pAvailabilityZone</span>
</pre></div>
<p><em>Server Configurations</em></p>

<p>These parameters will define instance type, AMI, key pair, instance profile and so forth.</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">Label</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">default</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">Server Security Configurations</span>
    <span class="l l-Scalar l-Scalar-Plain">Parameters</span><span class="p p-Indicator">:</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pInstanceType</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pAMIId</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pKeyPair</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pIAMInstanceProfile</span>
</pre></div>
<p><em>Rabbit MQ Configurations</em></p>

<p>Ansible Tower uses Rabbit MQ for clustering and therefore these parameters will handle Rabbit MQ requirements.</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">Label</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">default</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">RabbitMQ Configurations</span>
    <span class="l l-Scalar l-Scalar-Plain">Parameters</span><span class="p p-Indicator">:</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRabbitMQPort</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRabbitMQVHost</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRabbitMQUserName</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRabbitMQPassword</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRabbitMQCookie</span>
</pre></div>
<p><em>Ansible Tower Credentials</em></p>

<p>Ansible Tower has a Web UI. We must pass credentials for Ansible Web UI during the install and therefore these parameters will be used to set Tower credentials.</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">Label</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">default</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">Ansible Tower Credentials</span>
    <span class="l l-Scalar l-Scalar-Plain">Parameters</span><span class="p p-Indicator">:</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pAnsibleTowerPassword</span>
</pre></div>
<p>There are few more parameters for which please refer to the final template in my <a href="https://github.com/riteshapatel/cf-ansible-tower">Github</a> repository. But for now this should do it.</p>

<p>Does Ansible Tower use a database? You bet. I am using PostgreSql RDS instance with Ansible Tower and so you guessed it right! Next up we will look at the parameters required for RDS instance. We are almost done with the parameters so just stay with me.</p>

<hr>

<p><strong>RDS Parameters</strong></p>

<p>Quickly, what is RDS? It is a Relational Database Service offered by AWS. Here is an excerpt straight from AWS.</p>

<blockquote>
<p>Amazon Relational Database Service (Amazon RDS) is a web service that makes it easier to set up, operate, and scale a relational database in the cloud. It provides cost-efficient, resizable capacity for an industry-standard relational database and manages common database administration tasks.</p>
</blockquote>

<p>Below are the parameters we will use for our PostgreSql instance.</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">Label</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">default</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">Ansible Tower Credentials</span>
    <span class="l l-Scalar l-Scalar-Plain">Parameters</span><span class="p p-Indicator">:</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRDSAvailabilityZone</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRDSInstanceClass</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRDSMasterUsername</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRDSMasterUserPassword</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRDSDatabaseName</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRDSPort</span>
    <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">pRDSSubnetGroupName</span>
</pre></div>
<p>Phew! Quite a few parameters, but we are done with the parameters. Next, let&#39;s see how to assign a default value to one of the parameters. All parameters follow similar syntax for assigning default values.</p>

<hr>

<p><strong>A Sample Parameter Definition</strong></p>

<p>Let&#39;s look at sample RDS parameter with a default value.</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">Parameters</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">pRDSDatabaseName</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">Description</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">Database name</span>
        <span class="l l-Scalar l-Scalar-Plain">Type</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">String</span>
        <span class="l l-Scalar l-Scalar-Plain">Default</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">towerdb</span>
</pre></div>
<p>Notice &quot;Default: towerdb&quot;. That&#39;s it. Simple, right? Review the final template for additional parameters.</p>

<hr>

<p><strong>UserData and Init Scripts</strong></p>

<p>Yeah so CloudFormation is great, I get it. It allows me to spin up an instance or a cloud resource of my choice. But what if I have specific set of scripts to be executed when booting up a new instance? Meet UserData. Yes, any custom scripts you wish to execute upon instance startup must go under UserData.</p>

<p>And so what can go under UserData? I say anything that will rock your boat :D. Ok ok. In this template, I am installing Python, yum updates, running CloudInit scripts &amp; setting up few wait conditions. (more on wait conditions later).</p>

<p>Sample scripts under UserData.</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">UserData</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">Fn::Base64</span><span class="p p-Indicator">:</span> <span class="kt">!Sub</span> <span class="p p-Indicator">|</span>
        <span class="no">#!/bin/sh</span>
        <span class="no">export AWS_DEFAULT_REGION=us-east-1</span>
        <span class="no">yum -y update aws-cfn-bootstrap</span>
</pre></div>
<p>You get the gist!</p>

<hr>

<p><strong>ConfigSets</strong></p>

<p>What are the config sets? Nothing but merely a list of tasks to execute via CloudFormation::Init. As the name suggests it&#39;s initialization. Yes, I said any initialization should go under UserData. That&#39;s correct. CloudFormation::Init gets called from UserData. Let&#39;s look at how this happens!</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">UserData</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">Fn::Base64</span><span class="p p-Indicator">:</span> <span class="kt">!Sub</span> <span class="p p-Indicator">|</span>
        <span class="no">/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --configsets bootstrap</span>
</pre></div>
<p>And then under the same resource definition we define CloudFormation::Init like below.</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">Metadata</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">AWS::CloudFormation::Init</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">configsets</span><span class="p p-Indicator">:</span>
            <span class="l l-Scalar l-Scalar-Plain">bootstrap</span><span class="p p-Indicator">:</span>
                <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">install-ansible-engine</span>
                <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">get-tower</span>
                <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">create-inventory</span>
                <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">replace-inventory</span>
                <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">modify-hosts</span>
                <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">install-tower</span>
</pre></div>
<p>Pay attention to &quot;configsets&quot; defined under UserData and how configsets are executed via Init. In this case configsets tasks: </p>

<ul>
<li>will install ansible engine</li>
<li>download a tower bundle</li>
<li>create ansible inventory file</li>
<li>replace inventory in the extracted bundle </li>
<li>modifies host file to run the playbook</li>
<li>install tower. </li>
</ul>

<p>With me?</p>

<p>Before we install Ansible Tower we need a database. And so Ansible Tower install has a tight dependency with the database, right? So how exactly CloudFormation deals with resource dependencies?</p>

<p>Meet <strong>DependsOn</strong>.</p>

<hr>

<p><strong>DependsOn - Dependency Management in a Template</strong></p>

<p>To install Ansible Tower we need database name, database endpoint and few additional information from the database instance. So we have a strict dependency on spinning up resources. In other words, template must not create Ansible Tower Instance until a Database Instance is in-place. </p>

<p>CloudFormation is very good at handling implicit dependencies, but in this case we will explicitly use &quot;DependsOn&quot; property to declare a dependency.</p>

<p>And so this is how it goes...</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">rAnsibleTower</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">DependsOn</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">rRDSInstance</span>
    <span class="l l-Scalar l-Scalar-Plain">...</span>
    <span class="l l-Scalar l-Scalar-Plain">...</span>
    <span class="l l-Scalar l-Scalar-Plain">...</span>

<span class="l l-Scalar l-Scalar-Plain">rRDSInstance</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">Type</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">AWS::RDS::DBInstance</span>
    <span class="l l-Scalar l-Scalar-Plain">Properties</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">AllocatedStorage</span><span class="p p-Indicator">:</span> <span class="s">&#39;100&#39;</span>
        <span class="l l-Scalar l-Scalar-Plain">BackupRetentionPeriod</span><span class="p p-Indicator">:</span> <span class="s">&#39;30&#39;</span>
        <span class="l l-Scalar l-Scalar-Plain">...</span>
        <span class="l l-Scalar l-Scalar-Plain">...</span>
        <span class="l l-Scalar l-Scalar-Plain">...</span>
</pre></div>
<p>Just like above you may use DependsOn for additional resource dependencies. But, what if you have a resource that depends on multiple dependencies? Simple, just provide a list of strings. </p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">rAnsibleTower</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">DependsOn</span><span class="p p-Indicator">:</span> <span class="p p-Indicator">[</span> <span class="s">&#39;rRDSInstance&#39;</span><span class="p p-Indicator">,</span> <span class="s">&#39;rElasticIP&#39;</span> <span class="p p-Indicator">]</span>
    <span class="l l-Scalar l-Scalar-Plain">...</span>
    <span class="l l-Scalar l-Scalar-Plain">...</span>
    <span class="l l-Scalar l-Scalar-Plain">...</span>
</pre></div>
<p>With me?</p>

<p><strong>Remember UserData &amp; ConfigSets?</strong></p>

<p>My template on <a href="https://github.com/riteshapatel/cf-ansible-tower">GitHub</a> has additional resources to support Ansible Tower install but, we will strictly focus on the tasks for installing and configuring Ansible Tower. Remember the configsets I mentioned in UserData? Let&#39;s go through them one-by-one.</p>

<p><em>install-ansible-engine</em></p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">install-andible-engine</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">commands</span><span class="p p-Indicator">:</span>
            <span class="l l-Scalar l-Scalar-Plain">0-install-pip</span><span class="p p-Indicator">:</span>
              <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sudo easy_install pip</span>
            <span class="l l-Scalar l-Scalar-Plain">1-sleep</span><span class="p p-Indicator">:</span>
              <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sleep 10s</span>
            <span class="l l-Scalar l-Scalar-Plain">2-install-ansible</span><span class="p p-Indicator">:</span>
              <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sudo pip install ansible</span>
            <span class="l l-Scalar l-Scalar-Plain">3-sleep</span><span class="p p-Indicator">:</span>
              <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sleep 10s</span>
            <span class="l l-Scalar l-Scalar-Plain">4-print-ansible-version</span><span class="p p-Indicator">:</span>
              <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">ansible --version</span>
            <span class="l l-Scalar l-Scalar-Plain">5-pause-after-ansible-engine</span><span class="p p-Indicator">:</span>
              <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sleep 10s</span>
</pre></div>
<p>Self explanatory shell commands. YAML above will install ansible engine via pip, create a sample inventory file (/tmp/hosts) and pings localhost via ansible. A simple test to make sure ansible is able to make a connection to localhost.</p>

<p><em>get-tower</em></p>

<p>Let&#39;s download Ansible Tower tar file. We will download and extract tar ball in /tmp folder.</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">get-tower</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">commands</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">0-download-tower</span><span class="p p-Indicator">:</span>
            <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sudo wget https://releases.ansible.com/ansible-tower/setup/ansible-tower-setup-latest.tar.gz</span>
            <span class="l l-Scalar l-Scalar-Plain">cwd</span><span class="p p-Indicator">:</span> <span class="s">&#39;/tmp&#39;</span>
        <span class="l l-Scalar l-Scalar-Plain">1-extract-tower</span><span class="p p-Indicator">:</span>
            <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">tar xvzf ansible-tower-setup-latest.tar.gz</span>
            <span class="l l-Scalar l-Scalar-Plain">cwd</span><span class="p p-Indicator">:</span> <span class="s">&#39;/tmp&#39;</span>
        <span class="l l-Scalar l-Scalar-Plain">2-pause-after-tower-extract</span><span class="p p-Indicator">:</span>
            <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sleep 15s</span>
</pre></div>
<p>Again, YAML above will download ansible tar ball, extract tar into temporary folder (/tmp) and pause for 15 seconds.</p>

<p><em>create-inventory</em></p>

<p>Ansible Tower install is done through a playbook. It comes bundled with an inventory file. But with any config file it doesn&#39;t have specifics about our install. And so, we will create a new inventory file which will replace the inventory file bundled with the tar ball. </p>

<p>In this inventory file we will specify,</p>

<ul>
<li>Ansible credentials</li>
<li>RDS parameters - like a database name, port, database credentials, endpoint (RDS URL)</li>
<li>RabbitMQ parameters - port, credentials etc..</li>
</ul>

<p>Remember most of these parameters will take the values from RDS resource and template parameters.</p>

<p>And so the new inventory file. </p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">create-inventory</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">files</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">/tmp/inventory</span><span class="p p-Indicator">:</span>
            <span class="l l-Scalar l-Scalar-Plain">content</span><span class="p p-Indicator">:</span> <span class="kt">!Sub</span> <span class="p p-Indicator">|</span>
                <span class="no">[tower]</span>
                <span class="no">localhost ansible_connection=local</span>

                <span class="no">[database]</span>

                <span class="no">[all:vars]</span>
                <span class="no">ansible_become=true</span>
                <span class="no">admin_password=${pAdminPassword}</span>
                <span class="no">pg_host=${rRDSInstance.Endpoint.Address}</span>
                <span class="no">pg_port=${pRDSPort}</span>
                <span class="no">pg_database=${pDatabaseName}</span>
                <span class="no">pg_username=${pDatabaseAdmin}</span>
                <span class="no">pg_password=${pDatabasePassword}</span>

                <span class="no">rabbitmq_port=${pRabbitMQPort}</span>
                <span class="no">rabbitmq_vhost=${pRabbitMQVHost}</span>
                <span class="no">rabbitmq_username=${pRabbitMQAdmin}</span>
                <span class="no">rabbitmq_password=${pRabbitMQPassword}</span>
                <span class="no">rabbitmq_cookie=cookiemonster</span>
                <span class="no">rabbitmq_user_log_name=false</span>
        <span class="l l-Scalar l-Scalar-Plain">mode</span><span class="p p-Indicator">:</span> <span class="s">&#39;000644&#39;</span>
        <span class="l l-Scalar l-Scalar-Plain">owner</span><span class="p p-Indicator">:</span> <span class="s">&#39;root&#39;</span>
        <span class="l l-Scalar l-Scalar-Plain">group</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">root</span>
    <span class="l l-Scalar l-Scalar-Plain">DependsOn</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">rRDSAnsibleTower</span>
</pre></div>
<p>In YAML above notice &quot;ansible_connection=local&quot;. By specifying &quot;local&quot; we are instructing ansible engine to run the playbook on a local host. </p>

<p>Another property to notice is, &quot;ansible_become=true&quot;. This property will elevate privileges for the playbook execution. In other words, playbook will be executed as a &quot;sudo&quot;. Rest should be self explanatory.</p>

<p><em>replace-inventory</em></p>

<p>Now, let&#39;s replace the inventory file from the tower bundle with the one we created above.</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">replace-inventory</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">commands</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">0-copy-inventory</span><span class="p p-Indicator">:</span>
            <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">cp /tmp/inventory /tmp/ansible-tower-setup*/inventory</span>
    <span class="l l-Scalar l-Scalar-Plain">DependsOn</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">rRDSAnsibleTower</span>
</pre></div>
<p>Very simple. We will copy our new inventory file in the directory where we extracted the tar file. </p>

<p>Notice &quot;ansible-tower-setup*&quot;.When you extract the tar file, it will create a directory named &quot;ansible-tower-setup-(version number)&quot;. Version number could be 3.1.2, 3.1.3 or some future version. So we will use a &quot;wild-card&quot; for version numbers. After all, we don&#39;t want to have a tight coupling with the versions by hardcoding the version number, right? </p>

<p>Now remember I said Ansible Tower install is done through a playbook? Well, then we need to modify the host file used by the ansible engine. We will add a &quot;localhost&quot; to our ansible host file.</p>

<p><em>modify-hosts</em></p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">modify-hosts</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">files</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">/etc/ansible/hosts</span><span class="p p-Indicator">:</span>
            <span class="l l-Scalar l-Scalar-Plain">content</span><span class="p p-Indicator">:</span> <span class="kt">!Sub</span> <span class="p p-Indicator">|</span>
                <span class="no">[localhost]</span>
                <span class="no">127.0.0.1</span>
</pre></div>
<p>And finally we install tower.</p>

<p><em>install-tower</em></p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">install-tower</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">0-move-source-dir</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">cd /tmp/ansible-tower-setup*</span>
    <span class="l l-Scalar l-Scalar-Plain">1-release-min-var-requirements</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sed -i -e &quot;s/10000000000/100000000/&quot; /tmp/ansible-tower-setup*/roles/preflight/defaults/main.yml</span>
    <span class="l l-Scalar l-Scalar-Plain">2-allow-sudo</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sed -i -e &quot;s/Defaults    requiretty/Defaults    \!requiretty/&quot; /etc/sudoers</span>
    <span class="l l-Scalar l-Scalar-Plain">3-pause-before-tower-install</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sleep 120s</span>
    <span class="l l-Scalar l-Scalar-Plain">4-install-tower</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">command</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">sh /tmp/ansible-tower-setup-*/setup.sh -e &quot;nginx_disable_https=true&quot;</span>
<span class="l l-Scalar l-Scalar-Plain">DependsOn</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">rRDSAnsibleTower</span>
<span class="l l-Scalar l-Scalar-Plain">services</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">sysvinit</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">nginx</span><span class="p p-Indicator">:</span>
            <span class="l l-Scalar l-Scalar-Plain">enabled</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">true</span>
            <span class="l l-Scalar l-Scalar-Plain">ensureRunning</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">true</span> 
</pre></div>
<p>In the YAML above, we move to the install directory, release minimum of 10GB requirement (required for RHEL7), allow sudo in the sudoers, pause for 2 minutes and then install tower.</p>

<p>We also ensure, &quot;nginx&quot; service is enabled and running. </p>

<p><strong>WaitCondition</strong></p>

<p>So it&#39;s great how we can run custom scripts in UserData but how exactly do we know the scripts have finished execution? Ah, meet &quot;WaitCondition&quot;s. Very useful. WaitCondition signals notify the CloudFormation templates about stack completion.</p>

<p>So how exactly we implement a WaitCondition? Well, look at the very last line under UserData.</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">/opt/aws/bin/cfn-signal -e $? &#39;${towerWaitHandle}&#39;</span>
</pre></div>
<p>Our script is calling cfn-signal to keep an eye on &#39;towerWaitHandle&#39;. And here is what the &#39;towerWaitHandle&#39; looks like. </p>
<div class="highlight"><pre><span></span>  <span class="l l-Scalar l-Scalar-Plain">towerWaitHandle</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">Type</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">AWS::CloudFormation::WaitConditionHandle</span>
  <span class="l l-Scalar l-Scalar-Plain">cfnWaitCondition</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">Type</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">AWS::CloudFormation::WaitCondition</span>
    <span class="l l-Scalar l-Scalar-Plain">DependsOn</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">rAnsibleTower</span>
    <span class="l l-Scalar l-Scalar-Plain">Properties</span><span class="p p-Indicator">:</span>
      <span class="l l-Scalar l-Scalar-Plain">Handle</span><span class="p p-Indicator">:</span> <span class="kt">!Ref</span> <span class="l l-Scalar l-Scalar-Plain">towerWaitHandle</span>
      <span class="l l-Scalar l-Scalar-Plain">Timeout</span><span class="p p-Indicator">:</span> <span class="s">&#39;1200&#39;</span>
</pre></div>
<p>In the YAML above, we are creating a waitHandle and a waitCondition. waitHandle is referred by the waitCondition. </p>

<p>Now take a look at the waitCondition. It has a dependency on the resource &quot;rAnsibleTower&quot;. In other words, UserData from &quot;rAnsibleTower&quot; will signal the waitCondition once the scripts have completed the execution. </p>

<p>WaitCondition also has a timeout of 1200 seconds. So, our CloudFormation template will wait at least 20 minutes to receive a signal from rAnsibleTower. Remember signal could be triggered sooner than 20 minutes in case of a successful execution. </p>

<p>If no signal received then CloudFormation stack will roll back indicating failure else the CloudFormation status is changed to &quot;CREATE_COMPLETE&quot;.</p>

<hr>

<p><strong>Watch Progress</strong></p>

<p>So how do I track the execution status of UserData scripts? Yes, you do want to keep an eye on the logs as it installs Ansible Tower. As soon as EC2Instance is available, ssh into this new instance. Change user to sudo and go to /var/log directory. You should see few logs under this directory. One log you want to pay attention to is cfn-init-cmd.log. </p>

<p>Run the command below at the terminal. And voila! you should be able to see the progress of UserData scripts.</p>
<div class="highlight"><pre><span></span>tail -f cfn-init-cmd.log
</pre></div>
<p>Additionally, Ansible Tower install has it&#39;s own log, of course. Go to /tmp directory and find the folder where we extracted the tar file. You should see a setup.log file and if you tail this log file you should be able to track tower install as it happens :D</p>
<div class="highlight"><pre><span></span>tail -f setup.log
</pre></div>
<hr>

<p>That was a lot, I know. Hey, but it comes with a reward: You can have Ansible Tower up and running behind a load balancer within 30 minutes - give or take. Now that&#39;s a deal. </p>

<p>And finally here is the <a href="https://github.com/riteshapatel/cf-ansible-tower">GitHub</a> repo. Clone and enjoy! </p>

<p>Cheers!</p>
]]></content:encoded>
      </item>
      <item>
        <guid>https://riteshpatel.silvrback.com/number-field-override-for-extjs-6#30282</guid>
          <pubDate>Tue, 28 Feb 2017 16:14:48 -1100</pubDate>
        <link>https://riteshpatel.silvrback.com/number-field-override-for-extjs-6</link>
        <title>Number field override for ExtJs 6+</title>
        <description>Trail me with zeros!</description>
        <content:encoded><![CDATA[<p><strong>My Love for ExtJs</strong></p>

<p>I love ExtJs. It is truly a butt kicking framework. </p>

<p>So, yes ExtJs is one of the frameworks I use on a daily basis. ExtJs is a pretty well sized framework to build Enterprise Applications or as a matter of fact just about any application. Learning curve is steep but rewards are enormous. </p>

<p>Assuming you are somewhat familiar with the framework. If not, then I suggest you jump right in by reading up amazing docs @ <a href="https://www.sencha.com/">Sencha</a>.</p>

<p><strong>What my clients asked...</strong></p>

<p>Just recently a client asked to retain a precision for ExtJs&#39;s number field. Requirement goes to say, if a user types 2 then the field should display 2.00. Granted the field is configured for a decimal precision of 2. </p>

<p>By default the number field or as a matter of fact Javascript does not retain the precision like that. Sure enough, if you typed 2.12 then it will retain 2.12 but if you type 2.00 it gets converted to 2. We want the exact opposite of that. </p>

<p>Don&#39;t believe me then open up a Developer Console in your browser and type in the statements below. See what happens.</p>
<div class="highlight"><pre><span></span><span class="nb">parseFloat</span><span class="p">(</span><span class="s2">&quot;2.00&quot;</span><span class="p">);</span> <span class="c1">// gets converted to 2</span>
<span class="nb">parseFloat</span><span class="p">(</span><span class="s2">&quot;2&quot;</span><span class="p">);</span> <span class="c1">// gets converted to 2</span>
</pre></div>
<p>Our requirement goes to say: if you typed 2.00 then the field should retain 2.00 and not convert it to 2.</p>

<p>A very Peculiar requirement and not everyone will ask for it. But, I thought to share it anyways. Just-in-case it might help a loner like me :)</p>

<p>Remember ExtJs Number Field is actually a text field. And therefore it requires to use parseFloat to convert a decimal string into a float. Mouthful huh!</p>

<p><strong>And here is the solution...</strong></p>

<p>Solution is quite simple but requires to extend the Number field class from ExtJS framework and override <code>valueToRaw</code> function. In the code example below, <code>valueToRaw</code> is taken straight from the framework except the very last line with a <code>return</code> statement. </p>

<p>Pay attention the <code>return</code> statement. This is where the magic happens.</p>

<p>With <code>forcePrecision: true</code>, the function will apply the precision using <code>Ext.Number.toFixed(value, precision)</code> and return the number as is. </p>

<p>With <code>forcePrecision: false</code> the function will apply the precision but wrap the return value in a <code>parseFloat</code> to achieve the default behavior. </p>

<p>Let&#39;s look at the code. </p>
<div class="highlight"><pre><span></span><span class="nx">Ext</span><span class="p">.</span><span class="nx">define</span><span class="p">(</span><span class="s1">&#39;MyApp.view.component.Number&#39;</span><span class="p">,</span> <span class="p">{</span>
    <span class="nx">override</span><span class="o">:</span> <span class="s1">&#39;Ext.form.field.Number&#39;</span><span class="p">,</span>
    <span class="nx">forcePrecision</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>

    <span class="c1">// changing behavior of valueToRaw with forcePrecision</span>
    <span class="nx">valueToRaw</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">var</span> <span class="nx">me</span> <span class="o">=</span> <span class="k">this</span><span class="p">,</span>
            <span class="nx">decimalSeparator</span> <span class="o">=</span> <span class="nx">me</span><span class="p">.</span><span class="nx">decimalSeparator</span><span class="p">;</span>

        <span class="nx">value</span> <span class="o">=</span> <span class="nx">me</span><span class="p">.</span><span class="nx">parseValue</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span> <span class="c1">// parses value received from the field</span>
        <span class="nx">value</span> <span class="o">=</span> <span class="nx">me</span><span class="p">.</span><span class="nx">fixPrecision</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span> <span class="c1">// applies precision</span>
        <span class="nx">value</span> <span class="o">=</span> <span class="nx">Ext</span><span class="p">.</span><span class="nx">isNumber</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">?</span> <span class="nx">value</span> <span class="o">:</span> 
                <span class="nb">parseFloat</span><span class="p">(</span><span class="nb">String</span><span class="p">(</span><span class="nx">value</span><span class="p">).</span><span class="nx">replace</span><span class="p">(</span><span class="nx">decimalSeparator</span><span class="p">,</span> <span class="s1">&#39;.&#39;</span><span class="p">));</span> 
        <span class="nx">value</span> <span class="o">=</span> <span class="nb">isNaN</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">?</span> <span class="s1">&#39;&#39;</span> <span class="o">:</span> <span class="nb">String</span><span class="p">(</span><span class="nx">value</span><span class="p">).</span><span class="nx">replace</span><span class="p">(</span><span class="s1">&#39;.&#39;</span><span class="p">,</span> <span class="nx">decimalSeparator</span><span class="p">);</span>

        <span class="c1">// forcePrecision: true - retains a precision</span>
        <span class="c1">// forcePrecision: false - does not retain precision for whole numbers</span>
        <span class="k">return</span> <span class="nx">me</span><span class="p">.</span><span class="nx">forcePrecision</span> <span class="o">?</span> <span class="nx">Ext</span><span class="p">.</span><span class="nb">Number</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span>
                                            <span class="nb">parseFloat</span><span class="p">(</span><span class="nx">value</span><span class="p">),</span> 
                                            <span class="nx">me</span><span class="p">.</span><span class="nx">decimalPrecision</span><span class="p">)</span> 
                                    <span class="o">:</span> 
                                    <span class="nb">parseFloat</span><span class="p">(</span>
                                            <span class="nx">Ext</span><span class="p">.</span><span class="nb">Number</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span>
                                                <span class="nb">parseFloat</span><span class="p">(</span><span class="nx">value</span><span class="p">),</span> 
                                                <span class="nx">me</span><span class="p">.</span><span class="nx">decimalPrecision</span>
                                            <span class="p">)</span>
                                    <span class="p">);</span>
    <span class="p">}</span>
<span class="p">});</span>
</pre></div>
<p>And here goes the definition of our number field.</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
    <span class="nx">xtype</span> <span class="o">:</span> <span class="s1">&#39;numberfield&#39;</span><span class="p">,</span>
    <span class="nx">fieldLabel</span><span class="o">:</span> <span class="s1">&#39;Custom Number Field&#39;</span><span class="p">,</span>
    <span class="nx">minValue</span><span class="o">:</span> <span class="mf">0.10</span><span class="p">,</span>
    <span class="nx">maxValue</span><span class="o">:</span> <span class="mf">2.00</span><span class="p">,</span>
    <span class="nx">step</span><span class="o">:</span> <span class="mf">0.01</span><span class="p">,</span>
    <span class="nx">allowBlank</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
    <span class="nx">allowDecimals</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="nx">decimalPrecision</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="nx">forcePrecision</span><span class="o">:</span> <span class="kc">true</span>
<span class="p">}</span>
</pre></div>
<p>Snippet above says, to create a number field with precision of 2. Increase field value by 0.01. Minimum value must be 0.10 and maximum allowed value must be 2.00. </p>

<p><strong>Now take it for a spin!</strong></p>

<p>And to test...here is the fiddle. Click     <code>Run Code</code>. Which should show you a form with a number field. Enter 2 and tab off. To your surprise you will notice that code will apply a precision of two by converting 2 to 2.00. Exactly what my clients asked for! </p>

<p><iframe  text="Embed Fiddle" src="https://fiddle.sencha.com/fiddle/1qf0" width="100%" height="550"> Embed Fiddle </iframe></p>

<p>Again, not something that everyone will come looking for, but hopefully it will help someone who is in dire need for this override.</p>

<p>Here is the git repo: <a href="https://github.com/riteshapatel/ExtHelpers">ExtHelpers</a></p>

<p>Fork &amp; Enjoy ! </p>

<p>Happy Ext-ing!</p>
]]></content:encoded>
      </item>
      <item>
        <guid>https://riteshpatel.silvrback.com/content-provider-tutorial-for-vscode#28793</guid>
          <pubDate>Thu, 08 Dec 2016 08:00:00 -1100</pubDate>
        <link>https://riteshpatel.silvrback.com/content-provider-tutorial-for-vscode</link>
        <title>Content Provider Tutorial for VSCode</title>
        <description>fake this content!</description>
        <content:encoded><![CDATA[<p>First and foremost this tutorial is about <code>Visual Studio Code</code> and has nothing to do with <code>Visual Studio</code>. Additionally, this tutorial is mainly geared towards developers writing extensions for Visual Studio Code (VSCode). Interested? Then let&#39;s move forward.</p>

<p>Lately, I have been doing quite a bit of TypeScript and dealing with Visual Studio Code API for authoring an extension. One of the plugins for which I contribute had a peculiar requirement to display a <code>read-only</code> document in the editor. By using the API! </p>

<p>One would think it should be a very straight forward process, right? Well it is once you get a hang of how exactly to accomplish it using the API.  Meet the TextDocumentContentProvider. Say what?</p>

<p>Yeah, ContentProviders allow you to add read only documents to VSCode editor. How? Let&#39;s take it step-by-step.</p>

<ul>
<li>Understand URI(s)</li>
<li>Create a custom URI-scheme</li>
<li>Create a ContentProvider</li>
<li>Register URI scheme and a ContentProvider</li>
<li>Display read-only content</li>
</ul>

<h4 id="uri-uniform-resource-identifier">URI (Uniform Resource Identifier)</h4>

<p>Straight from the Wiki page: a Uniform Resource Identifier is a string of characters used to identify a resource. URI(s) have a scheme, authority, path, an optional query and an optional fragment. Here is a sample URI.</p>
<div class="highlight"><pre><span></span>file://ritesh@line89.com:123/path/to/fake/file?key=myfile#fragid1
</pre></div>
<p>Let&#39;s quickly dissect this URI and understand how it is formed.</p>

<p><em>file:</em> - is a scheme<br>
<em><a href="mailto:ritesh@line89.com">ritesh@line89.com</a>:123</em> - is authority<br>
<em>/path/to/fake/file</em> - is a path<br>
<em>?key=myfile</em> - is a query<br>
<em>fragid1</em> - is a fragment</p>

<p>Why am I showing this ? Well, it is a must to understand how URI(s) are formed before moving forward with the ContentProvider(s). </p>

<h4 id="create-a-custom-uri-scheme">Create a custom URI scheme</h4>

<p>To open a read-only document via VSCode API, one of the requirements is to create a custom URI scheme. We all know there are pre-defined schemes like http:, file:, untitled: and so on. For our purpose let&#39;s create a URI scheme called: <code>settings-preview:</code>.</p>

<p>In other words any time I wish to open a read-only settings I will use this URI scheme. Rest of the URI can be as fake as you want : as long as it resolves to a valid URI.</p>

<p>For this tutorial we will use the URI below.</p>
<div class="highlight"><pre><span></span>settings-preview://my-extension/fake/path/to/settings
</pre></div>
<p>Next, let&#39;s create a ContentProvider for our extension. We will call it a <code>SettingsProvider</code> since we wish to load read-only settings in the editor.</p>

<h3 id="create-a-contentprovider">Create a ContentProvider</h3>

<p>ContentProvider is nothing but merely a class that will extract the text from a resource of your choice. And a resource could be another text file, JSON document or a database. Your call.</p>

<p>To keep things simple, we will use a JSON document somewhere on the file system. Our ContentProvider will read this JSON file and return a string to be displayed in the editor. Main point to remember here is: Our SettingsProvider is implementing TextDocumentContentProvider interface.</p>
<div class="highlight"><pre><span></span><span class="s1">&#39;use strict&#39;</span><span class="p">;</span>

<span class="kr">import</span> <span class="o">*</span> <span class="nx">as</span> <span class="nx">vscode</span> <span class="nx">from</span> <span class="s1">&#39;vscode&#39;</span><span class="p">;</span>
<span class="kr">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;path&#39;</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">cjson</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;cjson&#39;</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;fs&#39;</span><span class="p">);</span>

<span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">SettingsProvider</span> <span class="kr">implements</span> <span class="nx">TextDocumentContentProvider</span> <span class="p">{</span>
      <span class="cm">/**</span>
<span class="cm">       *</span>
<span class="cm">       * @param {vscode.Uri} uri - a fake uri</span>
<span class="cm">       * @returns {string} - settings read from the JSON file</span>
<span class="cm">       **/</span>
    <span class="kr">public</span> <span class="nx">provideTextDocumentContent</span> <span class="p">(</span><span class="nx">uri</span> <span class="o">:</span> <span class="nx">Uri</span><span class="p">)</span> <span class="o">:</span> <span class="nx">string</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">settingsFilePath</span> <span class="o">=</span> <span class="s1">&#39;/path/to/settings/file/settings.json&#39;</span><span class="p">;</span>
        <span class="kd">let</span> <span class="nx">returnString</span> <span class="o">:</span> <span class="nx">string</span><span class="p">;</span>

        <span class="c1">// read settings file</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">fs</span><span class="p">.</span><span class="nx">existsSync</span> <span class="p">(</span><span class="nx">settingsFilePath</span><span class="p">))</span> <span class="p">{</span>
            <span class="nx">returnString</span> <span class="o">=</span> <span class="nx">cjson</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="nx">setttingsFilePath</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// return JSON object as a string</span>
        <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">returnString</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span> <span class="o">||</span> <span class="s1">&#39;&#39;</span><span class="p">;</span> <span class="c1">// prettify and return</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>A very simple ContentProvider class. Since our class is implementing TextDocumentContentProvider we must provide an implementation for <code>provideTextDocumentContent</code>. It is used to read and process the content.</p>

<p>Next we will register the URI scheme and a ContentProvider.</p>

<h4 id="register-uri-scheme-and-a-contentprovider">Register URI scheme and a ContentProvider</h4>

<p>In your <code>extension.ts</code> file register the URI scheme as below.</p>
<div class="highlight"><pre><span></span><span class="kr">import</span> <span class="p">{</span><span class="nx">workspace</span><span class="p">,</span> <span class="nb">window</span><span class="p">,</span> <span class="nx">Disposable</span><span class="p">,</span> <span class="nx">ExtensionContext</span><span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;vscode&#39;</span><span class="p">;</span>
<span class="kr">import</span> <span class="nx">SettingsProvider</span> <span class="nx">from</span> <span class="s1">&#39;./SettingsProvider&#39;</span><span class="p">;</span>

<span class="kr">export</span> <span class="kd">function</span> <span class="nx">activate</span> <span class="p">(</span><span class="nx">context</span> <span class="o">:</span> <span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// instantiate SettingsProvider</span>
    <span class="kr">const</span> <span class="nx">settingsProvider</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SettingsProvider</span><span class="p">();</span>

    <span class="c1">// register provider and a scheme</span>
    <span class="kr">const</span> <span class="nx">registration</span> <span class="o">=</span> <span class="nx">Disposable</span><span class="p">.</span><span class="nx">from</span> <span class="p">(</span>
        <span class="nx">workspace</span><span class="p">.</span><span class="nx">registerTextDocumentContentProvider</span> <span class="p">(</span><span class="s1">&#39;settings-preview&#39;</span><span class="p">,</span> <span class="nx">settingsProvider</span><span class="p">);</span>
    <span class="p">);</span>

    <span class="c1">// push it to context subscriptions</span>
    <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nx">push</span> <span class="p">(</span>
        <span class="nx">registration</span>
    <span class="p">);</span>
</pre></div>
<p>Done, our custom URI scheme and a ContentProvider is now registered with the extension.</p>

<h3 id="display-read-only-content">Display read-only content</h3>

<p>And now all left is to invoke the ContentProvider and display read-only content in the editor. ContentProvider is invoked when a request is made with the <code>settings-preview</code> URI. Because our URI scheme is registered with the extension, it will automatically invoke the ContentProvider registered with the scheme and provide the content.</p>

<p>One more time, here is the URI we created above.</p>
<div class="highlight"><pre><span></span>settings-preview://my-extension/fake/path/to/settings
</pre></div>
<p>Let&#39;s create a function to open a text document with this URI.</p>
<div class="highlight"><pre><span></span><span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">context</span> <span class="o">:</span> <span class="nx">ExtensionContext</span><span class="p">)</span>  <span class="o">:</span> <span class="nx">Thenable</span> <span class="o">&lt;</span><span class="nx">TextEditor</span><span class="o">&gt;</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span> <span class="o">&lt;</span><span class="nx">TextEditor</span><span class="o">&gt;</span> <span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">settingsUri</span> <span class="o">=</span> <span class="s1">&#39;settings-preview://my-extension/fake/path/to/settings&#39;</span><span class="p">;</span>

    <span class="c1">// open a fake document (content served from the SettingsProvider)</span>
        <span class="nx">workspace</span><span class="p">.</span><span class="nx">openTextDocument</span><span class="p">(</span><span class="nx">Uri</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">settingsUri</span><span class="p">))</span>
            <span class="p">.</span><span class="nx">then</span> <span class="p">(</span><span class="nx">doc</span> <span class="p">=&gt;</span> <span class="nb">window</span><span class="p">.</span><span class="nx">showTextDocument</span><span class="p">(</span><span class="nx">doc</span><span class="p">)</span>
            <span class="p">.</span><span class="nx">then</span> <span class="p">(</span><span class="nx">editor</span> <span class="p">=&gt;</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="nx">editor</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nx">resolve</span> <span class="p">(</span><span class="nx">editor</span><span class="p">);</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="nx">resolve</span> <span class="p">(</span><span class="kc">null</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}))</span>
    <span class="p">});</span>
<span class="p">}</span>
</pre></div>
<p>And voila! That&#39;s it!</p>

<p>If done correctly, the VSCode editor should display read-only settings from a  JSON document.</p>

<p>Cheers!</p>
]]></content:encoded>
      </item>
  </channel>
</rss>