コード例 #1
0
ファイル: actions.py プロジェクト: BrewBlox/brewblox-ctl
def edit_avahi_config():
    conf = Path(const.AVAHI_CONF)

    if not conf.exists():
        return

    config = ConfigObj(str(conf), file_error=True)
    copy = deepcopy(config)
    config.setdefault('server', {}).setdefault('use-ipv6', 'no')
    config.setdefault('publish', {}).setdefault('publish-aaaa-on-ipv4', 'no')
    config.setdefault('reflector', {}).setdefault('enable-reflector', 'yes')

    if config == copy:
        return

    utils.show_data(conf, config.dict())

    with NamedTemporaryFile('w') as tmp:
        config.filename = None
        lines = config.write()
        # avahi-daemon.conf requires a 'key=value' syntax
        tmp.write('\n'.join(lines).replace(' = ', '=') + '\n')
        tmp.flush()
        sh(f'sudo chmod --reference={conf} {tmp.name}')
        sh(f'sudo cp -fp {tmp.name} {conf}')

    if utils.command_exists('systemctl'):
        utils.info('Restarting avahi-daemon service...')
        sh('sudo systemctl restart avahi-daemon')
    else:
        utils.warn(
            '"systemctl" command not found. Please restart your machine to enable Wifi discovery.'
        )
コード例 #2
0
def rename(ctx, measurement, pattern, replace):
    ctx.ensure_object(utils.ContextOpts)

    keys = get_keys(measurement, pattern)
    if not keys:
        utils.warn('No keys matching "{}" found'.format(pattern))
        return

    for policy in POLICIES:
        read_fields(policy, measurement, keys)

    for policy in POLICIES:
        write_fields(policy, keys, pattern, replace)
コード例 #3
0
ファイル: docker.py プロジェクト: BrewBlox/brewblox-ctl
def kill(zombies):
    """Stop and remove all containers on this host.

    This includes those not from Brewblox.

    If the --zombies flag is set,
    leftover processes that are still claiming a port will be forcibly removed.
    Use this if you get "port is already allocated" errors after your system crashed.
    """
    utils.confirm_mode()
    sudo = utils.optsudo()
    sh(f'{sudo}docker rm --force $({sudo}docker ps -aq)', check=False)

    if zombies:
        # We can't use psutil for this, as we need root rights to get pids
        if not utils.command_exists('netstat'):
            utils.warn(
                'Command `netstat` not found. Please install it by running:')
            utils.warn('')
            utils.warn(
                '    sudo apt-get update && sudo apt-get install net-tools')
            utils.warn('')
            return

        procs = re.findall(r'(\d+)/docker-proxy',
                           sh('sudo netstat -pna', capture=True))

        if procs:
            utils.info(f'Removing {len(procs)} zombies...')
            sh('sudo service docker stop')
            sh([f'sudo kill -9 {proc}' for proc in procs])
            sh('sudo service docker start')
コード例 #4
0
ファイル: service.py プロジェクト: BrewBlox/brewblox-ctl
def remove(ctx, services):
    """Remove a service."""
    utils.check_config()
    utils.confirm_mode()

    config = utils.read_compose()
    for name in services:
        try:
            del config['services'][name]
            utils.info(f"Removed service '{name}'")
        except KeyError:
            utils.warn(f"Service '{name}' not found")

    if services:
        utils.write_compose(config)
        restart_services(ctx, compose_args=['--remove-orphans'])
コード例 #5
0
ファイル: test_utils.py プロジェクト: BrewBlox/brewblox-ctl
def test_logs(mocker):
    m_opts = mocker.patch(TESTED + '.ctx_opts').return_value
    m_secho = mocker.patch(TESTED + '.click.secho')

    m_opts.quiet = True
    utils.info('test')
    assert m_secho.call_count == 0
    utils.warn('warning')
    assert m_secho.call_count == 1
    utils.error('error')
    assert m_secho.call_count == 2

    m_opts.quiet = False
    utils.info('test')
    assert m_secho.call_count == 3
    utils.warn('warning')
    assert m_secho.call_count == 4
    utils.error('error')
    assert m_secho.call_count == 5
