def check_ports(): if utils.path_exists('./docker-compose.yml'): utils.info('Stopping services...') sh('{}docker-compose down --remove-orphans'.format(utils.optsudo())) ports = [ utils.getenv(key, const.ENV_DEFAULTS[key]) for key in [ const.HTTP_PORT_KEY, const.HTTPS_PORT_KEY, const.MDNS_PORT_KEY, ] ] utils.info('Checking ports...') retv = sh('sudo netstat -tulpn', capture=True) lines = retv.split('\n') used_ports = [] used_lines = [] for port in ports: for line in lines: if re.match(r'.*(:::|0.0.0.0:){}\s.*'.format(port), line): used_ports.append(port) used_lines.append(line) break if used_ports: utils.warn( 'Port(s) {} already in use. '.format(', '.join(used_ports)) + "Run 'brewblox-ctl service ports' to configure Brewblox ports.") for line in used_lines: utils.warn(line) if not utils.confirm('Do you want to continue?'): raise SystemExit(1)
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'])
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 name in config['services'] and not force: click.echo( 'Service "{}" already exists. Use the --force flag if you want to overwrite it' .format(name)) raise SystemExit(1) 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( "The existing Spark service '{}' does not have any connection settings." .format(nm)) 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("To reconfigure '{}', please run:".format(nm)) utils.warn('') utils.warn(' brewblox-ctl add-spark -f --name {}'.format(nm)) utils.warn('') utils.select('Press ENTER to continue or Ctrl-C to exit') if device_id is None and discover_now and not simulation: dev = find_device(discovery_type, device_host) if dev: device_id = dev['id'] elif device_host is None: # 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': '{}:{}'.format(image_name, utils.docker_tag(release)), 'privileged': True, 'restart': 'unless-stopped', 'command': ' '.join(commands) } if simulation: volume_dir = 'simulator__{}'.format(name) config['services'][name]['volumes'] = [ './{}:/app/simulator'.format(volume_dir) ] sh('mkdir -m 777 -p {}'.format(volume_dir)) utils.write_compose(config) click.echo("Added Spark service '{}'.".format(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('{}docker-compose up -d --remove-orphans'.format(sudo))
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)
def update(ctx, update_ctl, update_ctl_done, pull, avahi_config, migrate, prune, from_version): """Download and apply updates. This is the one-stop-shop for updating your Brewblox install. You can use any of the options to fine-tune the update by enabling or disabling subroutines. By default, all options are enabled. --update-ctl/--no-update-ctl determines whether it download new versions of brewblox-ctl and brewblox-ctl lib. If this flag is set, update will download the new version and then restart itself. This way, the migrate is done with the latest version of brewblox-ctl. If you're using dry run mode, you'll notice the hidden option --update-ctl-done. You can use it to watch the rest of the update: it\'s a flag to avoid endless loops. --pull/--no-pull governs whether new docker images are pulled. This is useful if any of your services is using a local image (not from Docker Hub). --avahi-config/--no-avahi-config. Check avahi-daemon configuration. This is required for TCP discovery of Spark controllers. --migrate/--no-migrate. Updates regularly require changes to configuration. To do this, services are stopped. If the update only requires pulling docker images, you can disable migration to avoid the docker-compose down/up. --prune/--no-prune (prompts if not set). Updates to docker images can leave unused old versions on your system. These can be pruned to free up disk space. Do note that this includes all images on your system, not just those created by Brewblox. \b Steps: - Update brewblox-ctl and extensions. - Restart update command to run with updated brewblox-ctl. - Pull docker images. - Stop services. - Migrate configuration files. - Copy docker-compose.shared.yml from defaults. - Start services. - Migrate service configuration. - Write version number to .env file. - Prune unused images. """ utils.check_config() utils.confirm_mode() sudo = utils.optsudo() prev_version = StrictVersion(from_version) if prev_version.version == (0, 0, 0): click.echo( 'This configuration was never set up. Please run brewblox-ctl setup first' ) raise SystemExit(1) if prev_version > StrictVersion(const.CURRENT_VERSION): click.echo( 'Your system is running a version newer than the selected release. ' + 'This may be due to switching release tracks.' + 'You can use the --from-version flag if you know what you are doing.' ) raise SystemExit(1) if Path.home().name != 'root' and Path.home().exists() \ and Path('/usr/local/bin/brewblox-ctl').exists(): # pragma: no cover utils.warn('brewblox-ctl appears to have been installed using sudo.') if utils.confirm('Do you want to fix this now?'): sh('sudo {} -m pip uninstall -y brewblox-ctl docker-compose'. format(const.PY), check=False) utils.pip_install('brewblox-ctl') # docker-compose is a dependency # Debian stretch still has the bug where ~/.local/bin is not included in $PATH if '.local/bin' not in utils.getenv('PATH'): sh('echo \'export PATH="$HOME/.local/bin:$PATH"\' >> ~/.bashrc' ) utils.info( 'Please run "exec $SHELL --login" to apply the changes to $PATH' ) return if update_ctl and not update_ctl_done: utils.info('Updating brewblox-ctl...') utils.pip_install('brewblox-ctl') utils.info('Updating brewblox-ctl libs...') utils.load_ctl_lib() # Restart ctl - we just replaced the source code sh(' '.join([ const.PY, *const.ARGS, '--update-ctl-done', '--prune' if prune else '--no-prune' ])) return if avahi_config: utils.update_avahi_config() if migrate: # Everything except downed_migrate can be done with running services utils.info('Stopping services...') sh('{}docker-compose down --remove-orphans'.format(sudo)) utils.info('Migrating configuration files...') apply_config() downed_migrate(prev_version) else: utils.info('Updating configuration files...') apply_config() if pull: utils.info('Pulling docker images...') sh('{}docker-compose pull'.format(sudo)) utils.info('Starting services...') sh('{}docker-compose up -d --remove-orphans'.format(sudo)) if migrate: utils.info('Migrating service configuration...') upped_migrate(prev_version) utils.info('Updating version number to {}...'.format( const.CURRENT_VERSION)) utils.setenv(const.CFG_VERSION_KEY, const.CURRENT_VERSION) if prune: utils.info('Pruning unused images...') sh('{}docker image prune -f'.format(sudo)) utils.info('Pruning unused volumes...') sh('{}docker volume prune -f'.format(sudo))