def validate_volumes(self): """ Check if the volumes we have are valid. Additionally, expand stuff like ~ """ if not bool(self.volumes): return for v in list(self.volumes): console.debug( f'processing plan [cyan]{self.name}[/] volume [bold]{v}[/]') if 'bind' not in self.volumes[v]: console.warn( f'plan [cyan]{self.name}[/] volume [bold]{v}[/] does not have a bind' ) self.valid = False return nv = str(Path(v).expanduser().resolve()) console.debug( f'normalised plan [cyan]{self.name}[/] host volume [bold]{v}[/] is [bold]{nv}[/]' ) self.volumes[nv] = self.volumes.pop(v)
def run(self) -> models.containers.Container: """ Run the containers for a plan """ self._ensure_net_exists() self._ensure_image_exists() # inline dockerfiles console.debug( f'starting service container [bold]{self.get_container_name()}[/]' f' for plan [bold]{self.plan.name}[/]') opts = self.plan.run_options() opts['name'] = self.get_container_name() container = self.get_client(). \ containers.run(self.plan.image_version(), network=config.net_name(), **opts) if not self.plan.exposed_ports: return container for port_map in self.plan.exposed_ports: inside, outside = port_map[0], port_map[1] self.run_net(outside, inside) return container
def _ensure_net_exists(self): """ Ensures that the network image and docker network exists. """ try: self.get_client().images.get(config.net_container_name()) self.get_client().networks.get(config.net_name()) except ImageNotFound as _: console.info( f'network image [bold]{config.net_container_name()}[/] does not exist, quickly building it' ) _, logs = self.get_client().images.build( path=str(NETWORK_CONTAINER_PATH), pull=True, tag=config.net_container_name(), rm=True, forcerm=True) for log in logs: console.debug(log) console.info( f'network container [bold]{config.net_container_name()}[/] built' ) self._ensure_net_exists() except NotFound as _: console.info( f'docker network [bold]{config.net_name()}[/] does not exist, creating it' ) self.get_client().networks.create(name=config.net_name(), check_duplicate=True) self._ensure_net_exists()
def add_commands(self, c: Union[str, list]): """ Adds a command to the plan :param c: :return: """ console.debug(f'adding commands {c} to plan {self.name}') self.command = self.command + ' ' + ' '.join(c)
def load_dist_plans(self): """ Load .yml files from the plans/ directory """ for p in DIST_PLAN_DIRECTORY.glob('**/*.yml'): console.debug(f'processing dist plan [bold]{p}[/]') with p.open() as f: d = yaml.load(f, Loader=yaml.SafeLoader) p = Plan(p) p.from_dict(d) self.plans.append(p)
def stop(self): """ Stops containers """ for container in self.containers(): console.debug( f'stopping container [bold]{container.name}[/] for plan [cyan]{self.plan.name}[/]' ) try: container.stop() except NotFound as _: # if the container is not found, it may already be gone (exited?) pass except Exception as e: console.warn( f'failed to stop container with error [dim]{type(e)}[/]: [bold]{e}[/]' )
def run_net(self, outside: int, inside: int): """ Run a network container for a plan """ self._ensure_net_exists() console.debug( f'starting network proxy [green]{inside}[/]<-{self.get_container_name()}<-' f'[red]{outside}[/] for plan [bold]{self.plan.name}[/]') self.get_client(). \ containers.run(config.net_container_name(), detach=True, environment={ 'REMOTE_HOST': self.get_container_name(), 'REMOTE_PORT': inside, 'LOCAL_PORT': outside, }, stderr=True, stdout=True, remove=True, network=config.net_name(), ports={outside: outside}, name=self.get_net_container_name_with_ports(outside, inside))
def populate_ports(self): """ Translates the ports property to a list of tuples in the exposed_ports property. """ if not self.ports: return if isinstance(self.ports, int): console.debug( f'adding plan [cyan]{self.name}[/] port map for single ' f'port: [bold]{self.ports}<-{self.ports}[/]') self.exposed_ports.append((self.ports, self.ports)) return if isinstance(self.ports, dict): for inside, outside in self.ports.items(): console.debug( f'adding plan [cyan]{self.name}[/] port map for port ' f'pair [bold]{inside}<-{outside}[/]') self.exposed_ports.append((inside, outside)) return # if we got a list, recursively validate & map if isinstance(self.ports, list): console.debug( f'processing plan [cyan]{self.name}[/] port map list ' f'({self.ports}) recursively') o = self.ports for mapping in o: self.ports = mapping self.populate_ports()
def load_user_plans(self): """ Load .yml files from the ~/.dwn/plans directory :return: """ for p in USER_PLAN_DIRECTORY.glob('**/*.yml'): console.debug(f'processing plan [bold]{p}[/]') with p.open() as f: d = yaml.load(f, Loader=yaml.SafeLoader) if not d: continue if self.get_plan(d['name'], valid_only=False): console.debug( f'possible duplicate plan called {d["name"]} from {p}') p = Plan(p) p.from_dict(d) self.plans.append(p)
def build_container(): """ Builds the network container """ console.info('building network container') try: client = docker.from_env() except DockerException as e: console.error(f'docker client failed: [bold]{e}[/]') return console.debug(f'path to docker context is: [bold]{NETWORK_CONTAINER_PATH}[/]') console.debug(f'network container will be called [bold]\'{config.net_container_name()}\'[/]') image, logs = client.images.build( path=str(NETWORK_CONTAINER_PATH), pull=True, tag=config.net_container_name()) for log in logs: console.debug(log) console.info(f'network container \'{config.net_container_name()}\' built')
def _ensure_image_exists(self): """ Ensures that an image exists if a plan has an inline dockerfile. """ # if the plan does not have an inline dockerfile, then we can rely on # the call to run() later to pull the image instead. if not self.plan.has_dockerfile(): return console.debug(f'checking if {self.plan.image_version()} is available') try: self.get_client().images.get(self.plan.image_version()) except ImageNotFound as _: console.warn( f'image for plan [cyan]{self.plan.name}[/] does not exist, quickly building it' ) dockerfile = BytesIO(self.plan.dockerfile.encode('utf-8')) console.debug(f'building dockerfile:\n{self.plan.dockerfile}') _, logs = self.get_client().images.build( fileobj=dockerfile, pull=True, tag=self.plan.image_version(), rm=True, forcerm=True) for log in logs: console.debug(log) console.info( f'container for [bold]{self.plan.image_version()}[/] built') self._ensure_net_exists()
def update(name): """ Update plan images. """ plan_targets = [] if not name and not click.confirm('> a plan name was not specified, ' 'pull all valid plan images?'): return try: client = docker.from_env() except DockerException as e: console.error(f'failed to connect to docker: [bold]{e}[/e]') return loader = Loader() if name: plan_targets.append(loader.get_plan(name)) else: [plan_targets.append(n) for n in loader.valid_plans()] for p in plan_targets: if p is None: continue try: # build the image if we have an inline dockerfile if p.has_dockerfile(): console.info(f'building image [bold]{p.image_version()}[/]') dockerfile = BytesIO(p.dockerfile.encode('utf-8')) _, logs = client.images.build(fileobj=dockerfile, pull=True, tag=p.image_version(), rm=True, forcerm=True, nocache=True) for log in logs: console.debug(log) console.info( f'container for [bold]{p.image_version()}[/] built') # pull the image instead else: console.info(f'pulling image [bold]{p.image_version()}[/]') client.images.pull(p.image, tag=p.version) except ImageNotFound as e: console.error(f'failed to pull image: [bold]{e}[/]') continue except DockerException as e: console.error(f'a docker exception occurred: [bold]{e}[/]') continue console.info( f'image [bold]{p.image_version()}[/] for plan [cyan]{p.name}[/] updated' )