44 "bytes"
55 "context"
66 "encoding/json"
7+ "errors"
78 "fmt"
89 "io"
910 "os"
@@ -143,28 +144,35 @@ func runApplyConfig(ctx context.Context, config *applyFile, dryRun bool) ([]appl
143144 return nil , fmt .Errorf ("failed to init k8s client: %w" , err )
144145 }
145146
146- results := make ([]applyResult , 0 , len (config .Tunnels ))
147+ tunnelIDs := make ([]string , 0 , len (config .Tunnels ))
147148 for _ , item := range config .Tunnels {
148- result , err := applyOneTunnel ( ctx , item , authData , client , kubeconfig , dryRun )
149+ tunnelID , err := applyTunnelID ( item . Name )
149150 if err != nil {
150- rollbackApplyResults (client , results )
151- return results , err
151+ return nil , err
152152 }
153- results = append (results , result )
153+ tunnelIDs = append (tunnelIDs , tunnelID )
154154 }
155- if ! dryRun {
155+
156+ results := make ([]applyResult , 0 , len (config .Tunnels ))
157+ err = withTunnelOperationLocks (tunnelIDs , func () error {
158+ for _ , item := range config .Tunnels {
159+ result , applyErr := applyOneTunnel (ctx , item , authData , client , kubeconfig , false )
160+ if applyErr != nil {
161+ return applyErrorWithRollback (applyErr , rollbackApplyResults (client , results ))
162+ }
163+ results = append (results , result )
164+ }
156165 if err := ensureDaemonRunningFn (); err != nil {
157- rollbackApplyResults (client , results )
158- return results , fmt .Errorf ("failed to start local daemon: %w" , err )
166+ return applyErrorWithRollback (fmt .Errorf ("failed to start local daemon: %w" , err ), rollbackApplyResults (client , results ))
159167 }
160168 for _ , result := range results {
161169 if err := waitForDaemonSession (result .TunnelID , daemonConnectTimeout ); err != nil {
162- rollbackApplyResults (client , results )
163- return results , err
170+ return applyErrorWithRollback (err , rollbackApplyResults (client , results ))
164171 }
165172 }
166- }
167- return results , nil
173+ return nil
174+ })
175+ return results , err
168176}
169177
170178func loadApplyFile (path string ) (* applyFile , error ) {
@@ -253,14 +261,22 @@ func applyOneTunnel(ctx context.Context, item applyTunnel, authData *auth.AuthDa
253261 existing , err := session .Get (normalized .TunnelID )
254262 if err == nil {
255263 alreadyExisted = true
256- existingSession = existing
257264 currentNamespace := ""
258265 if client != nil {
259266 currentNamespace = client .Namespace ()
260267 }
261268 if err := validateExistingApplySessionScope (* existing , authData , currentNamespace ); err != nil {
262269 return result , err
263270 }
271+ if client != nil {
272+ if err := refreshSessionFromRemoteLocked (ctx , existing ); err != nil {
273+ return result , fmt .Errorf ("tunnel %s: sync existing remote state: %w" , normalized .TunnelID , err )
274+ }
275+ }
276+ if strings .TrimSpace (existing .Secret ) == "" {
277+ return result , fmt .Errorf ("tunnel %s already exists but its local secret is unavailable; stop or cleanup the old session before apply" , existing .TunnelID )
278+ }
279+ existingSession = existing
264280 if existing .Secret != "" {
265281 secret = existing .Secret
266282 }
@@ -447,9 +463,6 @@ func validateExistingApplySessionScope(existing session.TunnelSession, authData
447463 return fmt .Errorf ("tunnel %s already belongs to namespace %s; current namespace is %s" , existing .TunnelID , existing .Namespace , currentNamespace )
448464 }
449465 }
450- if strings .TrimSpace (existing .Secret ) == "" {
451- return fmt .Errorf ("tunnel %s already exists but its local secret is unavailable; stop or cleanup the old session before apply" , existing .TunnelID )
452- }
453466 return nil
454467}
455468
@@ -488,7 +501,8 @@ func rollbackExistingApplyTunnel(client *k8s.Client, previous session.TunnelSess
488501 return firstErr
489502}
490503
491- func rollbackApplyResults (client * k8s.Client , results []applyResult ) {
504+ func rollbackApplyResults (client * k8s.Client , results []applyResult ) error {
505+ var rollbackErrors []error
492506 for i := len (results ) - 1 ; i >= 0 ; i -- {
493507 result := results [i ]
494508 if result .TunnelID == "" {
@@ -497,16 +511,30 @@ func rollbackApplyResults(client *k8s.Client, results []applyResult) {
497511 if result .NewTunnel {
498512 if client != nil {
499513 cleanupCtx , cancel := context .WithTimeout (context .Background (), tunnelCleanupTimeout )
500- _ = client .CleanupTunnel (cleanupCtx , result .TunnelID )
514+ if err := client .CleanupTunnel (cleanupCtx , result .TunnelID ); err != nil {
515+ rollbackErrors = append (rollbackErrors , fmt .Errorf ("cleanup tunnel %s: %w" , result .TunnelID , err ))
516+ }
501517 cancel ()
502518 }
503- _ = session .Delete (result .TunnelID )
519+ if err := session .Delete (result .TunnelID ); err != nil {
520+ rollbackErrors = append (rollbackErrors , fmt .Errorf ("delete local session %s: %w" , result .TunnelID , err ))
521+ }
504522 continue
505523 }
506524 if result .Previous != nil {
507- _ = rollbackExistingApplyTunnel (client , * result .Previous )
525+ if err := rollbackExistingApplyTunnel (client , * result .Previous ); err != nil {
526+ rollbackErrors = append (rollbackErrors , fmt .Errorf ("restore tunnel %s: %w" , result .TunnelID , err ))
527+ }
508528 }
509529 }
530+ return errors .Join (rollbackErrors ... )
531+ }
532+
533+ func applyErrorWithRollback (applyErr , rollbackErr error ) error {
534+ if rollbackErr == nil {
535+ return applyErr
536+ }
537+ return errors .Join (applyErr , fmt .Errorf ("rollback failed: %w" , rollbackErr ))
510538}
511539
512540func normalizeApplyTunnel (item applyTunnel ) (normalizedApplyTunnel , error ) {
@@ -763,6 +791,9 @@ func applyTunnelID(name string) (string, error) {
763791 if name != strings .ToLower (name ) || ! applyNamePattern .MatchString (name ) {
764792 return "" , fmt .Errorf ("invalid tunnel name %q: use lowercase DNS-compatible names, e.g. web or api-dev" , name )
765793 }
794+ if strings .HasPrefix (name , "mesh-" ) {
795+ return "" , fmt .Errorf ("invalid tunnel name %q: the mesh- prefix is reserved for Sealtun Mesh resources" , name )
796+ }
766797 return name , nil
767798}
768799
0 commit comments