@@ -29,8 +29,10 @@ func (c *SFCClient) CreateInstance(ctx context.Context, attrs v1.CreateInstanceA
2929 return nil , errors .WrapAndTrace (err )
3030 }
3131
32- // Create a name for the node
33- name := brevDataToSFCName (attrs .RefID , attrs .Name )
32+ // Pack cloud cred ref ID, brev stage, instance ref ID, and name into the SFC node name.
33+ // SFC has no tags API, so the node name is the only place to persist this metadata.
34+ stage := getStageFromTags (attrs .Tags )
35+ name := brevDataToSFCName (c .refID , stage , attrs .RefID , attrs .Name )
3436
3537 // Create the node
3638 resp , err := c .client .Nodes .New (ctx , sfcnodes.NodeNewParams {
@@ -231,11 +233,15 @@ type sfcNodeInfo struct {
231233}
232234
233235func (c * SFCClient ) sfcNodeToBrevInstance (node sfcNodeInfo ) (* v1.Instance , error ) {
234- // Get the refID and name from the node name
235- refID , name , err := sfcNameToBrevData (node .name )
236+ // Parse cloud cred ref ID, brev stage, instance ref ID, and name from the node name.
237+ // Old-format names (refID_name) return empty cloudCredRefID — fall back to c.refID.
238+ cloudCredRefID , _ , refID , name , err := sfcNameToBrevData (node .name )
236239 if err != nil {
237240 return nil , errors .WrapAndTrace (err )
238241 }
242+ if cloudCredRefID == "" {
243+ cloudCredRefID = c .refID
244+ }
239245
240246 // Get the instance type for the zone
241247 instanceType , err := getInstanceTypeForZone (* node .zone )
@@ -270,7 +276,7 @@ func (c *SFCClient) sfcNodeToBrevInstance(node sfcNodeInfo) (*v1.Instance, error
270276 Spot : false ,
271277 Stoppable : false ,
272278 Rebootable : false ,
273- CloudCredRefID : c . refID , // TODO: this should be pulled from the node itself
279+ CloudCredRefID : cloudCredRefID ,
274280 }
275281 return inst , nil
276282}
@@ -448,16 +454,45 @@ func (c *SFCClient) getSSHHostnameFromVM(ctx context.Context, vmID string, vmSta
448454 return sshResponse .SSHHostname , nil
449455}
450456
451- func brevDataToSFCName (refID string , name string ) string {
452- return fmt .Sprintf ("%s_%s" , refID , name )
457+ // brevDataToSFCName packs cloud credential ref ID, brev stage, instance ref ID, and instance
458+ // name into a single SFC node name, separated by underscores. This is necessary because SFC
459+ // has no tags/labels API — the node name is the only place to store metadata.
460+ //
461+ // Format: {cloudCredRefID}_{brevStage}_{refID}_{name}
462+ func brevDataToSFCName (cloudCredRefID string , brevStage string , refID string , name string ) string {
463+ return fmt .Sprintf ("%s_%s_%s_%s" , cloudCredRefID , brevStage , refID , name )
464+ }
465+
466+ // sfcNameToBrevData parses an SFC node name back into its components.
467+ //
468+ // Supports two formats for backward compatibility:
469+ // - New (4+ parts): {cloudCredRefID}_{brevStage}_{refID}_{name}
470+ // - Old (2 parts): {refID}_{name} — cloudCredRefID and brevStage returned empty
471+ func sfcNameToBrevData (name string ) (cloudCredRefID string , brevStage string , refID string , instanceName string , err error ) {
472+ parts := strings .SplitN (name , "_" , 4 )
473+ switch len (parts ) {
474+ case 4 :
475+ // New format: cloudCredRefID_brevStage_refID_name
476+ return parts [0 ], parts [1 ], parts [2 ], parts [3 ], nil
477+ case 2 :
478+ // Old format: refID_name (backward compat — cloudCredRefID and stage unknown)
479+ // TODO: remove this case once all old-format nodes have been cleaned up
480+ return "" , "" , parts [0 ], parts [1 ], nil
481+ default :
482+ return "" , "" , "" , "" , errors .WrapAndTrace (fmt .Errorf ("invalid node name %s: expected 2 or 4 underscore-separated parts" , name ))
483+ }
453484}
454485
455- func sfcNameToBrevData (name string ) (string , string , error ) {
456- parts := strings .SplitN (name , "_" , 2 )
457- if len (parts ) != 2 {
458- return "" , "" , errors .WrapAndTrace (fmt .Errorf ("invalid node name %s" , name ))
486+ // getStageFromTags extracts the control plane stage value from instance tags.
487+ // The tag key is prefixed by the control plane
488+ // so we match any key ending with "-stage" to avoid coupling to a specific prefix.
489+ func getStageFromTags (tags v1.Tags ) string {
490+ for k , v := range tags {
491+ if strings .HasSuffix (k , "-stage" ) {
492+ return v
493+ }
459494 }
460- return parts [ 0 ], parts [ 1 ], nil
495+ return "unknown"
461496}
462497
463498// Optional if supported:
0 commit comments