@@ -45,6 +45,8 @@ pub struct WorkloadMetadata {
4545 workload_name : Arc < str > ,
4646 /// The namespace of the workload this component belongs to
4747 workload_namespace : Arc < str > ,
48+ /// Optional user-provided name for this component (from spec)
49+ component_name : Option < String > ,
4850 /// The actual wasmtime [`Component`] that can be instantiated
4951 component : Component ,
5052 /// The wasmtime [`Linker`] used to instantiate the component
@@ -78,6 +80,11 @@ impl WorkloadMetadata {
7880 & self . workload_namespace
7981 }
8082
83+ /// Returns the optional user-provided name for this component.
84+ pub fn component_name ( & self ) -> Option < & str > {
85+ self . component_name . as_deref ( )
86+ }
87+
8188 /// Returns a reference to the wasmtime engine used to compile this component.
8289 pub fn engine ( & self ) -> & wasmtime:: Engine {
8390 self . component . engine ( )
@@ -235,6 +242,7 @@ impl WorkloadService {
235242 workload_id : workload_id. into ( ) ,
236243 workload_name : workload_name. into ( ) ,
237244 workload_namespace : workload_namespace. into ( ) ,
245+ component_name : None , // Services don't have names yet
238246 component,
239247 linker,
240248 volume_mounts,
@@ -285,6 +293,7 @@ impl WorkloadComponent {
285293 workload_id : impl Into < Arc < str > > ,
286294 workload_name : impl Into < Arc < str > > ,
287295 workload_namespace : impl Into < Arc < str > > ,
296+ component_name : Option < String > ,
288297 component : Component ,
289298 linker : Linker < Ctx > ,
290299 volume_mounts : Vec < ( PathBuf , VolumeMount ) > ,
@@ -296,6 +305,7 @@ impl WorkloadComponent {
296305 workload_id : workload_id. into ( ) ,
297306 workload_name : workload_name. into ( ) ,
298307 workload_namespace : workload_namespace. into ( ) ,
308+ component_name,
299309 component,
300310 linker,
301311 volume_mounts,
@@ -480,6 +490,122 @@ impl ResolvedWorkload {
480490 & self . host_interfaces
481491 }
482492
493+ /// Bind a single component to plugins and add/replace it in the resolved workload.
494+ /// This is used for component-specific restart operations.
495+ ///
496+ /// # Arguments
497+ /// * `component` - The initialized WorkloadComponent to bind and add
498+ /// * `target_component_id` - The component ID to use (for replacing existing components)
499+ /// * `plugins` - Available host plugins for binding
500+ ///
501+ /// # Returns
502+ /// The component ID that was bound
503+ pub async fn bind_and_add_component (
504+ & self ,
505+ mut component : WorkloadComponent ,
506+ target_component_id : String ,
507+ plugins : & HashMap < & ' static str , Arc < dyn HostPlugin + ' static > > ,
508+ host_interfaces : & [ WitInterface ] ,
509+ ) -> anyhow:: Result < String > {
510+ // Override the component ID to match the target (for replacement)
511+ component. metadata . id = Arc :: from ( target_component_id. as_str ( ) ) ;
512+
513+ // Override the component ID to match the target (for replacement)
514+ component. metadata . id = Arc :: from ( target_component_id. as_str ( ) ) ;
515+
516+ // Determine which interfaces this component needs
517+ let world = component. world ( ) ;
518+ let http_iface = WitInterface :: from ( "wasi:http/incoming-handler,outgoing-handler" ) ;
519+ let required_interfaces: HashSet < WitInterface > = host_interfaces
520+ . iter ( )
521+ . filter ( |wit_interface| !http_iface. contains ( wit_interface) )
522+ . filter ( |wit_interface| world. includes_bidirectional ( wit_interface) )
523+ . cloned ( )
524+ . collect ( ) ;
525+
526+ if required_interfaces. is_empty ( ) {
527+ // No plugins needed, just add the component
528+ // Set component state to Running first
529+ component
530+ . set_state ( crate :: types:: ComponentState :: Running )
531+ . await ;
532+
533+ self . components
534+ . write ( )
535+ . await
536+ . insert ( Arc :: from ( target_component_id. as_str ( ) ) , component) ;
537+ return Ok ( target_component_id) ;
538+ }
539+
540+ // Bind to matching plugins
541+ for ( _plugin_id, plugin) in plugins. iter ( ) {
542+ let plugin_world = plugin. world ( ) ;
543+ let plugin_matched_interfaces: HashSet < WitInterface > = required_interfaces
544+ . iter ( )
545+ . filter ( |interface| plugin_world. includes_bidirectional ( interface) )
546+ . cloned ( )
547+ . collect ( ) ;
548+
549+ if plugin_matched_interfaces. is_empty ( ) {
550+ continue ;
551+ }
552+
553+ // Note: We skip on_workload_bind for individual component restarts
554+ // as the workload is already bound. We only need on_component_bind.
555+
556+ // Bind plugin to component
557+ let matching_interfaces: HashSet < WitInterface > = plugin_matched_interfaces
558+ . iter ( )
559+ . filter ( |interface| world. includes_bidirectional ( interface) )
560+ . cloned ( )
561+ . collect ( ) ;
562+
563+ if !matching_interfaces. is_empty ( ) {
564+ if let Err ( e) = plugin
565+ . on_component_bind ( & mut component, matching_interfaces. clone ( ) )
566+ . await
567+ {
568+ warn ! (
569+ plugin_id = plugin. id( ) ,
570+ component_id = target_component_id,
571+ error = ?e,
572+ "failed to bind component to plugin during component restart"
573+ ) ;
574+ bail ! ( e) ;
575+ }
576+
577+ component. add_plugin ( plugin. id ( ) , plugin. clone ( ) ) ;
578+ }
579+
580+ // Notify plugin of resolution
581+ if let Err ( e) = plugin
582+ . on_workload_resolved ( self , & target_component_id)
583+ . await
584+ {
585+ warn ! (
586+ plugin_id = plugin. id( ) ,
587+ component_id = target_component_id,
588+ error = ?e,
589+ "failed to notify plugin of resolved workload during component restart"
590+ ) ;
591+ bail ! ( e) ;
592+ }
593+ }
594+
595+ // Set component state to Running
596+ component
597+ . set_state ( crate :: types:: ComponentState :: Running )
598+ . await ;
599+
600+ // Add/replace the component in the workload
601+ self . components
602+ . write ( )
603+ . await
604+ . insert ( Arc :: from ( target_component_id. as_str ( ) ) , component) ;
605+
606+ Ok ( target_component_id)
607+ }
608+
483609 async fn link_components ( & mut self ) -> anyhow:: Result < ( ) > {
484610 // A map from component ID to its exported interfaces
485611 let mut interface_map: HashMap < String , Arc < str > > = HashMap :: new ( ) ;
@@ -1697,6 +1823,7 @@ mod tests {
16971823 format ! ( "workload-{id}" ) ,
16981824 format ! ( "test-workload-{id}" ) ,
16991825 "test-namespace" . to_string ( ) ,
1826+ None , // No component name for test components
17001827 component,
17011828 linker,
17021829 Vec :: new ( ) ,
0 commit comments