// ABOUTME: Cloudlet management APIs for EdgeXR Master Controller // ABOUTME: Provides typed methods for creating, querying, and managing edge cloudlets package edgeconnect import ( "context" "encoding/json" "fmt" "net/http" sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/internal/http" ) // CreateCloudlet creates a new cloudlet in the specified region // Maps to POST /auth/ctrl/CreateCloudlet func (c *Client) CreateCloudlet(ctx context.Context, input *NewCloudletInput) error { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/CreateCloudlet" resp, err := transport.Call(ctx, "POST", url, input) if err != nil { return fmt.Errorf("CreateCloudlet failed: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "CreateCloudlet") } c.logf("CreateCloudlet: %s/%s created successfully", input.Cloudlet.Key.Organization, input.Cloudlet.Key.Name) return nil } // ShowCloudlet retrieves a single cloudlet by key and region // Maps to POST /auth/ctrl/ShowCloudlet func (c *Client) ShowCloudlet(ctx context.Context, cloudletKey CloudletKey, region string) (Cloudlet, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/ShowCloudlet" filter := CloudletFilter{ Cloudlet: Cloudlet{Key: cloudletKey}, Region: region, } resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return Cloudlet{}, fmt.Errorf("ShowCloudlet failed: %w", err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return Cloudlet{}, fmt.Errorf("cloudlet %s/%s in region %s: %w", cloudletKey.Organization, cloudletKey.Name, region, ErrResourceNotFound) } if resp.StatusCode >= 400 { return Cloudlet{}, c.handleErrorResponse(resp, "ShowCloudlet") } // Parse streaming JSON response var cloudlets []Cloudlet if err := c.parseStreamingCloudletResponse(resp, &cloudlets); err != nil { return Cloudlet{}, fmt.Errorf("ShowCloudlet failed to parse response: %w", err) } if len(cloudlets) == 0 { return Cloudlet{}, fmt.Errorf("cloudlet %s/%s in region %s: %w", cloudletKey.Organization, cloudletKey.Name, region, ErrResourceNotFound) } return cloudlets[0], nil } // ShowCloudlets retrieves all cloudlets matching the filter criteria // Maps to POST /auth/ctrl/ShowCloudlet func (c *Client) ShowCloudlets(ctx context.Context, cloudletKey CloudletKey, region string) ([]Cloudlet, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/ShowCloudlet" filter := CloudletFilter{ Cloudlet: Cloudlet{Key: cloudletKey}, Region: region, } resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return nil, fmt.Errorf("ShowCloudlets failed: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { return nil, c.handleErrorResponse(resp, "ShowCloudlets") } var cloudlets []Cloudlet if resp.StatusCode == http.StatusNotFound { return cloudlets, nil // Return empty slice for not found } if err := c.parseStreamingCloudletResponse(resp, &cloudlets); err != nil { return nil, fmt.Errorf("ShowCloudlets failed to parse response: %w", err) } c.logf("ShowCloudlets: found %d cloudlets matching criteria", len(cloudlets)) return cloudlets, nil } // DeleteCloudlet removes a cloudlet from the specified region // Maps to POST /auth/ctrl/DeleteCloudlet func (c *Client) DeleteCloudlet(ctx context.Context, cloudletKey CloudletKey, region string) error { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/DeleteCloudlet" filter := CloudletFilter{ Cloudlet: Cloudlet{Key: cloudletKey}, Region: region, } resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return fmt.Errorf("DeleteCloudlet 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, "DeleteCloudlet") } c.logf("DeleteCloudlet: %s/%s deleted successfully", cloudletKey.Organization, cloudletKey.Name) return nil } // GetCloudletManifest retrieves the deployment manifest for a cloudlet // Maps to POST /auth/ctrl/GetCloudletManifest func (c *Client) GetCloudletManifest(ctx context.Context, cloudletKey CloudletKey, region string) (*CloudletManifest, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/GetCloudletManifest" filter := CloudletFilter{ Cloudlet: Cloudlet{Key: cloudletKey}, Region: region, } resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return nil, fmt.Errorf("GetCloudletManifest failed: %w", err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("cloudlet manifest %s/%s in region %s: %w", cloudletKey.Organization, cloudletKey.Name, region, ErrResourceNotFound) } if resp.StatusCode >= 400 { return nil, c.handleErrorResponse(resp, "GetCloudletManifest") } // Parse the response as CloudletManifest var manifest CloudletManifest if err := c.parseDirectJSONResponse(resp, &manifest); err != nil { return nil, fmt.Errorf("GetCloudletManifest failed to parse response: %w", err) } c.logf("GetCloudletManifest: retrieved manifest for %s/%s", cloudletKey.Organization, cloudletKey.Name) return &manifest, nil } // GetCloudletResourceUsage retrieves resource usage information for a cloudlet // Maps to POST /auth/ctrl/GetCloudletResourceUsage func (c *Client) GetCloudletResourceUsage(ctx context.Context, cloudletKey CloudletKey, region string) (*CloudletResourceUsage, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/GetCloudletResourceUsage" filter := CloudletFilter{ Cloudlet: Cloudlet{Key: cloudletKey}, Region: region, } resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return nil, fmt.Errorf("GetCloudletResourceUsage failed: %w", err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("cloudlet resource usage %s/%s in region %s: %w", cloudletKey.Organization, cloudletKey.Name, region, ErrResourceNotFound) } if resp.StatusCode >= 400 { return nil, c.handleErrorResponse(resp, "GetCloudletResourceUsage") } // Parse the response as CloudletResourceUsage var usage CloudletResourceUsage if err := c.parseDirectJSONResponse(resp, &usage); err != nil { return nil, fmt.Errorf("GetCloudletResourceUsage failed to parse response: %w", err) } c.logf("GetCloudletResourceUsage: retrieved usage for %s/%s", cloudletKey.Organization, cloudletKey.Name) return &usage, nil } // parseStreamingCloudletResponse parses the EdgeXR streaming JSON response format for cloudlets func (c *Client) parseStreamingCloudletResponse(resp *http.Response, result interface{}) error { var responses []Response[Cloudlet] parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error { var response Response[Cloudlet] 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 cloudlets []Cloudlet var messages []string for _, response := range responses { if response.HasData() { cloudlets = append(cloudlets, 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 *[]Cloudlet: *v = cloudlets default: return fmt.Errorf("unsupported result type: %T", result) } return nil } // parseDirectJSONResponse parses a direct JSON response (not streaming) func (c *Client) parseDirectJSONResponse(resp *http.Response, result interface{}) error { decoder := json.NewDecoder(resp.Body) if err := decoder.Decode(result); err != nil { return fmt.Errorf("failed to decode JSON response: %w", err) } return nil }