Last active
January 3, 2016 11:49
-
-
Save dankrause/8459115 to your computer and use it in GitHub Desktop.
Run commands with sets of preconfigured environment variables. Mimics supernova (https://github.com/major/supernova) but not tied to Nova (or Openstack) in any way.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
"""supercmd | |
Usage: | |
supercmd [-c <file>] list | |
supercmd [-c <file>] show <environment> [<command>] | |
supercmd [-c <file>] keyring [set|get] <environment> <parameter> | |
supercmd [-c <file>] <environment> <command> [<args>...] | |
supercmd -h | --help | |
Options: | |
-h --help Show this screen. | |
-c --config <file> Specify a config file [default: ~/.supercmd] | |
Example Config: | |
[all] | |
USERNAME=myuser | |
API_VERSION=stable | |
[dev] | |
USERNAME=devuser | |
API_VERSION=latest | |
API_URL=https://dev.example.com/api/ | |
API_KEY=USE_KEYRING | |
[staging] | |
API_URL=https://staging.example.com/api/ | |
API_KEY=USE_KEYRING | |
[prod] | |
API_URL=https://example.com/api/ | |
API_KEY=USE_KEYRING['production-key'] | |
[int] | |
API_URL=https://internal-api.example.com/ | |
API_KEY=USE_KEYRING['production-key'] | |
downloader,uploader|DOWNLOAD_PATH=~/Downloads | |
Example Commands: | |
This calls "apiclient --foo bar" with the "dev" environment. | |
An environment-specific API_KEY value is fetched from the OS keyring. | |
supercmd dev apiclient --foo bar | |
This calls "apiclient --foo bar" with both "all" and "prod" environments. | |
Since "prod" is listed last in our command, its values will override any | |
values found in the "all" environment. We're now using a shared keyring | |
value, rather than an evironment-specific one. | |
supercmd all,prod apiclient --foo bar | |
This calls "apiclient --foo bar" with both "all" and "int" environments. | |
Note that in this case, DOWNLOAD_PATH is not set, because the command does | |
not match either "downloader" or "uploader". Also note that the exact same | |
value is used for API_KEY as the previous command. | |
supercmd all,int apiclient --foo bar | |
This calls "downloader --foo bar" with both "all" and "int" environments. | |
Since our command is named "downloader", we now match the DOWNLOAD_PATH | |
environment variable, which is set for this command. | |
supercmd all,int downloder --foo bar | |
""" | |
import ConfigParser | |
import getpass | |
import keyring | |
import os | |
import subprocess | |
import sys | |
import docopt | |
class SupercmdException(Exception): | |
pass | |
def get_conf(filename): | |
c = ConfigParser.ConfigParser() | |
c.read(os.path.expanduser(filename)) | |
conf = {} | |
for section in c.sections(): | |
conf[section] = dict([(k.upper(), v) for k, v in c.items(section)]) | |
return conf | |
def get_env(conf, env_string, cmd=None): | |
full_environment = {} | |
for env in env_string.split(','): | |
if env not in conf: | |
raise SupercmdException('No environment named "{}"'.format(env)) | |
env_values = conf[env] | |
for key, value in env_values.items(): | |
if '|' in key: | |
commands, key_name = key.split('|', 1) | |
commands = commands.split(',') | |
del(env_values[key]) | |
if cmd is None or cmd.upper() not in commands: | |
continue | |
key = key_name | |
env_values[key] = value | |
if value.startswith('USE_KEYRING'): | |
env_name = env | |
param_name = key | |
if value.startswith("USE_KEYRING['") and value.endswith("']"): | |
param_name = value[13:-2] | |
env_name = 'global' | |
env_values[key] = get_key(env_name, param_name) | |
full_environment.update(env_values) | |
return full_environment | |
def display_env(name, values): | |
output = [] | |
output.append('--- {:-<76}'.format(name + ' ')) | |
for key, val in values.items(): | |
output.append(' {:20} : {}'.format(key, val)) | |
return output | |
def get_key(env, param): | |
user = '{}:{}'.format(env, param) | |
result = keyring.get_password('supercmd', user) | |
if result is None: | |
message = 'Error getting keyring password: {} @ supercmd'.format(user) | |
raise SupercmdException(message) | |
return result.encode('ascii') | |
def set_key(env, param, key): | |
user = '{}:{}'.format(env, param) | |
try: | |
keyring.set_password('supercmd', user, key) | |
except keyring.backend.PasswordSetError: | |
raise SupercmdException('Error while setting password in keyring') | |
def run_cmd(conf, env, cmd, args): | |
full_cmd = [cmd] | |
full_cmd.extend(args) | |
full_env = os.environ.copy() | |
full_env.update(get_env(conf, env, cmd)) | |
try: | |
process = subprocess.Popen(full_cmd, stdout=sys.stdout, | |
stderr=sys.stderr, env=full_env) | |
except OSError: | |
message = 'Error while running command: {}'.format(' '.join(full_cmd)) | |
raise SupercmdException(message) | |
return process.wait() | |
def main(): | |
arguments = docopt.docopt(__doc__, options_first=True) | |
env = arguments['<environment>'] | |
param = arguments['<parameter>'] | |
cmd = arguments['<command>'] | |
args = arguments['<args>'] | |
list_env = arguments['list'] | |
show_env = arguments['show'] | |
use_keyring = arguments['keyring'] | |
set_keyring = arguments['set'] | |
conf = get_conf(arguments['--config']) | |
if use_keyring: | |
if env not in conf and env != 'global': | |
raise SupercmdException('No environment named "{}"'.format(env)) | |
if env != 'global' and param not in conf[env]: | |
msg = 'Parameter "{}" not in environment "{}"'.format(param, env) | |
raise SupercmdException(msg) | |
if set_keyring: | |
key = getpass.getpass('Key for {} in {}: '.format(param, env)) | |
set_key(env, param, key) | |
return 'Key set' | |
else: | |
return get_key(env, param) | |
elif list_env: | |
output = [] | |
for env_name, env_values in conf.items(): | |
output.extend(display_env(env_name, env_values)) | |
return '\n'.join(output) | |
elif show_env: | |
values = get_env(conf, env, cmd) | |
return '\n'.join(display_env(env, values)) | |
else: | |
run_cmd(conf, env, cmd, args) | |
return '' | |
if __name__ == '__main__': | |
try: | |
print main() | |
sys.exit(0) | |
except SupercmdException as e: | |
print e.message | |
sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment