Created
April 3, 2012 09:50
-
-
Save yaniv-aknin/2290744 to your computer and use it in GitHub Desktop.
muroku: small utility to aid syncing staging/testing/live heroku environments
This file contains 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 | |
# Written by @aknin | |
# https://gist.github.com/gists/2290744 | |
# This code has been placed in the public domain, no strings attached either way. | |
from __future__ import print_function | |
import platform | |
import os | |
import sys | |
import argparse | |
import json | |
from clint.textui import colored | |
import heroku | |
global options | |
class MurokuAbort(Exception): | |
pass | |
def parse_arguments(argv): | |
parser = argparse.ArgumentParser() | |
parser.add_argument('-u', '--username', help="Heroku username to use") | |
parser.add_argument('-p', '--password', help="Heroku password to use") | |
parser.add_argument('-k', '--key', help="Heroku API key to use") | |
parser.add_argument('--key-file', help="JSON file containing API keys to use") | |
parser.add_argument('--key-name', help="Name of key to use when loading key file", default="") | |
parser.add_argument('--ignore-prefixes', help="Variable prefixes to skip when comparing apps", nargs="+", | |
default=('PATH', 'PYTHONUNBUFFERED', 'LD_LIBRARY_PATH', 'LIBRARY_PATH', | |
'HEROKU_SHARED_POSTGRESQL_')) | |
parser.add_argument('app1') | |
parser.add_argument('app2') | |
options = parser.parse_args(argv[1:]) | |
if options.key_file: | |
if not os.path.exists(options.key_file): | |
parser.error("key file %s does not exist" % (options.key_file,)) | |
else: | |
options.key_file = os.path.expanduser("~/.heroku/keys.json") | |
if not options.key_name: | |
with os.popen("git config --get heroku.account 2> /dev/null") as handle: | |
options.key_name = handle.read().strip() | |
return options | |
def get_heroku_handle(): | |
if options.username and options.password: | |
return heroku.from_pass(options.username, options.password) | |
if options.key: | |
return heroku.from_key(options.key) | |
if os.path.exists(options.key_file): | |
with open(options.key_file) as handle: | |
keys = json.load(handle) | |
return heroku.from_key(keys[options.key_name]) | |
raise MurokuAbort("no credentials provided (see --help for help)") | |
def get_app_configs(handle): | |
try: | |
return handle.apps[options.app1].config, handle.apps[options.app2].config | |
except KeyError, error: | |
raise MurokuAbort("no such app %s" % (error,)) | |
def yield_keys(set1, set2, operation_name): | |
for key in getattr(set1, operation_name)(set2): | |
for prefix in options.ignore_prefixes: | |
if key.startswith(prefix): | |
break | |
else: | |
yield key | |
def report_missing_keys(this, that, colors): | |
for key in sorted(yield_keys(set(this.data), set(that.data), 'difference')): | |
print(colored.yellow(key), "is missing in", getattr(colored, colors[that.app.name])(that.app.name)) | |
def report_changed_keys(this, that, colors): | |
this_color = getattr(colored, colors[this.app.name]) | |
that_color = getattr(colored, colors[that.app.name]) | |
for key in sorted(yield_keys(set(this.data), set(that.data), 'intersection')): | |
if this.data[key] == that.data[key]: | |
continue | |
print(colored.yellow(key), 'is', this_color(this.data[key] or "''"), 'in', this_color(this.app.name)) | |
print(" " * len(key), 'is', that_color(that.data[key] or "''"), 'in', that_color(that.app.name)) | |
def main(): | |
handle = get_heroku_handle() | |
config1, config2 = get_app_configs(handle) | |
colors = {config1.app.name: "blue", config2.app.name: "green"} | |
report_missing_keys(config1, config2, colors) | |
report_missing_keys(config2, config1, colors) | |
report_changed_keys(config1, config2, colors) | |
if __name__ == '__main__': | |
try: | |
if platform.system() == 'Windows': | |
raise MurokuAbort("contact the author for paid development of Windows support") # :) | |
options = parse_arguments(sys.argv) | |
sys.exit(main() or 0) | |
except MurokuAbort, error: | |
print(colored.red("%s: %s" % (os.path.basename(sys.argv[0]), error))) | |
sys.exit(1) |
This file contains 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
certifi==0.0.8 | |
chardet==1.0.1 | |
clint==0.3.1 | |
heroku==0.1.2 | |
python-dateutil==1.5 | |
requests==0.11.1 | |
wsgiref==0.1.2 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment