diff --git a/.gitignore b/.gitignore index dec973c..c08c1df 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,3 @@ dist/ ### direnv ### .direnv .envrc - -edge-connect-client diff --git a/Makefile b/Makefile index a8695c5..496876e 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ clean: # Lint the code lint: - go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.2 run + golangci-lint run # Run all checks (generate, test, lint) check: test lint diff --git a/internal/apply/v2/manager.go b/internal/apply/v2/manager.go index 9bce91f..f43e933 100644 --- a/internal/apply/v2/manager.go +++ b/internal/apply/v2/manager.go @@ -317,16 +317,16 @@ func (rm *EdgeConnectResourceManager) restoreApp(ctx context.Context, backup *Ap appInput := &v2.NewAppInput{ Region: backup.Region, App: v2.App{ - Key: backup.App.Key, - Deployment: backup.App.Deployment, - ImageType: backup.App.ImageType, - ImagePath: backup.App.ImagePath, - AllowServerless: backup.App.AllowServerless, - DefaultFlavor: backup.App.DefaultFlavor, - ServerlessConfig: backup.App.ServerlessConfig, - DeploymentManifest: backup.App.DeploymentManifest, - DeploymentGenerator: backup.App.DeploymentGenerator, - RequiredOutboundConnections: backup.App.RequiredOutboundConnections, + Key: backup.App.Key, + Deployment: backup.App.Deployment, + ImageType: backup.App.ImageType, + ImagePath: backup.App.ImagePath, + AllowServerless: backup.App.AllowServerless, + DefaultFlavor: backup.App.DefaultFlavor, + ServerlessConfig: backup.App.ServerlessConfig, + DeploymentManifest: backup.App.DeploymentManifest, + DeploymentGenerator: backup.App.DeploymentGenerator, + RequiredOutboundConnections: backup.App.RequiredOutboundConnections, // Explicitly omit read-only fields like CreatedAt, UpdatedAt, Fields, etc. }, } diff --git a/internal/apply/v2/planner.go b/internal/apply/v2/planner.go index 797a411..33a809e 100644 --- a/internal/apply/v2/planner.go +++ b/internal/apply/v2/planner.go @@ -343,7 +343,8 @@ func (p *EdgeConnectPlanner) getCurrentInstanceState(ctx context.Context, desire }, } - appKey := v2.AppKey{Name: desired.AppName} + appKey := v2.AppKey{ Name: desired.AppName} + instance, err := p.client.ShowAppInstance(timeoutCtx, instanceKey, appKey, desired.Region) if err != nil { diff --git a/internal/apply/v2/strategy_recreate.go b/internal/apply/v2/strategy_recreate.go index 6af0a68..17ea3a7 100644 --- a/internal/apply/v2/strategy_recreate.go +++ b/internal/apply/v2/strategy_recreate.go @@ -586,7 +586,7 @@ func (r *RecreateStrategy) backupInstance(ctx context.Context, action InstanceAc }, } - appKey := v2.AppKey{Name: action.Desired.AppName} + appKey := v2.AppKey{ Name: action.Desired.AppName } instance, err := r.client.ShowAppInstance(ctx, instanceKey, appKey, action.Target.Region) if err != nil { diff --git a/internal/config/example_test.go b/internal/config/example_test.go index f7299c2..536399f 100644 --- a/internal/config/example_test.go +++ b/internal/config/example_test.go @@ -70,13 +70,13 @@ func TestValidateExampleStructure(t *testing.T) { config := &EdgeConnectConfig{ Kind: "edgeconnect-deployment", Metadata: Metadata{ - Name: "edge-app-demo", - AppVersion: "1.0.0", + Name: "edge-app-demo", + AppVersion: "1.0.0", Organization: "edp2", }, Spec: Spec{ DockerApp: &DockerApp{ // Use DockerApp to avoid manifest file validation - Image: "nginx:latest", + Image: "nginx:latest", }, InfraTemplate: []InfraTemplate{ { diff --git a/internal/delete/v2/types_test.go b/internal/delete/v2/types_test.go index 225c5ef..8dfa6b0 100644 --- a/internal/delete/v2/types_test.go +++ b/internal/delete/v2/types_test.go @@ -16,8 +16,8 @@ func TestDeletionPlan_IsEmpty(t *testing.T) { { name: "empty plan with no resources", plan: &DeletionPlan{ - ConfigName: "test-config", - AppToDelete: nil, + ConfigName: "test-config", + AppToDelete: nil, InstancesToDelete: []InstanceDeletion{}, }, expected: true, diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index 34e3486..2a6673c 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -213,10 +213,6 @@ func (c *Client) parseStreamingAppInstanceResponse(resp *http.Response, result i var errorMessage string parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error { - // On permission denied, Edge API returns just an empty array []! - if len(line) == 0 || line[0] == '[' { - return fmt.Errorf("%w", ErrFaultyResponsePerhaps403) - } // Try parsing as ResultResponse first (error format) var resultResp ResultResponse if err := json.Unmarshal(line, &resultResp); err == nil && resultResp.Result.Message != "" { diff --git a/sdk/edgeconnect/apps.go b/sdk/edgeconnect/apps.go index a086475..f197a68 100644 --- a/sdk/edgeconnect/apps.go +++ b/sdk/edgeconnect/apps.go @@ -15,8 +15,7 @@ import ( var ( // ErrResourceNotFound indicates the requested resource was not found - ErrResourceNotFound = fmt.Errorf("resource not found") - ErrFaultyResponsePerhaps403 = fmt.Errorf("faulty response from API, may indicate permission denied") + ErrResourceNotFound = fmt.Errorf("resource not found") ) // CreateApp creates a new application in the specified region @@ -180,10 +179,6 @@ func (c *Client) parseStreamingResponse(resp *http.Response, result interface{}) var responses []Response[App] parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error { - // On permission denied, Edge API returns just an empty array []! - if len(line) == 0 || line[0] == '[' { - return fmt.Errorf("%w", ErrFaultyResponsePerhaps403) - } var response Response[App] if err := json.Unmarshal(line, &response); err != nil { return err diff --git a/sdk/edgeconnect/types.go b/sdk/edgeconnect/types.go index 307ed52..7fd39fc 100644 --- a/sdk/edgeconnect/types.go +++ b/sdk/edgeconnect/types.go @@ -60,74 +60,74 @@ const ( // AppInstance field constants for partial updates (based on EdgeXR API specification) const ( - AppInstFieldKey = "2" - AppInstFieldKeyAppKey = "2.1" - AppInstFieldKeyAppKeyOrganization = "2.1.1" - AppInstFieldKeyAppKeyName = "2.1.2" - AppInstFieldKeyAppKeyVersion = "2.1.3" - AppInstFieldKeyClusterInstKey = "2.4" - AppInstFieldKeyClusterInstKeyClusterKey = "2.4.1" - AppInstFieldKeyClusterInstKeyClusterKeyName = "2.4.1.1" - AppInstFieldKeyClusterInstKeyCloudletKey = "2.4.2" - AppInstFieldKeyClusterInstKeyCloudletKeyOrganization = "2.4.2.1" - AppInstFieldKeyClusterInstKeyCloudletKeyName = "2.4.2.2" + AppInstFieldKey = "2" + AppInstFieldKeyAppKey = "2.1" + AppInstFieldKeyAppKeyOrganization = "2.1.1" + AppInstFieldKeyAppKeyName = "2.1.2" + AppInstFieldKeyAppKeyVersion = "2.1.3" + AppInstFieldKeyClusterInstKey = "2.4" + AppInstFieldKeyClusterInstKeyClusterKey = "2.4.1" + AppInstFieldKeyClusterInstKeyClusterKeyName = "2.4.1.1" + AppInstFieldKeyClusterInstKeyCloudletKey = "2.4.2" + AppInstFieldKeyClusterInstKeyCloudletKeyOrganization = "2.4.2.1" + AppInstFieldKeyClusterInstKeyCloudletKeyName = "2.4.2.2" AppInstFieldKeyClusterInstKeyCloudletKeyFederatedOrganization = "2.4.2.3" - AppInstFieldKeyClusterInstKeyOrganization = "2.4.3" - AppInstFieldCloudletLoc = "3" - AppInstFieldCloudletLocLatitude = "3.1" - AppInstFieldCloudletLocLongitude = "3.2" - AppInstFieldCloudletLocHorizontalAccuracy = "3.3" - AppInstFieldCloudletLocVerticalAccuracy = "3.4" - AppInstFieldCloudletLocAltitude = "3.5" - AppInstFieldCloudletLocCourse = "3.6" - AppInstFieldCloudletLocSpeed = "3.7" - AppInstFieldCloudletLocTimestamp = "3.8" - AppInstFieldCloudletLocTimestampSeconds = "3.8.1" - AppInstFieldCloudletLocTimestampNanos = "3.8.2" - AppInstFieldUri = "4" - AppInstFieldLiveness = "6" - AppInstFieldMappedPorts = "9" - AppInstFieldMappedPortsProto = "9.1" - AppInstFieldMappedPortsInternalPort = "9.2" - AppInstFieldMappedPortsPublicPort = "9.3" - AppInstFieldMappedPortsFqdnPrefix = "9.5" - AppInstFieldMappedPortsEndPort = "9.6" - AppInstFieldMappedPortsTls = "9.7" - AppInstFieldMappedPortsNginx = "9.8" - AppInstFieldMappedPortsMaxPktSize = "9.9" - AppInstFieldFlavor = "12" - AppInstFieldFlavorName = "12.1" - AppInstFieldState = "14" - AppInstFieldErrors = "15" - AppInstFieldCrmOverride = "16" - AppInstFieldRuntimeInfo = "17" - AppInstFieldRuntimeInfoContainerIds = "17.1" - AppInstFieldCreatedAt = "21" - AppInstFieldCreatedAtSeconds = "21.1" - AppInstFieldCreatedAtNanos = "21.2" - AppInstFieldAutoClusterIpAccess = "22" - AppInstFieldRevision = "24" - AppInstFieldForceUpdate = "25" - AppInstFieldUpdateMultiple = "26" - AppInstFieldConfigs = "27" - AppInstFieldConfigsKind = "27.1" - AppInstFieldConfigsConfig = "27.2" - AppInstFieldHealthCheck = "29" - AppInstFieldPowerState = "31" - AppInstFieldExternalVolumeSize = "32" - AppInstFieldAvailabilityZone = "33" - AppInstFieldVmFlavor = "34" - AppInstFieldOptRes = "35" - AppInstFieldUpdatedAt = "36" - AppInstFieldUpdatedAtSeconds = "36.1" - AppInstFieldUpdatedAtNanos = "36.2" - AppInstFieldRealClusterName = "37" - AppInstFieldInternalPortToLbIp = "38" - AppInstFieldInternalPortToLbIpKey = "38.1" - AppInstFieldInternalPortToLbIpValue = "38.2" - AppInstFieldDedicatedIp = "39" - AppInstFieldUniqueId = "40" - AppInstFieldDnsLabel = "41" + AppInstFieldKeyClusterInstKeyOrganization = "2.4.3" + AppInstFieldCloudletLoc = "3" + AppInstFieldCloudletLocLatitude = "3.1" + AppInstFieldCloudletLocLongitude = "3.2" + AppInstFieldCloudletLocHorizontalAccuracy = "3.3" + AppInstFieldCloudletLocVerticalAccuracy = "3.4" + AppInstFieldCloudletLocAltitude = "3.5" + AppInstFieldCloudletLocCourse = "3.6" + AppInstFieldCloudletLocSpeed = "3.7" + AppInstFieldCloudletLocTimestamp = "3.8" + AppInstFieldCloudletLocTimestampSeconds = "3.8.1" + AppInstFieldCloudletLocTimestampNanos = "3.8.2" + AppInstFieldUri = "4" + AppInstFieldLiveness = "6" + AppInstFieldMappedPorts = "9" + AppInstFieldMappedPortsProto = "9.1" + AppInstFieldMappedPortsInternalPort = "9.2" + AppInstFieldMappedPortsPublicPort = "9.3" + AppInstFieldMappedPortsFqdnPrefix = "9.5" + AppInstFieldMappedPortsEndPort = "9.6" + AppInstFieldMappedPortsTls = "9.7" + AppInstFieldMappedPortsNginx = "9.8" + AppInstFieldMappedPortsMaxPktSize = "9.9" + AppInstFieldFlavor = "12" + AppInstFieldFlavorName = "12.1" + AppInstFieldState = "14" + AppInstFieldErrors = "15" + AppInstFieldCrmOverride = "16" + AppInstFieldRuntimeInfo = "17" + AppInstFieldRuntimeInfoContainerIds = "17.1" + AppInstFieldCreatedAt = "21" + AppInstFieldCreatedAtSeconds = "21.1" + AppInstFieldCreatedAtNanos = "21.2" + AppInstFieldAutoClusterIpAccess = "22" + AppInstFieldRevision = "24" + AppInstFieldForceUpdate = "25" + AppInstFieldUpdateMultiple = "26" + AppInstFieldConfigs = "27" + AppInstFieldConfigsKind = "27.1" + AppInstFieldConfigsConfig = "27.2" + AppInstFieldHealthCheck = "29" + AppInstFieldPowerState = "31" + AppInstFieldExternalVolumeSize = "32" + AppInstFieldAvailabilityZone = "33" + AppInstFieldVmFlavor = "34" + AppInstFieldOptRes = "35" + AppInstFieldUpdatedAt = "36" + AppInstFieldUpdatedAtSeconds = "36.1" + AppInstFieldUpdatedAtNanos = "36.2" + AppInstFieldRealClusterName = "37" + AppInstFieldInternalPortToLbIp = "38" + AppInstFieldInternalPortToLbIpKey = "38.1" + AppInstFieldInternalPortToLbIpValue = "38.2" + AppInstFieldDedicatedIp = "39" + AppInstFieldUniqueId = "40" + AppInstFieldDnsLabel = "41" ) // Message interface for types that can provide error messages diff --git a/sdk/edgeconnect/v2/appinstance.go b/sdk/edgeconnect/v2/appinstance.go index 52dcf1f..013d053 100644 --- a/sdk/edgeconnect/v2/appinstance.go +++ b/sdk/edgeconnect/v2/appinstance.go @@ -10,7 +10,8 @@ import ( "fmt" "io" "net/http" - "strings" + + sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/internal/http" ) // CreateAppInstance creates a new application instance in the specified region @@ -33,7 +34,8 @@ func (c *Client) CreateAppInstance(ctx context.Context, input *NewAppInstanceInp } // Parse streaming JSON response - if _, err = parseStreamingResponse[AppInstance](resp); err != nil { + var appInstances []AppInstance + if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { return fmt.Errorf("ShowAppInstance failed to parse response: %w", err) } @@ -73,7 +75,7 @@ func (c *Client) ShowAppInstance(ctx context.Context, appInstKey AppInstanceKey, // Parse streaming JSON response var appInstances []AppInstance - if appInstances, err = parseStreamingResponse[AppInstance](resp); err != nil { + if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { return AppInstance{}, fmt.Errorf("ShowAppInstance failed to parse response: %w", err) } @@ -108,12 +110,12 @@ func (c *Client) ShowAppInstances(ctx context.Context, appInstKey AppInstanceKey return nil, c.handleErrorResponse(resp, "ShowAppInstances") } + var appInstances []AppInstance if resp.StatusCode == http.StatusNotFound { - return []AppInstance{}, nil // Return empty slice for not found + return appInstances, nil // Return empty slice for not found } - var appInstances []AppInstance - if appInstances, err = parseStreamingResponse[AppInstance](resp); err != nil { + if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { return nil, fmt.Errorf("ShowAppInstances failed to parse response: %w", err) } @@ -205,89 +207,88 @@ func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKe } // parseStreamingAppInstanceResponse parses the EdgeXR streaming JSON response format for app instances -func parseStreamingResponse[T Message](resp *http.Response) ([]T, error) { +func (c *Client) parseStreamingAppInstanceResponse(resp *http.Response, result interface{}) error { bodyBytes, err := io.ReadAll(resp.Body) if err != nil { - return []T{}, fmt.Errorf("failed to read response body: %w", err) + return fmt.Errorf("failed to read response body: %w", err) } - // todo finish check the responses, test them, and make a unify result, probably need - // to update the response parameter to the message type e.g. App or AppInst - isV2, err := isV2Response(bodyBytes) - if err != nil { - return []T{}, fmt.Errorf("failed to parse streaming response: %w", err) - } - - if isV2 { - resultV2, err := parseStreamingResponseV2[T](resp.StatusCode, bodyBytes) - if err != nil { - return []T{}, err + // Try parsing as a direct JSON array first (v2 API format) + switch v := result.(type) { + case *[]AppInstance: + var appInstances []AppInstance + if err := json.Unmarshal(bodyBytes, &appInstances); err == nil { + *v = appInstances + return nil } - return resultV2, nil } - resultV1, err := parseStreamingResponseV1[T](resp.StatusCode, bodyBytes) - if err != nil { - return nil, err - } - - if !resultV1.IsSuccessful() { - return []T{}, resultV1.Error() - } - - return resultV1.GetData(), nil -} - -func parseStreamingResponseV1[T Message](statusCode int, bodyBytes []byte) (Responses[T], error) { // Fall back to streaming format (v1 API format) - var responses Responses[T] - responses.StatusCode = statusCode + var appInstances []AppInstance + var messages []string + var hasError bool + var errorCode int + var errorMessage string - decoder := json.NewDecoder(bytes.NewReader(bodyBytes)) - for { - var d Response[T] - if err := decoder.Decode(&d); err != nil { - if err.Error() == "EOF" { - break + parseErr := sdkhttp.ParseJSONLines(io.NopCloser(bytes.NewReader(bodyBytes)), func(line []byte) error { + // Try parsing as ResultResponse first (error format) + var resultResp ResultResponse + if err := json.Unmarshal(line, &resultResp); err == nil && resultResp.Result.Message != "" { + if resultResp.IsError() { + hasError = true + errorCode = resultResp.GetCode() + errorMessage = resultResp.GetMessage() } - return Responses[T]{}, fmt.Errorf("error in parsing json object into Message: %w", err) + return nil } - if d.Result.Message != "" && d.Result.Code != 0 { - responses.StatusCode = d.Result.Code + // Try parsing as Response[AppInstance] + var response Response[AppInstance] + if err := json.Unmarshal(line, &response); err != nil { + return err } - if strings.Contains(d.Data.GetMessage(), "CreateError") { - responses.Errors = append(responses.Errors, fmt.Errorf("server responded with: %s", "CreateError")) + if response.HasData() { + appInstances = append(appInstances, response.Data) } - - if strings.Contains(d.Data.GetMessage(), "UpdateError") { - responses.Errors = append(responses.Errors, fmt.Errorf("server responded with: %s", "UpdateError")) + if response.IsMessage() { + msg := response.Data.GetMessage() + messages = append(messages, msg) + // Check for error indicators in messages + if msg == "CreateError" || msg == "UpdateError" || msg == "DeleteError" { + hasError = true + } } + return nil + }) - if strings.Contains(d.Data.GetMessage(), "DeleteError") { - responses.Errors = append(responses.Errors, fmt.Errorf("server responded with: %s", "DeleteError")) - } - - responses.Responses = append(responses.Responses, d) + if parseErr != nil { + return parseErr } - return responses, nil -} - -func isV2Response(bodyBytes []byte) (bool, error) { - if len(bodyBytes) == 0 { - return false, fmt.Errorf("malformatted response body") + // If we detected an error, return it + if hasError { + apiErr := &APIError{ + StatusCode: resp.StatusCode, + Messages: messages, + } + if errorCode > 0 { + apiErr.StatusCode = errorCode + apiErr.Code = fmt.Sprintf("%d", errorCode) + } + if errorMessage != "" { + apiErr.Messages = append([]string{errorMessage}, apiErr.Messages...) + } + return apiErr } - return bodyBytes[0] == '[', nil -} - -func parseStreamingResponseV2[T Message](statusCode int, bodyBytes []byte) ([]T, error) { - var result []T - if err := json.Unmarshal(bodyBytes, &result); err != nil { - return result, fmt.Errorf("failed to read response body: %w", err) + // Set result based on type + switch v := result.(type) { + case *[]AppInstance: + *v = appInstances + default: + return fmt.Errorf("unsupported result type: %T", result) } - return result, nil + return nil } diff --git a/sdk/edgeconnect/v2/appinstance_test.go b/sdk/edgeconnect/v2/appinstance_test.go index 04df669..bf4db81 100644 --- a/sdk/edgeconnect/v2/appinstance_test.go +++ b/sdk/edgeconnect/v2/appinstance_test.go @@ -174,7 +174,7 @@ func TestShowAppInstance(t *testing.T) { Name: "testcloudlet", }, }, - appKey: AppKey{Name: "testapp"}, + appKey: AppKey{ Name: "testapp" }, region: "us-west", mockStatusCode: 200, mockResponse: `{"data": {"key": {"organization": "testorg", "name": "testinst", "cloudlet_key": {"organization": "cloudletorg", "name": "testcloudlet"}}, "state": "Ready"}} @@ -192,7 +192,7 @@ func TestShowAppInstance(t *testing.T) { Name: "testcloudlet", }, }, - appKey: AppKey{Name: "testapp"}, + appKey: AppKey{ Name: "testapp" }, region: "us-west", mockStatusCode: 404, mockResponse: "", diff --git a/sdk/edgeconnect/v2/apps.go b/sdk/edgeconnect/v2/apps.go index 61c1f4c..80c3981 100644 --- a/sdk/edgeconnect/v2/apps.go +++ b/sdk/edgeconnect/v2/apps.go @@ -4,7 +4,9 @@ package v2 import ( + "bytes" "context" + "encoding/json" "fmt" "io" "net/http" @@ -71,7 +73,7 @@ func (c *Client) ShowApp(ctx context.Context, appKey AppKey, region string) (App // Parse streaming JSON response var apps []App - if apps, err = parseStreamingResponse[App](resp); err != nil { + if err := c.parseStreamingResponse(resp, &apps); err != nil { return App{}, fmt.Errorf("ShowApp failed to parse response: %w", err) } @@ -106,12 +108,12 @@ func (c *Client) ShowApps(ctx context.Context, appKey AppKey, region string) ([] return nil, c.handleErrorResponse(resp, "ShowApps") } + var apps []App if resp.StatusCode == http.StatusNotFound { - return []App{}, nil // Return empty slice for not found + return apps, nil // Return empty slice for not found } - var apps []App - if apps, err = parseStreamingResponse[App](resp); err != nil { + if err := c.parseStreamingResponse(resp, &apps); err != nil { return nil, fmt.Errorf("ShowApps failed to parse response: %w", err) } @@ -173,6 +175,70 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er return nil } +// parseStreamingResponse parses the EdgeXR streaming JSON response format +func (c *Client) parseStreamingResponse(resp *http.Response, result interface{}) error { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + // Try parsing as a direct JSON array first (v2 API format) + switch v := result.(type) { + case *[]App: + var apps []App + if err := json.Unmarshal(bodyBytes, &apps); err == nil { + *v = apps + return nil + } + } + + // Fall back to streaming format (v1 API format) + var responses []Response[App] + var apps []App + var messages []string + + parseErr := sdkhttp.ParseJSONLines(io.NopCloser(bytes.NewReader(bodyBytes)), func(line []byte) error { + var response Response[App] + 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 + for _, response := range responses { + if response.HasData() { + apps = append(apps, 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 *[]App: + *v = apps + default: + return fmt.Errorf("unsupported result type: %T", result) + } + + return nil +} + // getTransport creates an HTTP transport with current client settings func (c *Client) getTransport() *sdkhttp.Transport { return sdkhttp.NewTransport( diff --git a/sdk/edgeconnect/v2/types.go b/sdk/edgeconnect/v2/types.go index 7dea92e..0bb6875 100644 --- a/sdk/edgeconnect/v2/types.go +++ b/sdk/edgeconnect/v2/types.go @@ -60,74 +60,74 @@ const ( // AppInstance field constants for partial updates (based on EdgeXR API specification) const ( - AppInstFieldKey = "2" - AppInstFieldKeyAppKey = "2.1" - AppInstFieldKeyAppKeyOrganization = "2.1.1" - AppInstFieldKeyAppKeyName = "2.1.2" - AppInstFieldKeyAppKeyVersion = "2.1.3" - AppInstFieldKeyClusterInstKey = "2.4" - AppInstFieldKeyClusterInstKeyClusterKey = "2.4.1" - AppInstFieldKeyClusterInstKeyClusterKeyName = "2.4.1.1" - AppInstFieldKeyClusterInstKeyCloudletKey = "2.4.2" - AppInstFieldKeyClusterInstKeyCloudletKeyOrganization = "2.4.2.1" - AppInstFieldKeyClusterInstKeyCloudletKeyName = "2.4.2.2" + AppInstFieldKey = "2" + AppInstFieldKeyAppKey = "2.1" + AppInstFieldKeyAppKeyOrganization = "2.1.1" + AppInstFieldKeyAppKeyName = "2.1.2" + AppInstFieldKeyAppKeyVersion = "2.1.3" + AppInstFieldKeyClusterInstKey = "2.4" + AppInstFieldKeyClusterInstKeyClusterKey = "2.4.1" + AppInstFieldKeyClusterInstKeyClusterKeyName = "2.4.1.1" + AppInstFieldKeyClusterInstKeyCloudletKey = "2.4.2" + AppInstFieldKeyClusterInstKeyCloudletKeyOrganization = "2.4.2.1" + AppInstFieldKeyClusterInstKeyCloudletKeyName = "2.4.2.2" AppInstFieldKeyClusterInstKeyCloudletKeyFederatedOrganization = "2.4.2.3" - AppInstFieldKeyClusterInstKeyOrganization = "2.4.3" - AppInstFieldCloudletLoc = "3" - AppInstFieldCloudletLocLatitude = "3.1" - AppInstFieldCloudletLocLongitude = "3.2" - AppInstFieldCloudletLocHorizontalAccuracy = "3.3" - AppInstFieldCloudletLocVerticalAccuracy = "3.4" - AppInstFieldCloudletLocAltitude = "3.5" - AppInstFieldCloudletLocCourse = "3.6" - AppInstFieldCloudletLocSpeed = "3.7" - AppInstFieldCloudletLocTimestamp = "3.8" - AppInstFieldCloudletLocTimestampSeconds = "3.8.1" - AppInstFieldCloudletLocTimestampNanos = "3.8.2" - AppInstFieldUri = "4" - AppInstFieldLiveness = "6" - AppInstFieldMappedPorts = "9" - AppInstFieldMappedPortsProto = "9.1" - AppInstFieldMappedPortsInternalPort = "9.2" - AppInstFieldMappedPortsPublicPort = "9.3" - AppInstFieldMappedPortsFqdnPrefix = "9.5" - AppInstFieldMappedPortsEndPort = "9.6" - AppInstFieldMappedPortsTls = "9.7" - AppInstFieldMappedPortsNginx = "9.8" - AppInstFieldMappedPortsMaxPktSize = "9.9" - AppInstFieldFlavor = "12" - AppInstFieldFlavorName = "12.1" - AppInstFieldState = "14" - AppInstFieldErrors = "15" - AppInstFieldCrmOverride = "16" - AppInstFieldRuntimeInfo = "17" - AppInstFieldRuntimeInfoContainerIds = "17.1" - AppInstFieldCreatedAt = "21" - AppInstFieldCreatedAtSeconds = "21.1" - AppInstFieldCreatedAtNanos = "21.2" - AppInstFieldAutoClusterIpAccess = "22" - AppInstFieldRevision = "24" - AppInstFieldForceUpdate = "25" - AppInstFieldUpdateMultiple = "26" - AppInstFieldConfigs = "27" - AppInstFieldConfigsKind = "27.1" - AppInstFieldConfigsConfig = "27.2" - AppInstFieldHealthCheck = "29" - AppInstFieldPowerState = "31" - AppInstFieldExternalVolumeSize = "32" - AppInstFieldAvailabilityZone = "33" - AppInstFieldVmFlavor = "34" - AppInstFieldOptRes = "35" - AppInstFieldUpdatedAt = "36" - AppInstFieldUpdatedAtSeconds = "36.1" - AppInstFieldUpdatedAtNanos = "36.2" - AppInstFieldRealClusterName = "37" - AppInstFieldInternalPortToLbIp = "38" - AppInstFieldInternalPortToLbIpKey = "38.1" - AppInstFieldInternalPortToLbIpValue = "38.2" - AppInstFieldDedicatedIp = "39" - AppInstFieldUniqueId = "40" - AppInstFieldDnsLabel = "41" + AppInstFieldKeyClusterInstKeyOrganization = "2.4.3" + AppInstFieldCloudletLoc = "3" + AppInstFieldCloudletLocLatitude = "3.1" + AppInstFieldCloudletLocLongitude = "3.2" + AppInstFieldCloudletLocHorizontalAccuracy = "3.3" + AppInstFieldCloudletLocVerticalAccuracy = "3.4" + AppInstFieldCloudletLocAltitude = "3.5" + AppInstFieldCloudletLocCourse = "3.6" + AppInstFieldCloudletLocSpeed = "3.7" + AppInstFieldCloudletLocTimestamp = "3.8" + AppInstFieldCloudletLocTimestampSeconds = "3.8.1" + AppInstFieldCloudletLocTimestampNanos = "3.8.2" + AppInstFieldUri = "4" + AppInstFieldLiveness = "6" + AppInstFieldMappedPorts = "9" + AppInstFieldMappedPortsProto = "9.1" + AppInstFieldMappedPortsInternalPort = "9.2" + AppInstFieldMappedPortsPublicPort = "9.3" + AppInstFieldMappedPortsFqdnPrefix = "9.5" + AppInstFieldMappedPortsEndPort = "9.6" + AppInstFieldMappedPortsTls = "9.7" + AppInstFieldMappedPortsNginx = "9.8" + AppInstFieldMappedPortsMaxPktSize = "9.9" + AppInstFieldFlavor = "12" + AppInstFieldFlavorName = "12.1" + AppInstFieldState = "14" + AppInstFieldErrors = "15" + AppInstFieldCrmOverride = "16" + AppInstFieldRuntimeInfo = "17" + AppInstFieldRuntimeInfoContainerIds = "17.1" + AppInstFieldCreatedAt = "21" + AppInstFieldCreatedAtSeconds = "21.1" + AppInstFieldCreatedAtNanos = "21.2" + AppInstFieldAutoClusterIpAccess = "22" + AppInstFieldRevision = "24" + AppInstFieldForceUpdate = "25" + AppInstFieldUpdateMultiple = "26" + AppInstFieldConfigs = "27" + AppInstFieldConfigsKind = "27.1" + AppInstFieldConfigsConfig = "27.2" + AppInstFieldHealthCheck = "29" + AppInstFieldPowerState = "31" + AppInstFieldExternalVolumeSize = "32" + AppInstFieldAvailabilityZone = "33" + AppInstFieldVmFlavor = "34" + AppInstFieldOptRes = "35" + AppInstFieldUpdatedAt = "36" + AppInstFieldUpdatedAtSeconds = "36.1" + AppInstFieldUpdatedAtNanos = "36.2" + AppInstFieldRealClusterName = "37" + AppInstFieldInternalPortToLbIp = "38" + AppInstFieldInternalPortToLbIpKey = "38.1" + AppInstFieldInternalPortToLbIpValue = "38.2" + AppInstFieldDedicatedIp = "39" + AppInstFieldUniqueId = "40" + AppInstFieldDnsLabel = "41" ) // Message interface for types that can provide error messages @@ -291,8 +291,7 @@ type DeleteAppInstanceInput struct { // Response wraps a single API response type Response[T Message] struct { - ResultResponse `json:",inline"` - Data T `json:"data"` + Data T `json:"data"` } func (res *Response[T]) HasData() bool { @@ -327,7 +326,6 @@ func (r *ResultResponse) GetCode() int { type Responses[T Message] struct { Responses []Response[T] `json:"responses,omitempty"` StatusCode int `json:"-"` - Errors []error `json:"-"` } func (r *Responses[T]) GetData() []T { @@ -346,15 +344,12 @@ func (r *Responses[T]) GetMessages() []string { if v.IsMessage() { messages = append(messages, v.Data.GetMessage()) } - if v.Result.Message != "" { - messages = append(messages, v.Result.Message) - } } return messages } func (r *Responses[T]) IsSuccessful() bool { - return len(r.Errors) == 0 && (r.StatusCode >= 200 && r.StatusCode < 400) + return r.StatusCode >= 200 && r.StatusCode < 400 } func (r *Responses[T]) Error() error { @@ -415,7 +410,3 @@ type CloudletResourceUsage struct { Region string `json:"region"` Usage map[string]interface{} `json:"usage"` } - -type ErrorMessage struct { - Message string -}