@@ -4,18 +4,23 @@ import (
44 "fmt"
55 "strings"
66
7+ "github.com/Azure/aks-mcp/internal/components/fleet/kubernetes"
78 "github.com/Azure/aks-mcp/internal/config"
89)
910
1011// FleetExecutor handles structured fleet command execution
1112type FleetExecutor struct {
1213 * AzExecutor
14+ k8sClient * kubernetes.Client
15+ placementOps * kubernetes.PlacementOperations
16+ k8sClientInitialized bool
1317}
1418
1519// NewFleetExecutor creates a new fleet command executor
1620func NewFleetExecutor () * FleetExecutor {
1721 return & FleetExecutor {
18- AzExecutor : NewExecutor (),
22+ AzExecutor : NewExecutor (),
23+ k8sClientInitialized : false ,
1924 }
2025}
2126
@@ -37,16 +42,30 @@ func (e *FleetExecutor) Execute(params map[string]interface{}, cfg *config.Confi
3742 return "" , fmt .Errorf ("args parameter is required and must be a string" )
3843 }
3944
40- // Validate operation/resource combination
45+ // Route clusterresourceplacement operations to Kubernetes
46+ if resource == "clusterresourceplacement" {
47+ // Validate clusterresourceplacement operations separately
48+ if err := e .validateClusterResourcePlacementCombination (operation ); err != nil {
49+ return "" , err
50+ }
51+ return e .executeKubernetesClusterResourcePlacement (operation , args , cfg )
52+ }
53+
54+ // Validate operation/resource combination for non-placement resources
4155 if err := e .validateCombination (operation , resource ); err != nil {
4256 return "" , err
4357 }
4458
4559 // Construct the full command
46- command := fmt . Sprintf ( "az fleet %s %s" , resource , operation )
60+ var command string
4761 if operation == "list" && resource == "fleet" {
4862 // Special case: "az fleet list" without resource in between
4963 command = "az fleet list"
64+ } else if operation == "get-credentials" && resource == "fleet" {
65+ // Special case: "az fleet get-credentials"
66+ command = "az fleet get-credentials"
67+ } else {
68+ command = fmt .Sprintf ("az fleet %s %s" , resource , operation )
5069 }
5170
5271 // Check access level
@@ -72,7 +91,7 @@ func (e *FleetExecutor) Execute(params map[string]interface{}, cfg *config.Confi
7291// validateCombination validates if the operation/resource combination is valid
7392func (e * FleetExecutor ) validateCombination (operation , resource string ) error {
7493 validCombinations := map [string ][]string {
75- "fleet" : {"list" , "show" , "create" , "update" , "delete" },
94+ "fleet" : {"list" , "show" , "create" , "update" , "delete" , "get-credentials" },
7695 "member" : {"list" , "show" , "create" , "update" , "delete" },
7796 "updaterun" : {"list" , "show" , "create" , "start" , "stop" , "delete" },
7897 "updatestrategy" : {"list" , "show" , "create" , "delete" },
@@ -96,7 +115,7 @@ func (e *FleetExecutor) validateCombination(operation, resource string) error {
96115// checkAccessLevel ensures the operation is allowed for the current access level
97116func (e * FleetExecutor ) checkAccessLevel (operation , resource string , accessLevel string ) error {
98117 // Read-only operations are allowed for all access levels
99- readOnlyOps := []string {"list" , "show" }
118+ readOnlyOps := []string {"list" , "show" , "get" , "get-credentials" }
100119 for _ , op := range readOnlyOps {
101120 if operation == op {
102121 return nil
@@ -114,9 +133,13 @@ func (e *FleetExecutor) checkAccessLevel(operation, resource string, accessLevel
114133
115134// GetCommandForValidation returns the constructed command for security validation
116135func (e * FleetExecutor ) GetCommandForValidation (operation , resource , args string ) string {
117- command := fmt . Sprintf ( "az fleet %s %s" , resource , operation )
136+ var command string
118137 if operation == "list" && resource == "fleet" {
119138 command = "az fleet list"
139+ } else if operation == "get-credentials" && resource == "fleet" {
140+ command = "az fleet get-credentials"
141+ } else {
142+ command = fmt .Sprintf ("az fleet %s %s" , resource , operation )
120143 }
121144
122145 if args != "" {
@@ -125,3 +148,179 @@ func (e *FleetExecutor) GetCommandForValidation(operation, resource, args string
125148
126149 return command
127150}
151+
152+ // executeKubernetesClusterResourcePlacement handles clusterresourceplacement operations via Kubernetes API
153+ func (e * FleetExecutor ) executeKubernetesClusterResourcePlacement (operation , args string , cfg * config.ConfigData ) (string , error ) {
154+ // Check access level for clusterresourceplacement operations
155+ if err := e .checkAccessLevel (operation , "clusterresourceplacement" , cfg .AccessLevel ); err != nil {
156+ return "" , err
157+ }
158+
159+ // Initialize Kubernetes client if needed
160+ if ! e .k8sClientInitialized {
161+ if err := e .initializeKubernetesClient (); err != nil {
162+ return "" , err
163+ }
164+ }
165+
166+ // Check if placement operations are initialized
167+ if e .placementOps == nil {
168+ return "" , fmt .Errorf ("clusterresourceplacement operations not initialized" )
169+ }
170+
171+ // Parse arguments
172+ parsedArgs , parseErr := kubernetes .ParsePlacementArgs (args )
173+ if parseErr != nil {
174+ return "" , fmt .Errorf ("failed to parse clusterresourceplacement arguments: %w" , parseErr )
175+ }
176+
177+ // Execute the clusterresourceplacement operation with error recovery
178+ var result string
179+ var err error
180+
181+ func () {
182+ defer func () {
183+ if r := recover (); r != nil {
184+ // Provide a helpful error message without stdout pollution
185+ err = fmt .Errorf ("kubectl operation failed. Please ensure kubectl is installed, properly configured, and the cluster is accessible. Error: %v" , r )
186+ }
187+ }()
188+
189+ switch operation {
190+ case "create" :
191+ result , err = e .createClusterResourcePlacement (parsedArgs , cfg )
192+ case "get" , "show" :
193+ result , err = e .getClusterResourcePlacement (parsedArgs , cfg )
194+ case "list" :
195+ result , err = e .placementOps .ListPlacements (cfg )
196+ case "delete" :
197+ result , err = e .deleteClusterResourcePlacement (parsedArgs , cfg )
198+ default :
199+ err = fmt .Errorf ("unsupported clusterresourceplacement operation: %s" , operation )
200+ }
201+
202+ }()
203+
204+ // Clean and validate the result for MCP compatibility
205+ if err == nil {
206+ result = cleanResult (result )
207+ }
208+
209+ return result , err
210+ }
211+
212+ // cleanResult sanitizes the result for MCP compatibility
213+ func cleanResult (result string ) string {
214+ // Just clean problematic characters and return as-is
215+ cleaned := result
216+ cleaned = strings .ReplaceAll (cleaned , "\x00 " , "" ) // Null bytes
217+ cleaned = strings .ReplaceAll (cleaned , "\r " , "" ) // Carriage returns
218+ cleaned = strings .ReplaceAll (cleaned , "\x1b " , "" ) // Escape sequences
219+ cleaned = strings .TrimSpace (cleaned )
220+
221+ return cleaned
222+ }
223+
224+ // initializeKubernetesClient initializes the Kubernetes client
225+ func (e * FleetExecutor ) initializeKubernetesClient () error {
226+ defer func () {
227+ // Recover from any panics during client initialization
228+ if r := recover (); r != nil {
229+ e .k8sClientInitialized = false
230+ }
231+ }()
232+
233+ client , err := kubernetes .NewClient ()
234+ if err != nil {
235+ return fmt .Errorf ("failed to initialize Kubernetes client. Please ensure kubectl is installed and kubeconfig is properly configured: %w" , err )
236+ }
237+
238+ // Test if the client is actually usable
239+ if client == nil {
240+ return fmt .Errorf ("kubernetes client is nil after initialization" )
241+ }
242+
243+ e .k8sClient = client
244+ e .placementOps = kubernetes .NewPlacementOperations (client )
245+ e .k8sClientInitialized = true
246+
247+ return nil
248+ }
249+
250+ // validateClusterResourcePlacementCombination validates clusterresourceplacement operations
251+ func (e * FleetExecutor ) validateClusterResourcePlacementCombination (operation string ) error {
252+ validOps := []string {"list" , "show" , "get" , "create" , "delete" }
253+
254+ for _ , validOp := range validOps {
255+ if operation == validOp {
256+ return nil
257+ }
258+ }
259+
260+ return fmt .Errorf ("invalid operation '%s' for resource 'clusterresourceplacement'. Valid operations: %s" ,
261+ operation , strings .Join (validOps , ", " ))
262+ }
263+
264+ // createClusterResourcePlacement creates a clusterresourceplacement using placement operations
265+ func (e * FleetExecutor ) createClusterResourcePlacement (args map [string ]string , cfg * config.ConfigData ) (string , error ) {
266+ name , ok := args ["name" ]
267+ if ! ok || name == "" {
268+ return "" , fmt .Errorf ("--name is required for create operation" )
269+ }
270+
271+ selector := args ["selector" ]
272+ policy := args ["policy" ]
273+
274+ // Default policy if not specified
275+ if policy == "" {
276+ policy = "PickAll"
277+ }
278+
279+ // Validate policy
280+ validPolicies := []string {"PickAll" , "PickFixed" , "PickN" }
281+ isValidPolicy := false
282+ for _ , validPolicy := range validPolicies {
283+ if strings .EqualFold (policy , validPolicy ) {
284+ policy = validPolicy
285+ isValidPolicy = true
286+ break
287+ }
288+ }
289+ if ! isValidPolicy {
290+ return "" , fmt .Errorf ("invalid policy '%s'. Valid policies: %s" , policy , strings .Join (validPolicies , ", " ))
291+ }
292+
293+ if e .placementOps == nil {
294+ return "" , fmt .Errorf ("clusterresourceplacement operations not initialized" )
295+ }
296+
297+ return e .placementOps .CreatePlacement (name , selector , policy , cfg )
298+ }
299+
300+ // getClusterResourcePlacement retrieves a clusterresourceplacement using placement operations
301+ func (e * FleetExecutor ) getClusterResourcePlacement (args map [string ]string , cfg * config.ConfigData ) (string , error ) {
302+ name , ok := args ["name" ]
303+ if ! ok || name == "" {
304+ return "" , fmt .Errorf ("--name is required for get/show operation" )
305+ }
306+
307+ if e .placementOps == nil {
308+ return "" , fmt .Errorf ("clusterresourceplacement operations not initialized" )
309+ }
310+
311+ return e .placementOps .GetPlacement (name , cfg )
312+ }
313+
314+ // deleteClusterResourcePlacement deletes a clusterresourceplacement using placement operations
315+ func (e * FleetExecutor ) deleteClusterResourcePlacement (args map [string ]string , cfg * config.ConfigData ) (string , error ) {
316+ name , ok := args ["name" ]
317+ if ! ok || name == "" {
318+ return "" , fmt .Errorf ("--name is required for delete operation" )
319+ }
320+
321+ if e .placementOps == nil {
322+ return "" , fmt .Errorf ("clusterresourceplacement operations not initialized" )
323+ }
324+
325+ return e .placementOps .DeletePlacement (name , cfg )
326+ }
0 commit comments