xal¶
Xal is a contextual execution framework for Python. “xal” is the acronym of “eXecution Abstraction Layer”.
Warning
This project is experimental. Current goal is to implement a proof-of-concept that can be shown to, discussed with or tried by users of tools like subprocess, Fabric, zc.buildout, Salt...
Xal helps you create scripts to perform actions on a system, like managing non-Python resources, independantly from the execution context.
The main motivation of this library is about sharing system scripts:
- scripts are written with session as argument, they use an high-level abstract API;
- sessions are registries, they encapsulate API implementation: local Python shell, Fabric, Salt...
Example¶
Let’s create a xal-compatible function. It takes the execution context as input argument:
>>> def home_directory_exists(session):
... """Return True if home directory of session's user exists."""
... return session.dir.exists(session.dir.home)
Then create an execution session. The LocalSession used here is a pre-configured registry:
>>> from xal.session.local import LocalSession
>>> session = LocalSession()
Finally run the function in the session:
>>> home_directory_exists(session)
True
Ressources¶
- Documentation: https://xal.readthedocs.org
- PyPI: https://pypi.python.org/pypi/xal
- Code repository: https://github.com/benoitbryon/xal
- Bugtracker: https://github.com/benoitbryon/xal/issues
- Continuous integration: https://travis-ci.org/benoitbryon/xal
Contents¶
Installation¶
This code is open-source. See License for details.
If you want to contribute to the code and get a development environment, you should go to Contributor guide documentation.
Install the package with your favorite Python installer. As an example, with pip:
pip install xal
Resources¶
Xal resources are Python representations of system resources such as files, processes... One of xal’s main purpose is to define a consistent set of resources with a reference API.
Implementation of resources is not xal’s main scope. xal focuses on API. That said, as a proof of concept, xal implements some resource providers.
Warning
Work in progress!
The resources below are the ones currently implemented in xal. Since xal is not mature yet, many resources have not been implemented yet. Learn more about planned features in bugtracker [1].
Here are resources provided by xal itself.
Paths, files and directories¶
xal provides an interface to browse directories and manage files.
fs API¶
FileSystem resource¶
- class xal.fs.resource.FileSystem(path, *args, **kwargs)¶
>>> from xal.fs.resource import FileSystem
>>> def demo_resource(session):
... """Demonstrate use of ``FileSystem`` resource."""
... fs = session.fs # Just a shortcut.
...
... # ``session.fs`` is a resource factory.
... resource = fs('test-fs')
... assert isinstance(resource, FileSystem)
...
... # Just use path as strings.
... assert str(resource) == 'test-fs'
...
... # Paths remain relative until they are actually used. Absolute
... # values are used as soon as filesystem operation is performed.
... assert resource.is_relative()
... created_resource = resource.mkdir()
... assert created_resource.is_absolute()
... assert resource.is_relative() # Original value is preserved.
...
... # Cleanup.
... fs.rm(created_resource)
cd¶
- FileSystem.cd()¶
Change working directory.
>>> def demo_cd(session):
... """Demonstrate use of ``cd``."""
... fs = session.fs # Just a shortcut.
...
... # Let's create a new directory.
... path = fs.mkdir('test-fs')
...
... # Change working directory.
... initial_path = fs.cwd()
... assert fs.cwd() != path
... with fs.cd(path) as sub_path:
... assert fs.cwd() == path == sub_path
... assert sub_path.parent == initial_path
... assert fs.cwd() == initial_path
...
... # Also works without context manager.
... # Notice that once used, paths are absolute.
... rel_path = fs('test-fs')
... assert rel_path.is_relative()
... abs_path = rel_path.cd()
... assert abs_path.is_absolute()
...
... # Cleanup.
... fs.cd(initial_path)
... fs.rm(path)
mkdir¶
- FileSystem.mkdir(mode=511, parents=False)¶
Create directory.
>>> def demo_mkdir(session):
... """Demonstrate use of ``mkdir``."""
... fs = session.fs # Just a shortcut.
...
... # Let's create a new directory.
... assert not fs.exists('test-fs')
... path = fs.mkdir('test-fs')
... assert fs.exists('test-fs')
...
... # mkdir() returns absolute path, even if created from relative
... # value.
... assert str(path) != 'test-fs'
... assert path.is_absolute()
...
... # Cleanup.
... fs.rm(path)
Providers¶
Local¶
Let’s execute demo function above in local session.
First setup a local session:
>>> from xal.session.local import LocalSession
>>> session = LocalSession()
Then run generic code within specific session:
>>> demo_resource(session)
>>> demo_cd(session)
>>> demo_mkdir(session)
SSH using Fabric¶
Let’s do the same over SSH using Fabric!
Setup some SSH session:
>>> from xal.session.fabric import FabricSession
>>> session = FabricSession()
>>> session.client.connect('localhost')
True
Then run generic code within specific session:
>>> demo_resource(session)
>>> demo_cd(session)
>>> demo_mkdir(session)
sh commands¶
XAL provides an interface to run shell commands through sh.
Tip
With a local session, XAL is a convenient subprocess wrapper.
Differences with subprocess¶
XAL’s sh interface is made to run sh commands in a session. Think of the commands always run with sh -c. Whereas Python’s subprocess sets shell=False by default.
This postulate influences design. XAL’s sh interface helps you create and run commands through sh: pipes, redirects...
“sh” provider in session’s registry¶
Let’s start with a local session:
>>> from xal.session.local import LocalSession
>>> session = LocalSession()
By default, in LocalSession, “sh” provider is xal.sh.local.LocalShProvider:
>>> session.registry.default('sh') # Doctest: +ELLIPSIS
<xal.sh.local.LocalShProvider object at 0x...>
For convenience, we will set sh as a shortcut for session.sh:
>>> sh = session.sh
The ShCommand resource¶
The sh interface can be used as a factory to create xal.sh.resource.ShCommand resources:
>>> sh("echo -n 'Hello world!'") # Doctest: +ELLIPSIS
<xal.sh.resource.ShCommand object at 0x...>
Command resources are not executed automatically once created. You have to run them explicitely. They are callables. When called, they return a xal.sh.resource.ShResult instance:
>>> command = sh("echo -n 'Hello world!'")
>>> result = command()
>>> result # Doctest: +ELLIPSIS
<xal.sh.resource.ShResult object at 0x...>
>>> result.stdout
'Hello world!'
>>> result.return_code
0
>>> result.succeeded
True
Command constructor accepts strings or iterables:
>>> sh("echo -n 'Hello world!'")().stdout
'Hello world!'
>>> sh(["echo", "-n", "Hello world!"])().stdout
'Hello world!'
The sh interface has a run() shortcut that creates and runs Cmd instances:
>>> sh.run("echo -n 'Hello world!'").stdout
'Hello world!'
>>> sh.run(["echo", "-n", "Hello world!"]).stdout
'Hello world!'
>>> command = sh("echo -n 'Hello world!'")
>>> sh.run(command).stdout
'Hello world!'
You can create a resource for later use in one or several sessions:
>>> hello = sh("echo -n 'Hello world!'")
>>> from xal.session.local import LocalSession
>>> session = LocalSession()
>>> session.sh.run(hello).stdout
'Hello world!'
Pipes¶
You can create and handle pipes, they are commands too:
>>> echo = sh("echo -e 'hello\nworld'")
>>> grep = sh("grep 'world'")
>>> piped = echo.pipe(grep)
>>> piped().stdout
'world\n'
>>> piped = echo | grep
>>> piped().stdout
'world\n'
>>> sh.run(echo | grep).stdout
'world\n'
References
[1] | https://github.com/benoitbryon/xal/issues |
About xal¶
This section is about the project itself.
Vision¶
XAL is a contextual execution framework for Python. Through its contextual execution system, it makes a resource abstraction layer possible.
Contextual execution¶
In Python, there are several tools to perform actions on the local system. As an example, the subprocess module makes it possible to run arbitrary shell commands. So, let’s suppose you wrote a script that echoes “Hello world!” using the shell:
import subprocess
subprocess.call(['echo', u'Hello world!'])
Then what if you want to run commands on a remote machine? You can write a fabfile:
from fabric import api as fab_api
@fab_api.task
def hello():
fab_api.run(['echo', 'Hello world!'])
Then what if you want to run the command as an admin? With Fabric:
from fabric import api as fab_api
@fab_api.task
def hello():
fab_api.sudo(['echo', 'Hello world!'])
Then what if you want to run the command as a sudoer, on the local machine? Fabric doesn’t provide a wrapper for that:
from fabric import api as fab_api
@fab_api.task
def hello():
fab_api.local(['sudo', 'echo', 'Hello world!'])
Then what if you want to run it on a Windows machine? You’ll have to adapt the code.
Then what if you migrate to another deployment tool, such as zc.buildout or Salt? You’ll have to change the code.
Even with Fabric, we had to write 3 distinct scripts to be able to face all situations. As developers, we’d like to write only one function, then pass it parameters:
- run on local machine or on remote client;
- run as current user, as admin/root, or maybe as another user.
That’s why xal were created: write portable high-level system scripts.
A framework¶
There are so many commands and so many systems... it would be impossible to support them all. And even if it was, it would be made of tons of code and dependencies. That’s the first reason why XAL is a framework:
- focus on the API;
- allow plugins;
- keep framework lightweight.
Fully configurable, no global states¶
XAL is not designed to use global states. XAL registry is entirely configurable: you can change providers’ mapping, configure providers or write custom ones.
However, for convenience, XAL offers some configuration helpers, so that common use cases are covered easily.
License¶
Copyright (c) 2012-2013, Benoît Bryon. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of xal nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Authors & contributors¶
- Benoît Bryon <benoit@marmelune.net>
Changelog¶
0.2 (unreleased)¶
- Nothing changed yet.
0.1 (2013-07-06)¶
- Feature #6 - Introduced proof of concept implementation of sh interface. Using a basic implementation for local session, based on subprocess.
- Feature #7 - Proof of concept implementation of dir interface, with a basic implementation for local session based on Python builtins (os, os.path, ...).
- Feature #8 - Introduced contextual execution architecture: session, registry, providers, resources, client.
Presentations¶
Here are some presentations (slides) about XAL.
XAL - execution abstration layer¶
Presentation of XAL proof-of-concept, by Benoit Bryon.
This work is licensed under a Creative Commons Attribution 3.0 Unported License (CC BY 3.0)
Python for sysadmins¶
Python is great:
- shell, scripts, provisioners, frameworks...
- runs on almost any system
But...
Develop to XAL session¶
Write a script which takes a XAL session as argument:
def write_greetings(session):
"""Write 'Hello world!' in 'greetings.txt' file relative to user's home."""
home = session.user.home
file_path = session.file.join(home, 'greetings.txt')
file_resource = session.file(file_path)
if not file_resource.exists():
file_resource.write('Hello world!')
Fabric¶
Use it in a fabfile:
from fabric.api import task
import write_greetings
import xal
@task
def hello_fabric():
session = xal.fabric(sudoer=True)
write_hello_world(session)
zc.buildout¶
In a buildout recipe:
import write_greetings
import xal
class HelloBuildout(object):
def __init__(self, buildout, name, options):
self.session = xal.buildout(buildout, name, options)
def install(self):
write_hello_world(self.session)
def update(self)
pass
Salt¶
As a salt module:
import write_greetings
import xal
def hello_salt():
session = xal.salt(__salt__)
write_greetings(session)
Shell¶
In an interactive shell:
import write_greetings
import xal
session = xal.local()
write_greeting(session)
Resources¶
XAL session is a proxy to resources:
- files, directories,
- users,
- processes,
- packages,
- your own customized resources...
XAL is a proof of concept¶
- https://github.com/benoitbryon/xal
- feedback is welcome!
- are popular projects interested in?
xal¶
This document contains contents of xal poster session at EuroPython 2013 in Florence, Italy.
- Poster as PDF: https://github.com/benoitbryon/xal/raw/master/docs/presentations/2013-europython/poster.pdf
- Sources: https://github.com/benoitbryon/xal/tree/master/docs/presentations/2013-europython
Abstract¶
XAL is a proof of concept designed to write high-level scripts you can reuse within various tools such as Fabric, zc.buildout, Salt…
Python has strong features for sysadmin scripts: portability, a shell and great libraries. But, currently, you develop using tool’s specific implementation. As a consequence, users of each project develop different tools that do similar things.
XAL proposes a new approach for system-related scripts: develop to a session. Write scripts that get a session as argument. That session is an abstraction layer for system resources such as files, users, packages… It is just like an ORM abstracts the database implementation. This design makes it possible to share sysadmin scripts that you can use within all those great Python tools related to system and deployment. Reduced cost of change and improved collaboration!
This poster will present the concepts of XAL. This project is a proof-of-concept, so the author will be glad to get feedback, discuss the ideas and, if you are interested in, sprint on it!
xal¶
- xal
- execution abstraction layer for high-level system scripts
Develop to a session¶
def greetings(session):
"""Write 'Hello world!' in 'greetings.txt' file relative to user's home."""
home = session.user.home
file_path = session.file.join(home, u'greetings.txt')
file_resource = session.file(file_path)
with file_resource.open('w'):
file_resource.write(u'Hello world!')
Run it anywhere¶
Shell¶
In an interactive shell:
>>> from europython import greetings
>>> import xal
>>> session = xal.LocalSession()
>>> greetings(session)
Fabric¶
In a fabfile:
from europython import greetings
import xal
def hello_fabric():
session = xal.FabricSession(sudoer=True)
greetings(session)
zc.buildout¶
In a buildout recipe:
from europython import greetings
import xal
class HelloBuildout(object):
def __init__(self, buildout, name, options):
self.session = xal.BuildoutSession(buildout, name, options)
def install(self):
greetings(self.session)
Salt¶
In a salt module:
from europython import greetings
import xal
def hello_salt():
session = xal.SaltSession(__salt__)
greetings(session)
Improve your workflow¶
- Do not wait for full provisioning stack, enter your project ASAP
- First focus on what your script does
- Then configure execution environment
- Build the provisioning stack incrementally
- Change provisioner, keep deployment recipes
Exit the subprocess labyrinth¶
- subprocess
- async_subprocess
- chut
- clom
- Command
- commandwrapper
- cpopen
- desub
- EasyProcess
- envoy
- execute
- extcmd
- extproc
- gevent_subprocess
- iterpipes
- pbs
- pipeline
- pyutilib.subprocess
- sarge
- seminode.utils.command
- sh
- shellout
- spur
- subwrap
- ... and more...
- ... and maybe yours
Could xal help APIs converge?
Challenges¶
xal needs strong APIs:
- run commands (subprocess wrapper)
- consistent set of resources (files, users...)
=> PEPs?
Efficient and comprehensive session registry
Preconfigured registries: fabric, salt, buildout, local session...
Resolution of session’s dependencies
Smart handling of NotImplementedError
Unit tests with mocks, integration tests
xal is a proof of concept¶
Credits, license¶
xal is released under BSD license © 2012-2013, Benoît Bryon
Contributor guide¶
This document provides guidelines for people who want to contribute to the project.
Create tickets¶
Please use the bugtracker [1] before starting some work:
- check if the bug or feature request has already been filed. It may have been answered too!
- else create a new ticket.
- if you plan to contribute, tell us, so that we are given an opportunity to give feedback as soon as possible.
- Then, in your commit messages, reference the ticket with some refs #TICKET-ID syntax.
Fork and branch¶
- Work in forks and branches.
- Prefix your branch with the ticket ID corresponding to the issue. As an example, if you are working on ticket #23 which is about contribute documentation, name your branch like 23-contribute-doc.
Setup a development environment¶
System requirements:
Python [2] version 2.6 or 2.7, available as python command.
Note
You may use Virtualenv [3] to make sure the active python is the right one.
make and wget to use the provided Makefile.
Execute:
git clone git@github.com/benoitbryon/xal.git
cd xal/
make develop
If you cannot execute the Makefile, read it and adapt the few commands it contains to your needs.
The Makefile¶
A Makefile is provided to ease development. Use it to:
- setup the development environment: make develop
- update it, as an example, after a pull: make update
- run tests: make test
- build documentation: make documentation
The Makefile is intended to be a live reference for the development environment.
Test and build¶
Use the Makefile.
References
[1] | https://github.com/benoitbryon/xal/issues |
[2] | https://www.python.org |
[3] | https://virtualenv.pypa.io/en/latest/ |