├── LICENSE ├── README.md ├── app-address ├── env-list └── goreplay /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Pablo Aguiar 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tsuru-plugins 2 | 3 | This repo contains the following plugins: 4 | 5 | - [app-address](#app-address) - Display addresses of a given app 6 | - [env-list](#env-list) - List environment variables of a given app 7 | - [goreplay](#goreplay) - Sniff the network interface and replay requests on 8 | another app 9 | 10 | 11 | ## app-address 12 | 13 | Display all the addresses of a given app: 14 | 15 | ``` 16 | »»»» tsuru app-address -a foo-bar 17 | foo-bar.cloud.domain.com 18 | foo-bar.baz.domain.com 19 | foo-bar.baz.globo.com 20 | ``` 21 | 22 | For one address only: 23 | 24 | ``` 25 | »»»» tsuru app-address -a foo-bar -o 26 | foo-bar.baz.globo.com 27 | ``` 28 | 29 | Or, if you need the *IP* address: 30 | 31 | ``` 32 | »»»» tsuru app-address -a foo-bar -i 33 | foo-bar.cloud.domain.com 34 | ``` 35 | 36 | 37 | ## env-list 38 | 39 | List all the environment variables that were set (via `env-set`) for a given 40 | app: 41 | 42 | ``` 43 | »»»» tsuru env-list -a foo-bar 44 | BAR_ASYNC_TIMEOUT=359 45 | LOG_LEVEL=DEBUG 46 | MONGO_DATABASE=*** (private variable) 47 | MONGO_PASSWORD=*** (private variable) 48 | MONGO_USER=*** (private variable) 49 | NODE_ENV=production 50 | USE_PROXY=true 51 | ``` 52 | 53 | Display private variables too: 54 | 55 | ``` 56 | »»»» tsuru env-list -a foo-bar -p 57 | BAR_ASYNC_TIMEOUT=359 58 | LOG_LEVEL=DEBUG 59 | MONGO_DATABASE=my_database 60 | MONGO_PASSWORD=xptoxpto 61 | MONGO_USER=my_user 62 | NODE_ENV=production 63 | USE_PROXY=true 64 | ``` 65 | 66 | Or specific ones: 67 | 68 | ``` 69 | »»»» tsuru env-list -a foo-bar -p MONGO_DATABASE MONGO_PASSWORD MONGO_USER 70 | MONGO_DATABASE=my_database 71 | MONGO_PASSWORD=xptoxpto 72 | MONGO_USER=my_user 73 | ``` 74 | 75 | 76 | ## goreplay 77 | 78 | Sniff the network interface and replay requests on another app. 79 | 80 | Display installation instructions: 81 | 82 | ``` 83 | »»»» tsuru goreplay install 84 | To install, add the following to your tsuru.yaml and deploy your app: 85 | hooks: 86 | build: 87 | - 'sudo apt-get -y install screen' 88 | - 'curl -sfSL -o goreplay.tar.gz https://github.com/buger/goreplay/releases/download/v0.16.1/gor_0.16.1_x64.tar.gz' 89 | - 'tar xvf goreplay.tar.gz' 90 | ``` 91 | 92 | Replay **ALL** (!CAUTION!) requests received by app `foo-bar-prod` on another 93 | app (e.g. foo-bar-qa): 94 | 95 | ``` 96 | »»»» tsuru goreplay start -a foo-bar-prod '--output-http http://foo-bar-qa.domain.com' 97 | Starting... 98 | Done! 99 | ``` 100 | 101 | Replay 5% of requests: 102 | 103 | ``` 104 | »»»» tsuru goreplay start -a foo-bar-prod '--output-http "http://foo-bar-qa.domain.com|5%"' 105 | Starting... 106 | Done! 107 | ``` 108 | 109 | Replay 5% of `GET` requests: 110 | 111 | ``` 112 | »»»» tsuru goreplay start -a foo-bar-prod '--output-http "http://foo-bar-qa.domain.com|5%" --http-allow-method GET' 113 | Starting... 114 | Done! 115 | ``` 116 | 117 | Replay 5% of `GET` requests on `/foo/bar`: 118 | 119 | ``` 120 | »»»» tsuru goreplay start -a foo-bar-prod '--output-http "http://foo-bar-qa.domain.com|5%" --http-allow-method GET --http-allow-url /foo/bar' 121 | Starting... 122 | Done! 123 | ``` 124 | 125 | Once you're done, stop it: 126 | 127 | ``` 128 | »»»» tsuru goreplay stop -a foo-bar-prod 129 | Stopping... 130 | Done! 131 | ``` 132 | 133 | For GoReplay options: 134 | 135 | ``` 136 | »»»» tsuru goreplay options -a foo-bar-prod 137 | Gor is a simple http traffic replication tool written in Go. Its main goal is to replay traffic from production servers to staging and dev environments. 138 | Project page: https://github.com/buger/gor 139 | Author: leonsbox@gmail.com 140 | Current Version: 0.16.1 141 | 142 | -cpuprofile string 143 | write cpu profile to file 144 | -debug verbose 145 | Turn on debug output, shows all intercepted traffic. Works only when with verbose flag 146 | -exit-after duration 147 | exit after specified duration 148 | -http-allow-header value 149 | A regexp to match a specific header against. Requests with non-matching headers will be dropped: 150 | gor --input-raw :8080 --output-http staging.com --http-allow-header api-version:^v1 (default []) 151 | -http-allow-method value 152 | Whitelist of HTTP methods to replay. Anything else will be dropped: 153 | gor --input-raw :8080 --output-http staging.com --http-allow-method GET --http-allow-method OPTIONS (default []) 154 | -http-allow-url value 155 | A regexp to match requests against. Filter get matched against full url with domain. Anything else will be dropped: 156 | gor --input-raw :8080 --output-http staging.com --http-allow-url ^www. (default []) 157 | 158 | [snip] 159 | ``` 160 | 161 | Check GoReplay docs on https://github.com/buger/goreplay/wiki 162 | -------------------------------------------------------------------------------- /app-address: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2016 Pablo Santiago Blum de Aguiar . All rights 4 | # reserved. Use of this source code is governed by the BSD 2-Clause License that 5 | # can be found in the LICENSE file. 6 | 7 | import argparse 8 | import json 9 | import os 10 | import sys 11 | 12 | try: 13 | from urllib import request 14 | except ImportError: 15 | import urllib2 as request 16 | 17 | 18 | def fatal(msg): 19 | sys.stderr.write(msg + '\n') 20 | sys.exit(5) 21 | 22 | 23 | def get_env(name): 24 | env = os.environ.get(name) 25 | if not env: 26 | fatal('ERROR: missing {}'.format(name)) 27 | return env 28 | 29 | 30 | class Request(request.Request): 31 | 32 | def __init__(self, method, *args, **kwargs): 33 | self._method = method 34 | request.Request.__init__(self, *args, **kwargs) 35 | 36 | def get_method(self): 37 | return self._method 38 | 39 | 40 | def app_request(app): 41 | target = get_env('TSURU_TARGET').rstrip('/') 42 | token = get_env('TSURU_TOKEN') 43 | 44 | if not target.startswith('http://') and not target.startswith('https://'): 45 | target = 'http://{}'.format(target) 46 | 47 | url = '{}/apps/{}'.format(target, app) 48 | 49 | req = Request('GET', url) 50 | req.add_header('Authorization', 'bearer ' + token) 51 | 52 | headers = { 53 | 'Content-Type': 'application/json', 54 | 'Accept': 'text/plain' 55 | } 56 | if headers: 57 | for header, value in headers.items(): 58 | req.add_header(header, value) 59 | 60 | try: 61 | return request.urlopen(req, timeout=30) 62 | except Exception as e: 63 | fatal('{}'.format(e)) 64 | 65 | 66 | def get_app(app): 67 | resp = app_request(app) 68 | return json.loads(resp.read().decode('utf-8')) 69 | 70 | 71 | def parse_args(): 72 | parser = argparse.ArgumentParser(description="Show an App's address(es)") 73 | parser.add_argument( 74 | '-a', '--app', 75 | required=True, 76 | help='The name of the App', 77 | ) 78 | parser.add_argument( 79 | '-o', '--one', 80 | action='store_true', 81 | help='Show only one URL', 82 | ) 83 | parser.add_argument( 84 | '-i', '--ip', 85 | action='store_true', 86 | help='Show the "IP" URL', 87 | ) 88 | return parser.parse_args() 89 | 90 | 91 | def show_address(address, one): 92 | if type(address) is not list: 93 | address = [address] 94 | if one: 95 | print(address[-1]) 96 | else: 97 | print('\n'.join(address)) 98 | 99 | 100 | def main(): 101 | args = parse_args() 102 | app = get_app(args.app) 103 | address = app.get('cname', []) 104 | if not address or args.ip: 105 | address = app.get('ip') 106 | else: 107 | address.insert(0, app.get('ip')) 108 | show_address(address, args.one) 109 | 110 | 111 | if __name__ == '__main__': 112 | main() 113 | -------------------------------------------------------------------------------- /env-list: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | # Copyright 2016 Pablo Santiago Blum de Aguiar . All rights 5 | # reserved. Use of this source code is governed by the BSD 2-Clause License that 6 | # can be found in the LICENSE file. 7 | 8 | import argparse 9 | import json 10 | import logging 11 | import os 12 | import sys 13 | 14 | try: 15 | from urllib import request 16 | except ImportError: 17 | import urllib2 as request 18 | 19 | 20 | def set_logging_level(level): 21 | if level > 3: 22 | level = 3 23 | handler = request.HTTPHandler(debuglevel=level) 24 | request.install_opener(request.build_opener(handler)) 25 | log_level = ['ERROR', 'WARNING', 'INFO', 'DEBUG'][level] 26 | logging.basicConfig( 27 | level=getattr(logging, log_level), format='%(levelname)-8s %(message)s', 28 | ) 29 | 30 | 31 | def fatal(msg): 32 | logging.error(msg) 33 | sys.exit(5) 34 | 35 | 36 | def get_env(name): 37 | env = os.environ.get(name) 38 | if not env: 39 | fatal('ERROR: missing {}'.format(name)) 40 | return env 41 | 42 | 43 | class Request(request.Request): 44 | 45 | def __init__(self, method, *args, **kwargs): 46 | self._method = method 47 | request.Request.__init__(self, *args, **kwargs) 48 | 49 | def get_method(self): 50 | return self._method 51 | 52 | 53 | def env_request(app, env=None): 54 | target = get_env('TSURU_TARGET').rstrip('/') 55 | token = get_env('TSURU_TOKEN') 56 | 57 | if not target.startswith('http://') and not target.startswith('https://'): 58 | target = 'http://{}'.format(target) 59 | 60 | if env: 61 | env = '?' + '&'.join(['env=' + e for e in env]) 62 | 63 | url = '{}/apps/{}/env{}'.format(target, app, env or '') 64 | 65 | req = Request('GET', url) 66 | req.add_header('Authorization', 'bearer ' + token) 67 | 68 | headers = { 69 | 'Content-Type': 'application/json', 70 | 'Accept': 'application/json' 71 | } 72 | if headers: 73 | for header, value in headers.items(): 74 | req.add_header(header, value) 75 | 76 | try: 77 | return request.urlopen(req, timeout=30) 78 | except Exception as e: 79 | fatal('{}'.format(e)) 80 | 81 | 82 | def get_app_vars(app, env): 83 | resp = env_request(app, env) 84 | return json.loads(resp.read().decode('utf-8')) 85 | 86 | 87 | def parse_args(): 88 | parser = argparse.ArgumentParser( 89 | description="List an App's environment variables" 90 | ) 91 | parser.add_argument( 92 | '-a', '--app', 93 | required=True, 94 | help='the name of the App', 95 | ) 96 | parser.add_argument( 97 | '-p', '--private', 98 | action='store_true', 99 | help='list private variables', 100 | ) 101 | parser.add_argument( 102 | '-v', '--verbose', 103 | action='count', 104 | default=0, 105 | help='Logging level: v=warning, vv=info, vvv=debug', 106 | ) 107 | parser.add_argument( 108 | 'env', 109 | nargs='*', 110 | help='environment variables to list', 111 | ) 112 | return parser.parse_args() 113 | 114 | 115 | def show_var(env, private=False): 116 | if not env: 117 | return 118 | for var in sorted(env, key=lambda e: e['name']): 119 | if private or var['public']: 120 | print('{name}={value}'.format(**var)) 121 | else: 122 | print('{name}=*** (private variable)'.format(**var)) 123 | 124 | 125 | def main(): 126 | args = parse_args() 127 | set_logging_level(args.verbose) 128 | env = get_app_vars(args.app, args.env) 129 | show_var(env, args.private) 130 | 131 | 132 | if __name__ == '__main__': 133 | main() 134 | -------------------------------------------------------------------------------- /goreplay: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GOREPLAY_VERSION="0.16.1" 4 | GOREPLAY_URL="https://github.com/buger/goreplay/releases/download/v${GOREPLAY_VERSION}/gor_${GOREPLAY_VERSION}_x64.tar.gz" 5 | 6 | case "$1" in 7 | 8 | "install") echo "To install, add the following to your tsuru.yaml and deploy your app:" 9 | cat << EOF 10 | hooks: 11 | build: 12 | - 'sudo apt-get -y install screen' 13 | - 'curl -sfSL -o goreplay.tar.gz $GOREPLAY_URL' 14 | - 'tar xvf goreplay.tar.gz' 15 | EOF 16 | ;; 17 | 18 | "start") echo "Starting..." 19 | tsuru app-run ${@:2:2} 'sudo pkill goreplay || exit 0' 20 | tsuru app-run ${@:2:2} "sudo screen -d -m ./goreplay --input-raw :\$PORT ${@:4}" \ 21 | && echo "Done!" || { \ 22 | echo -e "\nTry placing GoReplay options inside single quotes ;-)" \ 23 | && exit 1; } 24 | ;; 25 | 26 | "stop") echo "Stopping..." 27 | tsuru app-run ${@:2:2} 'sudo pkill goreplay || exit 0' 28 | echo "Done!" 29 | ;; 30 | 31 | "options")tsuru app-run ${@:2:2} './goreplay --help || exit 0' 32 | ;; 33 | 34 | "version") echo "goreplay plugin 0.1 using GoReplay $GOREPLAY_VERSION" 35 | ;; 36 | 37 | *) cat << EOF 38 | GoReplay is the simplest and safest way to test your app using real traffic before you put it into production. Check out https://goreplay.org/ for more information. 39 | 40 | Usage: 41 | 42 | goreplay install 43 | goreplay start -a 44 | goreplay stop -a 45 | goreplay options -a 46 | goreplay version 47 | 48 | The commands are: 49 | 50 | install Show instructions on installing GoReplay on your App 51 | start Start goreplay on app_name 52 | stop Stop any goreplay proccess running on app_name 53 | options Show GoReplay options from app_name. App is needed to run the binary 54 | version Show plugin and GoReplay versions 55 | 56 | Report bugs to: https://github.com/scorphus/tsuru-plugins 57 | EOF 58 | ;; 59 | 60 | esac 61 | --------------------------------------------------------------------------------