예제 #1
0
파일: util.py 프로젝트: KevinPoole/monarch
def bosh_login(*, user=None, pswd=None, cacert=None, env=None):
    """
    Login to the bosh CLI. All parameters default to config values if not specified.
    :param user: Bosh username.
    :param pswd: Bosh password.
    :param cacert: Bosh client cert.
    :param env: Bosh environment to login to.
    :return: int, str, str; Returncode, stdout, stderr.
    """
    cfg = Config()['bosh']
    certfile = Certfile(cacert=cacert)

    creds = cfg.get('credentials')
    if not creds:
        assert user and pswd, "Must specify username and password!"
    else:
        user = user or creds['user']
        pswd = pswd or creds['pswd']

    cmd = [cfg['cmd'], '-e', env or cfg['env']]
    stdin = [user, pswd]
    if certfile:
        cmd.extend(['--ca-cert=' + certfile.name])
    cmd.append('login')
    return run_cmd(cmd, stdin)
예제 #2
0
파일: util.py 프로젝트: KevinPoole/monarch
def cf_login(*, user=None, pswd=None, api=None, skip_ssl_validation=None):
    """
    Login to the CF CLI. All parameters default to config values if not specified.
    :param user: str; Cf username.
    :param pswd: str; Cf password.
    :param api: str; Cf api url.
    :param skip_ssl_validation: bool; Whether cf ssl validation should be skipped, useful if certs are not installed.
    :return: int, str, str; Returncode, stdout, stderr.
    """
    cfg = Config()['cf']
    creds = cfg.get('credentials')
    if not creds:
        assert user and pswd and api, "Must specify username, password, and API address!"
    else:
        user = user or creds['user']
        pswd = pswd or creds['pswd']
        if skip_ssl_validation is None:
            skip_ssl_validation = creds.get('skip-ssl-validation') or False
    pswd = '"{}"'.format(pswd.replace('"', r'\"'))
    api = api or creds['api']
    cmd = [cfg['cmd'], 'login']
    if skip_ssl_validation:
        cmd.append('--skip-ssl-validation')
    cmd.extend(['-a', api, '-u', user, '-p', pswd])
    return run_cmd(cmd, stdin='\n')
예제 #3
0
def find_application_routes(app_guid):
    """
    Find the url routes of an application.
    :param app_guid: The application GUID for which to find routes.
    :return List[str]: The app's route(s).
    """

    cfg = Config()
    routes = []

    rcode, stdout, _ = util.run_cmd(
        [cfg['cf']['cmd'], 'curl', '/v2/apps/{}/routes'.format(app_guid)])
    if rcode:
        sys.exit("Failure to call cf curl!")

    resources = map(lambda v: v['entity'],
                    util.extract_json(stdout)[0]['resources'])
    for resource in resources:
        host = resource['host']
        path = resource['path']
        rcode, stdout, _ = util.run_cmd(
            [cfg['cf']['cmd'], 'curl', resource['domain_url']])
        if rcode:
            sys.exit("Failure to call cf curl!")
        domain = util.extract_json(stdout)[0]['entity']['name']
        route = '{}.{}'.format(host, domain)
        if path and path != '':
            route += '/{}'.format(path)
        routes.append(route)
    return routes
예제 #4
0
파일: util.py 프로젝트: KevinPoole/monarch
def bosh_cli(args, stdin=None, env=None, dep=None, suppress_output=False):
    """
    Call the bosh CLI.
    :param args: Union[List[str], str]; Arguments for the bosh CLI. This will allow chaining additional commands
    with '&&', '|', etc.
    :param env: str; The bosh environment to use. Defaults to config value.
    :param dep: str; The bosh deployment to use. Defaults to configured cf deployment value.
    :param stdin: Optional[Union[str, List[Union[str, List[str]]]]]; Input to pipe to the program.
    :param suppress_output: bool; If true, no extra debug output will be printed when an error occurs.
    :return: int, str, str; Returncode, stdout, stderr.
    """
    boshcfg = Config()['bosh']
    certfile = Certfile()
    cmd = [
        boshcfg['cmd'], '-e', env or boshcfg['env'], '-d', dep
        or boshcfg['cf-dep']
    ]
    if certfile:
        cmd.extend(['--ca-cert=' + certfile.name])
    if isinstance(args, list):
        cmd.extend(args)
    else:
        cmd.append(args)
    res = run_cmd(cmd, stdin=stdin, suppress_output=suppress_output)
    return res
예제 #5
0
def url():
    cfg = Config()
    depcfg = cfg['testing']

    push_app = bool(depcfg.get('push-app'))

    if push_app:  # got to push an app before we get the URL
        start_dir = os.getcwd()
        with TemporaryDirectory() as td:
            print("Downloading Spring Music")
            assert_cmd(['git clone https://github.com/cloudfoundry-samples/spring-music.git', td], timeout=60*2)
            os.chdir(td)
            assert_cmd('git checkout 963d307f9c0da020545d13c947ad6d5472e29c94')

            print("Building Spring Music")
            assert_cmd('./gradlew clean assemble', timeout=60*5)

            print("Deploying Spring Music...")
            assert_cmd([cfg['cf']['cmd'], 'create-service', depcfg['db-market-name'], depcfg['db-plan'],
                        depcfg['db-instance-name']])
            assert_cmd([cfg['cf']['cmd'], 'push', depcfg['appname']], timeout=60*5)
            assert_cmd([cfg['cf']['cmd'], 'bind-service', depcfg['appname'], depcfg['db-instance-name']])
            assert_cmd([cfg['cf']['cmd'], 'restage', depcfg['appname']], timeout=60*5)
            os.chdir(start_dir)

    routes = find_application_routes(find_application_guid(depcfg['appname']))
    url = routes[0]
    yield url

    if push_app:
        print("Tearing down Spring Music...")
        assert_cmd([cfg['cf']['cmd'], 'delete -f -r', depcfg['appname']])
        assert_cmd([cfg['cf']['cmd'], 'delete-service -f', depcfg['db-instance-name']])
예제 #6
0
def run_ctk(func, cfg, org, space, appname, msg=None):
    """
    This is a helper function to reduce code duplication when called by Chaos Toolkit.
    :param func: Fn[app] -> Optional[any]; A function which performs some actions using an app object.
    :param cfg: Dict[String, any]; Configuration information about the environment.
    :param org: String; Cloud Foundry organization containing the application.
    :param space: String; Cloud Foundry space containing the application.
    :param appname: String; Application in Cloud Foundry which is to be targeted.
    :param msg: Optional[String]; A message to display at the beginning of operations.
    :return: Dict[String, Any]; The serialized App object after all operations were performed.
    """
    if cfg:
        Config().load_dict(cfg)
    if msg:
        logger.info(msg)

    try:
        app = App.discover(org, space, appname)
        if not app:
            raise FailedActivity("Error discovering app!")
        result = func(app)
    except SystemExit as err:
        logger.exception(err)
        raise FailedActivity(err)

    if result:
        raise FailedActivity(
            "Error performing operation! Function returned {}.".format(result))

    logger.info("Done!")
    return app.serialize()
예제 #7
0
파일: util.py 프로젝트: KevinPoole/monarch
    def __init__(self, cacert=None):
        """
        Get the certfile from the config if it exists, otherwise just init to None.
        :param cacert: str; Cert override (instead of reading config).
        """
        self._crtfile = None
        self.name = None

        if not cacert:
            creds = Config()['bosh'].get('credentials')
            if creds:
                cacert = creds.get('cacert')
        if cacert:
            self._crtfile = NamedTemporaryFile(mode='w')
            self._crtfile.write(cacert)
            self._crtfile.flush()
            self.name = self._crtfile.name
예제 #8
0
파일: util.py 프로젝트: KevinPoole/monarch
def cf_target(org, space):
    """
    Target a specific organization and space using the cloud foundry CLI. This should be run before anything which calls
    out to cloud foundry. This will fail if cloud foundry is not logged in.
    :param org: The organization to target.
    :param space: The space within the organization to target.
    :return: The returncode of the cloud foundry CLI.
    """
    cfg = Config()
    rcode, _, _ = run_cmd([cfg['cf']['cmd'], 'target', '-o', org, '-s', space])
    return rcode
예제 #9
0
파일: util.py 프로젝트: KevinPoole/monarch
def run_cmd_on_container(dcid, contid, cmd, suppress_output=False):
    """
    Run one or more commands in the shell on a container on a diego cell.
    :param dcid: str; Diego-cell ID of the Diego Cell running the container.
    :param contid: str; Container ID of the container which is to be connected to.
    :param cmd: Union[str, List[str]]; Command(s) to run on the container.
    :param suppress_output: bool; If true, no extra debug output will be printed when an error occurs.
    :return: int, str, str; Returncode, stdout, stderr.
    """
    cfg = Config()
    use_containerd = cfg.get("use-containerd") is not None
    if use_containerd:
        # Refer to: https://devops.stackexchange.com/a/13781/27344
        shell_cmd = f'exec sudo /var/vcap/packages/containerd/bin/ctr -a /var/vcap/sys/run/containerd/containerd.sock -n garden tasks exec --exec-id my-shell --tty {contid} /bin/bash'
    else:
        shell_cmd = f'exec sudo /var/vcap/packages/runc/bin/runc exec -t {contid} /bin/bash'
    if isinstance(cmd, list):
        cmd.insert(0, shell_cmd)
    else:
        cmd = [shell_cmd, cmd]
    return run_cmd_on_diego_cell(dcid, cmd, suppress_output=suppress_output)
예제 #10
0
def get_apps():
    """
    Get the apps deployed in the cloud foundry cluster.
    :return: Example app object:
    ```
    {
        'app_guid': '57c6b9cc-7e48-4178-a8fc-a312afcdb42a'
        'process_guid': '57c6b9cc-7e48-4178-a8fc-a312afcdb42a-3d4c0341-31f1-49b3-8a49-41e100122fd6',
        'index': 0,
        'domain': 'cf-apps',
        'instance_guid': 'b39b4d2a-f36d-4aaf-42a9-0d1f',
        'cell_id': '373e05d9-270f-4358-935e-7af712078c4a',
        'address': '10.94.173.17',
        'ports': [
            {
                'container_port': 8080,
                'host_port': 61028,
                'container_tls_proxy_port': 61001,
                'host_tls_proxy_port': 61094
            },
            {
                'container_port': 2222,
                'host_port': 61029,
                'container_tls_proxy_port': 61002,
                'host_tls_proxy_port': 61095
            }
        ],
        'instance_address': '198.19.84.194',
        'crash_count': 0,
        'state': 'RUNNING',
        'since': 1551811902829311393,
        'modification_tag': {
            'epoch': '522dc0a9-88f9-4314-7ceb-5ee8a6402f85',
            'index': 2
        }
    }
    ```
    """
    cfg = Config()
    rcode, stdout, _ = monarch.pcf.util.run_cmd_on_diego_cell(
        cfg['bosh']['cfdot-dc'],
        ['source /etc/profile.d/cfdot.sh', 'cfdot actual-lrp-groups'])
    if rcode:
        logger.error("Failed retrieving actual LRP grups from %s",
                     cfg['bosh']['cfdot-dc'])
        return None
    apps = []
    for app in util.extract_json(stdout):
        app = app['instance']
        app['app_guid'] = app['process_guid'][:36]
        apps.append(app)
    return apps
예제 #11
0
 def add_services_from_cfg(self):
     """
     Read any custom services defined in the config and add them.
     :return: Dict[String, Service]; The list of all services bound to this application.
     """
     cfg = Config()
     if 'services' not in cfg:
         return self.services
     for service in cfg['services']:
         self.add_custom_service(service['name'], service['host'],
                                 [tuple(i) for i in service['ports']],
                                 service['user'], service['password'])
     return self.services
예제 #12
0
    def block_services(self, services=None, direction='egress'):
        """
        Block this application from accessing its services on all its known hosts.
        :param services: List[String]; List of service names to block, will target all if unset.
        :param direction: str; Traffic direction to block.
        :return: int; A returncode if any of the bosh ssh instances do not return 0.
        """
        cfg = Config()
        direction = util.parse_direction(direction)
        assert direction, "Could not parse direction!"

        for app_instance in self.instances:
            cmds = []
            for service in self.services:
                if service['type'] in cfg['service-whitelist']:
                    continue
                if services and service['name'] not in services:
                    continue
                logger.info("Blocking %s for %s:%s", service['name'],
                            app_instance['diego_id'], app_instance['cont_ip'])
                for (sip, protocol, port) in service['hosts']:
                    if direction in {'egress', 'both'}:
                        cmd = [
                            'sudo', 'iptables', '-I', 'FORWARD', '1', '-s',
                            app_instance['cont_ip'], '-d', sip, '-p', protocol
                        ]
                        if port != 'all':
                            cmd.extend(['--dport', port])
                        cmd.extend(['-j', 'DROP'])
                        cmds.append(' '.join(map(str, cmd)))

                    if direction in {'ingress', 'both'}:
                        cmd = [
                            'sudo', 'iptables', '-I', 'FORWARD', '1', '-d',
                            app_instance['cont_ip'], '-s', sip, '-p', protocol
                        ]
                        if port != 'all':
                            cmd.extend(['--sport', port])
                        cmd.extend(['-j', 'DROP'])
                        cmds.append(' '.join(map(str, cmd)))

            if not cmds:
                continue
            rcode, _, _ = app_instance.run_cmd_on_diego_cell(cmds)
            if rcode:
                logger.error("Received return code %d from iptables call.",
                             rcode)
                self.unblock_services(services=services)
                return rcode
        return 0
예제 #13
0
def find_application_guid(appname):
    """
    Find the GUID of an application using cloud foundry's CLI interface. The GUID acts as a unique identifier for
    the application which we can then use to find what containers are running it.
    :param appname: String; The name of the app to deserialize.
    :return: String; The application GUID.
    """
    assert appname
    cfg = Config()
    cmd = '{} app {} --guid'.format(cfg['cf']['cmd'], appname)
    rcode, stdout, _ = util.run_cmd(cmd)
    guid = stdout.splitlines()[0]
    if rcode:
        sys.exit(
            "Failed retrieving the GUID for the specified app. Make sure {} is in this space!"
            .format(appname))

    logger.debug(guid)
    return guid
예제 #14
0
    def unblock_services(self, services=None):
        """
        Unblock this application from accessing its services on all its known hosts.
        :param services: List[String]; List of service names to unblock, will target all if unset.
        """
        cfg = Config()
        for app_instance in self.instances:
            cmds = []
            for service in self.services:
                if service['type'] in cfg['service-whitelist']:
                    continue
                if services and service['name'] not in services:
                    continue
                logger.info("Unblocking %s for %s:%s", service['name'],
                            app_instance['diego_id'], app_instance['cont_ip'])
                for (sip, protocol, port) in service['hosts']:
                    cmd = [
                        'sudo', 'iptables', '-D', 'FORWARD', '-s',
                        app_instance['cont_ip'], '-d', sip, '-p', protocol
                    ]
                    if port != 'all':
                        cmd.extend(['--dport', port])
                    cmd.extend(['-j', 'DROP'])
                    for _ in range(TIMES_TO_REMOVE):
                        cmds.append(' '.join(map(str, cmd)))

                    cmd = [
                        'sudo', 'iptables', '-D', 'FORWARD', '-d',
                        app_instance['cont_ip'], '-s', sip, '-p', protocol
                    ]
                    if port != 'all':
                        cmd.extend(['--sport', port])
                    cmd.extend(['-j', 'DROP'])
                    for _ in range(TIMES_TO_REMOVE):
                        cmds.append(' '.join(map(str, cmd)))
            if not cmds:
                continue
            monarch.pcf.util.run_cmd_on_diego_cell(app_instance['diego_id'],
                                                   cmds,
                                                   suppress_output=True)
예제 #15
0
def find_application_services(appname):
    """
    Discover all services bound to an application. This will use `cf env` and parse the output for VCAP_SERVICES.
    :param appname: String; The name of the app to deserialize.
    :return: List[Service]; The list of all services bound to this application.
    """
    cfg = Config()
    rcode, stdout, _ = util.run_cmd('{} env {}'.format(cfg['cf']['cmd'],
                                                       appname))
    if rcode:
        sys.exit("Failed to query application environment variables.")

    json_objs = util.extract_json(stdout)
    if not json_objs:
        sys.exit("Error reading output from `cf env`")

    for obj in json_objs:
        if 'VCAP_SERVICES' not in obj:
            json_objs.remove(obj)

    if len(json_objs) != 1:
        logger.info("No services found for %s.", appname)
        return []

    services = []
    vservices = json_objs[0]['VCAP_SERVICES']
    logger.debug(json.dumps(vservices, indent='  '))

    for sname, sconfig in vservices.items():
        for instance_cfg in sconfig:
            service = Service.from_service_info(sname, instance_cfg)
            if service:
                logger.info("Found service: %s", service)
                services.append(service)

    return services
예제 #16
0
def find_application_instances(app_guid):
    """
    Finds the instances of an application and extracts the relevant information.
    :return: List[AppInstance]; The app instances app and their associated hosts.
    """
    cfg = Config()

    # for each instance, find information about where it is hosted and its connected ports
    instances = []
    raw_apps = bosh.get_apps()
    if not raw_apps:
        logger.warning("No application instances found for %s.", app_guid)
        return None
    for instance in raw_apps:
        if instance['app_guid'] != app_guid:
            continue
        if instance['state'] != 'RUNNING':
            continue
        diego_ip = instance['address']
        cont_ip = instance['instance_address']
        diego_id = 'diego_cell/' + instance['cell_id']
        app_ports = set(
        )  # ports the application is listening on within the container

        for ports in instance['ports']:
            diego_port = ports['host_port']  # node port on the diego-cell
            cont_port = ports[
                'container_port']  # port the application is listening on in the container

            if diego_port in cfg['host-port-whitelist']:
                continue
            if cont_port in cfg['container-port-whitelist']:
                continue

            app_ports.add((diego_port, cont_port))
            logger.debug('Found application at %s:%d with container port %d',
                         diego_ip, diego_port, cont_port)

        # Lookup the virtual network interface
        _, stdout, _ = monarch.pcf.util.run_cmd_on_diego_cell(diego_id, 'ip a')
        stdout = util.group_lines_by_hanging_indent(stdout)
        index = util.find_string_in_grouping(stdout,
                                             cont_ip.replace('.', r'\.'))
        if not index:
            logger.warning("Could not find virtual interface!")
            diego_vi = None
        else:
            diego_vi = stdout[index[0]][0]  # want to get parent of the match
            match = re.match(r'\d+: ([\w-]+)(@[\w-]+)?:', diego_vi)
            assert match  # This should never fail, so the regex must be wrong!
            diego_vi = match[1]
            logger.debug("Hosting diego-cell Virtual Interface: %s", diego_vi)

        # Lookup the Container ID
        cmd = "sudo cat /var/vcap/sys/log/rep/rep.stdout.log | grep {} | tail -n 1".format(
            cont_ip)
        rcode, stdout, _ = monarch.pcf.util.run_cmd_on_diego_cell(
            diego_id, cmd)
        if rcode:
            logger.error("Failed retrieving container GUID from %s.", diego_id)
            cont_id = None
        else:
            cont_id = util.extract_json(stdout)[0]['data']['container-guid']
            logger.debug("Hosting container GUID: %s.", cont_id)

        # Record the app instance information
        app_instance = AppInstance(diego_id=diego_id,
                                   diego_ip=diego_ip,
                                   cont_id=cont_id,
                                   cont_ip=cont_ip,
                                   app_ports=app_ports,
                                   diego_vi=diego_vi)
        instances.append(app_instance)
        logger.info("Found instance: %s", app_instance)
    return instances
예제 #17
0
 def _app_port_not_whitelisted(ports):
     cfg = Config()
     return ports[0] not in cfg['host-port-whitelist'] and \
            ports[1] not in cfg['container-port-whitelist']
예제 #18
0
def app():
    cfg = Config()['testing']
    app = App.discover(cfg['org'], cfg['space'], cfg['appname'])
    yield app
    if app:
        app.undo_all()
예제 #19
0
def cfg():
    cfg = Config()
    cfg.load_yaml('tests/config/app_test.yml')
    yield cfg