コード例 #6
0
ファイル: actions.py プロジェクト: BrewBlox/brewblox-ctl
def fix_ipv6(config_file=None, restart=True):
    utils.info('Fixing Docker IPv6 settings...')

    if utils.is_wsl():
        utils.info('WSL environment detected. Skipping IPv6 config changes.')
        return

    # Config is either provided, or parsed from active daemon process
    if not config_file:
        default_config_file = '/etc/docker/daemon.json'
        dockerd_proc = sh('ps aux | grep dockerd', capture=True)
        proc_match = re.match(r'.*--config-file[\s=](?P<file>.*\.json).*',
                              dockerd_proc,
                              flags=re.MULTILINE)
        config_file = proc_match and proc_match.group(
            'file') or default_config_file

    utils.info(f'Using Docker config file {config_file}')

    # Read config. Create file if not exists
    sh(f"sudo touch '{config_file}'")
    config = sh(f"sudo cat '{config_file}'", capture=True)

    if 'fixed-cidr-v6' in config:
        utils.info('IPv6 settings are already present. Making no changes.')
        return

    # Edit and write. Do not overwrite existing values
    config = json.loads(config or '{}')
    config.setdefault('ipv6', False)
    config.setdefault('fixed-cidr-v6', '2001:db8:1::/64')
    config_str = json.dumps(config, indent=2)
    sh(f"echo '{config_str}' | sudo tee '{config_file}' > /dev/null")

    # Restart daemon
    if restart:
        if utils.command_exists('service'):
            utils.info('Restarting Docker service...')
            sh('sudo service docker restart')
        else:
            utils.warn(
                '"service" command not found. Please restart your machine to apply config changes.'
            )
コード例 #7
0
ファイル: actions.py プロジェクト: BrewBlox/brewblox-ctl
def check_ports():
    if utils.path_exists('./docker-compose.yml'):
        utils.info('Stopping services...')
        sh(f'{utils.optsudo()}docker-compose down')

    ports = [
        int(utils.getenv(key, const.ENV_DEFAULTS[key])) for key in [
            const.HTTP_PORT_KEY,
            const.HTTPS_PORT_KEY,
            const.MQTT_PORT_KEY,
        ]
    ]

    try:
        port_connnections = [
            conn for conn in psutil.net_connections()
            if conn.laddr.ip in ['::', '0.0.0.0'] and conn.laddr.port in ports
        ]
    except psutil.AccessDenied:
        utils.warn(
            'Unable to read network connections. You need to run `netstat` or `lsof` manually.'
        )
        port_connnections = []

    if port_connnections:
        port_str = ', '.join(
            set(str(conn.laddr.port) for conn in port_connnections))
        utils.warn(f'Port(s) {port_str} already in use.')
        utils.warn(
            'Run `brewblox-ctl service ports` to configure Brewblox ports.')
        if not utils.confirm('Do you want to continue?'):
            raise SystemExit(1)
コード例 #8
0
ファイル: flash.py プロジェクト: BrewBlox/brewblox-ctl
def find_usb_spark() -> usb.core.Device:
    while True:
        devices = [
            *usb.core.find(find_all=True,
                           idVendor=const.VID_PARTICLE,
                           idProduct=const.PID_PHOTON),
            *usb.core.find(find_all=True,
                           idVendor=const.VID_PARTICLE,
                           idProduct=const.PID_P1),
            *usb.core.find(find_all=True,
                           idVendor=const.VID_ESPRESSIF,
                           idProduct=const.PID_ESP32),
        ]
        num_devices = len(devices)
        if num_devices == 0:
            utils.warn('No USB-connected Spark detected')
            utils.confirm_usb()
        elif num_devices == 1:
            return devices[0]
        else:
            utils.warn(f'{len(devices)} USB-connected Sparks detected.')
            utils.confirm_usb()
コード例 #9
0
ファイル: migration.py プロジェクト: BrewBlox/brewblox-ctl
def migrate_influxdb(
    target: str = 'victoria',
    duration: str = '',
    services: List[str] = [],
    offsets: List[Tuple[str, int]] = [],
):
    """Exports InfluxDB history data.

    The exported data is either immediately imported to the new history database,
    or saved to file.
    """
    opts = utils.ctx_opts()
    sudo = utils.optsudo()
    date = datetime.now().strftime('%Y%m%d_%H%M')

    utils.warn('Depending on the amount of data, this may take some hours.')
    utils.warn(
        'You can use your system as normal while the migration is in progress.'
    )
    utils.warn('The migration can safely be stopped and restarted or resumed.')
    utils.warn(
        'For more info, see https://brewblox.netlify.app/dev/migration/influxdb.html'
    )

    if opts.dry_run:
        utils.info('Dry run. Skipping migration...')
        return

    if not utils.path_exists('./influxdb/'):
        utils.info('influxdb/ dir not found. Skipping migration...')
        return

    utils.info('Starting InfluxDB container...')

    # Stop container in case previous migration was cancelled
    sh(f'{sudo}docker stop influxdb-migrate > /dev/null', check=False)

    # Start standalone container
    # We'll communicate using 'docker exec', so no need to publish a port
    sh(f'{sudo}docker run '
       '--rm -d '
       '--name influxdb-migrate '
       '-v "$(pwd)/influxdb:/var/lib/influxdb" '
       'influxdb:1.8 '
       '> /dev/null')

    # Do a health check until startup is done
    inner_cmd = 'curl --output /dev/null --silent --fail http://localhost:8086/health'
    bash_cmd = f'until $({inner_cmd}); do sleep 1 ; done'
    sh(f"{sudo}docker exec influxdb-migrate bash -c '{bash_cmd}'")

    # Determine relevant measurement
    # Export all of them if not specified by user
    if not services:
        services = _influx_measurements()

    utils.info(f'Exporting services: {", ".join(services)}')

    # Export data and import to target
    for svc in services:
        offset = next((v for v in offsets if v[0] == svc), ('default', 0))[1]
        _copy_influx_measurement(svc, date, duration, target, offset)

    # Stop migration container
    sh(f'{sudo}docker stop influxdb-migrate > /dev/null', check=False)
コード例 #10
0
ファイル: add_service.py プロジェクト: BrewBlox/brewblox-ctl
def add_spark(name,
              discover_now,
              device_id,
              discovery_type,
              device_host,
              command,
              force,
              release,
              simulation):
    """
    Create or update a Spark service.

    If you run brewblox-ctl add-spark without any arguments,
    it will prompt you for required info, and then create a sensibly configured service.

    If you want to fine-tune your service configuration, multiple arguments are available.

    For a detailed explanation: https://brewblox.netlify.com/user/connect_settings.html
    """
    # utils.check_config()
    utils.confirm_mode()

    image_name = 'brewblox/brewblox-devcon-spark'
    sudo = utils.optsudo()
    config = utils.read_compose()

    if not force:
        check_duplicate(config, name)

    for (nm, svc) in config['services'].items():
        img = svc.get('image', '')
        cmd = svc.get('command', '')
        if not any([
            nm == name,
            not img.startswith(image_name),
            '--device-id' in cmd,
            '--device-host' in cmd,
            '--simulation' in cmd,
        ]):
            utils.warn(f'The existing Spark service `{nm}` does not have any connection settings.')
            utils.warn('It will connect to any controller it can find.')
            utils.warn('This may cause multiple services to connect to the same controller.')
            utils.warn(f'To reconfigure `{nm}`, please run:')
            utils.warn('')
            utils.warn(f'    brewblox-ctl add-spark -f --name {nm}')
            utils.warn('')
            utils.select('Press ENTER to continue or Ctrl-C to exit')

    if discover_now and not simulation and not device_id:
        if device_host:
            dev = find_device_by_host(device_host)
        else:
            dev = choose_device(discovery_type, config)

        if dev:
            device_id = dev['id']
        else:
            # We have no device ID, and no device host. Avoid a wildcard service
            click.echo('No valid combination of device ID and device host.')
            raise SystemExit(1)

    commands = [
        '--name=' + name,
        '--discovery=' + discovery_type,
    ]

    if device_id:
        commands += ['--device-id=' + device_id]

    if device_host:
        commands += ['--device-host=' + device_host]

    if simulation:
        commands += ['--simulation']

    if command:
        commands += [command]

    config['services'][name] = {
        'image': f'{image_name}:{utils.docker_tag(release)}',
        'privileged': True,
        'restart': 'unless-stopped',
        'command': ' '.join(commands)
    }

    if simulation:
        mount_dir = f'simulator__{name}'
        config['services'][name]['volumes'] = [{
            'type': 'bind',
            'source': f'./{mount_dir}',
            'target': '/app/simulator'
        }]
        sh(f'mkdir -m 777 -p {mount_dir}')

    utils.write_compose(config)
    click.echo(f'Added Spark service `{name}`.')
    click.echo('It will automatically show up in the UI.\n')
    if utils.confirm('Do you want to run `brewblox-ctl up` now?'):
        sh(f'{sudo}docker-compose up -d')
コード例 #11
0
def upped_migrate(prev_version):
    """Migration commands to be executed after the services have been started"""
    if prev_version < StrictVersion('0.6.0'):
        utils.warn('')
        utils.warn('Brewblox now uses a new configuration database.')
        utils.warn('To migrate your data, run:')
        utils.warn('')
        utils.warn('    brewblox-ctl database from-couchdb')
        utils.warn('')

    if prev_version < StrictVersion('0.7.0'):
        utils.warn('')
        utils.warn('Brewblox now uses a new history database.')
        utils.warn('To migrate your data, run:')
        utils.warn('')
        utils.warn('    brewblox-ctl database from-influxdb')
        utils.warn('')