- Apache (for basic authentication, SSL and WSGI, virtual hosts) with mod_ssl and mod_wsgi.
- OpenSSL (for SSL and certificate generation).
- Python.
- Flask (py-based web services micro-framework).
- boto (py-based AWS API library).
- AWS KeyID/SecretKey credentials.
- Admin/sudo privileges
Please note, if you do not need SSL/htpasswd you can use Flask's bundled web server which is suitable for most in-house deployments; however in this example, I will be using Apache. Also to note: I'm not going to spend time in the installation process of the packages/SSL certicate generation above as it should be fairly straightforward for anyone with minimal dev/sysadmin experience as well as there being many well-written tutorials for the setup of these tools floating around the 'net.
First we need to make sure your tools are working. Make sure Apache is working, make sure mod_ssl is working, etc. Open up a python prompt and try importing flask, boto and so forth. Once you are fairly confident your tools are good to go then let's get moving.
1. Create an Apache virtual host entry file specifying the SSL certificate location, port number, location of the wsgi file and other important parameters as show below:
<VirtualHost *:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/[WEB_APP_NAME]
SSLEngine On
SSLCertificateFile /path/to/certs/server.crt
SSLCertificateKeyFile /path/to/certs/server.key
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
WSGIDaemonProcess [WEB_APP_NAME] user=[APACHE_USER] group=[APACHE_USER_GROUP] threads=5
WSGIScriptAlias / /var/www/[WEB_APP_NAME]/[WEB_APP_NAME].wsgi
<Directory /var/www/[WEB_APP_NAME]>
WSGIProcessGroup [WEB_APP_NAME]
WSGIApplicationGroup %{GLOBAL}
WSGIScriptReloading On
Options Indexes FollowSymLinks MultiViews
AllowOverride All #important line for using htpasswd
Order allow,deny
allow from all
</Directory>
#other settings here
</VirtualHost>
2. We then need to write the wsgi file telling mod_wsgi which app and instance it should start when a request comes along. It's a very simple file. Just make sure its name matches the ones you provided in the vhost file above. Its contents should look something like this:
from [WEB_APP_NAME] import app as application
One thing to bear in mind is that this way of using wsgi requires that your web app be anywhere in the $PYTHONPATH environment variable. Save, close and restart Apache.
3. For this example, as said above, I'm working under the assumption that your authentication needs are very basic and those requirements can be fulfilled with Apache's htpasswd. Assuming you setup Apache correctly with the right mods, you can go to your application's root directory and tell htpasswd to create a password file (.htpasswd) with a username-password pair. You can do so by running the following command:
sudo htpasswd -c .htpasswd [USERNAME]
It will then prompt you for a password which it will then encrypt using basic symmetric encryption algorithms and the create the .htpasswd file.
4. Next step is to setup your .htaccess file so that Apache knows when to ask for the credentials. Your .htaccess should have (at least) the following rules:
AuthUserFile /path/to/[WEB_APP_NAME]/.htpasswd
AuthGroupFile /dev/null
AuthName "EnterPassword"
AuthType Basic
require valid-user
You can test it by pointing your web browser to whatever URL you set up for this site. You should now be prompted with a username/password dialog.
5. Now that we have the basics ready, let us get to the meat and substance. First I highly suggest you give a quick perusal to Flask's "Quickstart" tutorial which can be found here and trying out the first few trivial examples to make sure you have everything setup correctly.
6. Make a new file named [WEB_APP_NAME].py and copy-and-paste the text below:
from flask import Flask, flash, abort, redirect, url_for, request, render_template
from boto.ec2.connection import EC2Connection
import boto.ec2
app = Flask(__name__)
akeyid = '[AWS_KEY_ID]'
seckey = '[AWS_SECRET_KEY]'
conn = EC2Connection(akeyid,seckey)
7. One of the concepts to bear in mind with respect to AWS API connections is regions. There is no global end-point for your AWS API calls and calls made to that region's API only make sense for services within that Region. For instance, you can't "see" your instances in the west coast Region ("us-west-1") from any other region. So, whatever regions you wish to have access to, you need to specify those explicitly. By default, boto connects to the "us-east-1" region.
8. So, our index page will be the Dashboard itself, that is to say, a place where users will be able to see all the instances from all regions. You can choose to limit the regions you wish to show/scour fairly easily, but for the sake of this example I'm going to simply gather all the instance information from all of AWS' regions. I will the create an object data structure with all the data and eventually I'll pass that data to the template rendering engine that Flask comes with. So, we're going to create an app route for the index page, use boto to create an EC2 connection, retrieve all available regions, get all the instance reservations in that region, get all the instances within each of those regions, then bundle the data in a data structure and pass it to the rendering engine.
a. Create the index route:
@app.route("/")
b. declare your method:
def my_method_name():
c. retrieve the list of all AWS available regions:
allinfo = []
regions = conn.get_all_regions()
for region in regions:
d. connect to each of those regions and retrieve all the instance reservations (something to note: I'm not sure if it's boto's or AWS' boo-boo, but retrieve_all_instances() method does not retrieve all instances per se, instead it retrieves all the instance reservations, which are instance "containers") :
rconn = EC2Connection(akeyid,seckey,region=region)
rsvs = rconn.get_all_instances() #read note above
e. loop over the reservations and gather all the instance information:
for rsv in rsvs:
insts = rsv.instances
for inst in insts:
#do stuff
f. now all together (including populating our instance info data structure and pass to the rendering engine):
@app.route("/")
def my_method_name():
allinfo = []
regions = conn.get_all_regions()
for region in regions:
regioninfo = {}
regioninfo['Name'] = region.name
rconn = EC2Connection(akeyid,seckey,region=region)
rsvs = rconn.get_all_instances()
instances = []
for rsv in rsvs:
insts = rsv.instances
for inst in insts:
instances.append({'Id': inst.id, 'Name':inst.tags['Name'],'State':inst.state, 'Type':inst.get_attribute('instanceType')['instanceType']})
regioninfo['instances'] = instances
allinfo.append(regioninfo)
return render_template('index.html',all_info=allinfo)
9. Now that we have the route and the code, we are going to use Flask's nifty template rendering engine (Jinja). To do so we need to create a file that matches the name in the render_template call above.
a. create a file with nano or your favorite text editor named index.html (or whatever name you chose ). This file has to be (by Flask convention) inside a directory called 'templates' and this directory should be at the same level as your web app Py script.
b. copy-paste the following "boilerplate" html:
<!doctype html>
<html>
<head><title>EC2 Dashboard</title>
</head>
<body>
<div class="header">Welcome to EC2 Dashboard</div>
<div class="content">
<div class="region-text">Regions Available</div>
{% for region in all_info %}
{% if region['instances'] %}
<div class="region-info"><span style="font-weight:bold">Region: {{region['Name']}}</span>
<span>Instances Avaliable</span></div>
<div class="region-content">
<table><tr><th>Instance Name</th><th>Instance State</th><th>Instance Type</th><th>Instance Id</th><th>Instance Actions</th></tr>
{% for instance in region['instances'] %}
<tr>
<td><span>{{instance['Name']}}</span></td>
<td><span>{{instance['State']}}</span></td>
<td><span>{{instance['Type']}}</span></td>
<td><span>{{instance['Id']}}</span></td>
<td>
<a href="/details/{{region['Name']}}/{{instance['Id']}}">See Details</button>
</td>
</tr>
{% endfor %}
</table>
</div>
{% else %}
<div class="region-info">
<span style="font-weight:bold">Region: {{region['Name']}}</span>
<span>No Instances Avaliable in this region</span>
</div>
{% endif %}
{% endfor %}
</div>
</body>
</html>
It should be obvious that you can (and should!) use your own html and CSS styles. The above example was to illustrate the rendering engine usage and syntax, which is pretty self explanatory and simple. Flask's and Jinja's documentation is very good and when in doubt you should consult those as your primary source.
This concludes part I of this tutorial. In part II I will then show how to post information, how to use boto for modifying instance information and more on Flask's routing/url facilities.
No comments:
Post a Comment