// ABOUTME: Application instance lifecycle management APIs for EdgeXR Master Controller // ABOUTME: Provides typed methods for creating, querying, and deleting application instances package edgeconnect import ( "context" "encoding/json" "fmt" "net/http" sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" ) // CreateAppInstance creates a new application instance in the specified region // Maps to POST /auth/ctrl/CreateAppInst func (c *Client) CreateAppInstance(ctx context.Context, input *NewAppInstanceInput) error { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/CreateAppInst" resp, err := transport.Call(ctx, "POST", url, input) if err != nil { return fmt.Errorf("CreateAppInstance failed: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "CreateAppInstance") } c.logf("CreateAppInstance: %s/%s created successfully", input.AppInst.Key.Organization, input.AppInst.Key.Name) return nil } // ShowAppInstance retrieves a single application instance by key and region // Maps to POST /auth/ctrl/ShowAppInst func (c *Client) ShowAppInstance(ctx context.Context, appInstKey AppInstanceKey, region string) (AppInstance, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/ShowAppInst" filter := AppInstanceFilter{ AppInstance: AppInstance{Key: appInstKey}, Region: region, } resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return AppInstance{}, fmt.Errorf("ShowAppInstance failed: %w", err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return AppInstance{}, fmt.Errorf("app instance %s/%s: %w", appInstKey.Organization, appInstKey.Name, ErrResourceNotFound) } if resp.StatusCode >= 400 { return AppInstance{}, c.handleErrorResponse(resp, "ShowAppInstance") } // Parse streaming JSON response var appInstances []AppInstance if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { return AppInstance{}, fmt.Errorf("ShowAppInstance failed to parse response: %w", err) } if len(appInstances) == 0 { return AppInstance{}, fmt.Errorf("app instance %s/%s in region %s: %w", appInstKey.Organization, appInstKey.Name, region, ErrResourceNotFound) } return appInstances[0], nil } // ShowAppInstances retrieves all application instances matching the filter criteria // Maps to POST /auth/ctrl/ShowAppInst func (c *Client) ShowAppInstances(ctx context.Context, appInstKey AppInstanceKey, region string) ([]AppInstance, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/ShowAppInst" filter := AppInstanceFilter{ AppInstance: AppInstance{Key: appInstKey}, Region: region, } resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return nil, fmt.Errorf("ShowAppInstances failed: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { return nil, c.handleErrorResponse(resp, "ShowAppInstances") } var appInstances []AppInstance if resp.StatusCode == http.StatusNotFound { return appInstances, nil // Return empty slice for not found } if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { return nil, fmt.Errorf("ShowAppInstances failed to parse response: %w", err) } c.logf("ShowAppInstances: found %d app instances matching criteria", len(appInstances)) return appInstances, nil } // UpdateAppInstance updates an application instance and then refreshes it // Maps to POST /auth/ctrl/UpdateAppInst func (c *Client) UpdateAppInstance(ctx context.Context, input *UpdateAppInstanceInput) error { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/UpdateAppInst" resp, err := transport.Call(ctx, "POST", url, input) if err != nil { return fmt.Errorf("UpdateAppInstance failed: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "UpdateAppInstance") } c.logf("UpdateAppInstance: %s/%s updated successfully", input.AppInst.Key.Organization, input.AppInst.Key.Name) return nil } // RefreshAppInstance refreshes an application instance's state // Maps to POST /auth/ctrl/RefreshAppInst func (c *Client) RefreshAppInstance(ctx context.Context, appInstKey AppInstanceKey, region string) error { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/RefreshAppInst" filter := AppInstanceFilter{ AppInstance: AppInstance{Key: appInstKey}, Region: region, } resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return fmt.Errorf("RefreshAppInstance failed: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "RefreshAppInstance") } c.logf("RefreshAppInstance: %s/%s refreshed successfully", appInstKey.Organization, appInstKey.Name) return nil } // DeleteAppInstance removes an application instance from the specified region // Maps to POST /auth/ctrl/DeleteAppInst func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKey, region string) error { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/DeleteAppInst" filter := AppInstanceFilter{ AppInstance: AppInstance{Key: appInstKey}, Region: region, } resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return fmt.Errorf("DeleteAppInstance failed: %w", err) } defer resp.Body.Close() // 404 is acceptable for delete operations (already deleted) if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { return c.handleErrorResponse(resp, "DeleteAppInstance") } c.logf("DeleteAppInstance: %s/%s deleted successfully", appInstKey.Organization, appInstKey.Name) return nil } // parseStreamingAppInstanceResponse parses the EdgeXR streaming JSON response format for app instances func (c *Client) parseStreamingAppInstanceResponse(resp *http.Response, result interface{}) error { var responses []Response[AppInstance] parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error { var response Response[AppInstance] if err := json.Unmarshal(line, &response); err != nil { return err } responses = append(responses, response) return nil }) if parseErr != nil { return parseErr } // Extract data from responses var appInstances []AppInstance var messages []string for _, response := range responses { if response.HasData() { appInstances = append(appInstances, response.Data) } if response.IsMessage() { messages = append(messages, response.Data.GetMessage()) } } // If we have error messages, return them if len(messages) > 0 { return &APIError{ StatusCode: resp.StatusCode, Messages: messages, } } // Set result based on type switch v := result.(type) { case *[]AppInstance: *v = appInstances default: return fmt.Errorf("unsupported result type: %T", result) } return nil }