Skip to content

Commit

Permalink
First commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
alfredo committed Feb 17, 2013
0 parents commit ee740e2
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .gitignore
@@ -0,0 +1,27 @@
*.py[cod]
# Packages
*.egg
*.egg-info
dist
build
eggs
develop-eggs
.installed.cfg

# Unit test / coverage reports
.coverage
.tox
nosetests.xml

#Translations
*.mo
# Ex
tra files
.DS_Store
*~
*-
\#*
\.\#*
*.orig
*.log
*.sql
7 changes: 7 additions & 0 deletions LICENSE
@@ -0,0 +1,7 @@
Copyright (C) 2013, Alfredo Aguirre

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
80 changes: 80 additions & 0 deletions README.rst
@@ -0,0 +1,80 @@
Mercurio
========

Mercurio is a set of components to run commands in a host. Also known as the deployment machine. It is designed to be plugglable, so you can run whatever you want to in the host.

The aim of Mercurio is to transmit a message to the host. It is most useful used as a deployment machine.

The other component is the Mercurio box


Mercurio listener
-----------------

Python listener on the serial port that translates the slugs passed into commands in the host machine.


Installation
------------

The installation can be done with the source::


python setup.py install


Configuration
-------------

The most important value in the configuration is the ``port`` where the ``mercurio box`` is attached.

This can be determined by running, after the ``mercurio listener`` has been installed:

python -m serial.tools.list_ports

The box by default will only send four possible targets:

* Test - Used to run the tests of the application.
* Development - Used to deploy to the development environment.
* Staging - Used to deploy to the staging environment.
* Production - Used to deploy to the producton environment.

These settings must be expressed in a ``mercurio.cfg`` file, located where the listener is run.

e.g.

[mercurio]
# Use python -m serial.tools.list_ports`` to determine it.
port: /dev/tty.usbserial-A900cfep
# Command to be run when this destination is targeted.
test : ls
development: ls
staging: ls
production: fab production deploy

Please note that at the moment the functionality of the host scripts that it can run is limited, at the moment it only runs a single command.


Start the listener
------------------

The listener can be run with the following command::


$ mercurio-run.py


It will show some output once the instruction has been received and the command is being executed.



Behind the scenes
-----------------

The listener expects the target to be passed in the following form::

target=production

After that it determines from the confing file what command needs to be run according to the target passed

If you require more complex functionality on your deployment, I suggest you look into fabric.
Empty file added mercurio/__init__.py
Empty file.
94 changes: 94 additions & 0 deletions mercurio/engine.py
@@ -0,0 +1,94 @@
import os
import ConfigParser
import subprocess

from datetime import datetime
from serial import Serial, serialutil
from clint.textui import puts, colored

TIMEOUT = 1
MERCURIO_CONFIG_NAME = 'mercurio.cfg'


def _dict_from_readline(line):
"""Creates a dictionary from the raw input from the serial.
e.g.
target=something
Is transformed into
{ 'target': 'something' }
"""
try:
items = ''.join(line.splitlines()).split('=', 1)
except ValueError:
items = None
if items and len(items) == 2:
return dict([items])
return {}


def _find_relative_file(filename):
config_path = os.path.join(os.getcwd(), MERCURIO_CONFIG_NAME)
if os.path.exists(config_path):
return os.path.abspath(config_path)
return None


def _read_config_file():
"""Reads the config file from the current directory"""
filename = _find_relative_file(MERCURIO_CONFIG_NAME)
if not filename:
raise ValueError('``mercurio.cfg`` file is missing.')
config = ConfigParser.ConfigParser()
config.readfp(open(filename))
config_dict = {
'port': config.get('general', 'port'),
'targets': dict([(i[0], i[1]) for i in config.items('targets')]),
}
return config_dict


def _prepare_command(command):
"""Prepares a single command to be executed by a subprocess"""
return command.split(' ')


def listen():
config = _read_config_file()
if not 'port' in config or not config['port']:
puts(colored.red('ERROR: port missing in ``mercurio.cfg`` file. '
'Try running ``python -m serial.tools.list_ports``'
' to figure out the port of the device.'))
exit(0)
port = config['port']
try:
serial = Serial(port, 9600, timeout=TIMEOUT)
except serialutil.SerialException:
puts(colored.red('ERROR: Mercurio device '
'not connected nor detected.'))
exit(0)
puts(colored.yellow('Mercurio is listening on %s.' % serial.portstr))
while True:
data = _dict_from_readline(serial.readline())
if not 'target' in data:
# Ignore any other output that doesn't have a target
continue
destination = data['target'].lower().rstrip()
if not destination in config:
# Inform that we received an unknown target
puts(colored.red("Target %s not found" % destination))
puts(colored.yellow("Instructions received."))
targets = config['targets']
command_args = _prepare_command(targets[destination])
puts(colored.yellow("Mercurio delivering to target:"
" %s" % destination.title()))
puts(colored.green(targets[destination]))
puts('-' * 60)
# ``subprocess.call`` will wait for the command to complete.
# no need of blocking the next execution since
# it won't be available until this is completed
subprocess.call(command_args)
puts('-' * 60)
puts(colored.yellow("Mercurio sucessfuly delivered on"
" %s." % datetime.now()))
6 changes: 6 additions & 0 deletions mercurio/mercurio-run.py
@@ -0,0 +1,6 @@
#!/usr/bin/env python
from mercurio import engine

if __name__ == "__main__":
engine.listen()
print "Done..."
25 changes: 25 additions & 0 deletions setup.py
@@ -0,0 +1,25 @@
#!/usr/bin/env python
from setuptools import setup, find_packages

setup(name='mercurio',
version='0.1',
description='Transmits messages between the arduino box and this listener',
long_description='Transmit messages between Arduino and this listener',
author='Alfredo Aguirre',
author_email='alfredo@madewithbyt.es',
scripts=['mercurio/mercurio-run.py'],
license='MIT',
eurl='http://github.com/alfredo/mercurio_listener/',
include_package_data=True,
classifiers=[
'Development Status :: 0.1 Alpha',
'Intended Audience :: Makers',
'License :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Utilities',
],
packages=find_packages(exclude=['tests']),
requires=['pyserial', 'clint'],
install_requires=['pyserial', 'clint'],
)
25 changes: 25 additions & 0 deletions tests/test_engine.py
@@ -0,0 +1,25 @@
from mercurio import engine


def test_dict_from_readline_with_valid_input():
value = "target=something"
result = engine._dict_from_readline(value)
assert result, {'target': 'something'}


def test_dict_from_readline_with_empty_input():
value = "\n\r"
result = engine._dict_from_readline(value)
assert result == {}


def test_dict_from_readline_with_invalid_input():
value = "somethinginvalid"
result = engine._dict_from_readline(value)
assert result == {}


def test_prepare_command():
command = "fab production deploy"
result = engine._prepare_command(command)
assert result == ['fab', 'production', 'deploy']

0 comments on commit ee740e2

Please sign in to comment.