Apply a SharedConfig layer onto a ResolvedConfig with proper merge semantics. - Scalars: override if `Some` - Lists (`host_ports`, `volumes`, `env`): combine with dedup/conflict resolution - Maps (`stack_config`, `agent_config`): combine keys, same key replaces entire value
(&self, resolved: &mut ResolvedConfig, config: &SharedConfig)
| 126 | /// - Lists (`host_ports`, `volumes`, `env`): combine with dedup/conflict resolution |
| 127 | /// - Maps (`stack_config`, `agent_config`): combine keys, same key replaces entire value |
| 128 | fn apply_shared_config(&self, resolved: &mut ResolvedConfig, config: &SharedConfig) { |
| 129 | if let Some(ref agent) = config.agent { |
| 130 | resolved.agent = agent.clone(); |
| 131 | } |
| 132 | if let Some(ref stack) = config.stack { |
| 133 | resolved.stack = stack.clone(); |
| 134 | } |
| 135 | if let Some(dind) = config.dind { |
| 136 | resolved.dind = dind; |
| 137 | } |
| 138 | if let Some(privileged) = config.privileged { |
| 139 | resolved.privileged = privileged; |
| 140 | } |
| 141 | if let Some(sudo) = config.sudo { |
| 142 | resolved.sudo = sudo; |
| 143 | } |
| 144 | if let Some(memory) = config.memory_gb { |
| 145 | resolved.memory_gb = memory; |
| 146 | } |
| 147 | if let Some(cpu) = config.cpu { |
| 148 | resolved.cpu = cpu; |
| 149 | } |
| 150 | if let Some(git_town) = config.git_town { |
| 151 | resolved.git_town = git_town; |
| 152 | } |
| 153 | if let Some(ref setup) = config.setup { |
| 154 | resolved.setup = Some(setup.clone()); |
| 155 | } |
| 156 | if let Some(ref squid_conf) = config.squid_conf { |
| 157 | resolved.squid_conf = Some(squid_conf.clone()); |
| 158 | } |
| 159 | |
| 160 | // host_ports: combine, deduplicate |
| 161 | for &port in &config.host_ports { |
| 162 | if !resolved.host_ports.contains(&port) { |
| 163 | resolved.host_ports.push(port); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | // devices: combine, deduplicate |
| 168 | for device in &config.devices { |
| 169 | if !resolved.devices.contains(device) { |
| 170 | resolved.devices.push(device.clone()); |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | // volumes: combine, higher-priority wins on container path conflict |
| 175 | for volume in &config.volumes { |
| 176 | let container_path = match volume { |
| 177 | VolumeMount::Bind(b) => &b.container, |
| 178 | VolumeMount::Named(n) => &n.container, |
| 179 | }; |
| 180 | resolved.volumes.retain(|v| { |
| 181 | let existing_path = match v { |
| 182 | VolumeMount::Bind(b) => &b.container, |
| 183 | VolumeMount::Named(n) => &n.container, |
| 184 | }; |
| 185 | existing_path != container_path |