diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 1619ef95..a0613ec5 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -77,6 +77,7 @@ def add_node_data( if isinstance(options, (DockerOptions, PodmanOptions)): options.image = node_proto.image options.compose = node_proto.compose + options.compose_name = node_proto.compose_name position = Position() position.set(node_proto.position.x, node_proto.position.y) if node_proto.HasField("geo"): @@ -307,9 +308,11 @@ def get_node_proto( emane_model = node.wireless_model.name image = None compose = None + compose_name = None if isinstance(node, (DockerNode, PodmanNode)): image = node.image compose = node.compose + compose_name = node.compose_name # check for wlan config wlan_config = session.mobility.get_configs( node.id, config_type=BasicRangeModel.name @@ -358,6 +361,7 @@ def get_node_proto( icon=node.icon, image=image, compose=compose, + compose_name=compose_name, services=services, dir=node_dir, channel=channel, diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index bae0eadc..ddf8e585 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -605,6 +605,7 @@ class Node: icon: str = None image: str = None compose: str = None + compose_name: str = None server: str = None geo: Geo = None dir: str = None @@ -645,6 +646,7 @@ def from_proto(cls, proto: core_pb2.Node) -> "Node": icon=proto.icon, image=proto.image, compose=proto.compose, + compose_name=proto.compose_name, server=proto.server, geo=Geo.from_proto(proto.geo), dir=proto.dir, @@ -684,6 +686,7 @@ def to_proto(self) -> core_pb2.Node: icon=self.icon, image=self.image, compose=self.compose, + compose_name=self.compose_name, server=self.server, dir=self.dir, channel=self.channel, diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 8f5a3620..39fb5d85 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -189,6 +189,7 @@ def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: self.type: tk.StringVar = tk.StringVar(value=self.node.model) self.container_image: tk.StringVar = tk.StringVar(value=self.node.image) self.compose_file: tk.StringVar = tk.StringVar(value=self.node.compose) + self.compose_name: tk.StringVar = tk.StringVar(value=self.node.compose_name) server = DEFAULT_SERVER if self.node.server: server = self.node.server @@ -227,6 +228,7 @@ def draw(self) -> None: overview_frame = ttk.Labelframe(frame, text="Overview", padding=FRAME_PAD) overview_frame.grid(row=row, columnspan=2, sticky=tk.EW, pady=PADY) + overview_frame.columnconfigure(1, weight=1) overview_row = 0 row += 1 @@ -263,6 +265,7 @@ def draw(self) -> None: # container image field if nutils.has_image(self.node.type): + # image name label = ttk.Label(overview_frame, text="Image") label.grid(row=overview_row, column=0, sticky=tk.EW, padx=PADX, pady=PADY) entry = ttk.Entry( @@ -270,7 +273,7 @@ def draw(self) -> None: ) entry.grid(row=overview_row, column=1, sticky=tk.EW) overview_row += 1 - + # compose file compose_frame = ttk.Frame(overview_frame) compose_frame.columnconfigure(0, weight=2) compose_frame.columnconfigure(1, weight=1) @@ -278,7 +281,7 @@ def draw(self) -> None: entry = ttk.Entry(compose_frame, textvariable=self.compose_file) entry.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button( - compose_frame, text="Compose", command=self.click_compose + compose_frame, text="Compose File", command=self.click_compose ) button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button( @@ -289,6 +292,14 @@ def draw(self) -> None: row=overview_row, column=0, columnspan=2, sticky=tk.EW, pady=PADY ) overview_row += 1 + # compose name + label = ttk.Label(overview_frame, text="Compose Name") + label.grid(row=overview_row, column=0, sticky=tk.EW, padx=PADX, pady=PADY) + entry = ttk.Entry( + overview_frame, textvariable=self.compose_name, state=state + ) + entry.grid(row=overview_row, column=1, sticky=tk.EW) + overview_row += 1 if nutils.is_rj45(self.node): ifaces = self.app.core.client.get_ifaces() @@ -433,6 +444,14 @@ def click_apply(self) -> None: if nutils.has_image(self.node.type): self.node.image = self.container_image.get() or None self.node.compose = self.compose_file.get() or None + self.node.compose_name = self.compose_name.get() or None + if not self.node.compose_name: + messagebox.showerror( + "Compose Error", + "Name required when using a compose file", + parent=self.top, + ) + return server = self.server.get() if nutils.is_container(self.node): if server == DEFAULT_SERVER: diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index a989a5c3..0b15a0f4 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -40,6 +40,10 @@ class DockerOptions(CoreNodeOptions): """ Path to a compose file, if one should be used for this node. """ + compose_name: str = None + """ + Service name to start, within the provided compose file. + """ @dataclass @@ -83,6 +87,7 @@ def __init__( super().__init__(session, _id, name, server, options) self.image: str = options.image self.compose: Optional[str] = options.compose + self.compose_name: Optional[str] = options.compose_name self.binds: list[tuple[str, str]] = options.binds self.volumes: dict[str, DockerVolume] = {} self.env: dict[str, str] = {} @@ -193,13 +198,20 @@ def startup(self) -> None: self.makenodedir() hostname = self.name.replace("_", "-") if self.compose: + if not self.compose_name: + raise CoreError( + "a compose name is required when using a compose file" + ) data = self.host_cmd(f"cat {self.compose}") template = Template(data) rendered = template.render_unicode(node=self, hostname=hostname) rendered = "\\n".join(rendered.splitlines()) compose_path = self.directory / "docker-compose.yml" self.host_cmd(f"printf '{rendered}' >> {compose_path}", shell=True) - self.host_cmd(f"{DOCKER_COMPOSE} up -d", cwd=self.directory) + self.host_cmd( + f"{DOCKER_COMPOSE} up -d {self.compose_name}", + cwd=self.directory, + ) else: # setup commands for creating bind/volume mounts binds = "" diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 749f6edb..3aa1150a 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -163,13 +163,16 @@ def add_class(self) -> None: clazz = "" image = "" compose = "" + compose_name = "" if isinstance(self.node, (DockerNode, PodmanNode)): clazz = "docker" if isinstance(self.node, DockerNode) else "podman" image = self.node.image compose = self.node.compose + compose_name = self.node.compose_name add_attribute(self.element, "class", clazz) add_attribute(self.element, "image", image) add_attribute(self.element, "compose", compose) + add_attribute(self.element, "compose_name", compose_name) def add_services(self) -> None: service_elements = etree.Element("services") @@ -661,6 +664,7 @@ def read_device(self, device_element: etree.Element) -> None: clazz = device_element.get("class") image = device_element.get("image") compose = device_element.get("compose") + compose_name = device_element.get("compose_name") server = device_element.get("server") canvas = get_int(device_element, "canvas") node_type = NodeTypes.DEFAULT @@ -685,6 +689,7 @@ def read_device(self, device_element: etree.Element) -> None: if isinstance(options, (DockerOptions, PodmanOptions)): options.image = image options.compose = compose + options.compose_name = compose_name # get position information position_element = device_element.find("position") position = None diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index b997ef72..f17b5bbc 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -585,6 +585,7 @@ message Node { repeated emane.NodeEmaneConfig emane_configs = 18; map wireless_config = 19; string compose = 20; + string compose_name = 21; } message Link {