2525 BuildTime = "unknown"
2626)
2727
28+ // Unbootstrap command flags
29+ var cleanupMode string
30+
2831// NewAgentCommand creates a new agent command
2932func NewAgentCommand () * cobra.Command {
3033 cmd := & cobra.Command {
@@ -44,12 +47,19 @@ func NewUnbootstrapCommand() *cobra.Command {
4447 cmd := & cobra.Command {
4548 Use : "unbootstrap" ,
4649 Short : "Remove AKS node configuration and Arc connection" ,
47- Long : "Clean up and remove all AKS node components and Arc registration from this machine" ,
50+ Long : `Clean up and remove all AKS node components and Arc registration from this machine.
51+
52+ For private clusters (config has private: true), this also handles VPN cleanup:
53+ --cleanup-mode=local Remove node and local VPN config, keep Gateway (default)
54+ --cleanup-mode=full Remove everything including Gateway VM and Azure resources` ,
4855 RunE : func (cmd * cobra.Command , args []string ) error {
4956 return runUnbootstrap (cmd .Context ())
5057 },
5158 }
5259
60+ cmd .Flags ().StringVar (& cleanupMode , "cleanup-mode" , "local" ,
61+ "Private cluster cleanup mode: 'local' (keep Gateway) or 'full' (remove all Azure resources)" )
62+
5363 return cmd
5464}
5565
@@ -76,6 +86,19 @@ func runAgent(ctx context.Context) error {
7686 return fmt .Errorf ("failed to load config from %s: %w" , configPath , err )
7787 }
7888
89+ // For private clusters, run Gateway/VPN setup before bootstrap
90+ if cfg .Azure .TargetCluster != nil && cfg .Azure .TargetCluster .Private {
91+ logger .Info ("Private cluster detected, running Gateway/VPN setup..." )
92+ if os .Getuid () != 0 {
93+ return fmt .Errorf ("private cluster setup requires root privileges, please run with 'sudo'" )
94+ }
95+ runner := privatecluster .NewScriptRunner ("" )
96+ if err := runner .RunPrivateInstall (ctx , cfg .Azure .TargetCluster .ResourceID ); err != nil {
97+ return fmt .Errorf ("private cluster setup failed: %w" , err )
98+ }
99+ logger .Info ("Private cluster setup completed" )
100+ }
101+
79102 bootstrapExecutor := bootstrapper .New (cfg , logger )
80103 result , err := bootstrapExecutor .Bootstrap (ctx )
81104 if err != nil {
@@ -101,14 +124,55 @@ func runUnbootstrap(ctx context.Context) error {
101124 return fmt .Errorf ("failed to load config from %s: %w" , configPath , err )
102125 }
103126
127+ // For private clusters, run VPN/Gateway cleanup first
128+ if cfg .Azure .TargetCluster != nil && cfg .Azure .TargetCluster .Private {
129+ logger .Info ("Private cluster detected, running VPN/Gateway cleanup..." )
130+
131+ // Validate cleanup mode
132+ var mode privatecluster.CleanupMode
133+ switch cleanupMode {
134+ case "local" :
135+ mode = privatecluster .CleanupModeLocal
136+ case "full" :
137+ mode = privatecluster .CleanupModeFull
138+ default :
139+ return fmt .Errorf ("invalid cleanup mode: %s (use 'local' or 'full')" , cleanupMode )
140+ }
141+
142+ // Check root privileges for private cluster cleanup
143+ if os .Getuid () != 0 {
144+ return fmt .Errorf ("private cluster cleanup requires root privileges, please run with 'sudo'" )
145+ }
146+
147+ options := privatecluster.UninstallOptions {
148+ Mode : mode ,
149+ AKSResourceID : cfg .Azure .TargetCluster .ResourceID ,
150+ }
151+ uninstaller := privatecluster .NewUninstaller (options )
152+ if err := uninstaller .Uninstall (ctx ); err != nil {
153+ logger .Warnf ("Private cluster cleanup had errors: %v" , err )
154+ // Continue with normal unbootstrap even if private cleanup has issues
155+ }
156+ logger .Info ("Private cluster cleanup completed" )
157+ }
158+
159+ // Run normal unbootstrap
104160 bootstrapExecutor := bootstrapper .New (cfg , logger )
105161 result , err := bootstrapExecutor .Unbootstrap (ctx )
106162 if err != nil {
107163 return err
108164 }
109165
110166 // Handle and log the result (unbootstrap is more lenient with failures)
111- return handleExecutionResult (result , "unbootstrap" , logger )
167+ if err := handleExecutionResult (result , "unbootstrap" , logger ); err != nil {
168+ return err
169+ }
170+
171+ // Print final success message
172+ fmt .Println ()
173+ fmt .Println ("\033 [0;32mSUCCESS:\033 [0m Unbootstrap completed successfully!" )
174+
175+ return nil
112176}
113177
114178// runVersion displays version information
@@ -119,106 +183,6 @@ func runVersion() {
119183 fmt .Printf ("Build Time: %s\n " , BuildTime )
120184}
121185
122- // Private cluster command variables
123- var (
124- aksResourceID string
125- cleanupModeFlag string
126- )
127-
128- // NewPrivateJoinCommand creates a new private-join command
129- func NewPrivateJoinCommand () * cobra.Command {
130- cmd := & cobra.Command {
131- Use : "private-join" ,
132- Short : "Join a Private AKS cluster (requires sudo)" ,
133- Long : `Join a Private AKS cluster.
134-
135- Prerequisites:
136- 1. A Private AKS cluster must exist with AAD and Azure RBAC enabled
137- See: pkg/privatecluster/create_private_cluster.md
138-
139- 2. Current user must have the following roles on the cluster:
140- - Azure Kubernetes Service Cluster Admin Role
141- - Azure Kubernetes Service RBAC Cluster Admin
142-
143- 3. Current user must be logged in via 'sudo az login'
144-
145- The full resource ID of the Private AKS cluster is required as the --aks-resource-id parameter.
146- This same resource ID can be used later with the private-leave command.` ,
147- RunE : func (cmd * cobra.Command , args []string ) error {
148- return runPrivateJoin (cmd .Context ())
149- },
150- }
151-
152- cmd .Flags ().StringVar (& aksResourceID , "aks-resource-id" , "" , "AKS cluster resource ID (required)" )
153- cmd .MarkFlagRequired ("aks-resource-id" )
154-
155- return cmd
156- }
157-
158- // NewPrivateLeaveCommand creates a new private-leave command
159- func NewPrivateLeaveCommand () * cobra.Command {
160- cmd := & cobra.Command {
161- Use : "private-leave" ,
162- Short : "Leave a Private AKS cluster (--mode=local|full, requires sudo)" ,
163- Long : `Remove this edge node from a Private AKS cluster.
164-
165- Cleanup modes:
166- --local Local cleanup only (default):
167- - Remove node from AKS cluster
168- - Run aks-flex-node unbootstrap
169- - Remove Arc Agent
170- - Stop VPN and remove client config
171- - Keep Gateway for other nodes
172-
173- --full Full cleanup (requires --aks-resource-id):
174- - All local cleanup steps
175- - Delete Gateway VM
176- - Delete Gateway subnet, NSG, Public IP
177- - Delete SSH keys
178-
179- This command requires the current user to be logged in via 'sudo az login'.` ,
180- RunE : func (cmd * cobra.Command , args []string ) error {
181- return runPrivateLeave (cmd .Context ())
182- },
183- }
184-
185- cmd .Flags ().StringVar (& cleanupModeFlag , "mode" , "local" , "Cleanup mode: 'local' (keep Gateway) or 'full' (remove all Azure resources)" )
186- cmd .Flags ().StringVar (& aksResourceID , "aks-resource-id" , "" , "AKS cluster resource ID (required for --mode=full)" )
187-
188- return cmd
189- }
190-
191- // runPrivateJoin executes the private cluster join process
192- func runPrivateJoin (ctx context.Context ) error {
193- if os .Getuid () != 0 {
194- return fmt .Errorf ("this command requires root privileges, please run with 'sudo'" )
195- }
196- runner := privatecluster .NewScriptRunner ("" )
197- return runner .RunPrivateInstall (ctx , aksResourceID )
198- }
199-
200- // runPrivateLeave executes the private cluster leave process
201- func runPrivateLeave (ctx context.Context ) error {
202- if os .Getuid () != 0 {
203- return fmt .Errorf ("this command requires root privileges, please run with 'sudo'" )
204- }
205- // Validate cleanup mode
206- var mode privatecluster.CleanupMode
207- switch cleanupModeFlag {
208- case "local" :
209- mode = privatecluster .CleanupModeLocal
210- case "full" :
211- mode = privatecluster .CleanupModeFull
212- if aksResourceID == "" {
213- return fmt .Errorf ("--aks-resource-id is required for full cleanup mode" )
214- }
215- default :
216- return fmt .Errorf ("invalid cleanup mode: %s (use 'local' or 'full')" , cleanupModeFlag )
217- }
218-
219- runner := privatecluster .NewScriptRunner ("" )
220- return runner .RunPrivateUninstall (ctx , mode , aksResourceID )
221- }
222186
223187// runDaemonLoop runs the periodic status collection and bootstrap monitoring daemon
224188func runDaemonLoop (ctx context.Context , cfg * config.Config ) error {
0 commit comments