@@ -154,7 +154,7 @@ func tailscaleCmd(o *Options) (*cobra.Command, error) {
154154}
155155
156156// NewTailscaleNode creates a new Tailscale node configuration.
157- func NewTailscaleNode (name , image , network , authKey string , acceptRoutes bool , isEphemeral bool , rt clabruntime.ContainerRuntime , labels map [string ]string ) * TailscaleNode {
157+ func NewTailscaleNode (name , image , network , authKey string , acceptRoutes , isEphemeral bool , rt clabruntime.ContainerRuntime , labels map [string ]string ) * TailscaleNode {
158158 log .Debugf ("Creating TailscaleNode: name=%s, image=%s, network=%s, acceptRoutes=%t, ephemeral=%t" ,
159159 name , image , network , acceptRoutes , isEphemeral )
160160
@@ -248,7 +248,7 @@ func getTailscaleStatus(ctx context.Context, rt clabruntime.ContainerRuntime, co
248248 return ip
249249}
250250
251- // get the actual node name in the tailnet (in case of duplicate names tailscale appends a hyphen + number)
251+ // get the actual node name in the tailnet (in case of duplicate names tailscale appends a hyphen + number).
252252func getTailscaleNodeName (ctx context.Context , rt clabruntime.ContainerRuntime , containerName string ) string {
253253 execCmd , err := clabexec .NewExecCmdFromString ("tailscale status --self --json" )
254254 if err != nil {
@@ -298,7 +298,7 @@ func waitForTailscaleReady(ctx context.Context, rt clabruntime.ContainerRuntime,
298298 if timeoutCtx .Err () == context .DeadlineExceeded {
299299 return fmt .Errorf ("tailscale container %s did not become healthy within %v" , containerName , timeout )
300300 }
301- return fmt .Errorf ("context cancelled while waiting for tailscale: %v" , timeoutCtx .Err ())
301+ return fmt .Errorf ("context canceled while waiting for tailscale: %v" , timeoutCtx .Err ())
302302
303303 case <- ticker .C :
304304 isHealthy , err := rt .IsHealthy (timeoutCtx , containerName )
@@ -333,26 +333,8 @@ func getMgmtNetworkSubnets(rt clabruntime.ContainerRuntime) []string {
333333 return subnets
334334}
335335
336- func tailscaleAttach (cobraCmd * cobra.Command , o * Options ) error {
337- ctx := cobraCmd .Context ()
338-
339- log .Debug ("tailscale attach called" ,
340- "labName" , o .Global .TopologyName ,
341- "containerName" , o .ToolsTailscale .ContainerName ,
342- "image" , o .ToolsTailscale .Image ,
343- "topoFile" , o .Global .TopologyFile ,
344- "acceptRoutes" , o .ToolsTailscale .AcceptRoutes ,
345- "ephemeral" , o .ToolsTailscale .Ephemeral )
346-
347- // Get lab topology information
348- clabInstance , err := clabcore .NewclabFromTopologyFileOrLabName (ctx , o .Global .TopologyFile ,
349- o .Global .TopologyName , o .Global .VarsFile , o .Global .Runtime , o .Global .DebugCount > 0 , o .Global .Timeout , o .Destroy .GracefulShutdown )
350- if err != nil {
351- return err
352- }
353-
336+ func setupTailscaleContainer (ctx context.Context , o * Options , clabInstance * clabcore.CLab , rt clabruntime.ContainerRuntime ) error {
354337 labName := clabInstance .Config .Name
355-
356338 networkName := clabInstance .Config .Mgmt .Network
357339 if networkName == "" {
358340 networkName = "clab-" + labName
@@ -373,33 +355,12 @@ func tailscaleAttach(cobraCmd *cobra.Command, o *Options) error {
373355 }
374356 }
375357
376- // Initialize runtime with management network info from the deployed lab
377- _ , rinit , err := clabcore .RuntimeInitializer (o .Global .Runtime )
378- if err != nil {
379- return fmt .Errorf ("failed to get runtime initializer for '%s': %w" , o .Global .Runtime , err )
380- }
381-
382- rt := rinit ()
383-
384- mgmtNet := clabInstance .Config .Mgmt
385- log .Debugf ("Using mgmt network from deployed lab: %+v" , mgmtNet )
386-
387- err = rt .Init (
388- clabruntime .WithConfig (& clabruntime.RuntimeConfig {Timeout : o .Global .Timeout }),
389- clabruntime .WithMgmtNet (mgmtNet ),
390- )
391- if err != nil {
392- return fmt .Errorf ("failed to initialize runtime: %w" , err )
393- }
394-
395358 // Check if container already exists
396359 filter := []* clabtypes.GenericFilter {{FilterType : "name" , Match : o .ToolsTailscale .ContainerName }}
397-
398360 containers , err := rt .ListContainers (ctx , filter )
399361 if err != nil {
400362 return fmt .Errorf ("failed to list containers: %w" , err )
401363 }
402-
403364 if len (containers ) > 0 {
404365 return fmt .Errorf ("container %s already exists" , o .ToolsTailscale .ContainerName )
405366 }
@@ -438,6 +399,10 @@ func tailscaleAttach(cobraCmd *cobra.Command, o *Options) error {
438399 return fmt .Errorf ("failed to start tailscale container: %v" , err )
439400 }
440401
402+ return nil
403+ }
404+
405+ func waitAndShowTailscaleInfo (ctx context.Context , o * Options , rt clabruntime.ContainerRuntime ) error {
441406 log .Info ("Tailscale container started. Waiting for tailnet connection" , "container" , o .ToolsTailscale .ContainerName )
442407
443408 if err := waitForTailscaleReady (ctx , rt , o .ToolsTailscale .ContainerName , 60 * time .Second ); err != nil {
@@ -452,7 +417,6 @@ func tailscaleAttach(cobraCmd *cobra.Command, o *Options) error {
452417 }
453418
454419 tsNodeName := getTailscaleNodeName (ctx , rt , o .ToolsTailscale .ContainerName )
455-
456420 subnets := getMgmtNetworkSubnets (rt )
457421
458422 log .Info ("Tailscale attached" ,
@@ -463,6 +427,49 @@ func tailscaleAttach(cobraCmd *cobra.Command, o *Options) error {
463427 return nil
464428}
465429
430+ func tailscaleAttach (cobraCmd * cobra.Command , o * Options ) error {
431+ ctx := cobraCmd .Context ()
432+
433+ log .Debug ("tailscale attach called" ,
434+ "labName" , o .Global .TopologyName ,
435+ "containerName" , o .ToolsTailscale .ContainerName ,
436+ "image" , o .ToolsTailscale .Image ,
437+ "topoFile" , o .Global .TopologyFile ,
438+ "acceptRoutes" , o .ToolsTailscale .AcceptRoutes ,
439+ "ephemeral" , o .ToolsTailscale .Ephemeral )
440+
441+ // Get lab topology information
442+ clabInstance , err := clabcore .NewclabFromTopologyFileOrLabName (ctx , o .Global .TopologyFile ,
443+ o .Global .TopologyName , o .Global .VarsFile , o .Global .Runtime , o .Global .DebugCount > 0 , o .Global .Timeout , o .Destroy .GracefulShutdown )
444+ if err != nil {
445+ return err
446+ }
447+
448+ // Initialize runtime with management network info from the deployed lab
449+ _ , rinit , err := clabcore .RuntimeInitializer (o .Global .Runtime )
450+ if err != nil {
451+ return fmt .Errorf ("failed to get runtime initializer for '%s': %w" , o .Global .Runtime , err )
452+ }
453+
454+ rt := rinit ()
455+ mgmtNet := clabInstance .Config .Mgmt
456+ log .Debugf ("Using mgmt network from deployed lab: %+v" , mgmtNet )
457+
458+ err = rt .Init (
459+ clabruntime .WithConfig (& clabruntime.RuntimeConfig {Timeout : o .Global .Timeout }),
460+ clabruntime .WithMgmtNet (mgmtNet ),
461+ )
462+ if err != nil {
463+ return fmt .Errorf ("failed to initialize runtime: %w" , err )
464+ }
465+
466+ if err := setupTailscaleContainer (ctx , o , clabInstance , rt ); err != nil {
467+ return err
468+ }
469+
470+ return waitAndShowTailscaleInfo (ctx , o , rt )
471+ }
472+
466473func tailscaleDetach (cobraCmd * cobra.Command , o * Options ) error {
467474 ctx := cobraCmd .Context ()
468475
@@ -515,8 +522,8 @@ func tailscaleDetach(cobraCmd *cobra.Command, o *Options) error {
515522
516523 log .Info ("Found tailscale containers for lab" , "lab" , labName , "count" , len (containers ))
517524
518- for _ , container := range containers {
519- containerName := strings .TrimPrefix (container .Names [0 ], "/" )
525+ for i := range containers {
526+ containerName := strings .TrimPrefix (containers [ i ] .Names [0 ], "/" )
520527 log .Info ("Removing tailscale container" , "container" , containerName )
521528
522529 if err := rt .DeleteContainer (ctx , containerName ); err != nil {
@@ -571,7 +578,8 @@ func tailscaleList(cobraCmd *cobra.Command, o *Options) error {
571578
572579 // Process containers and format output
573580 listItems := make ([]TailscaleListItem , 0 , len (containers ))
574- for _ , c := range containers {
581+ for i := range containers {
582+ c := & containers [i ]
575583 name := strings .TrimPrefix (c .Names [0 ], "/" )
576584 network := c .NetworkName
577585 if network == "" {
@@ -635,65 +643,7 @@ func tailscaleList(cobraCmd *cobra.Command, o *Options) error {
635643 return nil
636644}
637645
638- func tailscaleReattach (cobraCmd * cobra.Command , o * Options ) error {
639- ctx := cobraCmd .Context ()
640-
641- log .Debug ("tailscale reattach called" ,
642- "labName" , o .Global .TopologyName ,
643- "containerName" , o .ToolsTailscale .ContainerName ,
644- "image" , o .ToolsTailscale .Image ,
645- "topoFile" , o .Global .TopologyFile ,
646- "acceptRoutes" , o .ToolsTailscale .AcceptRoutes ,
647- "ephemeral" , o .ToolsTailscale .Ephemeral )
648-
649- // Get lab topology information
650- clabInstance , err := clabcore .NewclabFromTopologyFileOrLabName (ctx , o .Global .TopologyFile ,
651- o .Global .TopologyName , o .Global .VarsFile , o .Global .Runtime , o .Global .DebugCount > 0 , o .Global .Timeout , o .Destroy .GracefulShutdown )
652- if err != nil {
653- return err
654- }
655-
656- labName := clabInstance .Config .Name
657- networkName := clabInstance .Config .Mgmt .Network
658- if networkName == "" {
659- networkName = "clab-" + labName
660- }
661-
662- // Set container name if not provided
663- if o .ToolsTailscale .ContainerName == "" {
664- o .ToolsTailscale .ContainerName = fmt .Sprintf ("clab-%s-tailscale" , labName )
665- log .Debugf ("Container name not provided, generated name: %s" , o .ToolsTailscale .ContainerName )
666- }
667-
668- if o .ToolsTailscale .AuthKey == "" {
669- // grab from system env
670- if envKey := os .Getenv ("TS_AUTHKEY" ); envKey != "" {
671- o .ToolsTailscale .AuthKey = envKey
672- } else {
673- return fmt .Errorf ("auth key is required for tailscale. Use --auth-key flag or set the TS_AUTHKEY env var" )
674- }
675- }
676-
677- // Initialize runtime with management network info from the deployed lab
678- _ , rinit , err := clabcore .RuntimeInitializer (o .Global .Runtime )
679- if err != nil {
680- return fmt .Errorf ("failed to get runtime initializer for '%s': %w" , o .Global .Runtime , err )
681- }
682-
683- rt := rinit ()
684-
685- mgmtNet := clabInstance .Config .Mgmt
686- log .Debugf ("Using mgmt network from deployed lab: %+v" , mgmtNet )
687-
688- err = rt .Init (
689- clabruntime .WithConfig (& clabruntime.RuntimeConfig {Timeout : o .Global .Timeout }),
690- clabruntime .WithMgmtNet (mgmtNet ),
691- )
692- if err != nil {
693- return fmt .Errorf ("failed to initialize runtime: %w" , err )
694- }
695-
696- // Step 1: Remove existing Tailscale containers using labels
646+ func removeExistingTailscaleContainers (ctx context.Context , rt clabruntime.ContainerRuntime , labName string ) error {
697647 log .Info ("Removing existing tailscale containers for lab" , "lab" , labName )
698648
699649 // Use labels to find Tailscale containers for this lab
@@ -717,8 +667,8 @@ func tailscaleReattach(cobraCmd *cobra.Command, o *Options) error {
717667 return fmt .Errorf ("failed to list containers: %w" , err )
718668 }
719669
720- for _ , container := range containers {
721- containerName := strings .TrimPrefix (container .Names [0 ], "/" )
670+ for i := range containers {
671+ containerName := strings .TrimPrefix (containers [ i ] .Names [0 ], "/" )
722672 log .Debug ("Removing existing tailscale container" , "container" , containerName )
723673
724674 if err := rt .DeleteContainer (ctx , containerName ); err != nil {
@@ -728,6 +678,16 @@ func tailscaleReattach(cobraCmd *cobra.Command, o *Options) error {
728678 }
729679 }
730680
681+ return nil
682+ }
683+
684+ func recreateTailscaleContainer (ctx context.Context , o * Options , clabInstance * clabcore.CLab , rt clabruntime.ContainerRuntime ) error {
685+ labName := clabInstance .Config .Name
686+ networkName := clabInstance .Config .Mgmt .Network
687+ if networkName == "" {
688+ networkName = "clab-" + labName
689+ }
690+
731691 // Step 2: Create and attach new Tailscale container
732692 log .Info ("Pulling tailscale image" , "image" , o .ToolsTailscale .Image )
733693 if err := rt .PullImage (ctx , o .ToolsTailscale .Image , clabtypes .PullPolicyAlways ); err != nil {
@@ -764,6 +724,10 @@ func tailscaleReattach(cobraCmd *cobra.Command, o *Options) error {
764724 return fmt .Errorf ("failed to start tailscale container: %v" , err )
765725 }
766726
727+ return nil
728+ }
729+
730+ func showTailscaleReattachInfo (ctx context.Context , o * Options , rt clabruntime.ContainerRuntime ) error {
767731 log .Info ("Tailscale container started. Waiting for tailnet connection" , "container" , o .ToolsTailscale .ContainerName )
768732
769733 // Wait for Tailscale to be ready using healthcheck
@@ -780,7 +744,6 @@ func tailscaleReattach(cobraCmd *cobra.Command, o *Options) error {
780744 }
781745
782746 tsNodeName := getTailscaleNodeName (ctx , rt , o .ToolsTailscale .ContainerName )
783-
784747 subnets := getMgmtNetworkSubnets (rt )
785748
786749 log .Info ("Tailscale reattached" ,
@@ -790,3 +753,70 @@ func tailscaleReattach(cobraCmd *cobra.Command, o *Options) error {
790753
791754 return nil
792755}
756+
757+ func tailscaleReattach (cobraCmd * cobra.Command , o * Options ) error {
758+ ctx := cobraCmd .Context ()
759+
760+ log .Debug ("tailscale reattach called" ,
761+ "labName" , o .Global .TopologyName ,
762+ "containerName" , o .ToolsTailscale .ContainerName ,
763+ "image" , o .ToolsTailscale .Image ,
764+ "topoFile" , o .Global .TopologyFile ,
765+ "acceptRoutes" , o .ToolsTailscale .AcceptRoutes ,
766+ "ephemeral" , o .ToolsTailscale .Ephemeral )
767+
768+ // Get lab topology information
769+ clabInstance , err := clabcore .NewclabFromTopologyFileOrLabName (ctx , o .Global .TopologyFile ,
770+ o .Global .TopologyName , o .Global .VarsFile , o .Global .Runtime , o .Global .DebugCount > 0 , o .Global .Timeout , o .Destroy .GracefulShutdown )
771+ if err != nil {
772+ return err
773+ }
774+
775+ labName := clabInstance .Config .Name
776+
777+ // Set container name if not provided
778+ if o .ToolsTailscale .ContainerName == "" {
779+ o .ToolsTailscale .ContainerName = fmt .Sprintf ("clab-%s-tailscale" , labName )
780+ log .Debugf ("Container name not provided, generated name: %s" , o .ToolsTailscale .ContainerName )
781+ }
782+
783+ if o .ToolsTailscale .AuthKey == "" {
784+ // grab from system env
785+ if envKey := os .Getenv ("TS_AUTHKEY" ); envKey != "" {
786+ o .ToolsTailscale .AuthKey = envKey
787+ } else {
788+ return fmt .Errorf ("auth key is required for tailscale. Use --auth-key flag or set the TS_AUTHKEY env var" )
789+ }
790+ }
791+
792+ // Initialize runtime with management network info from the deployed lab
793+ _ , rinit , err := clabcore .RuntimeInitializer (o .Global .Runtime )
794+ if err != nil {
795+ return fmt .Errorf ("failed to get runtime initializer for '%s': %w" , o .Global .Runtime , err )
796+ }
797+
798+ rt := rinit ()
799+ mgmtNet := clabInstance .Config .Mgmt
800+ log .Debugf ("Using mgmt network from deployed lab: %+v" , mgmtNet )
801+
802+ err = rt .Init (
803+ clabruntime .WithConfig (& clabruntime.RuntimeConfig {Timeout : o .Global .Timeout }),
804+ clabruntime .WithMgmtNet (mgmtNet ),
805+ )
806+ if err != nil {
807+ return fmt .Errorf ("failed to initialize runtime: %w" , err )
808+ }
809+
810+ // Step 1: Remove existing Tailscale containers
811+ if err := removeExistingTailscaleContainers (ctx , rt , labName ); err != nil {
812+ return err
813+ }
814+
815+ // Step 2: Create new container
816+ if err := recreateTailscaleContainer (ctx , o , clabInstance , rt ); err != nil {
817+ return err
818+ }
819+
820+ // Step 3: Show info
821+ return showTailscaleReattachInfo (ctx , o , rt )
822+ }
0 commit comments