Skip to content

Instantly share code, notes, and snippets.

@dankrause
Last active January 3, 2016 11:49
Show Gist options
  • Save dankrause/8459115 to your computer and use it in GitHub Desktop.
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.
#!/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